Article directory
Spring transaction
Spring quick start
Transaction role : guarantee a series of database operations at the data layer with success and failure
The role of Spring transactions: to ensure that a series of database operations succeed and fail at the data layer or business layer
Spring provides an interface PlatformTransactionManager to handle transactions
public interface PlatformTransactionManager{
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
The most basic transaction management implementation class of PlatformTransactionManager interface DataSourceTransactionManager
public class DataSourceTransactionManager {
……
}
Case Study: Simulating a Transfer Between Bank Accounts
Requirement: Realize the transfer operation between any two accounts
Demand miniaturization: A account subtracts money, B account adds money
analyze:
①: The data layer provides basic operations, the designated account loses money (outMoney), and the designated account adds money (inMoney)
②: The business layer provides the transfer operation (transfer), calling the operation of reducing money and adding money
③: Provide 2 account numbers and the operation amount to perform the transfer operation
④: Build the above operations based on Spring integration of MyBatis environment
Result analysis:
①: When the program is executed normally, the account amount A is subtracted and B is added, there is no problem
②: After the execution of the subtraction of the account amount A is completed, the transfer fails after an abnormality occurs in the program, but the operation of subtracting the account amount A succeeds before the abnormality, and the operation of adding the account amount B fails after the abnormality. This is a very big security risk
Implementation steps :
First create a database with id, name, money three fields, and two pieces of data
create table tb_account(
id int primary key auto_increment,
name varchar(10),
money varchar(10)
)
insert into tb_account (name, money)
values ("chenyq", 1000), ("zhangsan", 1000);
Based on the integration of Mybatis and Junit environment ( you can read the previous article if you don’t know ), create a service layer interface AccountService
public interface AccountService {
/**
* @param out 转出对象
* @param in 转入对象
* @param money 转出金额
*/
void transfer(String out, String in, double money);
}
Create the dao layer interface AccountDao, and define a method for adding and subtracting money
public interface AccountDao {
@Update("update tb_account set money = money + #{money} where name = #{name};")
void inMoney(@Param("name") String name, @Param("money") String money);
@Update("update tb_account set money = money - #{money} where name = #{name};")
void outMoney(@Param("name") String name, @Param("money") String money);
}
The AccountServiceImpl implementation class of the service layer AccountService interface calls the data layer method
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void transfer(String out, String in, double money) {
accountDao.outMoney(out, money); // 账户减钱
accountDao.inMoney(in, money); // 账户加钱
}
}
Test in the test class, you can see that the modification is successful in the database
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() {
accountService.transfer("chenyq", "zhangsan", 100);
}
}
Next, in the AccountServiceImpl implementation class, after simulating the chenyq account transfer money out, the program is abnormal
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
public void transfer(String out, String in, double money) {
accountDao.outMoney(out, money); // 账户减钱
int i = 1 / 0; // 模拟程序出现异常
accountDao.inMoney(in, money); // 账户加钱
}
}
Re-execute the test class, we can find that although the program reports an error, the money of the chenyq account in the database has decreased by 100
We need to start the Spring transaction, so that the operations of adding money and reducing money can succeed or fail at the same time. The annotation transaction can be added to the business method to indicate that the current method starts the transaction, or it can be added to the interface to indicate that all methods of the current interface start the transaction. Spring enables The transaction steps are as follows
Step 1: Use the @Transactional annotation to start the task, but we generally do not start the transaction in the method of the implementation class, but in the method of the interface to reduce the degree of coupling
public interface AccountService {
/**
* @param out 转出对象
* @param in 转入对象
* @param money 转出金额
*/
@Transactional // 开启事务
void transfer(String out, String in, double money);
}
Step 2: In the JdbcConfig independent configuration file, define and set a transaction manager, and hand over the transaction manager to Spring management
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
Step 3: In the Spring core configuration file, enable Spring's annotation support for opening transactions through the EnableTransactionManagement annotation (in simple terms, tell Spring to use annotations to open transactions )
@Configuration
@ComponentScan("com.chenyq")
@PropertySource("classpath:jdbc.properties")
@Import({
JdbcConfig.class, MybatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
At this point, the transaction is opened and completed. The method of opening the transaction is the same as success and failure. There will be no situation where one business succeeds and the other fails due to abnormalities.
Transaction related configuration
The transaction has the following related configuration :
Attributes | effect | example |
---|---|---|
readOnly | Set whether it is a read-only transaction | readOnly=true read-only transaction |
timeout | Set transaction timeout | timeout = -1 (never timeout) |
rollbackFor | Set transaction rollback exception (class) | rollbackFor = {NullPointException.class} |
rollbackForClassName | Set transaction rollback exception (String) | same format as string |
noRollbackFor | Set transaction not to rollback exception (class) | noRollbackFor = {NullPointException.class} |
noRollbackForClassName | Set transaction not to rollback exception (String) | same format as string |
propagation | Set transaction propagation behavior | …… |
Example usage:
@Transactional(readOnly = true, timeout = -1)
void transfer(String out, String in, double money);
Some exceptions do not participate in transaction rollback, we need rollbackFor to set these exceptions to participate in transaction rollback
@Transactional(rollbackFor = {
NullPointException.class})
void transfer(String out, String in, double money);
If we call another method 2 in a method 1 that has opened a transaction, we need to set the transaction multicast behavior when we want the method 1 transaction to roll back, but the method 2 does not roll back
@Transactional(propagation = Propagation.REQUIRES_NEW)
Transaction propagation behavior has additional properties
propagation properties | Affairs administrator | business coordinator |
---|---|---|
REQUIRED (default) | Turn on T | Join T |
none | New T2 | |
REQUIRES_NEW | Turn on T | New T2 |
none | New T2 | |
SUPPORTS | Turn on T | Join T |
none | none | |
NOT_SUPPORTED | Turn on T | none |
none | none | |
MANDATORY | Turn on T | Join T |
none | ERROR | |
NEVER | Turn on T | ERROR |
none | none | |
NESTED | Set savePoint, once the transaction is rolled back, the transaction will be rolled back to savePoint, and the client will respond to commit/rollback |