In an in-depth understanding of the MyBatis principle, I'm not just trying to understand how the whole mybatis works, but I also want to extract some useful programming methods, programming ideas, annotations, and some utility classes from this process.
1. Introduction 1.1 Mybatis-config.xml in use
In the mybatis-config.xml file, we often see a similar configuration
<properties> <property name= "Driver" value= "Com.mysql.jdbc.Driver"/> <property name= "url" Value= "Jdbc:mysql://localhost:3306/mybatis"/> <property name= "username" value= "root"/> <property name = "Password" value= "aaabbb"/></properties><environments default= "Development" > <environment id= " Development "> <transactionmanager type=" JDBC "> <property name=" "value=" "/> </t Ransactionmanager> <datasource type= "Pooled" > <property name= "Driver" value= "${driver}"/> <property name= "url" value= "${url}"/> <!--fill in your database user name--<property name= "U Sername "value=" ${username} "/> <!--fill in your database password--<property name=" password "value=" ${PASSW Ord} "/> </dataSource> </environment></environments>
Some properties are placed in properties a sub-label under the label, and the value can be removed from the configuration file in the form of ${key} .
You can also configure the properties in an external file to tell the parser the relative path of the external file:
<properties resource="jdbc.properties"></properties>
Used in 1.2 xxxmapper.xml
Of course, in Xxxmapper.xml , we also use it when we write SQL statements, such as
select <include refid="Base_Column_List" /> from student where student_id=#{student_id, jdbcType=INTEGER}
will be #{student_id, jdbcType=INTEGER} replaced with the passed-in parameter.
2. Principle
In MyBatis, it is the class that deals with this process GenericTokenParser .
2.1 Generictokenparser member variables
GenericTokenParserClass has three member variables
// 开始标记private final String openToken;// 结束标记private final String closeToken;// 表处理器private final TokenHandler handler;
As an example,
Parse the ${driver}in the above configuration, then these member variables
openToken="${";closeToken="}";
handleris an TokenHandler interface
public interface TokenHandler { String handleToken(String content);}
In the actual process, we need to define our own processor, the processor implementation TokenHandler can be, the following example will have an example.
2.2 Generictokenparser Constructors
The constructor is simple enough to assign values to several member variables.
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) { this.openToken = openToken; this.closeToken = closeToken; this.handler = handler;}
2.3 Parsing Process 2.3.1 Overall process
The large process is as follows:
On the whole, it is to find the expression that needs to be processed, replace the contents of the expression with the processed content of the processor, and finally return the final string.
2.3.2 Process Detailed
First look at the code and the comments I gave
public string Parse (string text) {if (text = = NULL | | text.isempty ()) {return ""; }//Starting with the No. 0 bit, look for the start tag subscript int start = Text.indexof (Opentoken, 0); if (start = =-1) {//cannot be found, return the original parameter return text; } char[] src = text.tochararray (); Offset is used to record which int offset = 0 is read by the builder variable; Builder is the final returned string final StringBuilder builder = new StringBuilder (); Expression is every time the found expressions are to be processed in the processor StringBuilder expression = null; while (Start >-1) {if (Start > 0 && src[start-1] = = ' \ \ ') {//start tag is escaped, remove escape character ' \ ' Builder.append (src, offset, start-offset-1). Append (Opentoken); Offset = start + opentoken.length (); } else {//This branch was found with the closing tag, to find the end tag if (expression = = null) {expression = new Stringbui Lder (); } else {expression.setlength (0); }//The string before the start tag is added to the builder builder.append (SRC, offSet, Start-offset); Calculates the new offset offset = start + opentoken.length (); Starting from here find the end of the tag int end = Text.indexof (Closetoken, offset); while (End >-1) {if (End > Offset && src[end-1] = = ' \ \ ') {//This end tag is escaped Expression.append (src, offset, end-offset-1). Append (Closetoken); Offset = end + Closetoken.length (); End = Text.indexof (Closetoken, offset); } else {expression.append (src, offset, end-offset); Offset = end + Closetoken.length (); Break }} if (end = =-1) {//Cannot find end tag builder.append (SRC, start, src.length -start); offset = src.length; } else {//found the end tag, it is put into the processor for processing builder.append (handler.handletoken (expression. toString ())); Offset = end + Closetoken.length (); }}//Because there may be many expressions in the string that need to be parsed, start looking for the next expression, start = Text.indexof (Opentoken, offset); }//The last time the start tag was not found, the offset string is added to the builder if (offset < src.length) {builder.append (src, offset, src. Length-offset); } return Builder.tostring ();}
If you look at the code, you don't have to look at the detailed procedure below.
The first step: the non-empty processing of parameters
parameter is empty or "", returns "".
if (text == null || text.isEmpty()) { return "";}
Step two: Find the start tag, declare the variable
// 从第0位开始, 查找开始标记的下标 int start = text.indexOf(openToken, 0); if (start == -1) { // 找不到则返回原参数 return text; } char[] src = text.toCharArray(); // offset用来记录 builder 变量读取到的位置 int offset = 0; // builder 是最终返回的字符串 final StringBuilder builder = new StringBuilder(); // expression 是每一次找到的表达式, 要传入处理器中进行处理 StringBuilder expression = null;
If the incoming string does not have a start tag to process, it returns directly without the need to declare a bunch of variables.
If found, the declaration of the variable is made.
Step three: Loop through the tags and handle them
In this example, suppose the start tag is ${and the end tag is }
First, find the start tag first
It is only necessary to find the start tag to find the corresponding end tag, otherwise it is meaningless to find the end tag alone.
We found it ${ , there are two kinds of things:
- ${is the start tag
- ${is the character we want.
How to distinguish between these two situations, the normal situation is the situation 1, if you want to 2, in the parser, it is necessary to join the escape symbol \\ .
That is, we want to get the characters in the final character ${ , it should be written like this \\${ .
In case 2, the parser is treated like this
builder.append(src, offset, start - offset - 1).append(openToken); offset = start + openToken.length();
Remove the transfer character and add the string to the builder. The location at which the record was resolved offset. Continue to find the next start tag.
For Case 1,
Next, find the end tag
We found it } , there are two kinds of things:
- } is the start tag
- } is the character we want.
Then, as before, Case 2 also needs to be transferred to distinguish between tokens. Case 2 Treatment
// 此结束标记是转义的expression.append(src, offset, end - offset - 1).append(closeToken);offset = end + closeToken.length();end = text.indexOf(closeToken, offset);
Remove the transfer character and add the string to the builder. The location at which the record was resolved offset. Continue to find the next closing tag until you find Case 1, then jump out of the loop.
For Case 1, get ${ and } between the table string as expression , continue down
Processor Processing
// 找到了结束的标记, 则放入处理器进行处理builder.append(handler.handleToken(expression.toString()));offset = end + closeToken.length();
Finally, as long as you haven't found the last one, continue looking for the next start tag
start = text.indexOf(openToken, offset);
3. Testing
@Test public void simpleTest() { GenericTokenParser parser = new GenericTokenParser("${", "}", new VariableTokenHandler(new HashMap<String, String>() { { put("driver", "com.mysql.jdbc.Driver"); put("url", "jdbc:mysql://localhost:3306/mybatis"); put("username", "root"); put("password", "aaabbb"); } })); // 测试单个解析 assertEquals("com.mysql.jdbc.Driver", parser.parse("${driver}")); // 多个一起测试 assertEquals("驱动=com.mysql.jdbc.Driver,地址=jdbc:mysql://localhost:3306/mybatis,用户名=root", parser.parse("驱动=${driver},地址=${url},用户名=${username}")); }
4 Code
If you need code, please visit my github.
If you have any questions, please communicate with me.
MyBatis Extracted Tools-(a) universal tag parser (i.e., ready to use)