The realization principle of mapper dynamic agent in MyBatis

Source: Internet
Author: User
Tags rowcount wrapper
I. Overview

We know that the MyBatis implementation of additions and deletions need to be XML configuration, its basic configuration is as follows:

<?xml version= "1.0" encoding= "UTF-8"?> <!  
DOCTYPE Mapper Public  
  "-// mapper 3.0//en"  
<mapper namespace= "Org.mybatis.example.BlogMapper" >  
  <select id= "Selectblog" resulttype= " Blog ">  
    select * from Blog where id = #{id}  

The above configuration indicates that we define a query operation with ID Selectblog under namespace org.mybatis.example.BlogMapper, whose operation result set is blog, the corresponding statement is select * from Blog where id = # {ID}

So in practice, we can use the following form:

sqlsession session = Sqlsessionfactory.opensession ();  
try {  
  Blog blog = (blog) session.selectone ("Org.mybatis.example.BlogMapper.selectBlog", 101);  
} finally {  
  session.close ();  

The obvious drawback of this approach is that by using a string to invoke the SQL of the tag definition, the first error, the second is that when the ID in the XML is modified you do not know how many places in the program to use this ID, you need to manually find and modify.

Some improvements have been made in the new version of MyBatis, which supports this method of invocation: Define an interface method name, and the parameters need to be consistent with the XML definition. 1, create a new Com.kang.mapper package, define the map interface, interface name arbitrary, here is usermapper.

Package com.kang.mapper;  

Import java.util.List;  
Import Com.kang.pojo.User;  
Public interface Usermapper {  
    //query user information based on User ID public username  
    finduserbyid (int id) throws Exception;  
    Query user list public  
    list<user> Finduserbyusername (String username) throws Exception;  
    Add user Information public  
    void Insertuser (user user) throws Exception;   
2. Configuring the XML file Usermapper.xml
<?xml version= "1.0" encoding= "UTF-8"?> <! DOCTYPE Mapper Public "-// mapper 3.0//en" "Http://" > <map Per namespace= "Com.kang.mapper.UserMapper" > <!--Note that the namespace here must correspond to the full class name of the map interface---<select id= "Finduser  

    Byid "parametertype=" int "resulttype=" user "> select * from user where id = #{id} </select> <select id= "Finduserbyusername" parametertype= "java.lang.String" resulttype= "User" > select * FR  
        Om user where username like '%${value}% ' </select> <insert id= "Insertuser" parametertype= "User" > <selectkey keyproperty= "id" order= "after" resulttype= "Java.lang.Integer" > select Last_insert _id () </selectKey> insert into user (username,birthday,sex,address) VALUES (#{username},   #{birthday},#{sex},#{address}) </insert> </mapper>

Note that the id attribute value in the configuration file corresponds to the method name in the map interface, which is one by one. 3. Add the mapping file to the Sqlmapconfig.xml

<!--load the mapping file--  
        <mapper resource= "Map/usermapper.xml"/>  
4. Call method
        Get session  
        sqlsession session = Sqlsessionfactory.opensession ();  
        Gets the proxy object for the Mapper interface  
        usermapper usermapper = Session.getmapper (usermapper.class);  
        Invoke proxy object method  
        User user = Usermapper.finduserbyid;  
        SYSTEM.OUT.PRINTLN (user);  
        Close Session  
        Session.close ();  
        System.out.println ("---------executed-----------");  

So since we have modified the XML ID, we just need to modify the method in the interface, the compiler will be in other uses of the interface error, it is easy to modify. Of course, the benefits are not just these, but they can be seamlessly integrated with spring, dynamic injection, and so on.
For MyBatis's Mapp interface usage, please refer to this blog post mybatis mapper dynamic agent
In the example above, Usermapper is an interface and it does not implement a class, why can the interface be used directly? That's because Mybbatis uses the JDK dynamic proxy mechanism to dynamically generate proxy classes, and how is the proxy class sqlsession encapsulated? With these questions, let's explain these issues in the way we analyze the source code. Second, source code analysis

MyBatis The code for packing mapper is org.apache.ibatis.binding under this package. There are 4 of these classes:
The Mapperregistry class is a tool class that registers the mapper interface with the Get proxy class instance. Its source code is as follows:

Package org.apache.ibatis.binding;
Import Org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
Import org.apache.ibatis.session.Configuration;
Import org.apache.ibatis.session.SqlSession;
Import java.util.Collection;
Import java.util.Collections;
Import Java.util.HashMap;
Import Java.util.Map;
Import Java.util.Set;
  This class can be seen by name as the utility class public class Mapperregistry {//Global profile object) used to register the Mapper interface and get the instance of the generated proxy class, private configuration config; A HashMap key is an object of type Mapper, value is Mapperproxyfactory object//This mapperproxyfactory is the factory that created the Mapper proxy object we'll analyze the private final later. Map<class<?> mapperproxyfactory<?>> knownmappers = new Hashmap<class<?>
  Mapperproxyfactory<?>> ();
  Public mapperregistry (Configuration config) {this.config = config; }//Gets the generated proxy object @SuppressWarnings ("unchecked") public <T> T getmapper (class<t> type, sqlsession sqlsession {//Through the Mapper interface type to find out if it's empty, it throws an exception. Final MappErproxyfactory<t> mapperproxyfactory = (mapperproxyfactory<t>) knownmappers.get (type); if (mapperproxyfactory = = null) throw new Bindingexception ("type" + Type + "is not known to the Mapperregistry.")
    try {//Otherwise create a proxy object for the current interface and pass in Sqlsession return mapperproxyfactory.newinstance (sqlsession); } catch (Exception e) {throw new Bindingexception ("Error getting mapper instance.
    Cause: "+ E, E);
  }} public <T> Boolean hasmapper (class<t> type) {return Knownmappers.containskey (type); }//Register mapper interface public <T> void Addmapper (class<t> type) {if (Type.isinterface ()) {if (Hasmappe
      R (Type)) {throw new Bindingexception ("type" + Type + "is already known to the Mapperregistry.");
      Boolean loadcompleted = false;
        try {knownmappers.put (type, new mapperproxyfactory<t> (type)); It's important that the type was added before the parser is run//Otherwise the binding may automatically is 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 (!loadcompleted) {knownmappers.remove (type); }}}} public collection<class<?>> getmappers () {return collections.unmodifiablecollection
  (Knownmappers.keyset ());
    } resolverutil<class<?>> resolverutil = new resolverutil<class<?>> ();
    Resolverutil.find (New Resolverutil.isa (supertype), packagename); set<class<?
    Extends class<?>>> Mapperset = resolverutil.getclasses ();
    for (class<?> mapperclass:mapperset) {addmapper (mapperclass);
  }}//Scan through the package name below all interfaces public void Addmappers (String packagename) {addmappers (PackageName, Object.class); }


The Getmapper method of the

class will eventually call the Newinstance method of the Mapperproxyfactory class. From the above source can be seen, before calling the Getmapper method will initialize the mapperproxyfactory, it is the creation of Mapper proxy object factory, the source code is as follows:

Package org.apache.ibatis.binding;
Import Java.lang.reflect.Method;
Import Java.lang.reflect.Proxy;
Import Java.util.Map;
Import Java.util.concurrent.ConcurrentHashMap;
Import org.apache.ibatis.session.SqlSession; This class is responsible for creating a specific Mapper interface proxy object for the factory class public class Mapperproxyfactory<t> {////Mapper interface Class object private final CLASS&LT;T&G T
  Mapperinterface; The cache key for the following method of the interface is the method object value is the encapsulation of the method object in the interface private Map<method, mappermethod> methodcache = new concurrenthashmap<
  Method, mappermethod> ();
  Construction parameters Nothing to say public mapperproxyfactory (class<t> mapperinterface) {this.mapperinterface = Mapperinterface;
  } public class<t> Getmapperinterface () {return mapperinterface;
  } public Map<method, Mappermethod> Getmethodcache () {return methodcache; } @SuppressWarnings ("Unchecked") protected T newinstance (mapperproxy<t> mapperproxy) {//created a proxy class and returned//off The API of the Proxy can be viewed in Java Official API return (T) proxy.newproxyinstance (mapperinterface.geTclassloader (), new class[] {mapperinterface}, mapperproxy); }//Here Pass in sqlsession create a proxy class for Mapper interface public T newinstance (sqlsession sqlsession) {///Create Mapperproxy object Here This class implements the JDK Dynamic Proxy Interface Invocationhandler final mapperproxy<t> mapperproxy = new Mapperproxy<t> (sqlsession, MAPPERINTERFAC
    E, Methodcache);
  Call the above method to return an interface to the proxy class return newinstance (Mapperproxy); }

The key code in the above code is

Final mapperproxy<t> mapperproxy = new Mapperproxy<t> (sqlsession, Mapperinterface, MethodCache);

Here the Mapperproxy object is created this class implements the JDK dynamic Proxy interface Invocationhandler, the source code is as follows:

Package org.apache.ibatis.binding;
Import Java.lang.reflect.InvocationHandler;
Import Java.lang.reflect.Method;
Import Java.util.Map;
Import org.apache.ibatis.session.SqlSession; Implements the interface of the JDK Dynamic Agent Invocationhandler//implements the details of the proxy method call in the Invoke method public class Mapperproxy<t> implements
  Invocationhandler, Serializable {private static final long serialversionuid = -6424540398559729838l;
  Sqlsession private final sqlsession sqlsession;
  Type object of the interface private final class<t> mapperinterface;
  The cache of the methods in the interface is mapperproxyfactory passed over.
  Private final Map<method, mappermethod> Methodcache; Construction parameters Public Mapperproxy (sqlsession sqlsession, class<t> mapperinterface, Map<method, mappermethod>
    Methodcache) {this.sqlsession = sqlsession;
    This.mapperinterface = Mapperinterface;
  This.methodcache = Methodcache; }///Interface proxy object All method calls will call the method public object invoke (object proxy, method, object[] args) throws Throwable {//judgmentis not the underlying method such as ToString () hashcode (), etc., these methods call directly do not need to process if (Object.class.equals (Method.getdeclaringclass ())) {return met
    Hod.invoke (this, args);
    }//Here cache final Mappermethod Mappermethod = Cachedmappermethod (method);
  The place where the Mappermethod.execute core is called is in this method, which is the real wrapper call to Sqlsession return Mappermethod.execute (sqlsession, args); }//cache handling Private Mappermethod Cachedmappermethod (method) {Mappermethod Mappermethod = methodcache.get (Meth
      if (Mappermethod = = null) {Mappermethod = new Mappermethod (Mapperinterface, Method, Sqlsession.getconfiguration ());
    Methodcache.put (method, Mappermethod);
  } return Mappermethod; }

From the source meaning: Get the Mappermethod class instance corresponding to the execution method from the cache. If the Mappermethod class instance does not exist, create a join cache and return the associated instance. Finally, the Execute method of the Mappermethod class is called.
Here we can see that the Getmapper method is used to obtain the relevant data manipulation class interface. And the fact data operation class state has set up the dynamic agent. As a result, the Invoke method of each method corresponding to the Mapperproxy class is touched when the operation class executes the method. So the result of the Invoke method's return is the execution of the method based on the action class. This way, we know that the final task is given to the Mappermethod class instance.
then mappermethod the detailed source code as follows:

Package org.apache.ibatis.binding;
Import Org.apache.ibatis.annotations.MapKey;
Import Org.apache.ibatis.annotations.Param;
Import org.apache.ibatis.mapping.MappedStatement;
Import Org.apache.ibatis.mapping.SqlCommandType;
Import Org.apache.ibatis.reflection.MetaObject;
Import org.apache.ibatis.session.Configuration;
Import Org.apache.ibatis.session.ResultHandler;
Import Org.apache.ibatis.session.RowBounds;
Import org.apache.ibatis.session.SqlSession;
Import Java.lang.reflect.Array;
Import Java.lang.reflect.Method;
Import java.util.*; This class is the core class of the entire agent mechanism, and the operations in Sqlsession are encapsulated public class Mappermethod {//an inner envelope encapsulates the type of the SQL label Insert Update Delete select PRI
  vate final SqlCommand command;
  An inner class encapsulates the method's parameter information return type information such as private final methodsignature method;  Construction parameters Public Mappermethod (class<?> mapperinterface, method, Configuration config) {this.command = new
    SqlCommand (config, mapperinterface, method);
  This.method = new Methodsignature (config, method); }//This methodis a wrapper to Sqlsession call public object Execute (sqlsession sqlsession, object[] args) {//definition Returns the result of object result; If the INSERT operation if (Sqlcommandtype.insert = = Command.gettype ()) {//processing parameter Object param = Method.convertargsto
      Sqlcommandparam (args);
      The Insert method that calls sqlsession result = Rowcountresult (Sqlsession.insert (Command.getname (), param)); If the UPDATE operation is the same as} else if (sqlcommandtype.update = = Command.gettype ()) {Object param = Method.convertargstosql
      Commandparam (args);
      result = Rowcountresult (Sqlsession.update (Command.getname (), param)); If the DELETE operation is the same as} else if (Sqlcommandtype.delete = = Command.gettype ()) {Object param = Method.convertargstosql
      Commandparam (args);
      result = Rowcountresult (Sqlsession.delete (Command.getname (), param)); If it is a SELECT operation then the situation will be more but also with sqlsession query method one by one corresponding} else if ( = = Command.gettype ()) {//If return V OID and parameter has Resulthandler//call void Select (String sTatement, Object parameter, Resulthandler handler), method if (method.returnsvoid () && Method.hasresulthandler ())
        {Executewithresulthandler (sqlsession, args);
      result = NULL;
      If multiple rows of results are returned this call <E> list<e> selectlist (String statement, Object parameter);
      Executeformany this method calls the} else if (Method.returnsmany ()) {result = Executeformany (sqlsession, args); 
      If the return type is map, call the Executeformap method} else if (Method.returnsmap ()) {result = Executeformap (sqlsession, args);
        } else {//or else is querying a single object param = Method.convertargstosqlcommandparam (args);
      result = Sqlsession.selectone (Command.getname (), param); }} else {//If all does not match the method defined in mapper does not throw new bindingexception ("Unknown Execution for:" + Comman
    D.getname ()); }//If the return value is null and the method return value type is the underlying type and is not void, throw an exception if (result = = null && method.getreturntype (). Isprimitive () & &!METHOD.RETURNSVOID ()) {throw new Bindingexception ("Mapper method '" + command.getname () + "attempted to return null fro
    M a method with a primitive return type ("+ method.getreturntype () +"). ");
  return result;
    } private Object Rowcountresult (int rowCount) {final Object result;
    if (Method.returnsvoid ()) {result = null; } else if (Integer.class.equals (Method.getreturntype ()) | |
    Integer.TYPE.equals (Method.getreturntype ())) {result = RowCount; } else if (Long.class.equals (Method.getreturntype ()) | |
    Long.TYPE.equals (Method.getreturntype ())) {result = (long) RowCount; } else if (Boolean.class.equals (Method.getreturntype ()) | |
    Boolean.TYPE.equals (Method.getreturntype ())) {result = (RowCount > 0); } else {throw new Bindingexception ("Mapper method '" + command.getname () + "' have an unsupported return type:" + M
    Ethod.getreturntype ());
  } return result; } private void Executewithresulthandler (sqlsession sQlsession, object[] args) {mappedstatement ms = Sqlsession.getconfiguration (). Getmappedstatement (Command.getName ()); if (Void.class.equals (Ms.getresultmaps (). Get (0). GetType ())) {throw new Bindingexception ("method" + Tname () + "needs either a @ResultMap annotation, a @ResultType annotation," + "or a resulttype att
    Ribute in XML so a resulthandler can be used as a parameter. ");
    Object param = Method.convertargstosqlcommandparam (args);
      if (Method.hasrowbounds ()) {Rowbounds rowbounds = method.extractrowbounds (args); (Command.getname (), Param, rowbounds, Method.extractresulthandler (args));
    } else { (command.getname (), Param, Method.extractresulthandler (args));  }}//Return multiline result call sqlsession.selectlist method private <E> Object Executeformany (sqlsession sqlsession, object[] args)
    {list<e> result; Object param = Method.convertargstosqlcommandparam (args); If the parameter contains Rowbounds, the query for paging is called if (method

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.