Note of learning Transaction Management.
Preparations First create xml file and add configs. I will just use the last article’s xml file and add new component-scan.
1 <context:component-scan base-package ="org.example.tx" > </context:component-scan >
Then create two tables:
book table:
1 2 3 4 5 6 7 create table `t_book`( `book_id` int (11 ) not null auto_increment, `book_name` varchar (20 ) default null , `price` int (11 ) default null , `stock` int (10 ) unsigned default null , primary key(`book_id`) ) engine= innodb auto_increment= 3 default charset= utf8;
user table:
1 2 3 4 5 6 create table `t_user` ( `user_id` int (11 ) not null auto_increment, `username` varchar (20 ) default null , `balance` int (10 ) unsigned default null , primary key(`user_id`) ) engine= innodb auto_increment= 2 default charset= utf8;
Add some data into the tables:
1 2 insert into `t_book`(`book_id`, `book_name`, `price`, `stock`) values (1 , '三体' , 80 , 10 ), (2 , '消失' , 100 , 50 );insert into `t_user` (`user_id`, `username`, `balance`) values (1 , 'lucy' , 500 );
Then create some packages:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Repository public class BookDaoImpl implements BookDao {} @Controller public class BookController { @Autowired private BookService bookService; public void buyBook (Integer bookId, Integer userId) { bookService.buyBook(bookId, userId); } } @Service public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao; @Override public void buyBook (Integer bookId, Integer userId) { Integer price = bookDao.getBookPriceById(bookId); bookDao.updateStock(bookId); bookDao.updateUserBalance(userId, price); } }
Simulate a book buying process There are three steps:
get book price according to book id
stock of book -1
user balance - book price
So, let’s implements the three steps in BookDaoImpl
first.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Repository public class BookDaoImpl implements BookDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public Integer getBookPriceById (Integer bookId) { String sql = "select price from t_book where book_id=?" ; Integer price = jdbcTemplate.queryForObject(sql, Integer.class, bookId); return price; } @Override public void updateStock (Integer bookId) { String sql = "update t_book set stock=stock-1 where book_id=?" ; jdbcTemplate.update(sql, bookId); } @Override public void updateUserBalance (Integer userId, Integer price) { String sql = "update t_user set balance=balance-? where user_id=?" ; jdbcTemplate.update(sql, price, userId); } }
Now lets test it;
1 2 3 4 5 6 7 8 9 10 11 @SpringJUnitConfig(locations = "classpath:beans.xml") public class TestBook { @Autowired private BookController bookController; @Test public void testBuyBook () { bookController.buyBook(1 , 1 ); } }
we can check the database and found that the money of user 1 decreased and the book stocks decreased too.
Error Situation If the user’s balance is less than the book price, then we run this code, we will find that there is an error but the number of books still being reduced.
In this case, we need to introduce Transaction .
Transaction We use annotations to start transactions. Confiture it in xml file. And also add its namespace.
1 2 3 4 <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="druidDataSource" > </property > </bean > <tx:annotation-driven transaction-manager ="jdbcTemplate" />
If your transaction manager’s id is “transactionManager”, then you can declare like this:
Then we add the annotation @Transactional
to the method top or to the class.
If you add it to the class, then all the methods of the class will work with transaction.
Test: we can now run the code and found there is till error but the book number won’t change.
Properties of @Transactional
readOnly
It only allowed to do read operations, can’t do add, modify or delete operations.
1 @Transactional(readOnly = true)
timeout()
default is -1. If it’s not finished during the set time, then it will automatically throw exceptions and do rollback.
1 @Transactional(timeout = 3)
rollbackFor
, rollbackForClassName
, noRollbackFor
, noRollbackForClassName
: these are to set the rollback strategy, which exception do rollback and which don’t do.
1 @Transactional(noRollbackFor = ArithmeticException.class)
isolation()
: isolation level of the transaction
1 2 3 4 5 @Transactional(isolation = Isolation.DEFUALT) @Transactional(isolation = Isolation.READ_UNCOMMITED) @Transactional(isolation = Isolation.COMMITED) @Transactional(isolation = Isolation.REAPEATABLE_READ) @Transactional(isolation = Isolation.SERIALIZABLE)
propagation()
: how the transactions are called between methods
If we have two methods A and B, when A calls B, which transaction we should use, use A’s or B’s or Combine both.
REQUIRED: support the current transaction, if there is no transaction, then create a new one.
SUPPORTS: If there is transaction, support the current, if there isn’t, then run without transaction.
MANDATORY: Must run in a transaction, or it will throw an exception.
REQUIRES_NEW: Start a new transaction, if there is transaction at current, then suspend the current transaction.
NOT_SUPPORTED: run without transaction, if there is one, suspend it.
NEVER: un without transaction, if there is, throw exception.
NESTED: if there is transaction going on, then the method should run in a nested transaction. If not, it behave like REQUIRED
.
1 @Transactional(readOnly = Propagatioin.REQUIRED)
…
Full Annotation-based Configuration We can create a configuration class to replace configuration in xml file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package org.example.tx.config;import com.alibaba.druid.pool.DruidDataSource;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.datasource.DataSourceTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;@Configuration @ComponentScan("org.example.tx") @EnableTransactionManagement public class SpringConfig { @Bean public DataSource getDataSource () { DruidDataSource dataSource = new DruidDataSource (); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver" ); dataSource.setUsername("root" ); dataSource.setPassword("1130" ); dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false" ); return dataSource; } @Bean(name="jdbcTemplate") public JdbcTemplate getJdbcTemplate (DataSource datasource) { JdbcTemplate jdbcTemplate = new JdbcTemplate (); jdbcTemplate.setDataSource(datasource); return jdbcTemplate; } @Bean public DataSourceTransactionManager getDataSourceTransactionManager (DataSource dataSource) { DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager (); dataSourceTransactionManager.setDataSource(dataSource); return dataSourceTransactionManager; } }
Then we comment the <context:commponent-scan>
, <bean>
, <tx:annoation-driven>
out.
And we can use our previous code to test if its works.
1 2 3 4 5 6 7 8 public class TestFullyAnnoBook { @Test public void testFullyAnnoBook () { ApplicationContext applicationContext = new AnnotationConfigApplicationContext (SpringConfig.class); BookController controller = applicationContext.getBean("bookController" , BookController.class); controller.buyBook(1 , 1 ); } }
Full XML configuration XML file First, we create a xml config file just as before and copy the previous code structures of buy book method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xmlns:tx ="http://www.springframework.org/schema/tx" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd" > <context:component-scan base-package ="org.example.xmltx" > </context:component-scan > <context:property-placeholder location ="classpath:jdbc.properties" > </context:property-placeholder > <bean id ="druidDataSource" class ="com.alibaba.druid.pool.DruidDataSource" > <property name ="url" value ="${jdbc.url}" > </property > <property name ="driverClassName" value ="${jdbc.driver}" > </property > <property name ="username" value ="${jdbc.user}" > </property > <property name ="password" value ="${jdbc.password}" > </property > </bean > <bean id ="jdbcTemplate" class ="org.springframework.jdbc.core.JdbcTemplate" > <property name ="dataSource" ref ="druidDataSource" > </property > </bean > <bean id ="transactionManager" class ="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name ="dataSource" ref ="druidDataSource" > </property > </bean > </beans >
Transaction enhancement Then we can set transaction enhancement.
1 2 3 4 5 6 7 <tx:advice id ="txAdvice" transaction-manager ="transactionManager" > <tx:attributes > <tx:method name ="get*" read-only ="true" /> <tx:method name ="update*" read-only ="false" propagation ="REQUIRED" /> <tx:method name ="buy*" read-only ="false" propagation ="REQUIRED" /> </tx:attributes > </tx:advice >
the name means the method which name starts with get will be applied to a transaction. And we can set attributes of the transaction after the name property. such as read-only
.
And because we need to apply the transaction on the service
layer, so we also need to add rules to the buyBook()
method.
Pointcut and Advice Then we can set the
1 2 3 4 <aop:config > <aop:pointcut id ="pt" expression ="execution(* org.example.xmltx.service.*.*(..))" /> <aop:advisor advice-ref ="txAdvice" pointcut-ref ="pt" > </aop:advisor > </aop:config >
this means we want to apply the advice to the service
layer.
Then we can test a situation that the user’s balance is less than the book price. And we can see it throws errors and we didn’t minus the stocking with not being able to buy the book.
Author : o_oyao
License : All articles in this blog are licensed under
CC BY-NC-SA 4.0 unless stating additionally.