I Spring transaction management
-
What is a transaction
Transaction is a unit of concurrency control, which enables a user-defined sequence of operations. These transactions are either done or not done. They are an inseparable work unit. Through transactions, sql can bind a set of logically related operations together, so that the server can maintain the complete row of data. Transactions usually start with begin/start trsaction and end with commit or rollback. Commit refers to all operations that commit a transaction. Specifically, all data updates in the transaction are written to the physical database on the disk, and the transaction ends normally. Rollback means rollback, that is, if a fault occurs during the operation of the transaction and the transaction cannot continue, the system will undo all completed operations on the database in the transaction and roll to the start state of the transaction.
For example, for an online shopping transaction, the regular payment process includes at least the following database operations:
- Update the inventory information of goods purchased by customers
- Save customer payment information - may include interaction with the banking system
- Generate the order and save it to the database
-
Characteristics of the transaction (ACID)
-
Atomicity
Transaction is the logical work unit of the database, and it must be an atomic work unit. For the modification of its data, either all or no execution is performed
-
uniformity
When a transaction is completed, everything must be in a consistent state. In the relevant database, all rules must be applied to the modification of transactions to maintain the integrity of all things.
-
Isolation
The execution of a transaction cannot be affected by other transactions. The enterprise database can handle thousands of concurrent accesses every second, which brings the problem of concurrency control.
-
persistence
Once a transaction is committed, the transaction operation will be permanently saved to the DB. Even if the rollback operation is performed at this time, the changes can not be undone
-
-
Concurrency of transactions
-
Dirty read
A transaction reads the result of a data operation that is not committed by another transaction. This is quite dangerous because it is likely that all operations will be rolled back
-
NonRepeatable Read
A transaction reads the same record twice, but gets different results. For example, after transaction T1 reads a certain data, transaction T2 modifies it. When transaction T1 reads the transaction again, the value is different from the previous time
-
Phantom Read
The transaction performs two queries (table operations) during the operation. The results of the second query include the data that does not appear in the first query or the data that appears in the first query is missing. This is because another transaction inserts data during the two queries
-
-
Isolation level of transaction
-
Read uncommitted
Read uncommitted: the lowest level. None of the above conditions can be guaranteed
-
Read one submit
Read committed: to avoid dirty reading
-
Repeatable reading
Repeatable read: it can avoid dirty reading and non repeatable reading. Unreal reading cannot be avoided
-
Serial read
Serializable: transactions can only be executed one by one, avoiding dirty reading, non repeatable reading and phantom reading. Slow execution efficiency, use with caution.
-
-
Propagation behavior of transactions
The first aspect of a transaction is propagation behavior. When a transaction method is called by another transaction, you must formulate how the transaction should be propagated. For example, a method may continue to run in an existing transaction, or it may start a new transaction and run in its own transaction. spring defines the propagation behavior in 7:
Communication behavior
meaning
PROPAGATION_REQUIRED
Indicates that the current method must run in a transaction. If the current transaction exists, the method will run in that transaction. Otherwise, it will start another new thing
PROPAGATION_SUPPORTS
Indicates that the current method does not need a transaction context, but if there is a current transaction, the method will run in the transaction
PROPAGATION_MANDATORY
Indicates that the method must run in a transaction. If the current transaction does not exist, an exception will be thrown
PROPAGATION_NEQUIRED_NEW
Indicates that the current method must run in its own transaction. A new transaction will be started. If there is a current transaction, this method is used. During execution, the current transaction will be suspended. If JTAransactionManager is used, you need to access the transaction manager
PROPAGATION_NOT_SUPPORTED
Indicates that the method should not run in a transaction. If there is a current transaction, the current transaction will be suspended while the method is running. If you use JATransactionManager, you need to access the TransactionManager
PROPAGATION_NEVER
Indicates that the current method should not run in a transaction context. If a transaction is currently running, an exception is thrown
PROPAGATION_NESTED
Indicates that if a transaction currently exists, the method will run in the absconded transaction. Nested transactions can be committed or rolled back independently of the current transaction. If the current transaction does not exist, its behavior is the same as PROPAGATON_REQUIRED. Note that different vendors support this propagation behavior. Refer to the documentation of the resource manager to confirm whether they support nested transactions
II Spring transaction operations
Case: simple simulated transfer
dao
public interface ICounterDao { void updateIncreaseManyById(Integer id,Integer many); void updateDecreaseManyById(Integer id,Integer many); } @Repository("getCounterDao") public class CounterDao implements ICounterDao { // Create a JdbcTemplate @Resource(name = "getJdbcTemplate") private JdbcTemplate jdbcTemplate; @Override public void updateIncreaseManyById(Integer id, Integer many) { String sql = "update counter set cmany= cmany + ? where id = ?"; int update = jdbcTemplate.update(sql, many, id); System.out.println("Update results "+update); } @Override public void updateDecreaseManyById(Integer id, Integer many) { String sql = "update counter set cmany= cmany - ? where id = ?"; int update = jdbcTemplate.update(sql, many, id); System.out.println("Update results "+update); } }
service
public interface ICounterService { void transfer(); } @Service("CounterService") public class CounterService implements ICounterService { // Persistent operation @Resource(name = "getCounterDao") private ICounterDao CounterDao; @Override public void transfer() { // Simple demonstration, write code directly here // For example, Zhang San (1) transfers 200 money to Li Si (2) CounterDao.updateDecreaseManyById(1, 1000); // Throw exception manually System.out.println(10 / 0); CounterDao.updateIncreaseManyById(2, 1000); } }
xml
<?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:aop="http://www.springframework.org/schema/aop" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--Scan component--> <context:component-scan base-package="com.zhj"/> <!--Import jdbc--> <context:property-placeholder location="jdbc.properties"/> <!--create data source--> <bean id="getDataSourcePool" class="com.mchange.v2.c3p0.ComboPooledDataSource" > <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <bean id="getJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg name="dataSource" ref="getDataSourcePool" /> </bean> </beans>
test
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class MyTest { @Resource(name = "CounterService") private CounterService counterService; @Test public void test(){ counterService.transfer(); } }
When an exception occurs, the data is changed only once, which violates the principles of atomicity and consistency of the database
III Implementation of transactions in Spring
Declarative transaction management
Declarative transaction management based on @ Transactional annotation
-
Modify the configuration xml to configure the transaction manager
<?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" xmlns:aop="http://www.springframework.org/schema/aop" 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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd "> <!--Scan component--> <context:component-scan base-package="com.zhj"/> <!--Import jdbc--> <context:property-placeholder location="jdbc.properties"/> <!--create data source--> <bean id="getDataSourcePool" class="com.mchange.v2.c3p0.ComboPooledDataSource" > <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${jdbc.url}" /> <property name="user" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </bean> <bean id="getJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg name="dataSource" ref="getDataSourcePool" /> </bean> <!--Transaction manager--> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name="dataSource" ref="getDataSourcePool"/> </bean> <!--Support transaction annotations--> <tx:annotation-driven transaction-manager="txManager" /> </beans>
-
Add @ Transactional to the required method
@Service("CounterService") public class CounterService implements ICounterService { // Persistent operation @Resource(name = "getCounterDao") private ICounterDao CounterDao; @Override // What exception does the isolation level propagation path encounter to roll back @Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED,rollbackFor = ArithmeticException.class) public void transfer() { // Simple demonstration, write code directly here // For example, Zhang San (1) transfers 200 money to Li Si (2) CounterDao.updateDecreaseManyById(1, 1000); // Throw exception manually if(true){ System.out.println(10/0); } CounterDao.updateIncreaseManyById(2, 1000); } }
-
The call uses the corresponding interface to receive
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class MyTest { @Resource(name = "CounterService") private ICounterService counterService; @Test public void test(){ counterService.transfer(); } }