3
头图

Author: Xiao Fu Ge Blog: https://bugstack.cn

Precipitate, share, grow, and let yourself and others gain something! 😄

1. Preface: A Bug

没想到一个Bug,竟然搞我两次!

I was probably addicted to scrolling. I couldn't sleep horizontally and vertically. I sat up and turned on the Mac and the external monitor. There was no reason for this bug. I silently watched the abnormally printed screen. One was mine and the other was mine.


It may be the volume source code recently, and the volume is addicting. First "Handwriting Spring" , then "Handwriting Mybatis" , but I didn't expect a small problem to get me twice!

Today's problem is mainly reflected in the Mybatis that everyone usually uses. When inserting data, we can return the return value of the library table index through the input parameter object. But through my own handwritten Mybatis, every time it returns 0, not the index value inserted into the library table at the end. Because it was written by hand, instead of using Mybatis directly, I will continue to investigate from file parsing, object mapping, SQL query, and result encapsulation, etc., but the problem is not here? !

  • This is the configuration of this selectKey. After executing the insert SQL, start executing to obtain the last index value.
  • Usually, as long as the configuration is correct and there is a corresponding id field in the returned object, the return value can be obtained correctly. PS: The problem is here, Mybatis handwritten by Brother Fu only returns a 0!

2. Analysis: Diagnosing Abnormalities

Most of the R&D partners may not have read the Mybatis source code, so they may not know what happened here. Brother Fu will draw a picture for you and tell you what happened to make the returned result 0.

  • The processing process of Mybatis can be divided into two parts, one part is parsing and the other part is use. When parsing, the insert tag statement in Mapper XML is parsed, and the selectKey tag is parsed at the same time. After the final parsing is completed, the parsed statement information is stored using the MappedStatement mapping statement class. It is convenient for subsequent operations in DefaultSqlSession to be obtained and used from the Configuration configuration item.
  • Then there is a very important point here, that is, when insert is executed, it also contains a query operation. That is to say, we will include two execution statements in one Insert. Important : The bug happened here, why? Because when these two statements are executed at the beginning, when obtaining the link, each one obtains a new link, so that is to say, insert xxx, select LAST_INSERT_ID() is actually wrong when the two connection connections are executed. Yes, the index ID after insertion cannot be obtained. Only under a link or a transaction (one commit) can the transaction feature be obtained, and the self-incrementing ID after the inserted data can be obtained.
  • And because this part first hand-written JdbcTransaction to implement the Transaction interface to obtain a connection, each time it is a new link, the code block is as follows;

    • The link acquisition here does not have an if null judgment at the beginning, and each time the link is obtained directly, so the two SQL operations under this non-one link will definitely not get the correct result, which is equivalent SELECT LAST_INSERT_ID() just executing- SELECT LAST_INSERT_ID() So the final query result is 0! You can test this statement by copying it into the SQL query tool and executing it

3. Shock: the same pit

😂 But in fact, the problem of such a link was also encountered in Xiao Fu's handwritten Spring.

In Spring, there is a part about transaction processing. In fact, the operation of these transactions is also the packaging operation of JDBC, which relies on the link obtained by the data source to manage the transaction. And we usually use Spring in combination with the way Mybatis configures the data source, so how do we get the same link when operating multiple SQL statements under one transaction? Because from the above 👆🏻 case, we know that the characteristics of guaranteed transactions need to be under the same link, even if operating multiple SQL

Due to the operation of multiple SQLs, it is already equivalent to obtaining a new Session every time, and a new link is obtained from the connection pool, but in order to achieve the characteristics of transactions, it is necessary to have multiple SQLs under transaction operations before. Transaction operations need to be turned on, either manually or with annotations.

The opening action processing of this transaction does some restrictions on transaction propagation behavior and isolation level. In fact, it is more important that the links obtained by multiple SQL executions need to be the same. Therefore, ThreadLocal is introduced here based on its synchronization feature of saving information under the same thread operation. In fact, the link obtained from the transaction here is actually saved in the TransactionSynchronizationManager#resources property.

Although it's just such a small piece of content, when Brother Fu first wrote Spring, he missed it. It was not until the test that I found that the link discovery transaction was always unsuccessful. At first, I thought that the whole aspect logic was not cut in or my operation method was wrong. Until the debugging code was checked step by step, it was found that the execution of multiple SQLs was not the same link obtained, so the transaction could not take effect.

Fourth, common: transaction failure

It may be such a small link problem, and sometimes it will cause a bunch of exceptions. If we have not studied the source code, then we may not know how such a problem occurs. Therefore, in-depth research and exploration can make it easier and more direct when you explain a problem.

Then you say, what are the reasons for the failure of the transaction? - Share some common ones, if you come across others, you can send them to the comment area to have a look together.

  1. The database engine does not support transactions: Take MySQL as an example, its MyISAM engine does not support transaction operations, and InnoDB is the engine that supports transactions. Generally, InnoDB is used to support transactions. https://dev.mysql.com/doc/refman/8.0/en/storage-en... The default storage engine starting from MySQL 5.5.5 is: InnoDB, the previous default is: MyISAM, so this It is worth noting that the underlying engine does not support transactions no matter how you do it.
  2. Methods are not public: From the official Spring documentation [When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised , but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.] @Transactional can only be used on public methods, otherwise the transaction will not fail, If you want to use it on non-public methods, you can enable AspectJ proxy mode.
  3. Not managed by Spring: // @Service - 这里被注释掉了 public class OrderServiceImpl implements OrderService { @Transactional public void placeOrder(Order order) { // ... } }
  4. The data source is not configured with a transaction manager: generally from the self-developed database routing component @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
  5. The exception was swallowed. After the catch, it is eaten directly, and the transaction exception cannot be rolled back. At the same time, configure the corresponding exception @Transactional(rollbackFor = Exception.class)

V. Summary: Learning Experience

Many technical problems like this come from Xiao Fu's study of the source code. At first, when he encounters a problem, he looks at the source code. Although it is often difficult to smooth the entire logic, a little accumulation is indeed It will make the R&D personnel have a more solid understanding of the technology.

So now, the reason why I write Spring and Mybatis by hand is that I hope that by sorting out all this knowledge, I can learn complex logic design schemes, design principles and how to use design patterns to solve complex scene problems. PS: Usually the complexity of our business code is difficult to this level, so after seeing the "day", it is easy to design the business that we undertake in the future.

The other is the control of various technical details, and the accumulation of such experience to apply related technical design to the development of some similar SpringBoot Starter, only with such breadth, height and depth, can one really improve personal research and development capabilities stand up. PS: It is also to go further on the road of technology, whether it is senior development, architect, CTO!


小傅哥
4.7k 声望28.4k 粉丝

CodeGuide | 程序员编码指南 - 原创文章、案例源码、资料书籍、简历模版等下载。