Mybatis原理及源码剖析
作为Java程序员Mybatis应该是一个必会框架了,其源码体量只有Spring 的1/5,也是Hibernate的1/5 ,相比于其余风行框架Mybatis源码无疑是学习老本最低的,当做年轻人看的第一个框架源码,无疑是十分好的。
整体架构
对于一个生疏的事物切勿一头扎进细节里,咱们先要观其大略看看架构脉络,MyBatis 分为三层架构,别离是根底撑持层、外围解决层和接口层。
Mybatis 整体架构
根底撑持层
根底撑持层是这个Mybatis框架的基建,为整个Mybatis框架提供十分根底的性能。(篇幅无限上面咱们只对局部模块做简略的剖析)
- 类型转换模块,咱们在Mybatis中应用< typeAliase >标签定义一个别名就是应用类型转换模块实现的。类型转换模块最重要的性能还是实现了Mybatis中JDBC类型和Java类型之间的转换。次要体现在:
- 在SQL模板绑定用户参入实参的场景中,将Java类型转换成JDBC类型
-
在后果集中,将JDBC类型转换成Java类型。
Mybatis类型转换
- 日志模块,产生日志,定位异样。
- 反射工具,对原生的Java反射作了一些封装。
- Binding模块,咱们在执行Mybatis中的办法时都是通过SqlSession来获Mapper接口中的代理,这个代理将Mapper.xml文件中SQL进行关联就是通过Binding模块来实现的。值得注意点是这个过程是产生在编译期。能够将谬误提前到编译期。
- 数据源模块,数据源对于一个ORM来说算是十分外围的组件之一了。Mybatis默认的数据源是十分杰出的,Mybatis同时也反对集成第三方的数据源。
-
缓存模块,缓存模块的好坏间接影响这个ORM的性能。Mybatis提供了两级缓存,同时也反对第三方缓存中间件集成。
Mybatis缓存
- 解析器模块,次要是config.xml配置文件解析,和mapper.xml文件解析。
- 事务管理模块,事务模块对数据库的事务机制进行管制,对数据库事务进行了形象,同时也对spring框架进行了整合。具体的解决逻辑下文会联合源码具体解析。
外围解决层
外围解决层是咱们在学习Mybatis原理的时候须要花80%工夫的中央。外围解决层是 MyBatis 外围实现所在,其中波及 MyBatis 的初始化以及执行一条 SQL 语句的全流程。
配置解析
MyBatis 的初始化以及执行一条 SQL 语句的全流程中也蕴含了配置解析,咱们在事实开发中个别都是应用spring boot starter的主动配置。咱们一我的项目启动为终点一层一层剥开Mybatis的流程。先关上org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
首先明确一点就是MybatisAutoConfiguration的目标就是要失去一个SqlSessionFactory。
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
return factory.getObject();
}
这里是通过MybatisProperties外面的配置并放入到SqlSessionFactoryBean中,再由SqlSessionFactoryBean失去SqlSessionFactory。看到最初一行return factory.getObject();
咱们进去看看这个factory.getObject()的逻辑是如何失去一个SqlSessionFactory。
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
这一步没什么好说的,看看afterPropertiesSet()
办法
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
重点来了,看看这个buildSqlSessionFactory()
办法这里的外围目标就是将configurationProperties解析到Configuration对象中。代码太长了我就不贴出来了, buildSqlSessionFactory()
的逻辑我画了个图,有趣味的小伙伴自行看一下。
Mybatis配置解析1
咱们不要陷入细节之中,咱们看看中点看看buildSqlSessionFactory()
办法的最初一行this.sqlSessionFactoryBuilder.build(configuration)
点进去
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
通过buildSqlSessionFactory()
解析失去的Configuration对象创立一个DefaultSqlSessionFactory(config)
,到此咱们就失去了SqlSessionFactory
同时被配置成一个bean了。
咱们最终操作都是SqlSession,什么时候会通过SqlSessionFactory
失去一个SqlSession呢?
要解决这个问题咱们回到最开始的MybatisAutoConfiguration
的sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)
这个办法,点开SqlSessionTemplate
发现它是一个实现了SqlSession
到这里咱们猜想就是在这里SqlSessionFactory
会构建一个SqlSession
进去。咱们进入new SqlSessionTemplate(sqlSessionFactory)
看看源码。
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
再往下看,咱们就看到了
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
这里通过动静代理创立了一个SqlSession。
参数映射、SQL解析
咱们先看一下MapperFactoryBean
类,这个类实现了FactoryBean
在bean初始化的时候会调用getObject()
办法咱们看看这个类下重写的getObject()
办法里的内容。
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
这里调用了sqlSession
的getMapper()
办法。一层一层点进去外面返回的是一个代理对象。最初的执行是由MapperProxy
执行。
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);
}
}
接下来的流程我还是画个流程图,避免小伙伴们走丢。我这里的内容可能未必齐全和小标题一样,我次要依照sql执行的流程解说的。
Mybatis参数绑定
先看一下MapperProxy
中的invoke
办法,cachedMapperMethod()
办法将MapperMethod
缓存起来了。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
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;
}
咱们在往下看mapperMethod.execute(sqlSession, args)
办法。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
method.convertArgsToSqlCommandParam(args)
这里就是解决参数转换的逻辑。还有很多细节因为篇幅无限以及工夫仓促咱们不做过多的赘述,感兴趣的小伙伴能够联合下面的图本人看看。上面咱们看SQL的执行流程是怎么样的。整体流程如下图。
Mybatis执行流程
咱们就不对每一个执行器都剖析,我只挑一个SimpleExecutor来具体跟一下源码。咱们还是先看看图吧,避免本人把本人搞蒙。
以simpleExecutor为例的执行流程
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
这里获取了Configuration,创立了一个StatementHandler,预处理操作,具体执行的依据创立的预处理办法,最初执行query
办法
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}
到此咱们整顿了整个Mybatis的执行流程,剖析了其中的源码,因为篇幅无限很多中央都没有粗疏的剖析,然而也贴出了图,心愿能帮忙到你。
发表回复