This article is participating in “Java Theme Month – Java Development in Action”, see the activity link for details
preface
In the development last week, I encountered some problems related to transaction. The test environment was normal, but when I deployed to the formal environment, I threw exceptions and worked overtime for several days to solve this problem. Now I make a review of this problem and review the previous knowledge points. If there are any mistakes, please correct them.
What is a transaction
A database transaction is a mechanism, a sequence of operations, that contains commands for database operations. A transaction submits or revokes an action request to the system as a whole, a group of commands that either succeeds or fails.
Four properties of transactions (ACID) :
- atomic
A transaction is a complete operation. Elements within a transaction are indivisible. Elements in a transaction must be committed or rolled back as a whole. If any element in the transaction fails, the entire transaction fails.
- consistency
The data must be in a consistent state before the transaction begins; The state of the data must also be consistent after the transaction ends. Changes made to data through transactions cannot corrupt the data.
- Isolation,
Transaction execution is not interfered with by other transactions, and the intermediate results of transaction execution must be transparent to other transactions.
- persistence
For committed transactions, the system must ensure that 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, are based on the Spring framework. Spring supports two types of transaction management programmatic and declarative transactions
Programmatic transaction
A programmatic transaction is a transaction that is committed manually in code and rolled back when an exception occurs.
Inject PlatformTransactionManager in the implementation class
@Autowired
private PlatformTransactionManager transactionManager;
@Override
public Map<String, Object> saveResource(MultipartFile file) {
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// Related business
// Manually commit
transactionManager.commit(status);
} catch (Exception e) {
log.error("Exception:{}", ExceptionUtil.stacktraceToString(e));
// Roll back when an exception occurstransactionManager.rollback(status); }}Copy the code
Declarative transaction
This annotation can be placed ona class or a method. When placed ona class, all public methods of that class open the transaction. When placed on a method, it indicates that the current method supports transactions
@Transactional
@Override
public Map<String, Object> saveResource(MultipartFile file) {
// Related business
}
Copy the code
@Transactional
annotations
The annotated method performs the rollback and commit of a transaction through AOP
In the firstDispatcherServlet
Add the interceptor to the CgLib classReflectiveMethodInvocation
There are various implementations of interceptors, such as: parameter validation, AOP pre-intercept, post-intercept, throw exception intercept, surround intercept, and so onTransactionInterceptor
Transaction interceptor
Call the invoke() method
The rough execution logic of a transaction
protected Object invokeWithinTransaction(Method method, @NullableClass<? > targetClass,final InvocationCallback invocation) throws Throwable {... PlatformTransactionManager ptm = asPlatformTransactionManager(tm);/ / point of tangency
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null| |! (ptminstanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
// Create transaction
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Execute the business logic around the pointcut
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
// An exception occurs during the rollback
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); }}// Execute normally, commit transaction
commitTransactionAfterReturning(txInfo);
returnretVal; }... }Copy the code
CompleteTransactionAfterThrowing () method
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
// Determine the transaction state is not empty
if(txInfo ! =null&& txInfo.getTransactionStatus() ! =null) {
// Outputs debug logs
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
// Rollback under the specified exception
if(txInfo.transactionAttribute ! =null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
// Start rollback
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);
throwex2; }}// It is not the specified exception
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);
throwex2; }}}}Copy the code
CommitTransactionAfterReturning () method
// Execute commit without throwing an exception
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()); }}Copy the code
Common Attribute Configuration
propagation
Configure transaction propagation, default: required
disseminated | describe |
---|---|
required | Execute if there is a transaction, or create a new transaction if there is not |
required_news | Create a new transaction and suspend the current transaction if one exists |
supports | If there is a transaction, it runs under the current transaction, and if there is no transaction, it runs without a transaction |
not_supported | Runs in the non-transaction state and suspends the current transaction if one exists |
mandatory | A transaction must exist. If no transaction exists, throw an exceptionIllegalTransactionStateException |
never | Transactions are not supported. Throw an exception if there are transactions |
nested | When there is a transaction, there is a new transaction within the current transaction |
isolation
Set transaction isolation level to REPEATABLE-READ (MySQL)
View the isolation level of the data
Isolation, | describe |
---|---|
READ UNCOMMITTED | Read uncommitted content, all transactions can see the results of other uncommitted transactions, rarely used (dirty reads occur) |
READ COMMITTED | A transaction can only read the modified data of a committed transaction by another transaction, and the transaction can query the latest value each time the data is modified and committed by another transaction |
REPEATABLE READ (REPEATABLE READ) | One is that a transaction reads changes that have actually been committed by the transaction. The first time a record is read, even if other transactions have modified the record and committed it, the second time the record is read, it is still the first read value, rather than reading different data each time |
SERIALIZABLE | Transaction serial execution, no stampede, avoid dirty read, unreal read and non-repeatability, but inefficient |
timeout
Set the transaction timeout period. The default value is -1
readOnly
Set this parameter to true when only data needs to be queried internally
rollbackFor
The configuration needs to roll back data in those exceptional cases. By default, only RuntimeException and Error are rolled back, and it is best to configure Exception in development
Problems encountered during development
A transaction timed out due to a large transaction
Last week, there was a function that included FTP file uploading and database operation. I don’t know whether it was due to the network, it took a long time for FTP to transfer files to the server of the other party, resulting in the failure of subsequent library writing operations. After that, by splitting the function, the file upload is not performed inside the transaction, but is moved to the outer layer.
Transaction failure
- Non-public methods fail
Internal source code returns NULL for non-public execution. Non-public transaction support is not supported
- RollbackFor is set to the default value
When rollbackFor is configured as the default, a transaction cannot be rolled back when a non-checking exception is thrown
- The annotated methods are summarized in
cache
The exception is caught in the. There is no base throw
Resource resource = new Resource();
resource.setCreateUser("admin");
try {
resourceMapper.insertUseGeneratedKeys(resource);
} catch (Exception e) {
// Outputs stack information in the log and throws an exception
log.error("Exception:{}", ExceptionUtil.stacktraceToString(e));
throw new RuntimeException("System error");
}
Copy the code
- An engine that does not support transactions was used
The transaction engine in MySQL is InnoDB
Refer to the article
- JavaGuide (gitee.io)
- Rattle off six @Transactional annotation failure scenarios (juejin.cn)
- Database transaction concepts and features (biancheng.net)
- MySQL :: MySQL 5.7 Reference Manual :: 14.7.2.1 Transaction Isolation Levels
Read the original