1. 程式人生 > >Spring Boot入門教程(四十五): 事務@Transactional

Spring Boot入門教程(四十五): 事務@Transactional

一:簡介

在Spring中事務可以通過兩種方式來管理,一種是程式設計式事務另一種是宣告式事務

  • 宣告式事務:@Transactional 在方法的開頭開始事務,在方法的結束提交事務
  • 程式設計式事務:TransactionTemplate或者PlatformTransactionManager

宣告式事務和程式設計式事務的區別:宣告式事務開始事務和提交事務都是固定的,不夠靈活,而程式設計式事務通過程式碼在想要的地方開始事務,在想要的地方提交事務,更加靈活。

二:宣告式事務

宣告式事務:通過AOP對目標方法進行攔截,在方法的開始出開始事務(或者加入事務),在方法結束時提交事務,當發生異常時回滾事務, 可以通過@Transactional來實現

  • @Transactional註解在Service類上表示所有public方法都使用了事務
  • @Transactional註解在方法上表示該方法使用註解,方法級別的註解會覆蓋類級別的註解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    // 當配置了多個事務管理器時,可以使用該屬性指定使用哪個事務管理器
    @AliasFor("transactionManager"
)
String value() default "";
// 事務傳播級別 Propagation propagation() default Propagation.REQUIRED; // 設定是否只讀事務,true表示只讀事務(一般查詢設定為true),false表示讀寫事務 boolean readOnly() default false; // 指定哪些異常型別需要回滾事務,例如@Transactional(rollbackFor={RuntimeException.class, Exception.class}) Class<? extends Throwable>[]
rollbackFor() default {}; // 例如@Transactional(rollbackForClassName={“RuntimeException”,”Exception”}) String[] rollbackForClassName() default {}; // 指定當丟擲哪些異常時不需要回滾事務 Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {}; // 該屬性用於設定底層資料庫的事務隔離級別,事務隔離級別用於處理多事務併發的情況,通常使用資料庫的預設隔離級別即可,基本不需要進行設定 Isolation isolation() default Isolation.DEFAULT; // 設定事務的超時秒數,預設值為-1表示永不超時 int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; }

事務傳播行為

所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。事務的傳播行為,預設值為 Propagation.REQUIRED。

  • Propagation.REQUIRED: 如果當前存在事務,則加入該事務,如果當前不存在事務,則建立一個新的事務。

  • Propagation.SUPPORTS: 如果當前存在事務,則加入該事務;如果當前不存在事務,則以非事務的方式繼續執行。

  • Propagation.MANDATORY 如果當前存在事務,則加入該事務;如果當前不存在事務,則丟擲異常。

  • Propagation.REQUIRES_NEW 重新建立一個新的事務,如果當前存在事務,暫停當前的事務。

  • Propagation.NOT_SUPPORTED 以非事務的方式執行,如果當前存在事務,暫停當前的事務。

  • Propagation.NEVER 以非事務的方式執行,如果當前存在事務,則丟擲異常。

  • Propagation.NESTED 和 Propagation.REQUIRED 效果一樣。

事務超時

事務超時就是指一個事務所允許執行的最長時間,如果超過該時間限制但事務還沒有完成,則自動回滾事務。在 TransactionDefinition 中以 int 的值來表示超時時間,其單位是秒。
預設設定為底層事務系統的超時值,如果底層資料庫事務系統沒有設定超時值,那麼就是none,沒有超時限制。

事務隔離級別

隔離級別是指若干個併發的事務之間的隔離程度。TransactionDefinition 介面中定義了五個表示隔離級別的常量:

  • Isolation.DEFAULT 使用底層資料庫預設的隔離級別。
  • Isolation.READ_UNCOMMITTED
  • Isolation.READ_COMMITTED
  • Isolation.REPEATABLE_READ
  • Isolation.SERIALIZABLE

三:宣告式事務示例

本示例基於 Spring Boot入門教程(八): MyBatis

@RestController
@RequestMapping("/orders")
public class OrderController {
    @Autowired
    private OrderService orderService;


    /**
     * {
     *  "orderId": 1,
     *  "items": [
     *                {
     *          "orderId": 1,
     *          "goodsId": 1,
     *          "quantity": 2
     *        }
     *  ]
     * }
     */
    @PostMapping("/")
    public String addShoppingCart(@RequestBody OrderDTO orderDTO) {
        orderService.addShoppingCart(orderDTO);
        return "success";
    }
}
@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private OrderItemMapper orderItemMapper;

    @Override
    @Transactional(readOnly = true)
    public List<Order> getOrders() {
        return orderMapper.getOrders();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addShoppingCart(OrderDTO orderDTO) {
        Long orderId = orderDTO.getOrderId();
        orderMapper.updateOrderInfo(orderId);
        orderItemMapper.insertOrderItems(orderId, orderDTO.getItems());
        // 測試事務(當發生Exception時,回滾事務)
        int error = 1/0;
    }
}
public class OrderDTO {
    private Long orderId;
    private List<OrderItem> items;

    // Getter & Setter
}   
public class OrderItem {
    private Long orderId;
    private Long goodsId;
    private Long quantity;

    // Getter & Setter
}    
public interface OrderMapper {
    void updateOrderInfo(@Param("orderId") Long orderId);
}

OrderMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.mybatis.mapper.OrderMapper">
    <update id="updateOrderInfo">
        UPDATE tbl_order SET amount = amount + 2 WHERE id = #{orderId}
    </update>
</mapper>

OrderItemMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.mybatis.mapper.OrderItemMapper">
    <insert id="insertOrderItems">
        INSERT INTO tbl_order_item(order_id, goods_id, quantity) VALUES
        <foreach collection="items" item="item" separator=",">
            (#{item.orderId}, #{item.goodsId}, #{item.quantity})
        </foreach>
    </insert>
</mapper>

這裡寫圖片描述

注意 注意 注意 注意:

  • @Transactional用來類上,不要用來介面上,宣告在介面上可能註解會無效
  • @Transactional一般用在方法上,對於查詢方法不需要使用事務,如果用在類上,對查詢方法的效能有影響
  • @Transactional 註解只能應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯, 但是這個被註解的方法將不會展示已配置的事務設定
  • 外部呼叫某個類的沒有使用@Transactional註解的方法,該方法內部調這個類的一個有事務註解的其它方法,則即使這個方法使用了事務也不會生效。比如OrderService,它的一個方法addShoppingCart,addShoppingCart再呼叫OrderService本類的方法doAddShoppingCart(不管doAddShoppingCart是否public還是private),但addShoppingCart沒有宣告註解事務,而B有。則外部呼叫addShoppingCart之後,doAddShoppingCart的事務是不會起作用的。
@Override
//    @Transactional(rollbackFor = Exception.class)
public void addShoppingCart(OrderDTO orderDTO) {
    test(orderDTO);
}


@Transactional(rollbackFor = Exception.class)
public void doAddShoppingCart(OrderDTO orderDTO){
    Long orderId = orderDTO.getOrderId();
    orderMapper.updateOrderInfo(orderId);
    orderItemMapper.insertOrderItems(orderId, orderDTO.getItems());
    int error = 1/0;
}

Propagation.REQUIRES_NEW :自己新起一個事務,不使用別的事務, 如果已經在事務中則掛起當前事務

@Service
public class OrderServiceHelperImpl implements OrderServiceHelper {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private OrderItemMapper orderItemMapper;

     /**
     * REQUIRES_NEW: 重新建立一個新的事務,如果當前存在事務,暫停當前的事務
     * 自己獨立一個事務,不受其它事務干擾
     * 注意:test方法要放到其它service中,不能放在OrderServiceImpl中
     */
    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void test(OrderDTO orderDTO){
        Long orderId = orderDTO.getOrderId();
        orderMapper.updateOrderInfo(orderId);
        orderItemMapper.insertOrderItems(orderId, orderDTO.getItems());
    }
}
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderServiceHelper orderServiceHelper;

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void addShoppingCart(OrderDTO orderDTO) {
        // @Transactional(propagation = Propagation.REQUIRES_NEW)
        // 只要該方法不拋異常就可以提交,不受addShoppingCart的影響
        orderServiceHelper.test(orderDTO);

        // 即使addShoppingCart跑出異常,orderServiceHelper.test(orderDTO)也不受影響
        int error = 1/0;
    }
}

注意:該示例要想成功test方法必須放到其它service類中,不能和OrderServiceImpl放在同一個類中,放在同一個類中會被回滾

這裡寫圖片描述

四:程式設計式事務

public interface PlatformTransactionManager {
    // 根據指定的傳播行為,返回當前活動的事務或建立新的事務。
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
    // 提交事務
    void commit(TransactionStatus status) throws TransactionException;
    // 回滾事務
    void rollback(TransactionStatus status) throws TransactionException;
}
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
        implements ResourceTransactionManager, InitializingBean {

    @Nullable
    private DataSource dataSource;

    public DataSourceTransactionManager(DataSource dataSource);
}
public interface TransactionDefinition {

    int PROPAGATION_REQUIRED = 0;

    int PROPAGATION_SUPPORTS = 1;

    int PROPAGATION_MANDATORY = 2;

    int PROPAGATION_REQUIRES_NEW = 3;

    int PROPAGATION_NOT_SUPPORTED = 4;

    int PROPAGATION_NEVER = 5;

    int PROPAGATION_NESTED = 6;

    int ISOLATION_DEFAULT = -1;

    int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;

    int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;

    int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;

    int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;

    int TIMEOUT_DEFAULT = -1;
}

public class DefaultTransactionDefinition implements TransactionDefinition, Serializable {
    private int propagationBehavior = PROPAGATION_REQUIRED;

    private int isolationLevel = ISOLATION_DEFAULT;

    private int timeout = TIMEOUT_DEFAULT;

    private boolean readOnly = false;

    @Nullable
    private String name;

    public DefaultTransactionDefinition();

    public DefaultTransactionDefinition(TransactionDefinition other) {
        this.propagationBehavior = other.getPropagationBehavior();
        this.isolationLevel = other.getIsolationLevel();
        this.timeout = other.getTimeout();
        this.readOnly = other.isReadOnly();
        this.name = other.getName();
    }
}
public class TransactionTemplate extends DefaultTransactionDefinition implements TransactionOperations, InitializingBean {
    @Nullable
    private PlatformTransactionManager transactionManager;

    public TransactionTemplate(PlatformTransactionManager transactionManager);  
    @Override
    @Nullable
    public <T> T execute(TransactionCallback<T> action) throws TransactionException;    
}       
@FunctionalInterface
public interface TransactionCallback<T> {
    @Nullable
    T doInTransaction(TransactionStatus status);
}
public abstract class TransactionCallbackWithoutResult implements TransactionCallback<Object> {
    @Override
    @Nullable
    public final Object doInTransaction(TransactionStatus status) {
        doInTransactionWithoutResult(status);
        return null;
    }
}
public interface TransactionStatus extends SavepointManager, Flushable {
    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    @Override
    void flush();

    boolean isCompleted();
}

示例

public void test(OrderDTO orderDTO) {
  TransactionTemplate transactionTemplate = getTransactionTemplate();
    // TransactionTemplate 有返回值
    Object result = transactionTemplate.execute(new TransactionCallback<Object>() {
        @Override
        public Object doInTransaction(TransactionStatus status) {
            try {
                Long orderId = orderDTO.getOrderId();
                orderMapper.updateOrderInfo(orderId);
                orderItemMapper.insertOrderItems(orderId, orderDTO.getItems());
                return "success";
            } catch (Exception e) {
                status.setRollbackOnly();
            }
            return null;
        }
    });
    System.out.println(result);

    // 雖然這裡報錯,上面的事務扔不影響,已經提交過了
    int a = 1/0;
}

public void test2(OrderDTO orderDTO) {
    TransactionTemplate transactionTemplate = getTransactionTemplate();
    // TransactionTemplate 沒有返回值
    transactionTemplate.execute(new TransactionCallbackWithoutResult() {
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) {
            try {
                Long orderId = orderDTO.getOrderId();
                orderMapper.updateOrderInfo(orderId);
                orderItemMapper.insertOrderItems(orderId, orderDTO.getItems());
            } catch (Exception e) {
                status.setRollbackOnly();
            }
        }
    });
}

public void test3(OrderDTO orderDTO) {
    // 直接使用transactionManager事務管理器來管理事務
    DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
    PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
    TransactionStatus transactionStatus = transactionManager.getTransaction(definition);
    try {
        // coding
        Long orderId = orderDTO.getOrderId();
        orderMapper.updateOrderInfo(orderId);
        orderItemMapper.insertOrderItems(orderId, orderDTO.getItems());
        transactionManager.commit(transactionStatus);
    } catch (Exception e) {
        transactionManager.rollback(transactionStatus);
    }
}