Preface
During the development last week, I encountered transaction-related problems. The test environment was normal but the deployment to the formal environment throws an exception. I worked overtime for several days to solve the problem. Now I will review the problem and review the previous knowledge points. . If there are any errors, please correct me.
What is a transaction
The transaction of the database is a mechanism, a sequence of operations, including database operation commands. The transaction submits or cancels the operation request to the system together with all the commands as a whole, that is, this group of commands either succeeds or fails.
4 characteristics of transactions (ACID):
- Atomicity
A transaction is a complete operation. The elements in the transaction are indivisible. The elements in the transaction must be committed or rolled back as a whole. If any element in the transaction fails, the entire transaction will fail.
- consistency
Before the transaction begins, the data must be in a consistent state; after the transaction is over, the data state must also remain consistent. Modifications made to the data through transactions cannot damage the data.
- Isolation
The execution of the transaction is not interfered by other transactions, and the intermediate results of the execution of the transaction must be transparent to other transactions.
- Persistence
For committed transactions, the system must ensure that the changes made by the transaction to the database are not lost, even if the database fails.
How to implement transactions
In the current business development process, Spring framework is the mainstay. Spring supports two ways of transaction management programmatic transaction and
declarative transaction
Programmatic transaction
The so-called programmatic transaction is to manually complete the commit of the transaction in the code, and roll back when an exception occurs.
PlatformTransactionManager
in the implementation class
@Autowired
private PlatformTransactionManager transactionManager;
@Override
public Map<String, Object> saveResource(MultipartFile file) {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// 相关业务
// 手动提交
transactionManager.commit(status);
} catch (Exception e) {
log.error("Exception:{}", ExceptionUtil.stacktraceToString(e));
// 发生异常时进行回滚
transactionManager.rollback(status);
}
}
Declarative transaction
The so-called declarative transaction is to use the @Transactional
annotation to open the transaction. The annotation can be placed on the class and method. When placed on the class, all public methods of the class will open the transaction; when placed on the method, it means that the current method supports transactions.
@Transactional
@Override
public Map<String, Object> saveResource(MultipartFile file) {
// 相关业务
}
@Transactional
annotation
The method marked with this annotation completes the rollback and commit of the transaction through AOP
First DispatcherServlet
, and then add interceptors through CgLib. In class ReflectiveMethodInvocation
, there are a variety of interceptor implementations, such as: parameter verification, AOP pre-interception, post-interception, throw exception interception, surround interception Wait, there is TransactionInterceptor
transaction interceptor 060bc879d54931
Call invoke()
method
The general execution logic of the transaction
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,final InvocationCallback invocation) throws Throwable {
...
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
// 切点
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
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);
}
if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
// 正常执行,提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
...
}
completeTransactionAfterThrowing()
method
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
// 判断事务状态不为空的情况下
if (txInfo != null && txInfo.getTransactionStatus() != null) {
// 输出debug日志
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
// 在指定异常下回滚
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;
}
}
// 不是指定的异常,任然提交
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
}
commitTransactionAfterReturning()
method
// 结束执行,且没有抛出异常,就执行提交
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
}
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
Common attribute configuration
propagation
Configure the propagation characteristics of the transaction, the default is: required
Spreading | description |
---|---|
required | Execute in a transaction state, if not, create a new transaction |
required_news | Create a new transaction, and suspend the current transaction if there is a current transaction |
supports | If there is a transaction, it will run under the current transaction, and if there is no transaction, it will run in a no-transaction state. |
not_supported | Run in a transaction-free state, if there is a transaction, suspend the current transaction |
mandatory | There must be a transaction, if there is no transaction, an exception IllegalTransactionStateException |
never | Does not support transactions, throws an exception if there is a transaction |
nested | If there is a current transaction, start another transaction in the current transaction |
isolation
Configure transaction isolation level, the default is the default isolation level of the current database (MySQL is REPEATABLE-READ)
View the isolation level of the data
Isolation | description |
---|---|
READ UNCOMMITTED (uncommitted degree) | Read uncommitted content, all transactions can see the results of other uncommitted transactions, seldom actually used (dirty reads will occur) |
READ COMMITTED (commit to read) | A transaction can only read the modified data of the committed transaction of another transaction, and every time other transactions modify and commit the data, the transaction can query the latest value |
REPEATABLE READ (repeatable read) | One is that the transaction reads the modified data that has been committed by the transaction. When a record is read for the first time, even when other transactions modify the record and commit, it is still the first time to read this record later. Instead of reading different data each time |
SERIALIZABLE (Serialization) | The transaction is executed serially, without trampling, avoiding dirty reads, phantom reads, and non-repeatability, but the efficiency is low |
timeout
Configure the transaction timeout time, the default is: -1
readOnly
When the method only needs to query data, configure to true
rollbackFor
The configuration needs to roll back data in those abnormal situations. By default, only RuntimeException
and Error
are rolled back. It is best to configure it as Exception
Problems encountered during development
Large transaction causes transaction timeout
There was a function last week, including FTP file upload and database operation. I don't know if it is due to the network. It takes a long time for FTP to transfer files to the other party's server, which makes subsequent write database operations impossible. Later, by splitting the function, file uploading is not executed inside the transaction, and just moved to the outer layer.
Transaction failure
- Non-public methods fail
In the source code, it returns null for non-public execution, and does not support non-public transaction support
- rollbackFor is configured as default
After configuring rollbackFor as the default value, when an unchecked exception is thrown, the transaction cannot be rolled back
- The methods marked with annotations are summarized in
cache
and the exception is caught, and there is no basis to throw
Resource resource = new Resource();
resource.setCreateUser("admin");
try {
resourceMapper.insertUseGeneratedKeys(resource);
} catch (Exception e) {
// 在日志中输出堆栈信息,并抛出异常
log.error("Exception:{}", ExceptionUtil.stacktraceToString(e));
throw new RuntimeException("系统错误");
}
- An engine that does not support transactions is used
The engine that supports transactions in MySQL is innodb
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。