Mybatis源码分析(2)
Mybatis源码分析(2)
接上一章总结【从哪里开始?】
1、SqlSessionManager
实现至
SqlSession
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private final SqlSessionFactory sqlSessionFactory;
private final SqlSession sqlSessionProxy;
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();
}
代码中容易看到:SqlSessionManager
管理器继承自SqlSession
,里面维护了自己的一个SqlSessionFactory
和一个SqlSession
的代理类型(源码可以看出是jdk代理模式)以及一个当前线程可见全局的localSqlSession
public <T> T selectOne(String statement, Object parameter) {
return sqlSessionProxy.selectOne(statement, parameter);
}
以selectOne
为例我们能看到这是通过代理来完成的sql查询操作,那么这个代理是做了什么增强呢?
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
if (sqlSession != null) {
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);
}
}
}
}
我们可以看到这个代理,为我们做了如下几件事:
- 获取本地的SqlSession
- 如果本地有,并执行方法且不加增强
- 如果本地没有,用当前管理器的工厂创建一个新的SqlSession并自动提交或回滚
2、DefaultSqlSession
同上,也实现至
SqlSession
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;// 配置信息,也是生成Mapper代理类的方式
private final Executor executor;// 内部封装的执行器,用来真正执行操作
private final boolean autoCommit;// 暂不解释
private boolean dirty;// 暂不解释
private List<Cursor<?>> cursorList;// 暂不解释
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
}
4、SqlSession是如何被创建出来的?
到目前为止我们只是粗略的讲解了SqlSession
以及关联的类的一些作用,那么这些类中的字段等信息是如何生成并运作的呢?
我们上章节中可以看出SqlSession
是通过工厂模式创建出来的,里面都做了哪些事情,我们来深入解刨一下这个SqlSession
的作用
// DefaultSqlSessionFactory 中生成SqlSession的底层方法
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);
// 执行器(发放给将要创建的SqlSession,作为执行操作的核心)
final Executor executor = configuration.newExecutor(tx, execType);
// 生成SqlSession实例
return new DefaultSqlSession(configuration, executor, autoCommit);
} 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();
}
}
我们只看方法名字可以看出,我们将要从DataSource
中创建出来SqlSession
,但不巧的是,并没有数据源参数,我们的数据源从代码里可以看出来是从Environment
中来,我们从源码上其实可以看出环境又是从configuration
来的,配置类是抽象工厂(SqlSessionFactoryBuilder)从一开始根据xml配置能解析出来的所有的信息(里面内容非常的齐全),并通过DefaultSqlSessionFactory
构造函数注入到了实例中,而我们知道了配置类的由来,那么Environment
在Mybatis中又扮演者什么角色呢?
public final class Environment {
private final String id;
// 事务工厂
private final TransactionFactory transactionFactory;
// 数据源
private final DataSource dataSource;
public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
if (id == null) {
throw new IllegalArgumentException("Parameter 'id' must not be null");
}
if (transactionFactory == null) {
throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
}
this.id = id;
if (dataSource == null) {
throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
}
this.transactionFactory = transactionFactory;
this.dataSource = dataSource;
}
}
从代码中可以看出,Environment
中可以取出事务工厂用来创建事务,也可以获取到真正的数据源,而这个类,是在XML解析的时候已经构造好的,我们接下来翻看一下事务类是做什么的
public interface Transaction {
// 获取连接
Connection getConnection() throws SQLException;
// 连接提交
void commit() throws SQLException;
// 连接回滚
void rollback() throws SQLException;
// 连接关闭
void close() throws SQLException;
// 获取连接超时时间
Integer getTimeout() throws SQLException;
}
从方法上看,我们的事务其实就是对连接的一个封装,为了对事物有更深的体会,我们看一下下面两者的区别:
-
Common
package com.lsb.spring.boot.mybatisplus.源码解析.操作数据库; import org.apache.ibatis.transaction.Transaction; import org.apache.ibatis.transaction.jdbc.JdbcTransaction; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; /** * @author lishanbiao * @date 2022/6/6 5:06 下午 */ public class Common { private static final String DRIVER = "com.mysql.jdbc.Driver"; private static final String URL = "jdbc:mysql://127.0.0.1:3306/mydb2"; private static final String USER = "root"; private static final String PASSWORD = "root"; public static final String SQL = "insert into user(name,gender,salary) values(?,?,?)"; public static Transaction getTransaction() throws SQLException, ClassNotFoundException { Connection conn = getConnection(); Transaction transaction = new JdbcTransaction(conn); return transaction; } public static Connection getConnection() throws ClassNotFoundException, SQLException { // 注册数据库驱动 Class.forName(DRIVER); Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); // 获取连接 conn.setAutoCommit(false); return conn; } public static void printResult(ResultSet rs) throws SQLException { while (rs.next()) { int id = rs.getInt("id"); String name = rs.getString("name"); String gender = rs.getString("gender"); float salary = rs.getFloat("salary"); System.out.println(id + ":" + name + ":" + gender + ":" + salary); } } }
-
原生JDBC
package com.lsb.spring.boot.mybatisplus.源码解析.操作数据库.原生jdbc;
import com.lsb.spring.boot.mybatisplus.源码解析.操作数据库.Common;
import java.sql.*;
/**
* @author lishanbiao
* @date 2022/6/6 4:42 下午
*/
public class JDBCTest {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
Connection conn = Common.getConnection();
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 创建预编译方式
pstmt = conn.prepareStatement(Common.SQL);
// 设置参数位置和参数数据
pstmt.setString(1, "xiaozheng");
pstmt.setString(2, "男");
pstmt.setFloat(3, 8000);
//进行编译
rs = pstmt.executeQuery();
conn.commit();
Common.printResult(rs);
} catch (Exception e) {
try {
conn.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
e.printStackTrace();
} finally {
if (rs != null) {//轻量级,创建和销毁rs所需要的时间和资源较小
try {
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (pstmt != null) {//轻量级,创建和销毁rs所需要的时间和资源较小
try {
pstmt.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (conn != null) {//重量级,创建和销毁rs所需要的时间和资源较小
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
- 事务封装的JDBC
package com.lsb.spring.boot.mybatisplus.源码解析.操作数据库.事务Jdbc;
import com.lsb.spring.boot.mybatisplus.源码解析.操作数据库.Common;
import org.apache.ibatis.transaction.Transaction;
import java.sql.*;
/**
* 对比原生的jdbc连接,我们可以明显感觉到,Mybatis的事务类(Transaction)是用来管理连接的
* 包括创建、获取、提交、回滚、关闭等操作
*
* 但是Transaction仅仅是用来创建并封装连接的,并非用来自动管理的连接的
*
* @author lishanbiao
* @date 2022/6/6 4:59 下午
*/
public class JDBCTransactionTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
PreparedStatement pstmt = null;
ResultSet rs = null;
Transaction transaction = Common.getTransaction();
Connection conn = transaction.getConnection();
try {
// 创建预编译方式
pstmt = conn.prepareStatement(Common.SQL);
// 设置参数位置和参数数据
pstmt.setString(1, "xiaozheng");
pstmt.setString(2, "男");
pstmt.setFloat(3, 8000);
//进行编译
rs = pstmt.executeQuery();
transaction.commit();
Common.printResult(rs);
} catch (Exception e) {
transaction.rollback();
e.printStackTrace();
} finally {
if (rs != null) {//轻量级,创建和销毁rs所需要的时间和资源较小
try {
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (pstmt != null) {//轻量级,创建和销毁rs所需要的时间和资源较小
try {
pstmt.close();
} catch (Exception e) {
e.printStackTrace();
}
}
transaction.close();
}
}
}
所以一句话总结事务:Transaction仅仅是用来创建并封装连接的,并非用来自动管理的连接的,事务即连接
好的,我们知道事务之后,再回过头来看一下DefaultSqlSession
是不是有更深的理解了呢?(好吧,我承认是真的乱,我也刚看源码,后续有时间会整理的)
总结一下吧
- 事务即连接,之所以将连接用事务包装起来,我想应该是,不同的事务管理,会针对连接做不同的操作和状态更改,逻辑不同,但其实底层真正的还是用连接操作数据库的喔
SqlSession
内部维护一个已加载解析的配置类,理论上是已经获取了几乎所有的信息(事务、执行器类型、提交状态等等)SqlSession
内部维护的Executor
是底层真正执行操作的类SqlSession
总结下来就是拥有我们原生连接Connection
与statement
的所有信息、状态以及操作
4、Executor继承结构图(下章解释)
Executor 是SqlSession底层真正执行操作的执行器
5、Mapper:映射器实例
映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的。虽然从技术层面上来讲,任何映射器实例的最大作用域与请求它们的 SqlSession 相同。但方法作用域才是映射器实例的最合适的作用域。 也就是说,映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。 映射器实例并不需要被显式地关闭。尽管在整个请求作用域保留映射器实例不会有什么问题,但是你很快会发现,在这个作用域上管理太多像 SqlSession 的资源会让你忙不过来。 因此,最好将映射器放在方法作用域内。就像下面的例子一样:
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// 你的应用逻辑代码
}
6、SqlSession是如何生成Mapper实例的?
我们可以看出SqlSession是有两个实现类,其实现类中都维护一个配置信息实例,事实上不管是哪一个,最终都会走到Configuration
的一个方法中:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
这里我们是从MapperRegistry
中获取Mapper的
public class MapperRegistry {
private final Configuration config;
// 维护了一个类字节码与对应的Mapper的代理工厂缓存
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public MapperRegistry(Configuration config) {
this.config = config;
}
}
MapperRegistry
中维护了一个代理类型工厂容器,此工厂应该用来创建相应的Mapper接口的代理类,我们往下依次推下去:
/**
1 ---------------------------------------------------------------------------------------------
*/
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 根据Mapper类型获取Mapper代理工厂
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// Mapper代理工厂创建实例
return mapperProxyFactory.newInstance(sqlSession);
}
/**
2 -------------------------new MapperProxy实例用来创建代理类--------------------------------------
*/
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
/**
3 -----------------------mapperProxy实现了InvocationHandler-------------------------------------
*/
public class MapperProxy<T> implements InvocationHandler, Serializable {}
/**
4 -----------------------------动态代理生成Mapper代理类-------------------------------------------
*/
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
我们发现其实底层是通过SqlSession下面的子类
--> Configuration
-->MapperRegistry
-->MapperProxyFactory
-->MapperProxy
-->JDK 自带的基于接口的动态代理
这些步骤创建的Mapper实例