美文网首页
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