Preface
I recently looked at the N scenarios of spring transaction failures summarized on the Internet. The scenarios listed on the Internet are as follows
- The database engine does not support transactions
- Not managed by Spring
- Method is not public
- Self-calling problem
- The data source does not have a transaction manager configured
- Does not support transactions
- Was eaten abnormally
- Exception type error
One of the exceptions was eaten, which would cause the transaction to be unable to roll back. This aroused my curiosity, whether this is really the case, and I just didn’t write the material, let’s talk about the chemical reaction of transactions and exceptions in certain scenarios.
Sample material
1. A table with no business meaning, just for demonstration
CREATE TABLE `tx_test` (
`id` bigint NOT NULL AUTO_INCREMENT,
`tx_id` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci
2. A service interface that does not follow the coding standard
public interface TxTestService {
void saveTxTestA();
void saveTxTestB();
}
3. A non-essential unit test
@SpringBootTest
class TransactionDemoApplicationTests {
@Autowired
private TxTestService txTestService;
@Test
void testTxA() {
txTestService.saveTxTestA();
}
@Test
void testTxB() {
txTestService.saveTxTestB();
}
}
Note: uses junit5, so there is no need to add
@RunWith(SpringRunner.class)
You can automatically inject
Dinner
Note: demonstrated, I will clear the table first, and then demonstrate the next example
Scene 1: Abnormally eaten
1. Example 1: The code is as follows
private String addSql = "INSERT INTO tx_test (tx_id) VALUES (?);";
@Override
@Transactional(rollbackFor = Exception.class)
public void saveTxTestA() {
jdbcTemplate.update(addSql, "TX-A");
try {
int i = 1 % 0;
} catch (Exception e) {
e.printStackTrace();
}
}
problem thinking:
jdbcTemplate.update(addSql, "TX-A");
Is this sentence able to insert data successfully?
- Run unit test method
@Test
void testTxA() {
txTestService.saveTxTestA();
}
Get the following result
answer: can be inserted
Reason:
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
This is part of the source code of Spring Transaction. When our business code is captured, he cannot execute completeTransactionAfterThrowing(txInfo, ex); this method, this method is to perform the corresponding rollback operation, the relevant source code is as follows
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
2. Sample code two
@Autowired
private JdbcTemplate jdbcTemplate;
private String addSql = "INSERT INTO tx_test (tx_id) VALUES (?);";
@Autowired
private TxTestServiceImpl txTestService;
@Override
@Transactional
public void saveTxTestA() {
jdbcTemplate.update(addSql, "TX-A");
try {
txTestService.saveTxTestC();
} catch (RuntimeException e) {
e.printStackTrace();
}
}
@Transactional
public void saveTxTestC() {
jdbcTemplate.update(addSql, "TX-C");
throw new RuntimeException("异常了");
}
problem thinking:
jdbcTemplate.update(addSql, "TX-A");
Is this sentence able to insert data successfully?
- Run unit test method
@Test
void testTxA() {
txTestService.saveTxTestA();
}
Get the following result
Answer: has been rolled back and cannot be inserted successfully
Seeing this answer, some friends may be confused. Why the last example caught the exception and the data can be inserted successfully. This time the exception was also caught, but the data could not be inserted successfully.
Reason: This has to start with the propagation behavior of spring transactions. The default propagation behavior of spring transactions is REQUIRED. According to the meaning of the eight-legged essay REQUIRED is if there is a transaction currently, then join the transaction, if there is no transaction currently, create a new transaction
In the example
@Transactional
public void saveTxTestC() {
jdbcTemplate.update(addSql, "TX-C");
throw new RuntimeException("异常了");
}
saveTxTestC will be added to the transaction of saveTxTestA, that is, saveTxTestC and saveTxTestA belong to the same transaction, so saveTxTestC throws an exception and rolls back. According to the atomicity of the transaction, saveTxTestA will also roll back
problem extension: If you want saveTxTestC to throw an exception, saveTxTestA can still be inserted, is there any solution?
answer: in saveTxTestC add the following comment
@Transactional(propagation = Propagation.REQUIRES_NEW)
REQUIRES_NEW it will start a new transaction. If a transaction already exists, first suspend the existing transaction
Scene 2: Continue the extension of the previous scene
Example: Added Propagation.REQUIRES_NEW annotation to the method
@Autowired
private JdbcTemplate jdbcTemplate;
private String addSql = "INSERT INTO tx_test (tx_id) VALUES (?);";
@Autowired
private TxTestServiceImpl txTestService;
@Override
@Transactional
public void saveTxTestB() {
jdbcTemplate.update(addSql, "TX-B");
txTestService.saveTxTestD();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveTxTestD() {
jdbcTemplate.update(addSql, "TX-D");
throw new RuntimeException("异常了");
}
problem thinking:
jdbcTemplate.update(addSql, "TX-B");
Is this sentence able to insert data successfully?
- Run unit test method
@Test
void testTxB() {
txTestService.saveTxTestB();
}
Get the following result
Answer: has a rollback and cannot be inserted successfully
Seeing this answer, some friends may say, are you kidding me? Didn't you just say that adding REQUIRES_NEW will start a new transaction, that is, saveTxTestD and saveTxTestB are already different transactions, saveTxTestD rolls back, close saveTxTestB What's the matter, saveTxTestB is reasonable to be inserted
Reason: added REQUIRES_NEW, saveTxTestD and saveTxTestB are indeed different transactions, saveTxTestD rollback does not affect saveTxTestB. saveTxTestB will be rolled back, purely because of the exception thrown by saveTxTestD, it is passed to saveTxTestB, which causes saveTxTestB to roll back
problem extension: If you want saveTxTestD to throw an exception, saveTxTestB can still be inserted, is there any solution
The answer is as follows:
@Override
@Transactional
public void saveTxTestB() {
jdbcTemplate.update(addSql, "TX-B");
try {
txTestService.saveTxTestD();
} catch (Exception e) {
e.printStackTrace();
}
}
In saveTxTestB, catch the exception thrown by saveTxTestD
Run the unit test again and get the following results
to sum up
We may memorize some eight-legged essays for interviews, but the actual scene may be much more complicated than these eight-legged essays. Therefore, when we look at these eight-legged essays, we can think more and we may get some things that we usually ignore.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。