美文网首页
Mybatis源码分析与仿写

Mybatis源码分析与仿写

作者: 华盛顿可乐 | 来源:发表于2019-09-29 21:22 被阅读0次

    Mybatis源码分析

    项目介绍

    xml文件

    <!--GirlMapper.xml中-->
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.sz.mapper.GirlMapper">
        <select id="queryById" resultType="com.sz.pojo.Girl">
            select * from girl
            <where>
                <if test="id>0">
                    and id = #{id}
                </if>
            </where>
        </select>
    </mapper>
    <!--mybatis.cfg.xml-->
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <properties resource="jdbc.properties">
        </properties>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC" />
                <dataSource type="UNPOOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;serverTimezone=UTC"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <mapper resource="com.sz.mapper/GirlMapper.xml"/>
        </mappers>
    </configuration>
    

    java文件

    //GirlMapper接口中
    public interface GirlMapper {
        Girl queryById(@Param("id") long id);
    }
    //Girl类
    public class Girl implements Serializable {
        private Long id;
        private String name;
        private String flower;
        private Date birthday;
        //省略set,get,tostring方法
    }
    //工具类MybatisUtil
    public class MybatisUtil {
        private static SqlSessionFactory sqlSessionFactory;
        static {
            String resource = "mybatis.cfg.xml";
            try (InputStream in = Resources.getResourceAsStream(resource)) {
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        public static SqlSession getSession() {
            return sqlSessionFactory.openSession();
        }
    }
    //测试方法
    @Test
        public void m2() {
            SqlSession sqlSession = MybatisUtil.getSession();
            GirlMapper mapper = sqlSession.getMapper(GirlMapper.class );
            Girl g = mapper.queryById(1l);
            System.out.println(g.getId() + "  " + g.getName());
            sqlSession.commit();
            sqlSession.close();
        }
    

    源码分析

    1. 读取配置文件

      //Resources的getResourceAsStream方法作用如下:
      //1.从类路径加载配置文件(如 mybatis.cfg.xml)。 
      String resource = "mybatis.cfg.xml";
      InputStream in = Resources.getResourceAsStream(resource);
      

      这一步主要是加载5个类加载器去查找资源,返回包含mybatis.cfg.xml信息的InputStream

      ClassLoaderWrapper中
      //一共5个类加载器
        ClassLoader[] getClassLoaders(ClassLoader classLoader) {
          return new ClassLoader[]{
              classLoader,
              defaultClassLoader,
              Thread.currentThread().getContextClassLoader(),
              getClass().getClassLoader(),
              systemClassLoader};
        }
      
    1. 创建SqlSessionFactory

      private static SqlSessionFactory sqlSessionFactory;
      sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
      

      进入build方法里面查看源代码

      SqlSessionFactoryBuilder中
      public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
              SqlSessionFactory var5;
              try {
                  XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
                  //parser.parse()返回configuration对象
                  var5 = this.build(parser.parse());
      

      进入parse方法

      XMLConfigBuilder中
      public Configuration parse() {
              if (this.parsed) {
                  throw new BuilderException("Each XMLConfigBuilder can only be used once.");
              } else {
                  this.parsed = true;
                  this.parseConfiguration(this.parser.evalNode("/configuration"));
                  //对这些node进行解析,塞到configuration中。this.configuration对象就是解析mybatis.cfg.xml得到的对象
                  return this.configuration;
              }
          }
          private void parseConfiguration(XNode root) {
              try {
                  this.propertiesElement(root.evalNode("properties"));
                  Properties settings = this.settingsAsProperties(root.evalNode("settings"));
                  this.loadCustomVfs(settings);
                  this.typeAliasesElement(root.evalNode("typeAliases"));
                  this.pluginElement(root.evalNode("plugins"));
                  this.objectFactoryElement(root.evalNode("objectFactory"));
                  this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
                  this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
                  this.settingsElement(settings);
                  this.environmentsElement(root.evalNode("environments"));
                  this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
                  this.typeHandlerElement(root.evalNode("typeHandlers"));
                  this.mapperElement(root.evalNode("mappers"));
              } catch (Exception var3) {
                  throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
              }
          }
      

      解析完后,进行build,返回DefaultSqlSessionFactory。

      SqlSessionFactoryBuilder中
      public SqlSessionFactory build(Configuration config) {
      //DefaultSqlSessionFactory是SqlSessionFactory的子类
              return new DefaultSqlSessionFactory(config);
          }
      
    2. 生成SqlSession

      public static SqlSession getSession() {
              return sqlSessionFactory.openSession();
          }
      

      进入openSession方法

      DefaultSqlSessionFactory中
      public SqlSession openSession() {
              return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
          }
          
          private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
              Transaction tx = null;
      
              DefaultSqlSession var8;
              try {
                  Environment environment = this.configuration.getEnvironment();
                  //事务工厂
                  TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
                  //通过事务工厂来产生一个事务
                  tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
                  //生成一个执行器(事务包含在执行器里)
                  Executor executor = this.configuration.newExecutor(tx, execType);
                  //然后产生一个DefaultSqlSession
                  var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
              } catch (Exception var12) {
                  this.closeTransaction(tx);
                  throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
              } finally {
                  ErrorContext.instance().reset();
              }
      
              return var8;
          }
      
    3. JDK动态代理UserMapper接口

      在Test中
      GirlMapper mapper = sqlSession.getMapper(
                      GirlMapper.class
              );
      

      进入getMapper方法

      DefaultSqlSession中
      public <T> T getMapper(Class<T> type) {
              return this.configuration.getMapper(type, this);
          }
      

      进入configuration.getMapper(type, this);方法

      Configuration类中
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
              return this.mapperRegistry.getMapper(type, sqlSession);
          }
      

      进入mapperRegistry.getMapper(type, sqlSession);

      MapperRegistry中
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
              MapperProxyFactory<T> 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);
                  }
              }
          }
      

      进入mapperProxyFactory.newInstance(sqlSession);

      MapperProxyFactory中
      protected T newInstance(MapperProxy<T> mapperProxy) {
      //JDK动态代理返回一个实现了指定接口的代理对象
              return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
          }
      public T newInstance(SqlSession sqlSession) {
              MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
              //返回的mapperProxy传入this.newInstance(MapperProxy<T> mapperProxy);
              return this.newInstance(mapperProxy);
          }
      

      进入MapperProxy(sqlSession, this.mapperInterface, this.methodCache);

       MapperProxy中
       public class MapperProxy<T> implements InvocationHandler, Serializable {
       public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
              this.sqlSession = sqlSession;
              this.mapperInterface = mapperInterface;
              this.methodCache = methodCache;
          }
      //当UserMapper接口调用方法时就会进入invoke方法
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              try {
                  if (Object.class.equals(method.getDeclaringClass())) {
                      return method.invoke(this, args);
                  }
      
                  if (this.isDefaultMethod(method)) {
                      return this.invokeDefaultMethod(proxy, method, args);
                  }
              } catch (Throwable var5) {
                  throw ExceptionUtil.unwrapThrowable(var5);
              }
      
              MapperMethod mapperMethod = this.cachedMapperMethod(method);
              return mapperMethod.execute(this.sqlSession, args);
          }
      
    4. 当调用Girl g = mapper.queryById(1l);时,进入invoke方法,再进入 mapperMethod.execute(this.sqlSession, args);

      在MapperMethod中
      public Object execute(SqlSession sqlSession, Object[] args) {
              Object param;
              Object result;
              switch(this.command.getType()) {
              case INSERT:
                  param = this.method.convertArgsToSqlCommandParam(args);
                  result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
                  break;
              case UPDATE:
                  param = this.method.convertArgsToSqlCommandParam(args);
                  result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
                  break;
              case DELETE:
                  param = this.method.convertArgsToSqlCommandParam(args);
                  result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
                  break;
                  //mapper.queryById(1l);是查询方法所以进入这里面
              case SELECT:
                  if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                  //queryById返回值是null,进入这里
                      this.executeWithResultHandler(sqlSession, args);
                      result = null;
                  } else if (this.method.returnsMany()) {
                  //queryById返回值是集合,进入这里
                      result = this.executeForMany(sqlSession, args);
                  } else if (this.method.returnsMap()) {
                  //queryById返回值是Map,进入这里
                      result = this.executeForMap(sqlSession, args);
                  } else if (this.method.returnsCursor()) {
                  //queryById返回值是游标类型,进入这里
                      result = this.executeForCursor(sqlSession, args);
                  } else {
                  //由于我们的queryById方法返回是user对象,所以进入这里
                      param = this.method.convertArgsToSqlCommandParam(args);
                      result = sqlSession.selectOne(this.command.getName(), param);
                  }
                  break;
              case FLUSH:
                  result = sqlSession.flushStatements();
                  break;
              default:
                  throw new BindingException("Unknown execution method for: " + this.command.getName());
              }
      
              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;
              }
          }
      

      进入sqlSession.selectOne(this.command.getName(), param);

      DefaultSqlSession中
      public <T> T selectOne(String statement, Object parameter) {
              List<T> 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;
              }
          }
          public <E> List<E> selectList(String statement, Object parameter) {
              return this.selectList(statement, parameter, RowBounds.DEFAULT);
          }
          public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
              List var5;
              try {
                  MappedStatement ms = this.configuration.getMappedStatement(statement);
                  //执行器executor执行query方法
                  var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
              } catch (Exception var9) {
                  throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
              } finally {
                  ErrorContext.instance().reset();
              }
      
              return var5;
          }
      

      执行器executor里面封装了JDBC代码,通过JDBC对数据库进行操作

    仿写Mybatis源码

    Mybatis源码基本流程


    1.png

    详细步骤

    新建项目,导入mysql的依赖

    MyBatisXml类

    package com.yy.entity;
    import java.util.HashMap;
    import java.util.Map;
    public class MyBatisXml {
        public static String namespace="com.yy.mapper.UserMapper";
        public static Map<String,String> map=new HashMap<>();
        static {
            map.put("selectById","select * from users where id = ?");
        }
    }
    

    User类

    package com.yy.entity;
    
    public class User {
        private int id;
        private String username;
        private String password;
        //get set tostring 已经省略
    }
    

    Executor接口

    package com.yy.executor;
    public interface Executor {
        <T> T query(String sql,Object o);
    }
    

    SimpleExecutor接口

    package com.yy.executor;
    import com.yy.entity.User;
    import java.sql.*;
    public class SimpleExecutor implements Executor {
        @Override
        public <T> T query(String sql, Object o) {
            Connection c = getConnection();
            try {
                PreparedStatement statement=c.prepareStatement(sql);
                statement.setInt(1,(Integer) o);
                ResultSet resultSet = statement.executeQuery();
                User u=null;
                while(resultSet.next()){
                    u=new User();
                    u.setId(resultSet.getInt("id"));
                    u.setUsername(resultSet.getString("username"));
                    u.setPassword(resultSet.getString("password"));
                }
                return (T)u;
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return null;
        }
        public Connection getConnection(){
            String driver="com.mysql.jdbc.Driver";
            String url="jdbc:mysql://localhost:3306/user?useSSL=false&serverTimezone=UTC";
            String username="root";
            String password="root";
            try {
                Class.forName(driver);
                Connection c= DriverManager.getConnection(url,username,password);
                return c;
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    

    SqlSession类

    package com.yy.executor;
    import com.yy.mapper.MapperProxy;
    import java.lang.reflect.Proxy;
    public class SqlSession {
    private Executor e=new SimpleExecutor();
    public <T> T selectOne(String sql,Object o){
        return e.query(sql,o);
    }
    public <T> T getMapper(Class<T> tClass){
    //第一个参数:类加载器,在类类型上调用getClassLoader()方法是得到当前类型的类加载器,我们知道在Java中所有的类都是通过加载器加载到虚拟机中的
        //第二个参数:代理类需要实现的接口
        //第三个参数:InvocationHandler动态代理
        //因为被代理的接口是UserMapper,所以需要UserMapper的类加载器
        return (T) Proxy.newProxyInstance(tClass.getClassLoader(),new Class[]{tClass},new MapperProxy(this));
    }
    }
    

    MapperProxy类

    package com.yy.mapper;
    import com.yy.entity.MyBatisXml;
    import com.yy.entity.User;
    import com.yy.executor.SqlSession;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    public class MapperProxy implements InvocationHandler {
        private SqlSession sqlSession;
        public MapperProxy(SqlSession sqlSession){
            this.sqlSession=sqlSession;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
    
            System.out.println("-------"+method);
            if(method.getDeclaringClass().getName().equals(MyBatisXml.namespace)){
                String sql = MyBatisXml.map.get(method.getName());
                 return sqlSession.selectOne(sql,args[0]);
            }
            return null;
        }
    }
    

    测试类

    @Test
        public void test01(){
            SqlSession session=new SqlSession();
            UserMapper mapper = session.getMapper(UserMapper.class);
            User user = mapper.selectById(1);
            System.out.println(user);
        }
    

    调试遇坑

    调试仿写MYBATIS底层的代码的时候,出现mapper对象为null,MapperProxy类中invoke方法的 System.out.println("-------"+method);一直重复输出?


    2.png

    单步调试时IDEA会调用被代理类的toString()方法,调用一次被代理类的toString()方法就会进入一次invoke方法,因此会重复输出。invoke方法中method参数是代理类的toString,invoke的返回值就是对应的代理类toString方法的返回值,因为invoke方法返回的null所以mapper的toString方法返回的是null,即上图中的(mapper: "null")

    深度解析

    我们通过指定虚拟机启动参数,让它保存下来生成的代理类的 Class 文件。

    -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

    通过IDEA反编译这个Class

    package com.sun.proxy;
    import com.yy.entity.User;
    import com.yy.mapper.UserMapper;
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.lang.reflect.UndeclaredThrowableException;
    public final class $Proxy0 extends Proxy implements UserMapper {
        private static Method m1;
        private static Method m3;
        private static Method m2;
        private static Method m0;
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final User selectById(int var1) throws  {
            try {
                return (User)super.h.invoke(this, m3, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return (Integer)super.h.invoke(this, m0, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m3 = Class.forName("com.yy.mapper.UserMapper").getMethod("selectById", Integer.TYPE);
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    

    代码较多,我们拆开来看

    public final class $Proxy0 extends Proxy implements UserMapper {
        //首先,这个代理类的名字是很随意的,一个程序中如果有多个代理类要生成,「$Proxy + 数字」就是它们的类名。
    //接着,你会注意到这个代理类继承 Proxy 类和我们指定的接口 UserMapper(之前如果指定多个接口,这里就会继承多个接口)。
        //然后你会发现,这个构造器需要一个 InvocationHandler 类型的参数,并且构造器的主体就是将这个 InvocationHandler 实例传递到父类 Proxy 的对应字段进行保存,这也是为什么所有的代理类都必须使用 Proxy 作为父类的一个原因,就是为了公用父类中的 InvocationHandler 字段。
        
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
    //这一块内容也算是代理类中较为重要的部分了,它将于虚拟机静态初始化这个代理类的时候执行。这一大段代码就是完成反射出所有接口中方法的功能,所有被反射出来的方法都对应一个 Method 类型的字段进行存储。
    //除此之外,虚拟机还反射了 Object 中的三个常用方法,也就是说,代理类还会代理真实对象从 Object 那继承来的这三个方法。
        private static Method m1;
        private static Method m3;
        private static Method m2;
        private static Method m0;
    static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
                m3 = Class.forName("com.yy.mapper.UserMapper").getMethod("selectById", Integer.TYPE);
                m2 = Class.forName("java.lang.Object").getMethod("toString");
                m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    
    //从父类 Proxy 中取出构造实例化时存入的处理器类,并调用它的 invoke 方法。
    //方法的参数基本一样,第一个参数是当前代理类实例(事实证明这个参数传过去并没什么用),第二个参数是 Method 方法实例,第三个参数是方法的形式参数集合,如果没有就是 null。
    public final User selectById(int var1) throws  {
            try {
                return (User)super.h.invoke(this, m3, new Object[]{var1});
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    

    结束语

    总之,只有熟悉JDBC,反射,JDK动态代理以及类加载器才能对Mybatis的源码有深刻的理解,Mybatis避免了 JDBC 代码和手动设置参数以及获取结果集。MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs映射成数据库中的记录。
    单独使用mybatis是有很多限制的,而且很多业务系统本来就是使用spring来管理的事务,因此mybatis最好与spring集成起来使用。

    相关文章

      网友评论

          本文标题:Mybatis源码分析与仿写

          本文链接:https://www.haomeiwen.com/subject/rduqpctx.html