对于SqlSessionTemplate的理解

对于SqlSessionTemplate的理解写在开始最近利用闲暇时间猫了一下mybatis和mybatis-spring的源码,看后发现SqlSessionTemplate和MapperFactoryBean这两个类对于mybatis的事务操作起到了关键的作用,因此写个随笔记录一下。本篇主要讲述下我个人对于SqlSessionTemplate的理解,关于MapperFactoryBean后续有时间会再写一篇文章记录一下。SqlSessionTemplateSqlSessionTemplate对于Mybatis事务提交起到了一个关键作用。先

大家好,又见面了,我是你们的朋友全栈君。

写在开始

最近利用闲暇时间猫了一下mybatis和mybatis-spring的源码,看后发现SqlSessionTemplate和MapperFactoryBean这两个类对于mybatis的事务操作起到了关键的作用,因此写个随笔记录一下。本篇主要讲述下我个人对于SqlSessionTemplate的理解,关于MapperFactoryBean后续有时间会再写一篇文章记录一下。

SqlSessionTemplate

SqlSessionTemplate对于Mybatis事务提交起到了一个关键作用。先通过一个示例看一下不使用SqlSessionTemplate情况下通过Mybatis是如何来进行一次update操作。

1、使用DefaultSqlSession完成update操作

我的mapper配置文件如下所示(其他配置文件省略)

 

<?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.jorg.mybatis.mappers.StudentMapper">
    <update id="update" parameterType="java.util.Map">
        UPDATE
        student
        SET
        `name` = #{name}
        WHERE
        id = #{id}
    </update>
</mapper>

进行一次update操作

 

public class DefaultSqlSessionTest {
    
    private static SqlSessionFactory sqlSessionFactory;

    static {
        initSqlSessionFactoryBySqlSessionFactoryBuilder();
    }

    /**
     * 通过 SqlSessionFactoryBuilder 创建 SqlSessionFactory
     */
    public static void initSqlSessionFactoryBySqlSessionFactoryBuilder(){
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            inputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        //返回的是DefaultSqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        HashMap<String, Object> paramMaps = Maps.newHashMap();
        paramMaps.put("name","hello");
        paramMaps.put("id","100");
        sqlSession.update("update",paramMaps);
        sqlSession.commit();
    }

由于默认情况下autoCommit是关闭的,所以此时若要通过显示的调用DefaultSqlSession的commit方法才能完成真正的更新

2、使用SqlSessionTemplate完成update操作

我的mapper配置文件同上,进行一次update操作。

 

public class SqlSessionTemplateTest {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            inputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
        HashMap<String, Object> paramMaps = Maps.newHashMap();
        paramMaps.put("name","hello");
        paramMaps.put("id","100");
        sqlSessionTemplate.update("update",paramMaps);
    }
}

通过上述代码可以发现,使用SqlSessionTemplate无需通过调用commit方法就可以完成真正的更新。那么它背后具体是怎么实现的呢?来,跟进源码看一下。

 

public class SqlSessionTemplate implements SqlSession, DisposableBean {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;

  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;

  
  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
  }

  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
    this(sqlSessionFactory, executorType,
        new MyBatisExceptionTranslator(
            sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
  }
  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    //创建sqlSession代理对象
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }
}

上述是SqlSessionTemplate的构造方法(此处只截取了关键代码片段)。当通过调用SqlSessionTemplate(SqlSessionFactory sqlSessionFactory)构造器new对象的时候,其最终内部通过SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator)来完成初始化,这个方法的关键是内部通过调用JDK Proxy.newProxyInstance方法创建了一个SqlSession代理对象并赋值给了sqlSessionProxy。此时,我们需要把目光聚焦到SqlSessionInterceptor的实现逻辑,因为其中包含该代理对象的代理逻辑,其代码逻辑如下:

 

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          //执行commit
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

发现了没?在代理逻辑中先通过getSqlSession方法获取sqlSession(该方法逻辑后续再展开讲),而后执行method.invoke方法,之后通过isSqlSessionTransactional方法判断当前操作是否是事务操作,如果属于事务操作则执行sqlSession的commit方法,以此完成了事务的提交。

我们再回到之前通过SqlSessionTemplate完成update操作的例子上,先看下sqlSessionTemplate.update(“update”,paramMaps)源码。

 

  public int update(String statement, Object parameter) {
    return this.sqlSessionProxy.update(statement, parameter);
  }

看到了没,本质上调用的是sqlSessionProxy这个代理对象的update操作,结合刚才描述的代理逻辑,现在清楚了SqlSessionTemplate无需通过调用commit方法就可以完成真正的更新的背后原理了吧。

3、衍生一下

我们在日常开发中,应该很少以上述的方式来使用mybatis,更多的则是将对应Mapper配置关联到相应的interface,并直接调用interface中定义的方法来操作数据库。如下所示:

我的mapper文件

 

<mapper namespace="com.jorg.mybatis.transactional.StudentMapper">
    <update id="update">
        UPDATE
        student
        SET
        `name` = #{name}
        WHERE
        id = #{id}
    </update>
</mapper>

StudentMapper.java

 

public interface StudentMapper {
    int update(@Param("id") Integer id, @Param("name") String name);
}

执行代码

 

public class SqlSessionTemplateTest {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            inputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
     public static void main(String[] args) {
        SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
        StudentMapper studentMapper = sqlSessionTemplate.getMapper(StudentMapper.class);
        studentMapper.update(100,"hello");
    }
}

上述通过interface来执行数据库操作的使用方式应该是最常用的,那么该方式背后又是如何实现的呢?来跟进sqlSessionTemplate.getMapper代码看一下:

 

  @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }

  @Override
  public Configuration getConfiguration() {
    return this.sqlSessionFactory.getConfiguration();
  }

我们可以看到,这个getMapper方法内部的执行逻辑是:

  1. 先通过sqlSessionFactory.getConfiguration()返回Configuration
  2. 调用Configuration对象的getMapper方法来返回对应的Mapper

这个sqlSessionFactory是哪里来的?返回去看下我们的测试方法(如下所示)。发现了没,是我们自己创建的,并在执行new SqlSessionTemplate(sqlSessionFactory)创建SqlSessionTemplate的时候传进去的。

 

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            inputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

那么sqlSessionFactory中的Configuration是怎么来的呢?这个相对就比较复杂了,主要涉及到Mybatis对我们自定义配置文件的解析逻辑(感兴趣的话可以进入上面代码的SqlSessionFactoryBuilder类的build方法内部去看一下,或者看下这个博客MyBatis 源码分析 – 配置文件解析过程)。
在这里呢,我们可以把Configuration看作是我们自定义的xml配置文件所对应的java对象,即,在Configuration中包含了我们在xml配置文件中配置的所有信息。

那么,我们不妨跟近Configuration的getMapper方法看下里面做了什么。

 

Configuration.java

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

MapperRegistry.java

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

MapperProxyFactory<T>.java

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

进入源码以后可以发现,整个getMapper的执行链路是:Configuration.java getMapper–>MapperRegistry.java getMapper–> MapperProxyFactory<T>.java newInstance

整理一下整个链路,你发现了什么?没错,动态代理!这里最终返回的是一个interface对应Mapper的一个代理对象。

由于上述的代码只是我截取的部分源码片段,其中有几个Field可能大家感觉比较晕。这里简单描述一下:

  1. Configuration 中的 mapperRegistry :可以把它理解为Mybatis的一个全局Mapper注册中心,它是在Mybaits启动时候解析配置文件的时候生成的;Mybatis会对解析到的每个mapper xml文件中的信息在mapperRegistry中进行注册。
  2. MapperRegistry 中的 knownMappers:它在 MapperRegistry 中是一个HashMap类型,如下所示,它的key是每个mapper xml文件中namespace属性值对应的Interface Mapper(如上述示例中的com.jorg.mybatis.mappers.StudentMapper),它的value对应的就是一个MapperProxyFactory工厂,从名字上看就可以知道,该工厂是用来生产MapperProxy的,对应value的初始化也是在Mybatis解析配置文件阶段。

 

public class MapperRegistry {
    //....
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
    //.....
}

回到 MapperProxyFactory.java,我们继续看下它生成代理对象的过程,不难发现 MapperProxy 类封装了具体的代理逻辑,跟进其源码:

 

public class MapperProxy<T> implements InvocationHandler, Serializable {

 //...
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

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

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        //如果方法是定义在 Object 类中的,则直接调用
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //从缓存中获取 MapperMethod 对象,若缓存未命中,则创建 MapperMethod 对象
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }
  //....
}

其中invoke方法体是其代理逻辑,可以看到它最终的调用的是 mapperMethod.execute 方法。mapperMethod获取通过调用一个cachedMapperMethod方法,该方法执行逻辑涉及到二级缓存相关的内容,后面会有时间的情况下会单独写篇文章分析一下。在这里,可以把MapperMethod理解为 Mybatis对mapper文件中的每个执行逻辑的一个封装(如<select></select>,<update></update>等等),而每个MapperMethod的生成也是在Mybatis启动时解析文件时进行的。

下面跟进下 mapperMethod.execute 执行逻辑:

 

public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }
}

看着代码有点多,有点懵逼?莫慌兄弟!没那么复杂。方法体的执行逻辑有一个switch语句,而每个case是啥?INSERT、UPDATE、DELETE、SELECT,没错这对应的就是mapper里的标签啊。回想一下咱们一开始的测试代码的执行的是update操作对吧,那对应的case就是UPDATE了。So,看下UPDATE中的执行逻辑:sqlSession.update(command.getName(), param),像不像在“使用SqlSessionTemplate完成update操作”部分的测试代码里的sqlSessionTemplate.update(“update”,paramMaps)这个逻辑。有点像???又不太像。不像的地方是之前用的是sqlSessionTemplate,而这里是sqlSession;之前update传参是“update”,而这里是command.getName()。那到底是不是呢??

好了,也不卖关子了。其实这里完全就是一回事,这个sqlSession是怎么来的呢,回过去再顺着之前一路代码跟进的过程看(为了防止你失去信心,我又在下面梳理了一下代码片段):

 

SqlSessionTemplate.java

  @Override
  public <T> T getMapper(Class<T> type) {
      /*注意看,这里传入的是this*/
    return getConfiguration().getMapper(type, this);
  }
        |
        |
        V
Configuration.java

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
        |
        |
        V
MapperRegistry.java

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
        |
        |
        V
MapperProxyFactory<T>.java

    public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
  }
        |
        |
        V
//此处省略MapperProxy.java

好了,看明白了没?MapperProxy里面的sqlSession是谁?对,就是SqlSessionTemplate!那command.getName()是呢?这里直接告诉你吧,它的值就是“update”,它是什么时候给command这个对象设进去的?也是在Mybatis启动时候解析配置文件的时候设置的。

So,sqlSession.update(command.getName(), param)是完全等于sqlSessionTemplate.update(“update”,paramMaps)。

结尾

至此,我对SqlSessionTemplate这个类理解也阐述完了。如果看后觉得有哪些不合理和或者疏漏的,希望大家能够帮助矫正。

 

 

 

原文地址:https://www.jianshu.com/p/7cb4777a539e?from=singlemessage

参考文章1:https://www.cnblogs.com/daxin/p/3544188.html

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/138275.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 指令的四个周期_cpu指令周期流程图

    指令的四个周期_cpu指令周期流程图指令流程图的概念菱形:译码,测试,表示判断,如零指令字是0或者1.与前面的CPU周期紧密相连,不单独占用CPU周期。每个方框箭头下面的是公共操作符符号,表示一条指令结束。mov指令将R1寄存器的数据存储到R2寄存器中,lad指令时间主存中的数据存储到寄存器中。sto是将R2中的数据根据R3中的主存地址存储到主存中。lad和sto是寄存器-主存指令需要三个CPU周期,其他都是寄存器-…

    2022年10月13日
  • 指令周期,时钟周期,总线周期概念辨析图_总线周期是指

    指令周期,时钟周期,总线周期概念辨析图_总线周期是指《指令周期、时钟周期、总线周期概念辨析》由会员分享,可在线阅读,更多相关《指令周期、时钟周期、总线周期概念辨析(2页珍藏版)》请在人人文库网上搜索。指令周期、时钟周期、总线周期概念辨析在计算机中,为了便于管理,常把一条指令的执行过程划分为若干个阶段,每一阶段完成一项工作。例如,取指令、存储器读、存储器写等,这每一项工作称为一个基本操作。完成一个基本操作所需要的时间称为机器周期。一般情况下,一个机器周期由若干个S周期(状态周期)组成。通常用内存中读取一个指令字的最短时间来规定CPU周期,(也就是计算机通

    2022年10月10日
  • Iocomp 5.12 SP6 ActiveX Crack

    Iocomp 5.12 SP6 ActiveX Crack不需要安装,免去大家下载,Q578867473安装需要注册账号的麻烦的IocompActiveX/VCL标准包是由29个控件组成的套件,Q578867473用于使用ActiveX或VCL开发环境创建专业的仪表应用程序。这些控件可用于科学,工程,医学,石油和天然气,半导体,工厂自动化,航空航天,军事,机器人技术,电信,楼宇和家庭自动化,HMI,SCADA以及数百种其他类型的应用程序。所有Iocomp控件均启用OPC。如果您的项目需要OPC连接,则可以将任何属性连接到OPC项/标签。所有连接都可

  • 微信小程序之授权登录(附完整源码)

    微信小程序之授权登录(附完整源码)个人博客上已经同步更新了文章,有目录索引,阅读起来比较方便,欢迎大家移步个人博客上读阅~个人博客地址:http://zwd596257180.gitee.io/blog/2019/04/15/wechat_applet_login/微信小程序之授权登录一、前言由于微信官方修改了getUserIn…

  • 20那天android得知

    20那天android得知

  • TLSF算法分析

    TLSF算法分析注:本文的大部分内容摘录自论文《TLSF:aNewDynamicMemoryAllocatorforReal-TimeSystems》,可以通过“科学上网”访问如下链接阅读原文:http://www.gii.upv.es/tlsf/files/ecrts04_tlsf.pdf。什么是TLSFTLSF是TwoLevelSegregatedFitmemoryal

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号