Mybatis源码阅读(二)

Stella981
• 阅读 552

本文主要介绍Java中,不使用XML和使用XML构建SqlSessionFactory,通过SqlSessionFactory 中获取SqlSession的方法,使用SqlsessionManager管理Sqlsession复用等等..以及相关的示例代码

SqlSession

SqlSessions 是由 SqlSessionFactory 实例创建的。SqlSessionFactory 对象包含创建 SqlSession 实例的各种方法。而 SqlSessionFactory 本身是由 SqlSessionFactoryBuilder 创建的,它可以从 XML、注解或 Java 配置代码来创建 SqlSessionFactory。

使用 MyBatis 的主要 Java 接口就是 SqlSession。你可以通过这个接口来执行命令,获取映射器示例和管理事务。在介绍 SqlSession 接口之前,我们先来了解如何获取一个 SqlSession 实例。

举个例子

Mybatis源码阅读(二)

public class Ttest {
    private Long id;
    private String context;
....
}

TestMapper.java

public interface TestMapper {
    Ttest getOne(Long id);
}

TestMapper.xml

<mapper namespace="com.liangtengyu.mapper.TestMapper">
    <select id="getOne" resultType="com.liangtengyu.entity.Ttest">
        select * from t_test where id  = #{id}
    </select>
</mapper>

mybatis-config.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>
    <!--开启日志输出-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>
    <!--配置类别名,配置后在Mapper配置文件(通常我们将编写SQL语句的配置文件成为Mapper配置文件)中需要使用pojo包中的类时,使用简单类名即可-->
    <typeAliases>
        <package name="com.liangtengyu.entity"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name="com.liangtengyu.mapper"/>
    </mappers>

</configuration>

来个测试方法:

   @Test
    public void testMyBatisBuild() throws IOException {
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
        SqlSession sqlSession = factory.openSession();
        TestMapper mapper = sqlSession.getMapper(TestMapper.class);
        Ttest one = mapper.getOne(1L);
        System.out.println(one);
        sqlSession.close();
    }

运行测试方法,控制台打印日志:

Checking to see if class com.liangtengyu.mapper.TestMapper matches criteria [is assignable to Object]
Opening JDBC Connection 
Created connection 2083117811.   //创建的连接名
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@7c29daf3]
==>  Preparing: select * from t_test where id = ?
==> Parameters: 1(Long)
<==    Columns: id, context
<==        Row: 1, 123



<==      Total: 1
Ttest{id=1, context='123'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@7c29daf3]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@7c29daf3]
Returned connection 2083117811 to pool. //用完了又放回连接池中

SqlSessionFactoryBuilder 创建出SqlSessionFactory,然后从SqlSessionFactory中得到SqlSession,最后通过SqlSession得到Mapper接口对象进行数据库操作。

我们打个断点.来跟踪SqlSessionFactoryBuilder的源代码:

Mybatis源码阅读(二) F7跟进 发现一堆build 而我们现在用的是传入reader的那个方法

Mybatis源码阅读(二) 我们可以看到,他帮我们传了2个Null参数给下一个build,我们跟着这个build继续往下跟.

Mybatis源码阅读(二) 这个build会将xml解析.然后调用parser.parse()方法将xml转化成Configuration,传入下一个build 继续下一个build

Mybatis源码阅读(二) 这个build终于干了我们最关心的事,他创建了DefaultSqlSessionFactory 返回SqlSessionFactory. 我们进来看看这个类:

public class DefaultSqlSessionFactory implements SqlSessionFactory {
        //它是SqlSessionFactory的实现类.
  private final Configuration configuration;
        //通过传入一个Configuration 来构造
  public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }
  ...

这样一看,那我们直接给它来个configuration不就可以创建一个SqlSessionFactory吗.

我们来试试

 //使用传入Configuration方式创建SqlSessionFactoryBuilder
    @Test
    public void testMyBatisBuild1() throws IOException {
        DataSource datasource = getDatasource();//首先创建数据源
        Environment e = new Environment("test", new JdbcTransactionFactory(), datasource);//传入datasource和JdbcTransactionFactory
        Configuration configuration = new Configuration();//构建一个Configuration
        configuration.setEnvironment(e);
        configuration.setLogImpl(StdOutImpl.class);//使用控制台输出日志实现
        configuration.getTypeAliasRegistry().registerAlias(Ttest.class);
        configuration.addMapper(TestMapper.class);
        //传入configuration                               
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(configuration);
        SqlSession sqlSession = build.openSession();
        TestMapper mapper = sqlSession.getMapper(TestMapper.class);
        Ttest one = mapper.getOne(1L);
        System.out.println(one);
        sqlSession.close();
    }
    //获取数据源方法
    public UnpooledDataSource getDatasource(){
        UnpooledDataSource unpooledDataSource = new UnpooledDataSource();
        unpooledDataSource.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8");
        unpooledDataSource.setDriver("com.mysql.jdbc.Driver");
        unpooledDataSource.setUsername("root");
        unpooledDataSource.setPassword("123456");
        return unpooledDataSource;
    }

运行结果:

Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
Opening JDBC Connection
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3dd4520b]
==>  Preparing: select * from t_test where id = ?
==> Parameters: 1(Long)
<==    Columns: id, context
<==        Row: 1, 123



<==      Total: 1
Ttest{id=1, context='123'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3dd4520b]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3dd4520b]


SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(configuration);
//在构造SqlSessionFactory时实际调用的还是DefaultSqlSessionFactory 所以我们直接使用
DefaultSqlSessionFactory factory = new DefaultSqlSessionFactory(configuration);
//也是一样的效果

Mybatis源码阅读(二) 那么他的内部是如何创建session的我们来看看源代码 根据断点我们到了factory.opensession()方法 F7进入方法

Mybatis源码阅读(二)

F8单步 发现调用了openSessionFromDataSource方法

Mybatis源码阅读(二) 三个参数.第一个参数是configuration.getDefaultExecutorType() 这个参数是Configuration类中定义的默认类型.

ExecutorType

Mybatis源码阅读(二)

package org.apache.ibatis.session;
  //还有其它 的类型 如下.
/**
 * @author Clinton Begin
 */
public enum ExecutorType {
  SIMPLE, REUSE, BATCH
}

大家可能对 ExecutorType 参数感到陌生。这个枚举类型定义了三个值:

ExecutorType.SIMPLE:该类型的执行器没有特别的行为。它为每个语句的执行创建一个新的预处理语句。

ExecutorType.REUSE:该类型的执行器会复用预处理语句。

ExecutorType.BATCH:该类型的执行器会批量执行所有更新语句,如果 SELECT 在多个更新中间执行,将在必要时将多条更新语句分隔开来,以方便理解。这里不再深入讨论

level

是称为 TransactionIsolationLevel,

事务隔离级别支持 JDBC 的五个隔离级别(NONEREAD_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE),并且与预期的行为一致。

autoCommit

向 autoCommit 可选参数传递 true 值即可开启自动提交功能


继续往下 进到openSessionFromDataSource方法

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);//返回DefaultSqlSession
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

返回的DefaultSqlSession实现了SqlSession的所有方法

Mybatis源码阅读(二) 我们进入到Sqlsession类中查看一下实现它的都有哪些类

Mybatis源码阅读(二) 一共有两个,而且SqlSessionManager还实现了SqlSessionFactory

SqlSessionManager

public class SqlSessionManager implements SqlSessionFactory, SqlSession {

  private final SqlSessionFactory sqlSessionFactory;
  private final SqlSession sqlSessionProxy;//这里使用了代理,来增强sqlsession

  private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();
  //使用Threadlocal管理本线程的sqlsession来复用sqlsession
  private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
    this.sqlSessionFactory = sqlSessionFactory;
    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[]{SqlSession.class},
        new SqlSessionInterceptor());
  }
  //这个方法帮我们直接创建了sqlSessionFactory并且将传入的sqlSessionFactory的SqlSession进行了代理
  public static SqlSessionManager newInstance(Reader reader) {
    return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, null));
  }
  .....
   public static SqlSessionManager newInstance(Reader reader) {
    return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, null));
  }

  public static SqlSessionManager newInstance(Reader reader, String environment) {
    return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, environment, null));
  }

  public static SqlSessionManager newInstance(Reader reader, Properties properties) {
    return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, properties));
  }

  public static SqlSessionManager newInstance(InputStream inputStream) {
    return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, null));
  }

  public static SqlSessionManager newInstance(InputStream inputStream, String environment) {
    return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, environment, null));
  }

  public static SqlSessionManager newInstance(InputStream inputStream, Properties properties) {
    return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, properties));
  }

  public static SqlSessionManager newInstance(SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionManager(sqlSessionFactory);
  }
  
  .....
   @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //使用Threadlocal管理本线程的sqlsession来复用sqlsession
      final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
      if (sqlSession != null) {//获取本线程的sqlsession
        try {
          return method.invoke(sqlSession, args);//实际调用
        } catch (Throwable t) {
          throw ExceptionUtil.unwrapThrowable(t);
        }
      } else {
        try (SqlSession autoSqlSession = openSession()) {
          try {
            final Object result = method.invoke(autoSqlSession, args);
            autoSqlSession.commit();
            return result;
          } catch (Throwable t) {
            autoSqlSession.rollback();
            throw ExceptionUtil.unwrapThrowable(t);
          }
        }
      }
    }

SqlSessionManager 他实现了Session接口。意味着,SqlSessionManager集成了 sqlSessionFactory和session 的功能。通过SqlSessionManager,开发者可以不在理会SqlSessionFacotry的存在,直接面向Session编程。

SqlSessionManager 内部提供了一个sqlSessionProxy,这个sqlSessionProxy提供了所有Session接口的实现,而实现中正是使用了上面提到的本地线程保存的session实例。

这样,在同一个线程实现不同的sql操作,可以复用本地线程session,避免了DefaultSqlSessionFactory实现的每一个sql操作都要创建新的session实例

下面让我们用一个简单的实例来试试

@Test
    public void testMyBatisBuild3() throws IOException {
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
// SqlSessionFactory build = new SqlSessionFactoryBuilder().build(reader);
// 不使用SqlSessionFactory 使用SqlSessionManager.newInstance();
        SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(reader);
        SqlSession sqlSession = sqlSessionManager.openSession();
        TestMapper mapper = sqlSession.getMapper(TestMapper.class);
        Ttest one = mapper.getOne(1L);
        System.out.println(one);
        sqlSession.close();
    }

运行结果:

Reader entry: ����1      getOne 0(Ljava/lang/Long;)Lcom/liangtengyu/entity/Ttest; 
Find JAR URL: file:/Users/tengyu/IdeaProjects/mybatis-study/target/classes/com/liangtengyu/mapper/TestMapper.xml
Not a JAR: file:/Users/tengyu/IdeaProjects/mybatis-study/target/classes/com/liangtengyu/mapper/TestMapper.xml
Reader entry: <?xml version="1.0" encoding="UTF-8"?>
Checking to see if class com.liangtengyu.mapper.TestMapper matches criteria [is assignable to Object]
Opening JDBC Connection
Created connection 1585787493.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5e853265]
==>  Preparing: select * from t_test where id = ?
==> Parameters: 1(Long)
<==    Columns: id, context
<==        Row: 1, 123



<==      Total: 1
Ttest{id=1, context='123'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5e853265]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5e853265]
Returned connection 1585787493 to pool.

本章主要讲了MyBatis构建SqlSessionFactory方式,过程,和sqlsession的创建,以及使用SqlSessionManager管理session复用的实现方式.

下一篇研究数据源的池化和数据源加载过程.

加群一起学习吧 Mybatis源码阅读(二)

关注公众号:java宝典 Mybatis源码阅读(二)

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这