对于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

 

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

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

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 用BT3激活成功教程无线网络密码的教程以及如何制作U盘版和光盘版BT3

    用BT3激活成功教程无线网络密码的教程以及如何制作U盘版和光盘版BT3 用BT3激活成功教程无线网络密码的教程以及如何制作U盘版BT3招:论坛版主 新建文本文档.rar(1K)下载次数:81 一分钟制作BT3U盘版方便,快捷简单光盘版BT3,大概694MB,直接刻盘,然后用光盘引导,即可进入bt3,连接为:看附件迅雷专用高速下载 U盘版Bt3,大概783MB,连接为:看附件[/url]准备工作:一张1G以上的U盘,或是SD卡,或是TF卡,当然,TF或S

    2022年10月1日
    2
  • ubuntu重启nginx_ubuntu配置nginx

    ubuntu重启nginx_ubuntu配置nginx大家好,我是极智视界,本文介绍一下ubuntu安装nginx的方法。

    2022年9月19日
    4
  • Eclipse 汉化教程完美版

    Eclipse 汉化教程完美版1.首先查看自己的Eclipse版本号点来Help中的AboutEclipse,我的是最新版本的Oxygen。2、去官网上找对应的汉化安装包路径浏览器打开网址 http://www.eclipse.org/babel/downloads.php 找到对应自己版本的安装包我的是http://archive.eclipse.org/technology/babel/update-site/R0.15…

    2022年6月5日
    30
  • 苹果屏蔽更新描述文件_屏蔽iPhone更新的iOS描述文件安装办法「建议收藏」

    屏蔽iOS更新分两种,第一种非越狱设备,只能系统在12.1以下的才能安装屏蔽描述文件safari浏览器输入ibeta.me回车,找到下图的屏蔽OTA更新,直接安装就行了第二种越狱设备,首先添加源https://xsf1re.github.io/repo/安装下图这个插件这个文件可以解除12.1以上设备描述文件失效不允许安装的问题,作者说写着兼容12.4-13.3,其他系统的老铁们自…

    2022年4月11日
    456
  • java中集合转数组中_JAVA中集合转数组遍历[通俗易懂]

    java中集合转数组中_JAVA中集合转数组遍历[通俗易懂]JAVA中集合的遍历的一种方法时集合转数组遍历,也是就调用Collection中的toArray().代码:publicstaticvoidmain(String[]args){//TODOAuto-generatedmethodstubCollectionc=newArrayList();c.add(newStudent(“kj”,12));c.add(newStude…

    2022年6月15日
    29
  • maven 打的包在哪_maven打包流程学习「建议收藏」

    maven 打的包在哪_maven打包流程学习「建议收藏」前言:最近工作中遇到了几次跟maven打包相关的问题,每个问题上网查资料解决都花了不少时间,很影响工作进度。既然遇到好几次,每次都能发现知识盲点,干脆总结整理一下,啃掉这个难啃的骨头。ps:最近看到了一个很有意思句子:因为今天不想跑步,所以才去跑,这是长距离跑者的思维方式。转载:正文:还是首先描述一下最近遇到的几个问题吧:一、初见springboot多模块项目mvn打包遇到的问题-存在依赖但却…

    2022年5月11日
    42

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

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