乐趣区

关于java:Mybatis源码阅读二

本文次要介绍 Java 中,不应用 XML 和应用 XML 构建 SqlSessionFactory,通过 SqlSessionFactory 中获取 SqlSession 的办法,应用 SqlsessionManager 治理 Sqlsession 复用等等.. 以及相干的示例代码

SqlSession

SqlSessions 是由 SqlSessionFactory 实例创立的。SqlSessionFactory 对象蕴含创立 SqlSession 实例的各种办法。而 SqlSessionFactory 自身是由 SqlSessionFactoryBuilder 创立的,它能够从 XML、注解或 Java 配置代码来创立 SqlSessionFactory。

应用 MyBatis 的次要 Java 接口就是 SqlSession。你能够通过这个接口来执行命令,获取映射器示例和治理事务。在介绍 SqlSession 接口之前,咱们先来理解如何获取一个 SqlSession 实例。

举个例子

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 的源代码:


F7 跟进 发现一堆 build 而咱们当初用的是传入 reader 的那个办法


咱们能够看到, 他帮咱们传了 2 个 Null 参数给下一个 build, 咱们跟着这个 build 持续往下跟.


这个 build 会将 xml 解析. 而后调用 parser.parse() 办法将 xml 转化成 Configuration, 传入下一个 build
持续下一个 build


这个 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);
// 也是一样的成果 


那么他的外部是如何创立 session 的咱们来看看源代码
依据断点咱们到了 factory.opensession() 办法 F7 进入办法

F8 单步 发现调用了 openSessionFromDataSource 办法


三个参数. 第一个参数是 configuration.getDefaultExecutorType()
这个参数是 Configuration 类中定义的默认类型.

ExecutorType

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 的所有办法


咱们进入到 Sqlsession 类中查看一下实现它的都有哪些类


一共有两个, 而且 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    getOne0(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 复用的实现形式.

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

加群一起学习吧

关注公众号:java 宝典

退出移动版