MyBatis原理总结
文章目录
简介
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
org.mybatis
内容:
- 入门
- XML配置文件
- XML映射文件
- 动态SQL
- Java API
关键点
-
Java API作用域
对象 作用 作用域 安全 SqlSessionFactoryBuilder
创建 sqlSessionFactory
建议局部方法变量 SqlSessionFactory
建议应用作用域 线程安全 SqlSession
建议请求或方法作用域 线程不安全 -
缓存
缓存 说明 原理 一级缓存,本地 默认开启 BaseExecutor持有PerpetualCache对象,内部持有一个HashMap 二级缓存,全局 默认不开启 CachingExecutor -
延迟加载:动态代理实现
-
自定义拦截器
<plugins> <plugin interceptor="com.xl.read.mybatis.source.plugin.QueryInterceptor"> <property name="value" value="100"/> </plugin> </plugins>
@Intercepts({@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class}) }) public class QueryInterceptor implements Interceptor { @Override public Object plugin(Object target) { return target instanceof Executor? Plugin.wrap(target, this):target; } @Override public void setProperties(Properties properties) { Interceptor.super.setProperties(properties); } @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; Object parameter = invocation.getArgs()[1]; // 前置处理 Object ret = invocation.proceed(); // 后置处理 return ret; } }
说明:type指定Executor.class,然后查看方法,找到Executor#update,如下,按照该参数完成类上注解属性args
int update(MappedStatement ms, Object parameter) throws SQLException;
setProperties作用:plugin的字标签property配置,可用来存储,然后在拦截前后使用
intercept作用:前后可进行增强
架构
模块实现
动态代理
获取代理:MapperProxyFactory创建MapperProxy,自定义Mapper的方法会走到MapperProxy#invoke
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
代理拦截:MapperMethodInvoker#invoke
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
execute根据SqlCommandType,委托SqlSession#update执行更新、或SqlSession#query执行查询
public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
}
Executor
构造:CachingExecutor
委托:BaseExecutor, SimpleExecutor(ReuseExecutor, BatchExecutor)
作用:缓存管理、委托StatementHandler
缓存
二级缓存:CachingExecutor,需要配置开启,持有变量TransactionalCacheManager tcm
,然后tcm内部持有变量HashMap<Cache, TransactionalCache>
一级缓存:BaseExecutor,默认开启,持有变量PerpetualCache localCache
,然后localCache内部持有HashMap<Object, Object> cache
StatementHandler
构造:RoutingStatementHandler
委托:PreparedStatementHandler(SimpleStatementHandler, CallableStatementHandler)
处理:
- 实例化
- prepare Statement
- prepare:实例化Statement、timeout、fetchSize
- parameterize:委托ParameterHandler设置参数
- query:执行
PreparedStatement#execute
,然后委托ResultSetHandler处理响应
ParameterHandler
构造:DefaultParameterHandler
处理:遍历ParameterMapping,区分additionalParameter、null参数、有typeHandler、其他四种场景设置value,获取jdbcType,调用typeHandler.setParameter(ps, i + 1, value, jdbcType);
设置参数
typeHandler具体类型,根据TypeHandlerRegistry typeHandlerRegistry
的成员变量ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap
,由参数类型Type、jdbc类型JdbcType获取,最终调用PreparedStatement#setXxx系列方法
设置参数
ResultHandler
构造:DefaultResultSetHandler
处理:?
说明:?
-
逻辑分页 RowBounds
-
最终会结合ResultMap解析ResultSet
拦截器
目标:Executor, StatementHandler, ParameterHandler, ResultSetHandler
例如:插件会拦截,最终返回Plugin.wrap
->Proxy.newProxyInstance
创建的代理对象,而Plugin实现了InvocationHandler,会拦截调用处,触发当前方法的invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> 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);
}
}
流程分析
流程
- 读取配置文件,构造SqlSessionFactory
- 构造SqlSession
- 获取XxxMapper
- 调用方法
第一步:读取配置文件,构造SqlSessionFactory
SqlSessionFactoryBuilder#build
方法签名
public SqlSessionFactory build(InputStream inputStream) {
// 第2个参数:String environment
// 第3个参数:Properties properties
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
处理流程
-
构造XMLConfigBuilder
-
XMLConfigBuilder#parse
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; parseConfiguration(parser.evalNode("/configuration")); return configuration; }
说明:parseConfiguration会逐个解析配置文件的子标签
-
构造SqlSessionFactory
public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; }
parseConfiguration
作用:解析mybatis配置文件的标签
properties
作用:变量
settings
作用:虚拟文件系统、日志、其他设置
loadCustomVfs(settings);
loadCustomLogImpl(settings);
private void settingsElement(Properties props) {
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
configuration.setShrinkWhitespacesInSql(booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false));
configuration.setDefaultSqlProviderType(resolveClass(props.getProperty("defaultSqlProviderType")));
}
typeAliases
说明:保存到Configuration#typeAliasRegistry
成员变量中,该变量类型为TypeAliasRegistry
,内部持有一个HashMap,用来映射key到Class
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
String key = alias.toLowerCase(Locale.ENGLISH);
if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
}
typeAliases.put(key, value);
}
plugins
说明:拦截器
-
使用:定义拦截器并配置使用,见上面[自定义拦截器]
-
解析
private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); Properties properties = child.getChildrenAsProperties(); Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); } } } public void addInterceptor(Interceptor interceptor) { // InterceptorChain interceptorChain = new InterceptorChain() interceptorChain.addInterceptor(interceptor); }
其中,拦截器链持有
List<Interceptor>
,然后在生成被拦截对象时,调用Interceptor#plugin
生成代理对象,调用被拦截对象的方法时实际调用被代理对象例如:被拦截对象
Executor
,拦截器XxxInterceptor
,生成Executor
后会基于拦截器链创建代理,返回代理对象,调用方法时先执行Interceptor#intercept
environment
作用:解析environments标签下面的environment,只处理environment.id等于environments.default的environment标签,包含transactionManager和dataSource两个子标签的处理
-
事务管理器:有两种类型事务管理器
JDBC|MANAGED
,JDBC依赖数据源的Connection管理事务,而MANAGED将事务交给容器管理。private TransactionFactory transactionManagerElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance(); factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a TransactionFactory."); }
-
数据源:有三种类型数据源
UNPOOLED|POOLED|JNDI
,非连接池每次打开关闭,连接池使用池管理,JNDI则是在EJB或应用服务器这类容器中使用,集中或外部配置数据源
typeHandlers
作用:注册类型处理器,用于实现javaType和jdbcType的互转处理
使用:
- 定义一个TypeHandlers
- 注册到mybatis
- 使用
注册流程:标签解析阶段,最终存储为ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>> typeHandlerMap
其他标签
objectFactory
objectWrapperFactory
reflectorFactory
databaseIdProvider
mappers
第二步:构造SqlSession
DefaultSqlSessionFactory#openSession()
方法签名
// ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
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);
} 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();
}
}
处理流程
-
从Environment中,获取TransactionFactory、DataSource
Q:何时实例化?
A:解析environments——>transactionManager标签时,反射获取实例
-
构造Transaction
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) { return new JdbcTransaction(ds, level, autoCommit); }
-
构造Executor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { 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); } // 二级缓存开关,默认为true if (cacheEnabled) { executor = new CachingExecutor(executor); } // 这就是上面插件处理,在创建Executor后,经过Interceptor#plugin生成代理对象 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
-
构造DefaultSqlSession
第三步:获取XxxMapper
DefaultSqlSession#getMapper
方法签名
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
处理流程
-
从MapperRegistry#knownMappers获取MapperProxyFactory
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
-
创建MapperProxy
public T newInstance(SqlSession sqlSession) { // MapperProxy实现了InvocationHandler final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
-
创建XxxMapper的代理实例
protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
说明:从Configuration.mapperRegistry获取MapperProxyFactory,然后调用newInstance创建代理实例
说明:上述的MapperProxy,实现Invocationhandler,有个方法invoke,在XxxMapper方法调用时,会进入MapperProxy#invoke
第四步:update
-
XxxMapper#insert
-
MapperProxy#invoke
-
MapperMethodInvoker#invoke
-
MapperMethod#execute
-
SqlSession#insert
-
SqlSession#update
- CachingExecutor#update:代理(插件+Executor)
- BaseExecutor#update
- BaseExecutor#doUpdate
- RoutingStatementHandler#update
- PreparedStatementHandler#update
第四步:query
- XxxMapper#selectList
- MapperProxy#invoke
- MapperMethodInvoker#invoke
- MapperMethod#execute
- SqlSession#selectList
- CachingExecutor#query
- BaseExecutor#query
- SimpleExecutor#doQuery
- RoutingStatementHandler#query
- PreparedStatementHandler#query