Mybatis-原生Mybatis原理源码分析篇

Mybatis框架概述

Mybatis是持久层的框架,它内部封装了jdbc,使开发的时候只需要关注sql语句本身,不需要话费精力去处理加载驱动、创建连接、创建statement等。下面我们也是通过一个实例来对它进行分析。

首先编写一个SqlMapConfig.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>
 
    <!--配置Mybatis的环境-->
        <environments default="mysql">
        <!--配置mysql环境-->
        <environment id="mysql">
            <!--配置事务类型-->
            <transactionManager type="JDBC"></transactionManager>
            <!--配置数据源,也就是连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/ssm? 
                   characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!--配置mybatis映射的位置-->
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

在resources目录下新建一个mappers目录,在然后新建一个UserMapper.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.jd.dao.UserMapper">
 
    <select id="findUser" resultType="com.jd.domain.User">
        select id ,userName as userName,birthday as birthday,sex as sex,
        address as address FROM user where yn=1
    </select>
 
</mapper>

新建一个实体类


@Data
public class User {
    private Integer id;
    private Date birthday;
    private String userName;
    private String sex;
    private String address;
    private Integer yn;
}

创建一个接口类

public interface UserMapper {
    /**
     * 查询所用用户
     */
 
    public List<User> findUser();
 
}

编写测试类


public class MybatisTest {
 
    @Test
    public void test() throws IOException {
        //第一部分初始化工作,解析配置文件

        //1、读取配置文件
        InputStream in=Resources.getResourceAsStream("SqlMapConfig.xml");
        //2、创建SqlSessionFactory的构建者对象
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3、使用构建者创建工厂对象SqlSessionFactory
        SqlSessionFactory factory= builder.build(in);
        
        //第二部分,执行sql

        //4、使用SqlSessionFactory创建SqlSession
        SqlSession session=factory.openSession();
        //5、使用SqlSession创建dao接口的代理对象
        UserMapper userDao =session.getMapper(UserMapper.class);



        //6、使用代理对象执行查询方法
        List<User> list=userDao.findUser();
 
        for (User user:list){
            System.out.println(user);
        }
        //7、释放资源
        session.close();
        try {
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
      
}

在这个位置,其实我们还有另外一种写法,

我们本次说说使用代理对象调用接口中的方法

扫描二维码关注公众号,回复: 10452939 查看本文章

接下来我们就要分析原生Mybatis的原理了

主要分为两个部分,

这部分代码主要做的是初始化工作,解析配置文件

这部分代码主要做的是执行sql

首先我们先分析第一部分

 //1、读取配置文件
 InputStream in=Resources.getResourceAsStream("SqlMapConfig.xml");
 //2、创建SqlSessionFactory的构建者对象
 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
 //3、使用构建者创建工厂对象SqlSessionFactory
 SqlSessionFactory factory= builder.build(in);

开始上源码:

// 1.我们最初调用的build
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
//调用了重载方法
        return this.build((InputStream)inputStream, (String)null, properties);
    }
// 2.调用的重载方法
 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {

            //  XMLConfigBuilder是专门解析mybatis的配置文件的类
            XMLConfigBuilder e = new XMLConfigBuilder(inputStream, environment, properties);

           //又调用buid的一个重载方法,e.parse()的返回值是Configuration对象 注
            var5 = this.build(e.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                inputStream.close();
            } catch (IOException var13) {
                ;
            }

        }

        return var5;
    }

解:Configuration的对象是什么呢

Configuration的对象跟xml配置文件的对象类似

比如xml配置文件中的标签包括:

properties(属性),settings(设置),typeAliases(类型别名),typeHandlers(类型处理器),objectFactory(对象工厂),mappers(映射器)等

Configuration类的成员变量

public class Configuration {
    protected Environment environment;
    protected boolean safeRowBoundsEnabled;
    protected boolean safeResultHandlerEnabled;
    protected boolean mapUnderscoreToCamelCase;
    protected boolean aggressiveLazyLoading;
    protected boolean multipleResultSetsEnabled;
    protected boolean useGeneratedKeys;
    protected boolean useColumnLabel;
    protected boolean cacheEnabled;
    protected boolean callSettersOnNulls;
    protected boolean useActualParamName;
    protected boolean returnInstanceForEmptyRow;
    protected String logPrefix;
    protected Class<? extends Log> logImpl;
    protected Class<? extends VFS> vfsImpl;
    protected LocalCacheScope localCacheScope;
    protected JdbcType jdbcTypeForNull;
    protected Set<String> lazyLoadTriggerMethods;
    protected Integer defaultStatementTimeout;
    protected Integer defaultFetchSize;
    protected ExecutorType defaultExecutorType;
    protected AutoMappingBehavior autoMappingBehavior;
    protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior;
    protected Properties variables;
    protected ReflectorFactory reflectorFactory;
    protected ObjectFactory objectFactory;
    protected ObjectWrapperFactory objectWrapperFactory;
    protected boolean lazyLoadingEnabled;
    protected ProxyFactory proxyFactory;
    protected String databaseId;
    protected Class<?> configurationFactory;
    protected final MapperRegistry mapperRegistry;
    protected final InterceptorChain interceptorChain;
    protected final TypeHandlerRegistry typeHandlerRegistry;
    protected final TypeAliasRegistry typeAliasRegistry;
    protected final LanguageDriverRegistry languageRegistry;
    protected final Map<String, MappedStatement> mappedStatements;
    protected final Map<String, Cache> caches;
    protected final Map<String, ResultMap> resultMaps;
    protected final Map<String, ParameterMap> parameterMaps;
    protected final Map<String, KeyGenerator> keyGenerators;
    protected final Set<String> loadedResources;
    protected final Map<String, XNode> sqlFragments;
    protected final Collection<XMLStatementBuilder> incompleteStatements;
    protected final Collection<CacheRefResolver> incompleteCacheRefs;
    protected final Collection<ResultMapResolver> incompleteResultMaps;
    protected final Collection<MethodResolver> incompleteMethods;
    protected final Map<String, String> cacheRefMap;
    // 省略部分源码
}

从而可以看出初始化本质,就是把xml配置文件里的数据封装到Configuration这个类的属性中。

接下来我们继续分析e.prase这个方法

//3、进入XMLConfigBuilder 类中的prase
 public Configuration parse() {
        if(this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            
            this.parsed = true;
            //parser是XPathParser解析器对象,读取节点内数据,<configuration>是MyBatis配置文件 
            // 中的顶层标签
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }
//4、进入parseConfiguration方法,这个方法就是是把xml配置文件里的标签封装到Configuration属性中
private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            Properties e = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(e);
            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(e);
            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);
        }
    }

到这里xml的解析就完事了,接着我们由回到build的重载方法 

//5、接着回到这个build重载的方法 var5 = this.build(e.parse());
//把Configuration的对象当做入参传入,返回一个SessionFactory
public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

下面是第二部分执行sql了

SqlSession是一个接口,它有一个默认的实现类DefaultSqlSession,一个会话只要一个SqlSession,并且在使用完成后需要close()关闭。

Executor这个也是个接口,他有三个实现类,BatchExecutor(重用语句并执行批量更新),ReuseExecutor(重用预处理语句prepared statements),SimpleExecutor(普通的执行器,默认)。

//使用SqlSessionFactory创建SqlSession
SqlSession session=factory.openSession();

获得session

//6、进入DefaultSqlSessionFactory 类中的openSession
  public SqlSession openSession() {
        //getDefaultExecutorType() 返回的是一个SimpleExecutor
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }


//7、进入openSessionFromDataSource这个方法
//ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别,autoCommit是否开启事务
//openSession的多个重载方法可以指定获得的SeqSession的Executor类型和事务的处理
 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
            Environment e = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(e);
            tx = transactionFactory.newTransaction(e.getDataSource(), level, autoCommit);

            //根据参数创建指定类型的Executor
            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;
    }

接下来回到之前的测试代码

 //使用SqlSession创建dao接口的代理对象
  UserMapper userDao =session.getMapper(UserMapper.class);
  //使用代理对象执行查询方法
  List<User> list=userDao.findUser();

我们创建的Mapper接口类,没有实现这个接口类,但是让然可以调用里面的方法,这其实是采用了设计模式中的jdk动态代理技术,如果大家对这个动态代理不是很清楚,可以看我写的这篇博客。https://blog.csdn.net/qq_30353203/article/details/103681191

在这说源码之前,先介绍下Mybatis初始化时对接口的处理,大家可以翻看上面面Configuration的源码属性里 ,就有一个MapperRegistry属性,它的内部维护了一个HashMap用于存储mapper接口的工厂类,每个接口对应一个工厂。mappers可以配置接口的包路径、或者某个具体的接口类

当解析mappers标签时,它会判断解析到的是mapper配置文件时,会再将对应配置文件中的增删改查标签一 一封装成MappedStatement对象,存入mappedStatements中。


当解析到mappers标签的时候,会做个判断,如果是解析到接口,会创建此接口对应的MapperProxyFactory对象,存入HashMap中,key = 接口的字节码对象,value = 此接口对应的MapperProxyFactory对象。
 

//进入MapperRegistry这个类
public class MapperRegistry {
    private final Configuration config;
    //创建一个HashMap,用于存储key是字节码对象,value是对应接口类的工厂MapperProxyFactory
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
        
        //判断类型,如果是解析到的接口类型,就存入到HashMap中
        public <T> void addMapper(Class<T> type) {
        if(type.isInterface()) {
            if(this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }

            boolean loadCompleted = false;

            try {
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if(!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

    }
    
    省略部分源码
}

接下来给大家看下源码,Mybatis是如何通过jdk动态代理创建代理对象的。

//使用SqlSession创建dao接口的代理对象
 UserMapper userDao =session.getMapper(UserMapper.class);
//进入DefaultSqlSession 类,是SqlSession 接口的实现类
public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
    }

//进入Configuration类中 的getMapper方法
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

//进入MapperRegistry类中,的getMapper方法
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        //从刚才存到HashMap中 根据不同的key,取出value的值,也就是MapperProxyFactory
        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);
            }
        }
    }

//进入MapperProxyFactory 中的newInstance 方法
 public T newInstance(SqlSession sqlSession) {
        //创建了JDK动态代理的Handler类
        MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, 
        this.methodCache);
        //调用构造函数 newInstance
        return this.newInstance(mapperProxy);
    }

//这个构造函数 ,重载的方法,由动态代理创建新示例返回。newProxyInstance有三个入参,最后一个入参需要实现InvocationHandler接口类。这个返回的对象就是动态代理的对象,会执行invoke方法
protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

//MapperProxy 实现了InvocationHandler接口类
public class MapperProxy<T> implements InvocationHandler, Serializable {

 private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

    //构造函数,传入的入参是SqlSession,说明每个session中的代理对象的不同的!
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    省略部分源码


}

动态代理后返回的实例,就可以调用mapper接口中的方法,说明在MapperProxy中的invoke方法中已经为我们实现了方法。

//进入MapperProxy 中的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);
        // 重点在这:MapperMethod最终调用了执行的方
        return mapperMethod.execute(this.sqlSession, args);
    }

//进入excute方法
public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        
//根据命令转顺序执行不同的crud,调用的还是SqlSession中的方法.
switch(null.$SwitchMap$org$apache$ibatis$mapping$SqlCommandType[this.command.getType().ordinal()]) {
        case 1:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case 2:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case 3:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case 4:
            if(this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if(this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if(this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if(this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
            }
            break;
        case 5:
            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;
        }
    }

至此,Mybatis的原理,源码分析就结束了。

发布了311 篇原创文章 · 获赞 58 · 访问量 28万+

猜你喜欢

转载自blog.csdn.net/qq_30353203/article/details/103794212