前言

  上篇笔记我们成功的装载了Configuration,并写入了我们全部需要的信息。根据这个Configuration创建了DefaultSqlSessionFactory。本篇我们实现构建SqlSession即mybatis的一次sql会话,并获取到我们常用的代理mapper接口类。在正文之前先放上之前的一段代码

  

  1. @Autowired
  2. private SqlSessionFactory sqlSessionFactory;
  3.  
  4. @GetMapping("/get")
  5. public List<AssetInfo> get(){
  6. SqlSession sqlSession = sqlSessionFactory.openSession();
  7. AssetInfoMapper mapper = sqlSession.getMapper(AssetInfoMapper.class);
  8. List<AssetInfo> test = mapper.get("测试删除" , "123123123");
  9. System.out.println(test);
  10. return test;
  11. }
  1. public interface AssetInfoMapper {
  2. List<AssetInfo> get(@Param("name") String name, @Param("id")String id);
  3. }
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.mapper.AssetInfoMapper">
  4. <select id="get" resultType="com.entity.AssetInfo">
  5. select * from asset_info where id =#{id} and `name` = #{name}
  6. </select>
  7. </mapper>

  这算是个比较经典的mybatis执行案例了,根据SqlSessionFactory获取到SqlSession,然后构建指定mapper接口的代理类,并最终调用执行xml中对应的sql,封装返回结果并返回。本文则根据这个案例来说明。注意这个SqlSessionFactory即是我们第一步中最终构建的DefaultSqlSessionFactory

正文

1.sqlSessionFactory.openSession();

  根据图中代码可以知道我们是根据这个方法获取到SqlSession,我们看下方法的主要内容。方法进行了多次重载,我们直接看最终的方法

  1. /*
  2. *execType 执行器类型 默认传入了ExecutorType.SIMPLE;
  3. *level 事物的隔离级别 默认传null
  4. * autoCommit 事物是否自动提交 默认为false
  5. **/
  6. private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  7. Transaction tx = null;
  8. try {
  9. //获取到配置中设置的当前环境 里面包含了环境id 数据库信息 和事物信息
  10. //我们在构建的时候已经构建了一个environment
  11. final Environment environment = configuration.getEnvironment();
  12. //根据环境获取其事物级别,如果环境为空或者其事物级别为空则返回默认的ManagedTransactionFactory,
  13. //我们使用SPring集成 所以会使用Spring的事物管理器 SpringManagedTransactionFactory
  14. final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
  15. //根据数据源,事物隔离级别,是否自动提交生成事物管理器 这儿会生成Spring的事物SpringManagedTransaction
  16. tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  17. //根据事物和执行器类别生成执行器
  18. //由于二级缓存默认打开 所以这儿的SimpleExecutor会被包装为CacheExecutor
  19. final Executor executor = configuration.newExecutor(tx, execType);
  20. //返回DefaultSqlSession
  21. return new DefaultSqlSession(configuration, executor, autoCommit);
  22. } catch (Exception e) {
  23. //捕获到异常则关闭这次的事物
  24. closeTransaction(tx); // may have fetched a connection so lets call close()
  25. throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
  26. } finally {
  27. ErrorContext.instance().reset();
  28. }
  29.  
  30. }

  该方法的重要操作有两步,第一步是根据我们设置的Environment获取到对应的事物,第二步是根据事物和executor的type来构造一个Executor。两步的代码我们都看一下。

  第一步 获取事物工厂的方法中可以看到如果我们没设置Environment,或者Environment中没有事物工厂,则会产生一个默认的,本例使用的是Spring的事物工厂。最后当然也会产生一SpringManagedTransaction

  1. private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
  2. if (environment == null || environment.getTransactionFactory() == null) {
  3. return new ManagedTransactionFactory();
  4. }
  5. return environment.getTransactionFactory();
  6. }
  1. public class SpringManagedTransactionFactory implements TransactionFactory {
  2. ...
  3. public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
  4. return new SpringManagedTransaction(dataSource);
  5. }
  6. ...
  7. }

   第二步 构造Executor,这儿即判断是否为空,然后选择对应的处理器,这儿选择普通执行器,并且包装一下为CacheExecutor ,最后记得这儿会执行一次所有拦截器的plugin方法,可以在拦截器中对Executor做自己的处理

  1. public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  2. //如果为null则设置为默认的type 即ExecutorType.SIMPLE
  3. executorType = executorType == null ? defaultExecutorType : executorType;
  4. //如果默认的还是为null(config中默认的type被修改)则手动再设置为ExecutorType.SIMPLE
  5. executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  6. Executor executor;
  7. //批量操作模式
  8. if (ExecutorType.BATCH == executorType) {
  9. executor = new BatchExecutor(this, transaction);
  10. }
  11. //复用预处理语句
  12. else if (ExecutorType.REUSE == executorType) {
  13. executor = new ReuseExecutor(this, transaction);
  14. }
  15. //普通执行器
  16. else {
  17. executor = new SimpleExecutor(this, transaction);
  18. }
  19. //是否开启二级缓存 默认开启
  20. if (cacheEnabled) {
  21. //进行一次包装 为CachingExecutor
  22. executor = new CachingExecutor(executor);
  23. }
  24. //注意这儿会执行一次所有拦截器的plugin方法
  25. executor = (Executor) interceptorChain.pluginAll(executor);
  26. return executor;
  27. }

  两步操作完成后我们获取到了需要的Executor,并通过传入有参构造,构建出默认的DefaultSqlSession并返回

2.sqlSession.getMapper(Class<?> class)

  根据代码我们可以看到,系统是根据这个方法最后获取到我们的代理mapper的。这儿SqlSession注意为DefaultSqlSession,我们先看下DefaultSqlSession的大概构造

  先看下其继承图,可以看到其实现了SqlSession接口,我们其实主要使用的方法即其接口中的方法。通过接口中的方法可以得知主要包含了事物操作方法,和数据库的增删改查。

  相信根据方法名大家也都能明白其中的意思。具体的方法则等后面用到的时候再分析

  然后再看下DefaultSqlSession中所包含的成员变量

  1. private final Configuration configuration;
  2. private final Executor executor;
  3.  
  4. private final boolean autoCommit;
  5. private boolean dirty;
  6. private List<Cursor<?>> cursorList;
  • configuration即我们一开始构建的config类,里面包含了mybatis所有的配置信息以及mapper信息
  • executor  即执行器 我们这里是包装了SimpleExecutor的CachingExecutor,以便支持二级缓存
  • autoCommit是否自动提交事务,默认为false
  • dirty 记录本次executor执行过程中是否有过更改操作
  • cursorList  新特性   分批读取时使用

  了解完SqlSession的大致结构后,我们开始分析getMapper即获取mapper接口代理类的方法

2.getMapper

   2.1 根据我们构建的configuration来获取代理

  1. @Override
  2. public <T> T getMapper(Class<T> type) {
  3. return configuration.getMapper(type, this);
  4. }

     2.2 configuration中根据mapperRegistry.getMapper获取

  1. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  2. return mapperRegistry.getMapper(type, sqlSession);
  3. }

    2.3 mapperRegistry最后的获取逻辑

  1. private final Configuration config; //即mybatis初始化的config
  2. private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(); //存储mapper接口及其代理工厂的map
  3.  
  4. @SuppressWarnings("unchecked")
  5. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  6. //根据存储接口获取其MapperProxyFactory
  7. final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  8. if (mapperProxyFactory == null) {
  9. throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  10. }
  11. try {
  12. //根据MapperProxyFactory获取代理类并返回
  13. return mapperProxyFactory.newInstance(sqlSession);
  14. } catch (Exception e) {
  15. throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  16. }
  17. }

    可以看到,最终获取代理类的操作还是使用我们构建的config中MapperRegistry中的方法来获取。上面方法中knownMappers 中的key则是mapper.xml中nameSpace反射生成的接口class属性,value则是根据当前接口构建的代理工厂。在构建SqlSessionFactory中解析mapper.xml文件方法中就将所有的xml信息缓存到了这个knownMappers中。这点第一篇分析中已经有过分析,这儿不在赘述。本方法最终调用的是MapperProxyFactory的newInstance方法,我们接着看

3.MapperProxyFactory.newInstance

  1. public class MapperProxyFactory<T> {
  2. //代理工厂对应的接口
  3. private final Class<T> mapperInterface;
  4. //对应的方法缓存
  5. private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
  6.  
  7. public MapperProxyFactory(Class<T> mapperInterface) {
  8. this.mapperInterface = mapperInterface;
  9. }
  10.  
  11. public Class<T> getMapperInterface() {
  12. return mapperInterface;
  13. }
  14.  
  15. public Map<Method, MapperMethod> getMethodCache() {
  16. return methodCache;
  17. }
  18. //根据MapperProxy构建代理类并返回
  19. @SuppressWarnings("unchecked")
  20. protected T newInstance(MapperProxy<T> mapperProxy) {
  21. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  22. }
  23. //本方法主要根据传入的SqlSession 构建一个MapperProxy
  24. public T newInstance(SqlSession sqlSession) {
  25. final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  26. return newInstance(mapperProxy);
  27. }
  28.  
  29. }

  本类即是每个mapper接口的代理工厂类。注意其中的methodCache,这个为了支持mybatis缓存所创建的。本类有两个newInstance方法,上面的是根据传入的mapperProxy创建代理,我们都知道创建jdk代理需要一个实现InvocationHandler接口的类作为参数,而下面的newInstance则主要是为了创建实现了InvocationHandler接口的MapperProxy实例,注意传入了SqlSession,本mapper代表的接口,还有方法缓存

4.MapperProxy

  我们都知道jdk动态代理类最终执行的是实现了InvocationHandler接口的类的方法。所以本类算是mapper最终方法执行的入口了。我们来看几个比较核心的属性

  1. private final SqlSession sqlSession;
  2. private final Class<T> mapperInterface;
  3. private final Map<Method, MapperMethod> methodCache;
  • SqlSession即我们创建的一次会话
  • mapperInterface即我们要代理的接口
  • methodCache即MethodProxyFactory中的方法缓存

  我们看下其构造方法,也就是做了简单的赋值操作

  1. public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
  2. this.sqlSession = sqlSession;
  3. this.mapperInterface = mapperInterface;
  4. this.methodCache = methodCache;
  5. }

  到此InvocationHandler的接口实现类则创建完毕,然后回到步骤3中当做参数传入jdk动态代理的方法,最终构造出mapper的代理类并返回到我们的main方法中。

 

下面有一张流程图作为参考

完结

  可以看到,我们通过之前初始化构造的SqlSessionFactory创建除了SqlSession,内部包含了执行器Executor,并根据这个SqlSession 通过我们初始化时对每个接口创建的代理类工厂proxyFactory来创建代理类并返回,我们mapper最终执行的方法也是mapperProxy中的invoke方法。

  关于mapper的方法执行流程即mapperProxy的invoke方法的执行涉及到了mybatis的sql执行内容,这个在下一个章节中会解析

mybatis源码探索笔记-2(构建SqlSession并获取代理mapper)的更多相关文章

  1. mybatis源码探索笔记-1(构建SqlSessionFactory)

    前言 mybatis是目前进行java开发 dao层较为流行的框架,其较为轻量级的特性,避免了类似hibernate的重量级封装.同时将sql的查询与与实现分离,实现了sql的解耦.学习成本较hibe ...

  2. mybatis源码探索笔记-5(拦截器)

    前言 mybatis中拦截器主要用来拦截我们在发起数据库请求中的关键步骤.其原理也是基于代理模式,自定义拦截器时要实现Interceptor接口,并且要对实现类进行标注,声明是对哪种组件的指定方法进行 ...

  3. mybatis源码探索笔记-4(缓存原理)

    前言 mybatis的缓存大家都知道分为一级和二级缓存,一级缓存系统默认使用,二级缓存默认开启,但具体用的时候需要我们自己手动配置.我们依旧还是先看一个demo.这儿只贴出关键代码 public in ...

  4. mybatis源码探索笔记-3(使用代理mapper执行方法)

    前言 前面两章我们构建了SqlSessionFactory,并通过SqlSessionFactory创建了我们需要的SqlSession,并通过这个SqlSession获取了我们需要的代理mapper ...

  5. mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)

    目录 1. 简介 2. 解析 3 StrictMap 3.1 区别HashMap:键必须为String 3.2 区别HashMap:多了成员变量 name 3.3 区别HashMap:key 的处理多 ...

  6. MyBatis源码解析【6】SqlSession运行

    前言 这个分类比较连续,如果这里看不懂,或者第一次看,请回顾之前的博客 http://www.cnblogs.com/linkstar/category/1027239.html 经过之前的学习我们知 ...

  7. MyBatis源码解读(4)——SqlSession(上)

    在上一篇博客中提到MyBatis是如何实现代理类MapperProxy,并抛出了一个问题--是怎么执行一个具体的sql语句的,在文末中提到了MapperMethod的execute采用命令模式来判断是 ...

  8. MyBatis源码解析【4】反射和动态代理

    通过之前的介绍,我们了解了几个组件的生命周期. 它也是我们重要装备之一. 今天我们需要搞一件更加强的装备,叫做反射和动态代理. 如果没有这件装备的话,显然后面的源码boss是打不动的. 顺便说一下,下 ...

  9. MyBatis源码探索

    每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的.SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获 ...

随机推荐

  1. Javaweb项目的命名规范

    项目名称:一般是英文 包名:公司域名的倒写,例如com.baidu 数据访问层:dao,persist,mapper 实体:entity,model,bean,javabean,pojo 业务逻辑:s ...

  2. POJ 2018 Best Cow Fences(二分答案)

    题目链接:http://poj.org/problem?id=2018 题目给了一些农场,每个农场有一定数量的奶牛,农场依次排列,问选择至少连续排列F个农场的序列,使这些农场的奶牛平均数量最大,求最大 ...

  3. python入门(十九讲):多进程

    1.进程概念 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动.是系统进行资源分配和调度的基本单位,是操作系统结构的基础. 狭义定义:进程是正在运行的程序的实例. 在早期面向进程设 ...

  4. .NTE Core Web API Example

    Source from :https://www.codeproject.com/Articles/1260600/Speed-up-ASP-NET-Core-WEB-API-application- ...

  5. win7安装mysql数据库

    1. 软件准备,以64位系统为例如果是32位的下载32位压缩包即可] https://dev.mysql.com/downloads/mysql/ 2.下载解压到本地,将解压路径的bin目录配置到环境 ...

  6. opencv:图像梯度

    常见的图像梯度算子: 一阶导数算子: #include <opencv2/opencv.hpp> #include <iostream> using namespace cv; ...

  7. mvc 上传文件 HTTP 错误 404.13 - Not Found 请求筛选模块被配置为拒绝超过请求内容长度的请求。 maxRequestLength与 maxReceivedMessageSize 和 maxAllowedContentL区别

    具体的错误信息如下: 在线上遇到了文件上传问题,在测试环境试了好久都没有发现问题到底出在哪里,以为是服务器做了各种限制,然后一点思绪都没有.最后,尝试将线上的代码包拷贝一份,在测试环境运行,刚开始的时 ...

  8. 猴博士4小时讲完C语言视频教程

    猴博士4小时讲完C语言视频教程,一共有9节课. 目录结构如下: 目录:/2020030-猴博士4小时讲完C语言 [1G] ┣━━1.C语言基本语句(上)(更多资源访问:www.jimeng365.cn ...

  9. dfs序与求子树子节点(染了色)的个数

    https://blog.csdn.net/hpu2022/article/details/81910490 https://blog.csdn.net/qq_39670434/article/det ...

  10. S3C2440_时钟和电源管理_阅读开发手册记录

    1.如何进入sleep mode 1)要把一些重要的数据存放在状态寄存器里,状态寄存器里的数据在掉电后不会丢失. 2)要设置好唤醒源 3)配置相关寄存器,使其进入sleep mode 2.如何从sle ...