Spring implements transaction management based on annotations

In the "XML-based Transaction Management in Spring" section, we greatly simplified the XML configuration required for Spring declarative transactions through the tx:advice element. But in fact, we can further simplify it in another way, which is "using annotations to implement transaction management".

In Spring, besides XML, declarative transactions can also be implemented using annotations to further reduce the coupling between codes. Let's introduce how to implement declarative transaction management through annotations.

  1. Open annotation transaction
    The tx namespace provides a tx:annotation-driven element, which is used to open annotation transaction and simplify the XML configuration of Spring declarative transaction.

The usage of tx:annotation-driven element is also very simple, we only need to add such a line of configuration in the Spring XML configuration.
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

Like the tx:advice element, tx:annotation-driven also needs to define a transaction manager through the transaction-manager attribute, and the default value of this parameter is transactionManager. If the id of the transaction manager we use is the same as the default value, the configuration of this property can be omitted, and the form is as follows.
tx:annotation-driven/

After the annotation transaction is enabled through the tx:annotation-driven element, Spring will automatically check the beans in the container, find the beans annotated with @Transactional, and provide transaction support for them.
2. Use @Transactional annotation
@Transactional annotation is the core annotation of Spring declarative transaction programming, which can be used on both classes and methods.
@Transactional
public class XXX { @Transactional public void A(Order order) { ... } public void B(Order order) { ... } }







If the @Transactional annotation is used on a class, it means that all methods in the class support transactions; if the @Transactional annotation is used on a method, it means that the current method supports transactions.

Spring finds all beans annotated with @Transactional in the container and automatically adds transaction notifications to them. The transaction attributes of the notification are defined through the attributes of the @Transactional annotation.

The @Transactional annotation contains multiple attributes, among which the common attributes are as follows.
insert image description here
Example 1
Below, we will use an example to demonstrate how to implement declarative transactions through annotations. The steps are as follows.

  1. Create a new database instance named spring-tx-db in the MySQL database, and execute the following SQL statement in this database.

DROP TABLE IF EXISTS account;
CREATE TABLE account(
idbigint NOT NULL AUTO_INCREMENT COMMENT 'id',
user_idbigint DEFAULT NULL COMMENT 'userid',
totaldecimal(10,0) DEFAULT NULL COMMENT 'total amount',
useddecimal(10,0) DEFAULT NULL COMMENT ' Used balance',
residuedecimal(10,0) DEFAULT '0' COMMENT 'Remaining available amount',
PRIMARY KEY ( id)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO accountVALUES ('1', '1', '1000', '0', '1000');
DROP TABLE IF EXISTS order;
CREATE TABLE order(
idbigint NOT NULL AUTO_INCREMENT,
order_idvarchar(200) NOT NULL,
user_idvarchar(200) NOT NULL COMMENT 'userid',
product_idvarchar(200) NOT NULL COMMENT 'product id',
countint DEFAULT NULL COMMENT 'quantity',
moneydecimal(11,0) DEFAULT NULL COMMENT 'amount',
statusint DEFAULT NULL COMMENT 'order status: 0: creating; 1: completed',
PRIMARY KEY ( id)
) ENGINE=InnoDB DEFAULT CHARSET =utf8;
DROP TABLE IF EXISTS storage;
CREATE TABLE storage(
idbigint NOT NULL AUTO_INCREMENT,
product_idbigint DEFAULT NULL COMMENT 'product id',
totalint DEFAULT NULL COMMENT 'total inventory',
usedint DEFAULT NULL COMMENT 'used inventory',
residueint DEFAULT NULL COMMENT 'remaining Inventory',
PRIMARY KEY ( id)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO storageVALUES ('1', '1', '100', '0', '100');

Through the above SQL statement, we create three database tables: order (order table), storage (commodity inventory table), account (user account table).

  1. Create a new Java project named my-spring-tx-demo and import the following dependencies into the project.
    spring-beans-5.3.13.RELEASE.jar
    spring-context-5.3.13.RELEASE.jar
    spring-core-5.3.13.RELEASE.jar
    spring-expression-5.3.13.RELEASE.jar
    commons-logging-1.2. jar
    spring-jdbc-5.3.13.RELEASE.jar
    spring-tx-5.3.13.RELEASE.jar
    spring-aop-5.3.13.jar
    mysql-connector-java-8.0.23.jar
    aspectjweaver-1.9.7.jar
    spring-aspects-5.3.13.jar

  2. Under the net.biancheng.c.entity package, create an entity class named Order, the code is as follows.
    package net.biancheng.c.entity;
    import java.math.BigDecimal;
    public class Order { //self-increment id private Long id; //order id private String orderId; //user id private String userId; //commodity id private String productId; //order item quantity private Integer count; //order amount private BigDecimal money; //order status private Integer status; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getOrderId() { return orderId; }























    public void setOrderId(String orderId) {
    this.orderId = orderId;
    }
    public String getUserId() {
    return userId;
    }
    public void setUserId(String userId) {
    this.userId = userId;
    }
    public String getProductId() {
    return productId;
    }
    public void setProductId(String productId) {
    this.productId = productId;
    }
    public Integer getCount() {
    return count;
    }
    public void setCount(Integer count) {
    this.count = count;
    }
    public BigDecimal getMoney() {
    return money;
    }
    public void setMoney(BigDecimal money) {
    this.money = money;
    }
    public Integer getStatus() {
    return status;
    }
    public void setStatus(Integer status) {
    this.status = status;
    }
    }

  3. Under the net.biancheng.c.entity package, create an entity class named Account, the code is as follows.
    package net.biancheng.c.entity;
    import java.math.BigDecimal;
    public class Account { //Auto-increment id private Long id; //User id private String userId; //Account total amount private BigDecimal total; //Used Account amount private BigDecimal used; //Remaining account amount private BigDecimal residue; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUserId() { return userId; } public void setUserId(String userId) { this. userId = userId; }






















    public BigDecimal getTotal() {
    return total;
    }
    public void setTotal(BigDecimal total) {
    this.total = total;
    }
    public BigDecimal getUsed() {
    return used;
    }
    public void setUsed(BigDecimal used) {
    this.used = used;
    }
    public BigDecimal getResidue() {
    return residue;
    }
    public void setResidue(BigDecimal residue) {
    this.residue = residue;
    }
    }

  4. Under the net.biancheng.c.entity package, create an entity class named Storage, the code is as follows.
    package net.biancheng.c.entity;
    public class Storage { //Auto-increment id private Long id; //Commodity id private String productId; //Product inventory total private Integer total; //Used commodity quantity private Integer used; / /remaining product quantity private Integer residue; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getProductId() { return productId; } public void setProductId(String productId) { this. productId = productId; }






















    public Integer getTotal() {
    return total;
    }
    public void setTotal(Integer total) {
    this.total = total;
    }
    public Integer getUsed() {
    return used;
    }
    public void setUsed(Integer used) {
    this.used = used;
    }
    public Integer getResidue() {
    return residue;
    }
    public void setResidue(Integer residue) {
    this.residue = residue;
    }
    }

  5. Under the net.biancheng.net.dao package, create an interface named OrderDao, the code is as follows.
    package net.biancheng.c.dao;
    import net.biancheng.c.entity.Order;
    public interface OrderDao { /**

    • Create Order
    • @param order
    • @return
      /
      int createOrder(Order order);
      /
      *
    • Modify order status
    • Change order status from not completed (0) to completed (1)
    • @param orderId
    • @param status
      */
      void updateOrderStatus(String orderId, Integer status);
      }
  6. Under the net.biancheng.net.dao package, create an interface named AccountDao, the code is as follows.
    package net.biancheng.c.dao;
    import net.biancheng.c.entity.Account;
    import java.math.BigDecimal;
    public interface AccountDao { /**

    • Query the account amount according to the user
    • @param userId
    • @return
      /
      Account selectByUserId(String userId);
      /
      *
    • Deduct account amount
    • @param userId
    • @param money
    • @return
      */
      int decrease(String userId, BigDecimal money);
      }
  7. Under the net.biancheng.net.dao package, create an interface named StorageDao, the code is as follows.
    package net.biancheng.c.dao;
    import net.biancheng.c.entity.Storage;
    public interface StorageDao { /**

    • Check the inventory of the product
    • @param productId
    • @return
      /
      Storage selectByProductId(String productId);
      /
      *
    • Deduction of commodity inventory
    • @param record
    • @return
      */
      int decrease(Storage record);
      }
  8. 在 net.biancheng.c.dao.impl 包下,创建 OrderDao 的实现类 OrderDaoImpl,代码如下。
    package net.biancheng.c.dao.impl;
    import net.biancheng.c.dao.OrderDao;
    import net.biancheng.c.entity.Order;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    @Repository
    public class OrderDaoImpl implements OrderDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public int createOrder(Order order) {
    String sql = “insert into order (order_id,user_id, product_id, count, money, status) values (?,?,?,?,?,?)”;
    int update = jdbcTemplate.update(sql, order.getOrderId(), order.getUserId(), order.getProductId(), order.getCount(), order.getMoney(), order.getStatus());
    return update;
    }
    @Override
    public void updateOrderStatus(String orderId, Integer status) {
    String sql = " update order set status = 1 where order_id = ? and status = ?;";
    jdbcTemplate.update(sql, orderId, status);
    }
    }

  9. 在 net.biancheng.c.dao.impl 包下,创建 AccountDao 的实现类 AccountDaoImpl,代码如下。
    package net.biancheng.c.dao.impl;
    import net.biancheng.c.dao.AccountDao;
    import net.biancheng.c.entity.Account;
    import net.biancheng.c.entity.Order;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    import java.math.BigDecimal;
    @Repository
    public class AccountDaoImpl implements AccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public Account selectByUserId(String userId) {
    String sql = " select * from account where user_id = ?";
    return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(Account.class), userId);
    }
    @Override
    public int decrease(String userId, BigDecimal money) {
    String sql = “UPDATE account SET residue = residue - ?, used = used + ? WHERE user_id = ?;”;
    return jdbcTemplate.update(sql, money, money, userId);
    }
    }

  10. 在 net.biancheng.c.dao.impl 包下,创建 StorageDao 的实现类 StorageDaoImpl,代码如下。
    package net.biancheng.c.dao.impl;
    import net.biancheng.c.dao.StorageDao;
    import net.biancheng.c.entity.Storage;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    @Repository
    public class StorageDaoImpl implements StorageDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Override
    public Storage selectByProductId(String productId) {
    String sql = “select * from storage where product_id = ?”;
    return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(Storage.class), productId);
    }
    @Override
    public int decrease(Storage record) {
    String sql = " update storage set used =? ,residue=? where product_id=?";
    return jdbcTemplate.update(sql, record.getUsed(), record.getResidue(), record.getProductId());
    }
    }

  11. Under the net.biancheng.c.service package, create an interface named OrderService, the code is as follows.
    package net.biancheng.c.service;
    import net.biancheng.c.entity.Order;
    public interface OrderService { /**

    • Create Order
    • @param order
    • @return
      */
      public void createOrder(Order order);
      }
  12. 在 net.biancheng.c.service.impl 包下,创建 OrderService 的实现类 OrderServiceImpl,代码如下。
    package net.biancheng.c.service.impl;
    import net.biancheng.c.dao.AccountDao;
    import net.biancheng.c.dao.OrderDao;
    import net.biancheng.c.dao.StorageDao;
    import net.biancheng.c.entity.Account;
    import net.biancheng.c.entity.Order;
    import net.biancheng.c.entity.Storage;
    import net.biancheng.c.service.OrderService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Isolation;
    import org.springframework.transaction.annotation.Propagation;
    import org.springframework.transaction.annotation.Transactional;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    @Service(“orderService”)
    public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderDao orderDao;
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private StorageDao storageDao;
    /**

    • Use the @Transactional annotation on the method,
    • @param order
      */
      @Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, timeout = 10, readOnly = false)
      @Override
      public void createOrder(Order order) { //Automatically generate order id SimpleDateFormat df = new SimpleDateFormat( "yyyyMMddHHmmssSSS"); String format = df.format(new Date()); String orderId = order.getUserId() + order.getProductId() + format; System.out.println("The automatically generated order id is:" + orderId); order.setOrderId(orderId); System.out.println("Start creating order data, the order number is: " + orderId); //Create order data orderDao.createOrder(order); System.out.println( "The order data is created, the order number is:" + orderId); System.out.println("Start querying the product inventory, the product id is: " + order.getProductId());











      Storage storage = storageDao.selectByProductId(order.getProductId());
      if (storage != null && storage.getResidue().intValue() >= order.getCount().intValue()) { System.out.println(" The product inventory is sufficient, and the product inventory is being deducted"); storage.setUsed(storage.getUsed() + order.getCount()); storage.setResidue(storage.getTotal().intValue() - storage.getUsed()); int decrease = storageDao.decrease(storage); System.out.println("Commodity inventory deduction complete"); } else { System.out.println("Warning: Insufficient inventory, rollback operation is in progress!"); throw new RuntimeException("Insufficient inventory"); } System.out.println("Start querying the user's account amount"); Account account = accountDao.selectByUserId(order.getUserId());











      if (account != null && account.getResidue().intValue() >= order.getMoney().intValue()) { System.out.println("The account amount is sufficient, and the account amount is being deducted"); accountDao. decrease(order.getUserId(), order.getMoney()); System.out.println("Account deduction complete"); } else { System.out.println("Warning: Insufficient account balance, performing rollback Operation!"); throw new RuntimeException("Insufficient account balance"); } System.out.println("Begin to modify the order status, not completed》》》》》); orderDao.updateOrderStatus(order.getOrderId() , 0); System.out.println("Modify order status completed!"); } }











  13. In the src directory, create a configuration file jdbc.properties, the configuration content is as follows.
    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://127.0.0.1:3306/spring-tx-db
    jdbc.username=root
    jdbc.password=root

  14. Create an XML configuration file Beans.xml in the src directory, the configuration content is as follows.

<?xml version="1.0" encoding="UTF-8"?>



tx:annotation-driven/

<context:component-scan base-package=“net.biancheng.c”></context:component-scan>

<context:property-placeholder location=“classpath:jdbc.properties”></context:property-placeholder>




















  1. Under net.biancheng.c package, create a class named MainApp, the code is as follows.
    package net.biancheng.c;
    import net.biancheng.c.entity.Order;
    import net.biancheng.c.service.OrderService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import java.math.BigDecimal;
    public class MainApp { public static void main(String[] args) { ApplicationContext context2 = new ClassPathXmlApplicationContext(“Beans.xml”); OrderService orderService = context2.getBean(“orderService”, OrderService.class); Order order = new Order(); //Set product id order.setProductId("1"); //Product quantity order.setCount(30);








    //Commodity amount
    order.setMoney(new BigDecimal(600));
    //Set user id
    order.setUserId("1");
    //Order status is incomplete
    order.setStatus(0);
    orderService.createOrder(order);
    }
    }

  2. Execute the main method in the MainApp class, and the console output is as follows.
    The automatically generated order id is: 1120220111173635296
    Start creating order data, the order number is: 1120220111173635296 The
    order data is created, the order number is: 1120220111173635296
    Start querying the product inventory, the product id is: 1
    The product inventory is sufficient, and the product
    inventory Subtraction completed
    Start querying the user's account amount
    The account amount is sufficient, and the account amount is being deducted The account
    amount deduction is complete
    Start to modify the order status, not completed """""Completed
    Modify the order status completed!

  3. View the data in the order table, storage table and account table respectively, and the results are as follows.
    insert image description here

  4. Execute the main method in MainApp again, and the console output is as follows.
    The automatically generated order id is: 1120220111175556986
    Start to create order data, the order number is: 1120220111175556986 The
    order data is created, the order number is: 1120220111175556986
    Start to check the commodity inventory, the commodity id is: 1
    The commodity inventory is sufficient, and the
    commodity The subtraction is completed
    Start querying the user's account amount
    Warning: The account balance is insufficient, and the rollback operation is being performed!
    Exception in thread “main” java.lang.RuntimeException: Insufficient account balance
    at net.biancheng.c.service.impl.OrderServiceImpl.createOrder(OrderServiceImpl.java:61)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun .reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.transaction.interceptor.TransactionInterceptor 1. p r o c e e d W i t h I n v o c a t i o n ( T r a n s a c t i o n I n t e r c e p t o r . j a v a : 123 ) a t o r g . s p r i n g f r a m e w o r k . t r a n s a c t i o n . i n t e r c e p t o r . T r a n s a c t i o n A s p e c t S u p p o r t . i n v o k e W i t h i n T r a n s a c t i o n ( T r a n s a c t i o n A s p e c t S u p p o r t . j a v a : 388 ) a t o r g . s p r i n g f r a m e w o r k . t r a n s a c t i o n . i n t e r c e p t o r . T r a n s a c t i o n I n t e r c e p t o r . i n v o k e ( T r a n s a c t i o n I n t e r c e p t o r . j a v a : 119 ) a t o r g . s p r i n g f r a m e w o r k . a o p . f r a m e w o r k . R e f l e c t i v e M e t h o d I n v o c a t i o n . p r o c e e d ( R e f l e c t i v e M e t h o d I n v o c a t i o n . j a v a : 186 ) a t o r g . s p r i n g f r a m e w o r k . a o p . i n t e r c e p t o r . E x p o s e I n v o c a t i o n I n t e r c e p t o r . i n v o k e ( E x p o s e I n v o c a t i o n I n t e r c e p t o r . j a v a : 97 ) a t o r g . s p r i n g f r a m e w o r k . a o p . f r a m e w o r k . R e f l e c t i v e M e t h o d I n v o c a t i o n . p r o c e e d ( R e f l e c t i v e M e t h o d I n v o c a t i o n . j a v a : 186 ) a t o r g . s p r i n g f r a m e w o r k . a o p . f r a m e w o r k . J d k D y n a m i c A o p P r o x y . i n v o k e ( J d k D y n a m i c A o p P r o x y . j a v a : 215 ) a t c o m . s u n . p r o x y . 1.proceedWithInvocation(TransactionInterceptor.java:123) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) at com.sun.proxy. 1.proceedWithInvocation(TransactionInterceptor.java:123)atorg.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)atorg.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)atorg.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)atorg.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)atorg.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)atorg.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)atcom.sun.proxy.Proxy13.createOrder(Unknown Source)
    at net.biancheng.c.MainApp.main(MainApp.java:25)

  5. Query the database table again and find that there is no change in the three database tables, indicating that after the deduction account was abnormal, the transaction was rolled back

Guess you like

Origin blog.csdn.net/weixin_64842782/article/details/124859715