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) :

  1. 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.

  1. 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.

  1. Isolation,

Transaction execution is not interfered with by other transactions, and the intermediate results of transaction execution must be transparent to other transactions.

  1. 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

@Transactionalannotations

The annotated method performs the rollback and commit of a transaction through AOP

In the firstDispatcherServletAdd the interceptor to the CgLib classReflectiveMethodInvocationThere are various implementations of interceptors, such as: parameter validation, AOP pre-intercept, post-intercept, throw exception intercept, surround intercept, and so onTransactionInterceptorTransaction 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

  1. Non-public methods fail

Internal source code returns NULL for non-public execution. Non-public transaction support is not supported

  1. 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

  1. The annotated methods are summarized incacheThe 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
  1. An engine that does not support transactions was used

The transaction engine in MySQL is InnoDB

Refer to the article

  1. JavaGuide (gitee.io)
  2. Rattle off six @Transactional annotation failure scenarios (juejin.cn)
  3. Database transaction concepts and features (biancheng.net)
  4. MySQL :: MySQL 5.7 Reference Manual :: 14.7.2.1 Transaction Isolation Levels

Read the original