前言
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL, 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
mybatis核心流程
以下是mybatis对Jdbc的一个封装流程。
Configuration
配置类。用配置文件(也可以走注解)描述就是如下图所示
<configuration>
<!-- 事务管理器和数据源的设置,和spring集成的关键 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/iMybatis?characterEncoding=GBK" />
<property name="username" value="iMybatis" />
<property name="password" value="iMybatis" />
</dataSource>
</environment>
</environments>
<!-- 配置mapper,sql的脚本 -->
<mappers>
<mapper resource="org/iMybatis/abc/dao/UserDao.xml" />
</mappers>
</configuration>
<mapper namespace="org.iMybatis.abc.dao.UserDao">
<select id="queryUsers" parameterType="UserDto" resultType="UserDto"
useCache="false">
<![CDATA[
select * from t_user t where t.username = #{username}
]]>
</select>
</mapper>
MappedStatement
mapper 解析映射的主要类。
- sqlSource 对应sql文本
- ResultMap 对应结果集与实体的映射关系
- ParameterMap 入参实体与sql参数的映射关系
- …(具体参考MappedStatement)
可通过configuration.getMappedStatement(statement) 获取。
注:statement为namespace + id.
其中 忏
SqlSession
MyBatis中会话。相当于jdbc中一事务。查看SqlSession的构建类SqlSessionFactory,主要有以下4种参数
- autoCommit 是否自动提交
- connection 使用此连接创建
- level 事务的隔离级别
- executorType 执行类型
Executor
MyBatis执行器。相当于jdbc的事务中每一条语句的执行。根据SqlSession
中的executorType,Mybatis提供了三种模式
- simple: 常规执行器,每次执行都会创建一个statement,用完后关闭
- batch:批处理型执行器。doUpdate预处理存储过程或批处理操作
官方说明这个executor是用于执行存储过程的和批量操作的,因此这个方法是循环或者多次执行构建一个存储过程或批处理过程。相当于jdbc中的addBatch
- reuse:可重用执行器,将statement存入map中,操作map中的statement而不会重复创建statement
不会每次执行完关闭statement,而是把statement放到缓存中。缓存的key为sql语句,value即为对应的statement。也就是说不会每一次调用都去创建一个 Statement 对象,而是会重复利用以前创建好的(如果SQL相同的话),这也就是在很多数据连接池库中常见的 PSCache 概念 。
BoundSql
通过MappedStatement与实现转入参数真实动态解析SQL文件。并将解析结果存入BoundSql类中。
解析方法为MappedStatement.getBoundSql
public class BoundSql {
//解析后的sql
private final String sql;
//解析后的参数映射关系
//一般情况下是一样的
//sql包含foreach语法,则会根据入参list自动生成对应有关系
private final List<ParameterMapping> parameterMappings;
//入参
private final Object parameterObject;
//parameterMappings中系统有自动生成时,会根据入参list自动生成对应名称的值
private final Map<String, Object> additionalParameters;
//入参的反射类,方便取指
private final MetaObject metaParameters;
}
StatementHandler
StatementHandler 封装了JDBC Statement操作.
- STATEMENT:直接操作sql
同时sql里的属有变量只支持${xxxx},而不支持#{xxx} .sql就是直接进行的字符串拼接
- PREPARED:预处理,参数,进行预编译
支持${xxxx}和#{xxx}。 我们的#会转换为?再设置对应的参数的值, 为 mybatis 默认模式
- CALLABLE:执行存储过程
ParameterHandler
负责对用户传递的参数转换成JDBC Statement 所需要的参数。
//DefaultParameterHandler
//设置参数到sql的核心代码
public void setParameters(PreparedStatement ps) {
//取出sql脚本中所有的类型设置
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
//sql中参数名称
String propertyName = parameterMapping.getProperty();
//这属性是不是解析Sql脚本自动生成的
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
}else{
//代理实际参数类,方便使用反射
MetaObject metaObject = configuration.newMetaObject(parameterObject);
//从实现参数中获取值
value = metaObject.getValue(propertyName);
}
//取出转换器
TypeHandler typeHandler = parameterMapping.getTypeHandler();
//设置值
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
TypeHandler
负责java数据类型和jdbc数据类型之间的映射和转换.TypeHandlerRegistry是其管理类。在Configuration创建时,自动创建。里面已经注大多数TypeHandler。主要有
// jdbc - handler
Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
// java - jdbc - handler 双维度表
// 支持jdbc 为 null
Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();
// javaTypeClass - handler javaClass对应 handler
Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
//根据 java - jdbc 双维度去查找
//ParameterMapping-typeHadler 默认为 type - 参数的java类型, jdbcType 是 null
//ResultMapping - typeHadler 默认为 type - 参数的java类型, jdbcType 是 数据库的的类型
getTypeHandler(Type type, JdbcType jdbcType)
//对它过以下设置,可以自定义typeHandler
<typeHandlers>
<typeHandler jdbcType="VARCHAR" javaType="string" handler="com.mybatis.test.MyTypeHandler"/>
</typeHandlers>
ResultSetHandler
负责将JDBC返回的ResultSet结果集对象转换成JAVA类型的集合.
//DefaultResultSetHandler
//将返回结果集包装
public List<Object> handleResultSets(Statement stmt) throws SQLException {
//最终结果存储
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
//一般情况下。第一个结果集
//使用存储程的时候可能有多个结果集
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
//处理结果集
//取mapping 找 typeHandler
//通后反射设置值
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
//多结果集及加嵌套代码省略
return collapseSingleResultList(multipleResults);
}
插件扩展
不能自定义扩展的,不是好框架。Mybatis也提供了Plugin方法。在创建ParameterHandler、ResultSetHandler、StatementHandler、Executor类时,会调.Plugin.wrap方法进行代理。
//取得每个interceptor,signature的内容,看看其是否符合代理要求
//如果符合则使用JDK提供的动态代理技术
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> 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;
}
另外Mybatis也提供了ResultHandler,结果遍历,切入结果集每一行的收订。
//具体用计参才DefaultResultHandler
public interface ResultHandler<T> {
// resultContext 第个结果集
void handleResult(ResultContext<? extends T> resultContext);
}
缓存
mybatis 中存在一,二级缓存的概念。
- 一级缓存:在BaseExecutor 中localCache。也就是其存储作用域为 Session
- 二缓缓存:cacheEnabled= true 触发 CachingExecutor。其缓存取的是MappedStatement.getCache 也就是其存储作用域为全局 MappedStatement。(因此推荐不开)
Mapper映射
上术讲的是Mybatis的核心流程,我们可以使用Executor提供的接口调用了
User user1 = sqlSession.selectOne("mapper.UserMapper.selectUser2", 30);
System.out.println(user1.getUsername());
但我们直接开发中,更希望希望直接调用接口的方式
User user = sqlSession.getMapper(UserMapper.class).selectUser(30, Arrays.asList(118767287L, 1L));
通过JDK态方式代码接口
//代理接口
//MapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
//调用接口时真正触发
//类MapperMethod
//省略了大部份代码
public Object execute(SqlSession sqlSession, Object[] args) {
//封闭参数,将bean之类转成Map
Object param = method.convertArgsToSqlCommandParam(args);
//调用直接的代码
Object result = sqlSession.selectOne(command.getName(), param);
//判断接口返回值,来类型转换
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
return result;
}
总结
Mybatis是半自动代的ORM开源框架。但还免不了要写一些SQL.外面也有人开发了减少SQL的mybatis辅助框架。
- mybatis-plus
- mybatis-generator(代码生成)
个人觉的:很多功能的的瓶颈都在SQL上,所以我觉的以mybatis-generator为主,参考mybatis-plus功能点。
主要参考
《Mybatis架构与原理》
《Mybatis文档》
《MyBatis-Plus文档》
《mybatis 3.x源码深度解析与最佳实践》