Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”. This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

Source articles have been uploaded to the cloud, the reference document README. Md deployment operation Handwritten series code cloud address: [email protected]: tangjingshan/the TJS – study – m… This article code paths: {@ link the TJS. Styudy. Mini. Springboot. Demo. Transaction. DoTestOfAppplaction# main}

preface

This article is a summary of the mini version of @Transactional Transactional written by hand. For details on how to implement this version, see the blog @Transactional Transactional Written by Hand.

How do I run Demo

  1. Execute the following script to create the required database
DROP TABLE IF EXISTS `user_test`;
CREATE TABLE `user_test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) DEFAULT NULL,
  `balance` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
INSERT INTO `user_test` VALUES (1.'a'.10);
INSERT INTO `user_test` VALUES (2.'b'.20);
Copy the code
  1. Introduce the code cloud project at the beginning of this article

  2. Find a test class the TJS. Styudy. Mini. Springboot. Demo. Transaction. DoTestOfAppplaction# main

  1. Change data source information in the test class

  2. Observe what the test code does

UpdateSonOfBalance calls the updateFatherOfUserName method, where the Propagation level of the updateFatherOfUserName thing is Propagation.requires_new), which creates a new thing

@MiniTransactional @Service public class TransactionalService { @Autowired JdbcTemplate jdbcTemplate; @Autowired TransactionalService transactionalService; //@MiniTransactional(propagation = Propagation.REQUIRED) public void updateSonOfBalance(boolean isThrowException) { jdbcTemplate.execute("UPDATE user_test ut SET ut.balance = ut.balance + 1 WHERE ut.id = 1;" ); / / injection of beans to go agent transactionalService. UpdateFatherOfUserName (isThrowException); UpdateFatherOfUserName = new fatherofusername = new fatherofUsername = new fatherofUsername Will be under the same thing. / / this updateFatherOfUserName (isThrowException); System.out.println("updateFatherOfUserName done, data in library... ") ); "} @minitransactional (propagation = propagation.REQUIRES_NEW)" Public void updateFatherOfUserName(Boolean isThrowException) {// If the isolation level is Propagation.REQUIRES_NEW // UpdateSonOfBalance changes data 1 but does not commit the transaction, so data 1 is still locked. //jdbcTemplate.execute("UPDATE user_test ut SET ut.user_name = CONCAT(ut.user_name, "_", ut.balance+1) WHERE ut.id = 1;" ); jdbcTemplate.execute("UPDATE user_test ut SET ut.user_name = CONCAT(ut.user_name, "_", ut.balance+1) WHERE ut.id = 2;" ); if (isThrowException) { throw new RuntimeException("test rollback....." ); } } public void find(String pre) { List<JSONObject> userTests = jdbcTemplate.query("select * from user_test ut where ut.id = 1;" ); System.out.println(pre + ":" + json.tojsonString (userTests)); }}Copy the code

After the script is initialized, the following information indicates whether the rollback is successful

  • Roll back the success

  • Roll back the failure

  1. Comment out the@MiniTransactionalAnnotation to demonstrate rollback failure scenario

  1. add@MiniTransactionalNotes to demonstrate a rollback success scenario

  1. add@MiniTransactionalNotes to demonstrate the segmentation submission success scenario

Change the startup class to no error, and make a breakpoint at the following locations

Due to theupdateFatherOfUserNameA new Connection is used, soupdateFatherOfUserNameOperations on the database within a method should be performed inupdateFatherOfUserNameThe method is put into the library after it is called

“At this point, mini declarative transactions @minitransactional work well for most stand-alone applications, but distributed transactions require addressing the issues of how to trigger commit/rollback transactions across services, which we’ll cover later.

Analyze what needs to be done

From the analysis of the last blog post, you can see that the declarative Transactional Transactional does the following

  1. Load the relevant configuration classes into IOC
  2. The section intercepts the transaction, and what the section does is pseudocode as follows
1. Instantiate TransactionInfo try {//2. Call the target method} catch (Exception ex) {//3.1 Roll back the transaction and release the resource throw ex; } //3.2 Commit a transaction to release resourcesCopy the code
  1. writeJdbcTemplate
  2. Writing test classes

Start writing by hand

Next, according to the above analysis, I began to write the specific handwritten code in the code cloud project. If there are more codes, I will not paste the code location of key nodes here

/** ** {@link tjs.styudy.mini.springboot.transaction.aop.TransactionAspect#around(org.aspectj.lang.ProceedingJoinPoint)} * Commit transaction: {@link tjs.styudy.mini.springboot.transaction.config.TransactionManager#commit(tjs.styudy.mini.springboot.transaction.config.Tr } * Rollback transaction: {@link tjs.styudy.mini.springboot.transaction.config.TransactionManager#rollBack(tjs.styudy.mini.springboot.transaction.config. TransactionInfo)} * Clear resources: {@ link the TJS. Styudy. Mini. Springboot. Transaction. The config. TransactionManager# cleanupAfterCompletion ()} * get the current connection: {@link tjs.styudy.mini.springboot.transaction.config.ConnectionHolder#getResource()} */Copy the code

4. Problems encountered

4.1 Propagation.REQUIRES_NEW Causes a deadlock

The reason:

  1. updateSonOfBalanceConnectionA, modify row 1 to add a row lock to it
  2. updateFatherOfUserNameConnectionB is also going to modify the row with ID 1, butupdateSonOfBalanceThe transaction has not committed and the row lock has not been released
  3. Eventually, the result is a deadlock, waiting for a timeout

This scenario is not limited to the Mini version, but also the Spring version

4.2 reduction ThreadLocal

Things to do:

  1. Enter theupdateSonOfBalanceThreadLocal is currently ConnectionA
  2. Enter theupdateFatherOfUserNameThreadLocal is currently ConnectionB
  3. The end of theupdateFatherOfUserName, current value of ThreadLocalRevert to ConnectionA

How to restore? The Mini version (and spring does a similar thing) stores the value of ThreadLocal as an objecttjs.styudy.mini.springboot.transaction.config.ConnectionHolder, which has two propertiesReset the current ThreadLocal value to the ConnectionHolder of the previous method when the resource is freed at the end of each methodThis satisfies both of the following requirements

  1. A method that starts something new is restored to the Connection of the previous method at the end of the call

UpdateFatherOfUserName the current ThreadLocal value is restored to ConnectionA

  1. The top-level method, when terminating the call, sets the current value of ThreadLocal to null, indirectly cleaning up the resource

End updateSonOfBalance restores the current ThreadLocal value to NULL

4.3 Handling the case without annotations

If there is no identification annotation, the connection should be taken directly from the connection pool and returned, as shown in the JdbcTemplate code

Public void execute(String SQL) {// Identify MiniTransactional annotations, Gets the current Connection from the threadLocal Connection Connection. = ConnectionHolder getCurConnectionStatic (); if (connection ! = null) { try (Statement stmt = connection.createStatement()) { stmt.execute(sql); return; } catch (SQLException throwables) { throw new RuntimeException(throwables); }} / / no logo MiniTransactional annotations, from the Connection pool for the current Connection try (Connection connectionNew. = this dataSource. The getConnection (); Statement stmtNew = connectionNew.createStatement()) { stmtNew.execute(sql); } catch (SQLException throwables) { throw new RuntimeException(throwables); }}Copy the code