Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Source Code Analysis of the principle of binding Mapper to Interface in Mybatis

2025-01-16 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/02 Report--

Today, I will talk to you about the source code analysis of the binding principle of Mapper and interface in Mybatis. Many people may not know much about it. In order to make you understand better, the editor has summarized the following contents for you. I hope you can get something according to this article.

Here, let's analyze the principle of binding Mapper to interface.

This chapter questions: / / 5. Operate Mapper interface UserMapper mapper = sqlSession.getMapper (UserMapper.class); public interface UserMapper {public UserEntity getUser (int id);}

Why is UserMapper an interface without an implementation class, so how does it initialize it? Why can the getMapper () method be called?

How is the mapper interface initialized? A reflex? No, the interface is not reflective initialization. Secret: it is actually an agent design pattern [dynamic proxy], and the underlying layer is implemented using AOP.

The most important thing in MyBayis is SqlSession: manipulating SQL statements.

Before analyzing the source code, let's review the dynamic proxy technology, which is introduced in detail in my blog: talk about Java [Agent Design pattern]-- just read this article.

Question to consider: dynamic agents are divided into jdk dynamic agents and CGLIB dynamic agents, so what kind of agent design pattern does Mybatis use?

Answer: MyBatis uses the jdk dynamic proxy because the proxy is the interface.

The general steps to review the jdk dynamic proxy JDK dynamic proxy are as follows:

1. Create proxied interfaces and classes

two。 Implement the InvocationHandler interface to uniformly handle all methods declared in the target interface

3. Call the static method of Proxy, create the proxy class and generate the corresponding proxy object

Code to achieve jdk dynamic proxy: / * 1. Create the interface and class to be proxied; * / public interface OrderService {public String add ();} public class OrderServiceImpl implements OrderService {public String add () {System.out.println ("OrderServiceImpl add.") ; return "success";}} / * * 2. Implement the InvocationHandler interface and uniformly handle all methods declared in the target interface; * / public class JdkMapperProxy implements InvocationHandler {/ / target object, proxied object private Object targect; public JdkMapperProxy (Object targect) {this.targect=targect;} public Object invoke (Object proxy, Method method, Object [] args) throws Throwable {System.out.println ("advance notification. Handle ") before proxy method; / / Target method, target method parameter Object result = method.invoke (targect, args); / / executed target method, proxied method System.out.println (" post notification. Handle after the proxy method "); return null;}} / * 3. Call the static method of Proxy, create the proxy class and generate the corresponding proxy object * / public class TestMybatis02 {public static void main (String [] args) {System.getProperties (). Put ("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); OrderService orderService = (OrderService) Proxy.newProxyInstance (OrderServiceImpl.class.getClassLoader (), OrderServiceImpl.class.getInterfaces (), new JdkMapperProxy (new OrderServiceImpl ()); orderService.add ();}}

The result of running TestMybatis02 is as follows:

Advance notice. Handled before the proxy method

OrderServiceImpl add .

Post notice. Handled after the proxy method

Generated proxy class

After reviewing the jdk dynamic proxy, let's start the source code analysis and think about whether the following configuration will be changed to the entity class select * from user where id=# {id}.

The answer is yes, where is the analysis carried out? Let's start to analyze the source code: here's where the parsing is.

Private void configurationElement (XNode context) {try {String namespace = context.getStringAttribute ("namespace"); if (namespace! = null & &! namespace.equals ("")) {. / / enter this.buildStatementFromContext ("select | insert | update | delete");} else {throw new BuilderException ("Mapper's namespace cannot be empty");}} catch (Exception var3) {throw new BuilderException ("Error parsing Mapper XML. Cause:" + var3, var3);}}

Focus on this code:

This.buildStatementFromContext ("select | insert | update | delete"); private void buildStatementFromContext (List list) {if (this.configuration.getDatabaseId ()! = null) {/ / will enter here this.buildStatementFromContext (list, this.configuration.getDatabaseId ());} this.buildStatementFromContext (list, (String) null);} private void buildStatementFromContext (List list, String requiredDatabaseId) {Iterator i$ = list.iterator () While (i$.hasNext ()) {XNode context = (XNode) i$.next (); XMLStatementBuilder statementParser = new XMLStatementBuilder (this.configuration, this.builderAssistant, context, requiredDatabaseId); try {/ / enter here statementParser.parseStatementNode ();} catch (IncompleteElementException var7) {this.configuration.addIncompleteStatement (statementParser) } public void parseStatementNode () {String id = this.context.getStringAttribute ("id"); String databaseId = this.context.getStringAttribute ("databaseId"); if (this.databaseIdMatchesCurrent (id, databaseId, this.requiredDatabaseId)) {. If (this.configuration.hasKeyGenerator (keyStatementId)) {keyGenerator = this.configuration.getKeyGenerator (keyStatementId);} else {keyGenerator = this.context.getBooleanAttribute ("useGeneratedKeys", this.configuration.isUseGeneratedKeys () & & SqlCommandType.INSERT.equals (sqlCommandType))? New Jdbc3KeyGenerator (): new NoKeyGenerator ();} / / finally this.builderAssistant.addMappedStatement (id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator) keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets) Public MappedStatement addMappedStatement (String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class parameterType, String resultMap, Class resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) {if (this.unresolvedCacheRef) {throw new IncompleteElementException ("Cache-ref not yet resolved");} else {. / / enter here this.configuration.addMappedStatement (statement); return statement;}} public void addMappedStatement (MappedStatement ms) {/ / final result this.mappedStatements.put (ms.getId (), ms);} protected final Map mappedStatements;this.mappedStatements = new Configuration.StrictMap ("Mapped Statements collection"); protected static class StrictMap extends HashMap {

Through the above code execution process, we finally know how each sql statement in the configuration file in mapper.xml is converted to an object and saved. In the end, it is encapsulated into a MappedStatement object, and then saved through a HashMap collection.

According to the source code, HadhMap has been put twice.

Let's analyze the getMapper () method later: the default is DefaultSqlSession.

/ / 5. Operate Mapper interface UserMapper mapper = sqlSession.getMapper (UserMapper.class)

Public T getMapper (Class type) {return this.configuration.getMapper (type, this);} public T getMapper (Class type, SqlSession sqlSession) {return this.mapperRegistry.getMapper (type, sqlSession);} public T getMapper (Class type, SqlSession sqlSession) {MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) this.knownMappers.get (type); if (mapperProxyFactory = = null) {throw new BindingException ("Type" + type + "is not known to the MapperRegistry.") } else {try {return mapperProxyFactory.newInstance (sqlSession);} catch (Exception var5) {throw new BindingException ("Error getting mapper instance. Cause: "+ var5, var5);}

From the above code: through configuration.getMapper () to check whether we have registered the mapper interface before, if not, it will report: useless binding interface error.

Let's take a look at the contents of the mapperRegistery introduced in the previous article: what is stored is the mapper interface, key is: interface, value is: MapperProxyFactory

Here we have registered the mapper API, and we will enter this code in the else branch: create a proxy class using mapperProxyFactory:

Return mapperProxyFactory.newInstance (sqlSession); public T newInstance (SqlSession sqlSession) {MapperProxy mapperProxy = new MapperProxy (sqlSession, this.mapperInterface, this.methodCache); return this.newInstance (mapperProxy);}

Comparison: mybatis's jdk dynamic proxy and our own jdk dynamic proxy:

Implementation of public class MapperProxy implements InvocationHandler, Serializable {/ / mybatis public class JdkMapperProxy implements InvocationHandler {/ / our implementation protected T newInstance (MapperProxy mapperProxy) {/ / mybatis implementation return Proxy.newProxyInstance (this.mapperInterface.getClassLoader (), new Class [] {this.mapperInterface}, mapperProxy);} OrderService orderService = (OrderService) Proxy.newProxyInstance (OrderServiceImpl.class.getClassLoader () / / our implementation, OrderServiceImpl.class.getInterfaces (), new JdkMapperProxy (new OrderServiceImpl ()

Finally, the mapper information is returned as follows: mapper is: the proxy class MapperProxy that we created through: mapperProxyFactory

So when we call the getUser () method of mapper, we execute the invoke () method of the MapperProxy proxy class

UserEntity user = mapper.getUser (2); public Object invoke (Object proxy, Method method, Object [] args) throws Throwable {if (Object.class.equals (method.getDeclaringClass () {/ / determine whether the mapper interface has an implementation class. Obviously, we mapper have no implementation class try {return method.invoke (this, args);} catch (Throwable var5) {throw ExceptionUtil.unwrapThrowable (var5) }} else {/ / will execute this branch MapperMethod mapperMethod = this.cachedMapperMethod (method); / / get method return mapperMethod.execute (this.sqlSession, args) from cache; / / execute sql statement}}

Question to consider: in Mybatis, there are multiple methods in the mapper interface. Will the same invoke () method be used for each call?

Answer: no, because the class in each MapperRegistry is the mapper interface and has an independent MapperProxyFactory, because the key in the MapperRegistry stores the mapper interface and the value is MapperProxyFactory.

We use MapperProxyFactory to create the MapperProxy to create the proxy, so every time we call the getMapper () method to get the same mapper, we will take the same invoke () method, and vice versa, every time we call mapper, we will use a different invoke () method.

Generally speaking, if we define the Mapper interface as global, we will use the same invoke () method. Unless we set = to multiple instances, we will new differently each time and take a different invoke () method.

Mybatis is a proxy class based on several different mapper interfaces. Different mapper interfaces take different invoke methods. If it is the same mapper interface and different methods, it must be the same invoke method.

Then there is a problem, multiple different mapper interfaces will produce multiple proxy classes (new MapperProxy ()), which takes up too much memory, which will be explained later.

MapperProxy mapperProxy = new MapperProxy (sqlSession, this.mapperInterface, this.methodCache)

After we have seen the mapper interface above, if we execute mapper.getUser (2), we will go to invoke (). Let's look at the invoke () method.

Public Object invoke (Object proxy, Method method, Object [] args) throws Throwable {if (Object.class.equals (method.getDeclaringClass () {try {return method.invoke (this, args);} catch (Throwable var5) {throw ExceptionUtil.unwrapThrowable (var5);}} else {/ / enter here MapperMethod mapperMethod = this.cachedMapperMethod (method) Return mapperMethod.execute (this.sqlSession, args);}} private MapperMethod cachedMapperMethod (Method method) {MapperMethod mapperMethod = (MapperMethod) this.methodCache.get (method); / / check whether there is method in the cache. Here is the useless if (mapperMethod = = null) {mapperMethod = new MapperMethod (this.mapperInterface, method, this.sqlSession.getConfiguration ()) / will come here this.methodCache.put (method, mapperMethod);} return mapperMethod;}} public MapperMethod (Class mapperInterface, Method method, Configuration config) {this.command = new MapperMethod.SqlCommand (config, mapperInterface, method); this.method = new MapperMethod.MethodSignature (config, method);}

Take a look at this one first.

This.command = new MapperMethod.SqlCommand (config, mapperInterface, method); public enum SqlCommandType {UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH

SqlCommandType is related to the sql statement

Public SqlCommand (Configuration configuration, Class mapperInterface, Method method) {String statementName = mapperInterface.getName () + "." + method.getName (); MappedStatement ms = null; if (configuration.hasStatement (statementName)) {/ / enter here ms = configuration.getMappedStatement (statementName);} else if (! mapperInterface.equals (method.getDeclaringClass () {String parentStatementName = method.getDeclaringClass (). GetName () + ". + method.getName () If (configuration.hasStatement (parentStatementName)) {ms = configuration.getMappedStatement (parentStatementName);}} if (ms = = null) {if (method.getAnnotation (Flush.class) = = null) {throw new BindingException ("Invalid bound statement (not found):" + statementName);} this.name = null; this.type = SqlCommandType.FLUSH } else {/ / ms is not null, then execute here this.name = ms.getId (); this.type = ms.getSqlCommandType (); if (this.type = = SqlCommandType.UNKNOWN) {throw new BindingException ("Unknown execution method for:" + this.name);} configuration.hasStatement (statementName) public boolean hasStatement (String statementName) {return this.hasStatement (statementName, true);}

GetId () is namespace+id

Associate the sql statement configured in mapper.xml with the corresponding mapper interface method and put it in the map cache, which will be cached directly later. Finally, execute the execute () method

Public Object execute (SqlSession sqlSession, Object [] args) {Object param; Object result; if (SqlCommandType.INSERT = = this.command.getType ()) {param = this.method.convertArgsToSqlCommandParam (args); result = this.rowCountResult (sqlSession.insert (this.command.getName (), param));} else if (SqlCommandType.UPDATE = = this.command.getType ()) {param = this.method.convertArgsToSqlCommandParam (args) Result = this.rowCountResult (sqlSession.update (this.command.getName (), param));} else if (SqlCommandType.DELETE = = this.command.getType ()) {param = this.method.convertArgsToSqlCommandParam (args); result = this.rowCountResult (sqlSession.delete (this.command.getName (), param)) } else if (SqlCommandType.SELECT = = this.command.getType ()) {/ / select type go here if (this.method.returnsVoid () & & this.method.hasResultHandler ()) {/ / determine whether the method does not return a result, not this.executeWithResultHandler (sqlSession, args); result = null } else if (this.method.returnsMany ()) {/ / determines whether the returned result returns multiple result sets, not result = this.executeForMany (sqlSession, args);} else if (this.method.returnsMap ()) {/ / whether it returns the map collection? Not result = this.executeForMap (sqlSession, args);} else {/ / so go here param = this.method.convertArgsToSqlCommandParam (args); / / conversion parameter result = sqlSession.selectOne (this.command.getName (), param) / / focus on this: selectOne ()}} else {if (SqlCommandType.FLUSH! = this.command.getType ()) {throw new BindingException ("Unknown execution method for:" + this.command.getName ());} result = sqlSession.flushStatements () } if (result = = null & & this.method.getReturnType (). IsPrimitive () & &! this.method.returnsVoid ()) {throw new BindingException ("Mapper method'" + this.command.getName () + "attempted to return null from a method with a primitive return type (" + this.method.getReturnType () + ").);} else {return result;}} public T selectOne (String statement, Object parameter) {List list = this.selectList (statement, parameter) If (list.size () = = 1) {return list.get (0);} else if (list.size () > 1) {throw new TooManyResultsException ("Expected one result (or null) to be returned by selectOne (), but found:" + list.size ());} else {return null;}}

Through the source code, we can change it to the following: selectOne (). Later, we will analyze the source code for selectOne ().

/ / UserEntity user = mapper.getUser (2); sqlSession.selectOne ("com.mayikt.mapper.UserMapper.getUser", 2); Summary: analysis process of MybatisMapper interface binding principle

1. Each sql statement in the configuration file in mapper.xml is eventually encapsulated into a MappedStatement object and saved through a HashMap collection.

2. Execute the getMapper () method to determine whether the mapper interface has been registered. Once registered, mapperProxyFactory will be used to generate the proxy class MapperProxy.

3. When the target method is executed, the invoke () method of the MapperProxy proxy class is called

4. Associate the sql statement configured in mapper.xml with the corresponding mapper interface method and put it into the map cache, which will be cached directly later. Finally, execute the execute () method

5. Execute the execute () method and finally call the selectOne () method to execute the result.

After reading the above, do you have any further understanding of the source code analysis of the binding principle of Mapper and interface in Mybatis? If you want to know more knowledge or related content, please follow the industry information channel, thank you for your support.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report