In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-30 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >
Share
Shulou(Shulou.com)06/02 Report--
In this issue, the editor will bring you about what the Mybatis agent modules are and how to implement them. The article is rich in content and analyzes and narrates it from a professional point of view. I hope you can get something after reading this article.
When using Mybatis, everyone may have a question: why can you manipulate the database only by writing the Mapper interface?
Its main implementation idea is to use dynamic proxy to generate the implementation class, and then cooperate with the SQL statement in the mapping file of xml to access the database.
Mybatis programming model
Mybatis evolved from the ORM framework in iBatis, so Mybatis will eventually transform the code into the iBatis programming model, while the Mybatis agent phase mainly transforms the interface-oriented programming model into the ibatis programming model through dynamic agents.
The reason we do not use the iBatis programming model directly is for decoupling. From the following two examples, we can see that the iBatis programming model is heavily coupled with the configuration file.
Interface-oriented programming model @ Test// interface-oriented programming model public void quickStart () throws Exception {/ / 2. Get sqlSession try (SqlSession sqlSession = sqlSessionFactory.openSession ()) {initH2dbMybatis (sqlSession); / / 3. Get the corresponding mapper PersonMapper mapper = sqlSession.getMapper (PersonMapper.class); JdkProxySourceClassUtil.writeClassToDisk (mapper.getClass (). GetSimpleName (), mapper.getClass ()); / / 4. Execute the query statement and return the result Person person = mapper.selectByPrimaryKey (1L); System.out.println (person.toString ());}} ibatis programming model @ Test// ibatis programming model public void quickStartIBatis () throws Exception {/ / 2. Get sqlSession try (SqlSession sqlSession = sqlSessionFactory.openSession ()) {initH2dbMybatis (sqlSession); / / ibatis programming model (heavily coupled to the configuration file) Person person = sqlSession.selectOne ("com.xiaolyuh.domain.mapper.PersonMapper.selectByPrimaryKey", 1L); System.out.println (person.toString ());}} Agent module core class
Registry of MapperRegistry:Mapper Interface dynamic Agent Factory (MapperProxyFactory)
The dynamic proxy factory class corresponding to the MapperProxyFactory:Mapper interface. There is an one-to-one correspondence between Mapper interface and MapperProxyFactory factory class
The enhancer of the MapperProxy:Mapper interface, which implements the InvocationHandler interface and realizes the access to the database through the enhanced invoke method
MapperMethod: wrapper class for insert, update, delete, select, flush node methods, which completes the operation of the database through sqlSession
Agent initializes loading Mapper interface to memory
In the Mybatis source code (2), we will find that when the configuration file parsing is completed, the last step is to call the org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace method. The main purpose of this method is to register the dynamic proxy factory (MapperProxyFactory) of the Mapper interface with the MapperRegistry according to the namespace property. The source code is as follows
Private void bindMapperForNamespace () {/ / get the namespace attribute (corresponding to the full class name of the Mapper interface) String namespace = builderAssistant.getCurrentNamespace (); if (namespace! = null) {Class boundType = null; try {boundType = Resources.classForName (namespace) } catch (ClassNotFoundException e) {/ / ignore, bound type is not required} if (boundType! = null) {/ / prevent repeated loading of if (! configuration.hasMapper (boundType)) {/ / Spring may not know the real resource name so we set a flag / / to prevent loading again this resource from the mapper interface / / look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource ("namespace:" + namespace) / / register the dynamic proxy factory of the Mapper interface with the MapperRegistry configuration.addMapper (boundType);}
Read the namespace attribute to get the full class name of the Mapper interface
Load the Mapper interface into memory based on the full class name
Determine whether the Mapper interface is loaded repeatedly
Call the addMapper method of the Mybatis configuration class (configuration) to complete the next steps
Register the agent factory class
Org.apache.ibatis.session.Configuration#addMapper this method goes directly back and calls the org.apache.ibatis.binding.MapperRegistry#addMapper method to complete the registration.
Public void addMapper (Class type) {/ / must be interface if (type.isInterface ()) {if (hasMapper (type)) {/ / prevent re-registration of throw new BindingException ("Type" + type + "is already known to the MapperRegistry.");} boolean loadCompleted = false; try {/ / create MapperProxyFactory proxy factory class knownMappers.put (type, new MapperProxyFactory (type) based on the interface class) / / It's important that the type is added before the parser is run / / otherwise the binding may automatically be attempted by the / / mapper parser. If the type is already known, it won't try. MapperAnnotationBuilder parser = new MapperAnnotationBuilder (config, type); parser.parse (); loadCompleted = true;} finally {/ / if the load is abnormal, remove the corresponding Mapper if (! loadCompleted) {knownMappers.remove (type);}
Determine whether the load type is an interface
Duplicate registration check. If the check fails, a BindingException exception is thrown.
Create a MapperProxyFactory proxy factory class based on the interface class
If there is an exception in loading, you need to remove the corresponding Mapper
Get proxy object
In the quick start of the Mybatis source code (1), the following code is used to obtain the proxy object of Mapper through sqlSession:
PersonMapper mapper = sqlSession.getMapper (PersonMapper.class); getMapper gets the proxy object
SqlSession.getMapper (PersonMapper.class) finally calls the org.apache.ibatis.binding.MapperRegistry#getMapper method, and finally returns the proxy object of the PersonMapper API. The source code is as follows:
Public T getMapper (Class type, SqlSession sqlSession) {/ / get the corresponding agent factory final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get (type) by type; if (mapperProxyFactory = = null) {throw new BindingException ("Type" + type + "is not known to the MapperRegistry.");} try {/ / create a new proxy object based on the factory class and return return mapperProxyFactory.newInstance (sqlSession) } catch (Exception e) {throw new BindingException ("Error getting mapper instance. Cause: "+ e, e);}}
Get the corresponding agent factory according to the type
Create a new proxy object based on the factory class and return
NewInstance creates a proxy object
Each Mapper interface corresponds to a MapperProxyFactory factory class. MapperProxyFactory creates a proxy object through a JDK dynamic proxy, and the proxy object of the Mapper interface is at the method level, so you need to create a new proxy object every time you access the database. The source code is as follows
Protected T newInstance (MapperProxy mapperProxy) {/ / generate proxy instance return (T) Proxy.newProxyInstance (mapperInterface.getClassLoader (), new Class [] {mapperInterface}, mapperProxy) using JDK dynamic proxy;} public T newInstance (SqlSession sqlSession) {/ / Mapper enhancer final MapperProxy mapperProxy = new MapperProxy (sqlSession, mapperInterface, methodCache); return newInstance (mapperProxy);}
First obtain the Mapper corresponding intensifier (MapperProxy)
Generate proxy objects using JDK dynamic proxies according to enhancers
The decompilation result of the proxy class is import com.sun.proxy..Proxy8;import com.xiaolyuh.domain.model.Person;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy8 extends Proxy implements Proxy8 {private static Method m 3;... Public $Proxy8 (InvocationHandler var1) throws {super (var1);}. Public final Person selectByPrimaryKey (Long var1) throws {try {return (Person) super.h.invoke (this, m3, new Object [] {var1});} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException (var4) }} static {try {m3 = Class.forName ("com.sun.proxy.$Proxy8"). GetMethod ("selectByPrimaryKey", Class.forName ("java.lang.Long"));} catch (NoSuchMethodException var2) {throw new NoSuchMethodError (var2.getMessage ());} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError (var3.getMessage ());}
From the decompilation result of the proxy class, the invoke method of the enhancer is called directly, and then the access to the database is realized.
Executive agent
By appealing to decompile the proxy object, we can find that all access to the database is implemented in the enhancer org.apache.ibatis.binding.MapperProxy#invoke.
Execute Enhancer MapperProxy@Overridepublic Object invoke (Object proxy, Method method, Object [] args) throws Throwable {try {/ / if the method of Object itself does not enhance if (Object.class.equals (method.getDeclaringClass () {return method.invoke (this, args);} / / determine whether it is the default method else if (method.isDefault ()) {if (privateLookupInMethod = = null) {return invokeDefaultMethodJava8 (proxy, method, args) } else {return invokeDefaultMethodJava9 (proxy, method, args);} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable (t);} / / get the MapperMethod object final MapperMethod mapperMethod = cachedMapperMethod (method) from the cache; / / execute MapperMethod return mapperMethod.execute (sqlSession, args);}
If the method of Object itself is not enhanced
Determine whether it is the default method
Get the MapperMethod object from the cache
Execute MapperMethod
Model transformation MapperMethod
MapperMethod encapsulates the information of the corresponding method (MethodSignature) and the corresponding sql statement (SqlCommand) in the Mapper interface; it is the bridge between the mapper interface and the sql statement in the mapping configuration file; the MapperMethod object does not record any state information, so it can be shared among multiple proxy objects.
SqlCommand: gets the namespace of the method from configuration. Method name and type of SQL statement
MethodSignature: encapsulates information about mapper interface methods (input parameters, return types)
ParamNameResolver: parsing the input parameters in the mapper interface method
Public Object execute (SqlSession sqlSession, Object [] args) {Object result; / / invokes different methods depending on the SQL type. / / here we can see that switch (command.getType ()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam (args); result = rowCountResult (sqlSession.insert (command.getName (), param)); break;} case UPDATE: {Object param = method.convertArgsToSqlCommandParam (args); result = rowCountResult (sqlSession.update (command.getName (), param)); break } case DELETE: {Object param = method.convertArgsToSqlCommandParam (args); result = rowCountResult (sqlSession.delete (command.getName (), param)); break } case SELECT: / / confirm which method called sqlSession / / has no return value or has a result processor if (method.returnsVoid () & & method.hasResultHandler ()) {executeWithResultHandler (sqlSession, args); result = null based on the method return type } / / whether the return value is of collection type or array else if (method.returnsMany ()) {result = executeForMany (sqlSession, args);} / / whether the return value is Map else if (method.returnsMap ()) {result = executeForMap (sqlSession, args) } / / whether the return value is of cursor type else if (method.returnsCursor ()) {result = executeForCursor (sqlSession, args);} / / query single record else {/ / Parameter resolution Object param = method.convertArgsToSqlCommandParam (args); result = sqlSession.selectOne (command.getName (), param) If (method.returnsOptional () & (result = = null | |! method.getReturnType (). Equals (result.getClass () {result = Optional.ofNullable (result);}} break; case FLUSH: result = sqlSession.flushStatements (); break; default: throw new BindingException ("Unknown execution method for:" + command.getName ()) } if (result = = null & & method.getReturnType (). IsPrimitive () & &! method.returnsVoid ()) {throw new BindingException ("Mapper method'" + command.getName () + "attempted to return null from a method with a primitive return type (" + method.getReturnType () + ").");} return result;} private Object executeForMany (SqlSession sqlSession, Object [] args) {List result; / / convert the method parameter to the SqlCommand parameter Object param = method.convertArgsToSqlCommandParam (args) If (method.hasRowBounds ()) {/ / get paging parameters RowBounds rowBounds = method.extractRowBounds (args); result = sqlSession.selectList (command.getName (), param, rowBounds);} else {result = sqlSession.selectList (command.getName (), param);} / / issue # 510 Collections & arrays support if (! method.getReturnType (). IsAssignableFrom (result.getClass () {if (method.getReturnType (). IsArray ()) {return convertToArray (result) } else {return convertToDeclaredCollection (sqlSession.getConfiguration (), result);}} return result;}
The transformation from interface-oriented programming model to iBatis programming model is completed in execute method, and the conversion process is as follows:
Through MapperMethod.SqlCommand. Type+MapperMethod.MethodSignature.returnType to determine which method in SqlSession needs to be called
Through MapperMethod.SqlCommand. Name to find the full class name of the method to be executed
Convert the parameters that need to be passed through MapperMethod.MethodSignature.paramNameResolver
SqlSession
In Mybatis, SqlSession is equivalent to a facade, and all operations on the database need to go through the SqlSession interface. SqlSession defines all the operation methods to the database, such as database read and write commands, access mapper, management transactions and so on. It is also one of the few annotated classes in Mybatis.
Flow chart
Through the above source code parsing, it can be found that Mybatis interface-oriented programming is realized through JDK dynamic proxy pattern. The main implementation process is:
After the mapping file is initialized, register the agent factory class MapperProxyFactory of the corresponding Mapper interface with MapperRegistry
Each time the database is manipulated, sqlSession obtains the proxy class of the Mapper interface through MapperProxyFactory
The proxy class calls the wrapper class MapperMethod of the SQL node in the XML mapping file through the enhancer MapperProxy.
Transform Mybatis interface-oriented programming model into iBatis programming model (SqlSession model) through MapperMethod
Complete the operation of the database through SqlSession
These are the Mybatis proxy modules shared by the editor and how to implement them. If you happen to have similar doubts, you might as well refer to the above analysis to understand. If you want to know more about it, you are welcome to follow the industry information channel.
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.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.