缘由
Mybatis 框架为作为Java开发人员的必备工具,现在大多使用 mybatis-plus,但是mybatis框架中的运行原理和技术点还是值得一探究竟的。
简述
Mybatis官网 上面介绍的非常精炼,支持自定义SQL、存储过程以及高级映射,不过存储过程现在已经很少使用,其余两点使用频率非常的高。框架最大的特点,也是ORM最大的特点,替代开发人员编写JDBC代码、参数设置、获取结果集,开发人员只要在注解或者xml文件中编辑SQL。减轻开发人员工作量,能够在更有意义的事情上投入更多的精力。
正文
框架替开发人员做了很多事情,这些事情是如何做到的呢?本文会娓娓道来。
一个Mybatis应用的中有一个实例非常的关键,就是 SqlSessionFactory。可以从它是怎么来的、起到了什么作用两个方面来详细说说。
SqlSessionFactotry的由来
SqlSessionFactory 是一个接口类,有两个实现类 SqlSessionManager\ DefaultSqlSessionFactory,我们先把注意力放在 DefaultSqlSessionFactory 上面,它的创建则是在 SqlSessionFactoryBuilder 中,显然是通过构造者模式进行的创建,若一个对象创建的过程不是简单两行代码的事情,有必要使用构造器模式,将创建逻辑独立出来,与类的定义进行隔离。
SqlSessionFactoryBuilder 中的构建逻辑最终目的就是 new DefaultSqlSessionFactory(Configuration config)
,所有的构建方式最终会生成 Configuration,可以是 XMLConfigBuilder.parse(),也可以使用new Configuration。两种方式都会将开发人员对Mybatis应用的配置转换到 SqlSessionFactory 的创建过程中。
SqlSessionFactotry的使用
SqlSessionFactory 的目的就是为了创建一个SqlSession,有多个重载的接口方法 openSession(),最终会执行到 new DefaultSqlSession(configuration, executor)
,configuration还是熟悉的那个,Executor 是一个接口,有几个简单的实现 BatchExecutor 、ReuseExecutor、SimpleExecutor、CachingExecutor,可以根据参数执行指定,两外开发人员自定义的Interceptor也会集成到Executor实现当中,Executor 的构造需要 Configuration 和 transaction(后续单独说明)。
SqlSession接口定义了crud 和 事务相关 缓存相关 的方法。最终的执行还是会使用 Executor 实现类。比如所有的查询方法会进入到 List
,statement就是开发人员指定的SQL语句标识符,调用executor之前,通过 configuration.getMappedStatement(statement) 获取到 MappedStatement 作为执行对象,参与执行过程。
Executor 执行器的query(),主要是调用 doQuery(),在调用前会创建缓存key,doQuery()中存在sqlSession 级别的缓存逻辑,缓存没有命中会进入 queryFromDatabase()。
queryFromDatabase() 方法会将执行结果放入缓存,执行逻辑是在 doQuery(ms, parameter, rowBounds, resultHandler, boundSql) 中,此方法是一个抽象方法,进入 SimpleExecutor的抽象实现中。
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 根据开发人员定义的sql语句时指定的 StatementType 生成对应的 StatementHandler
// 这一点也是一个扩展实现
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建 statement ,并将sql参数放入到 statement 中
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行查询,并映射结果集
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
// 执行查询,并映射结果集
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler. handleResultSets(ps);
}
以上就是mybatis应用中 sqlSessionFactory 大致执行逻辑,下面会从框架的扩展性方面说明。
扩展性
StatementHandler 扩展
其实现类 RoutingStatementHandler 采用简单工厂和装饰器的方式,根据 StatementType 枚举创建不同的实现。
switch (ms.getStatementType()) {
case STATEMENT:
// sql 直接执行
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
// 使用 PreparedStatement 预处理参数,防止sql注入
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
// 使用 CallableStatement 方式,可以指定额外的回调
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
Executor 扩展
Configuration 类中可根据 ExecutorType 创建指定的 Executor 实现类
public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
// 重用预处理语句并批量处理
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
// 重用预处理语句
executor = new ReuseExecutor(this, transaction);
} else {
// 简单执行器
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
// 装饰模式,增加sqlSessionFactory级别的缓存
// 二级缓存当中的事务也需要特别处理
executor = new CachingExecutor(executor, autoCommit);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
插件机制
SQL执行过程中,可以对其进行监控和拦截调用。插件的执行逻辑入口在 configuration中,ParameterHandler、ResultSetHandler、StatementHandler、Executor 获取过程中都会执行 interceptorChain.pluginAll() 方法。
interceptorChain负责收集开发人员定义的interceptor,并执行pluginAll() 对目标对象进行增强,采用动态代理的方式,将目标对象进行包装。
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public static Object wrap(Object target, Interceptor interceptor) {
// 获取自定义的interceptor上面注解
Map, Set> signatureMap = getSignatureMap(interceptor);
Class> type = target.getClass();
// 获取目标对象的接口与拦截器的注解对比,匹配到的接口
// 下面的代理对象会根据匹配到的接口创建
Class>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
// 指定代理逻辑所在的类
new Plugin(target, interceptor, signatureMap));
}
return target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
// 拦截器中的逻辑得到执行
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
Mapper 代理对象
开发过程当中总会在使用依赖注入的方式引入 Mapper 对象,开发业务功能时总会定义一些UserMapper 之类的接口,方法上面使用注解SQL或者xml文件与Mapper绑定的方式编写SQL。
本身是一个接口,却可以当作对象引入,必定是Mybatis提供了动态代理机制,将一个接口生成为一个代理对象,供其他类使用。
SqlSession 提供一个接口方法
定义获取Mapper代理对象的行为,DefaultSqlSession 中使用了 configuration.getMapper(type,this); --> mapperRegistry.getMapper(type, sqlSession); 具体实现是在 mapperRegistry当中:
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null)
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
try {
// mapperProxyFactory 肯定会创建一个 mapperProxy 对象,代理对象mapper的执行 也会在 mapperProxy 当中
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// 实现 InvocationHandler 接口
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// MapperMethod 对象代表一个userMapper中的接口方法
final MapperMethod mapperMethod = cachedMapperMethod(method);
// execute 方法中的执行逻辑最终会调用到 sqlSession 完成与数据库的交互
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
事务
开发人员可指定事务管理 TransactionFactory,不过一般都是 JdbcTransactionFactory,使用数据库事务,获取sqlSession 指定是事务隔离级别,作用在JdbcTransactionFactory 创建数据库连接时指定到连接当中。
JdbcTransactionFactory 创建的 JdbcTransaction 会参与到执行器executor的创建中,执行器中的事务方法最终会调用到 JdbcTransaction 执行事务调用。
public void commit(boolean required) throws SQLException {
if (closed) throw new ExecutorException("Cannot commit, transaction is already closed");
// 清空一级缓存
clearLocalCache();
flushStatements();
if (required) {
// 事务提交
transaction.commit();
}
}
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
flushStatements(true);
} finally {
if (required) {
// 事务回滚
transaction.rollback();
}
}
}
}
总结
Mybatis框架提供了半orm机制,开发人员可以编辑SQL,剩下的工作交给框架处理,节省开发人员精力。框架的原理进行了梳理,执行流程基本说明,篇幅有限,其中的技术细节可自行延申。