大家好,又见面了,我是你们的朋友全栈君。
写在开始
最近利用闲暇时间猫了一下mybatis和mybatis-spring的源码,看后发现SqlSessionTemplate和MapperFactoryBean这两个类对于mybatis的事务操作起到了关键的作用,因此写个随笔记录一下。本篇主要讲述下我个人对于SqlSessionTemplate的理解,关于MapperFactoryBean后续有时间会再写一篇文章记录一下。
SqlSessionTemplate
SqlSessionTemplate对于Mybatis事务提交起到了一个关键作用。先通过一个示例看一下不使用SqlSessionTemplate情况下通过Mybatis是如何来进行一次update操作。
1、使用DefaultSqlSession完成update操作
我的mapper配置文件如下所示(其他配置文件省略)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jorg.mybatis.mappers.StudentMapper">
<update id="update" parameterType="java.util.Map">
UPDATE
student
SET
`name` = #{name}
WHERE
id = #{id}
</update>
</mapper>
进行一次update操作
public class DefaultSqlSessionTest {
private static SqlSessionFactory sqlSessionFactory;
static {
initSqlSessionFactoryBySqlSessionFactoryBuilder();
}
/**
* 通过 SqlSessionFactoryBuilder 创建 SqlSessionFactory
*/
public static void initSqlSessionFactoryBySqlSessionFactoryBuilder(){
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
//返回的是DefaultSqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
HashMap<String, Object> paramMaps = Maps.newHashMap();
paramMaps.put("name","hello");
paramMaps.put("id","100");
sqlSession.update("update",paramMaps);
sqlSession.commit();
}
由于默认情况下autoCommit是关闭的,所以此时若要通过显示的调用DefaultSqlSession的commit方法才能完成真正的更新。
2、使用SqlSessionTemplate完成update操作
我的mapper配置文件同上,进行一次update操作。
public class SqlSessionTemplateTest {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
HashMap<String, Object> paramMaps = Maps.newHashMap();
paramMaps.put("name","hello");
paramMaps.put("id","100");
sqlSessionTemplate.update("update",paramMaps);
}
}
通过上述代码可以发现,使用SqlSessionTemplate无需通过调用commit方法就可以完成真正的更新。那么它背后具体是怎么实现的呢?来,跟进源码看一下。
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType,
new MyBatisExceptionTranslator(
sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}
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;
//创建sqlSession代理对象
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
}
上述是SqlSessionTemplate的构造方法(此处只截取了关键代码片段)。当通过调用SqlSessionTemplate(SqlSessionFactory sqlSessionFactory)构造器new对象的时候,其最终内部通过SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator)来完成初始化,这个方法的关键是内部通过调用JDK Proxy.newProxyInstance方法创建了一个SqlSession代理对象并赋值给了sqlSessionProxy。此时,我们需要把目光聚焦到SqlSessionInterceptor的实现逻辑,因为其中包含该代理对象的代理逻辑,其代码逻辑如下:
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
//执行commit
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
发现了没?在代理逻辑中先通过getSqlSession方法获取sqlSession(该方法逻辑后续再展开讲),而后执行method.invoke方法,之后通过isSqlSessionTransactional方法判断当前操作是否是事务操作,如果属于事务操作则执行sqlSession的commit方法,以此完成了事务的提交。
我们再回到之前通过SqlSessionTemplate完成update操作的例子上,先看下sqlSessionTemplate.update(“update”,paramMaps)源码。
public int update(String statement, Object parameter) {
return this.sqlSessionProxy.update(statement, parameter);
}
看到了没,本质上调用的是sqlSessionProxy这个代理对象的update操作,结合刚才描述的代理逻辑,现在清楚了SqlSessionTemplate无需通过调用commit方法就可以完成真正的更新的背后原理了吧。
3、衍生一下
我们在日常开发中,应该很少以上述的方式来使用mybatis,更多的则是将对应Mapper配置关联到相应的interface,并直接调用interface中定义的方法来操作数据库。如下所示:
我的mapper文件
<mapper namespace="com.jorg.mybatis.transactional.StudentMapper">
<update id="update">
UPDATE
student
SET
`name` = #{name}
WHERE
id = #{id}
</update>
</mapper>
StudentMapper.java
public interface StudentMapper {
int update(@Param("id") Integer id, @Param("name") String name);
}
执行代码
public class SqlSessionTemplateTest {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
StudentMapper studentMapper = sqlSessionTemplate.getMapper(StudentMapper.class);
studentMapper.update(100,"hello");
}
}
上述通过interface来执行数据库操作的使用方式应该是最常用的,那么该方式背后又是如何实现的呢?来跟进sqlSessionTemplate.getMapper代码看一下:
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
@Override
public Configuration getConfiguration() {
return this.sqlSessionFactory.getConfiguration();
}
我们可以看到,这个getMapper方法内部的执行逻辑是:
- 先通过sqlSessionFactory.getConfiguration()返回Configuration
- 调用Configuration对象的getMapper方法来返回对应的Mapper
这个sqlSessionFactory是哪里来的?返回去看下我们的测试方法(如下所示)。发现了没,是我们自己创建的,并在执行new SqlSessionTemplate(sqlSessionFactory)创建SqlSessionTemplate的时候传进去的。
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
那么sqlSessionFactory中的Configuration是怎么来的呢?这个相对就比较复杂了,主要涉及到Mybatis对我们自定义配置文件的解析逻辑(感兴趣的话可以进入上面代码的SqlSessionFactoryBuilder类的build方法内部去看一下,或者看下这个博客MyBatis 源码分析 – 配置文件解析过程)。
在这里呢,我们可以把Configuration看作是我们自定义的xml配置文件所对应的java对象,即,在Configuration中包含了我们在xml配置文件中配置的所有信息。
那么,我们不妨跟近Configuration的getMapper方法看下里面做了什么。
Configuration.java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
MapperRegistry.java
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);
}
}
MapperProxyFactory<T>.java
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
进入源码以后可以发现,整个getMapper的执行链路是:Configuration.java getMapper–>MapperRegistry.java getMapper–> MapperProxyFactory<T>.java newInstance
整理一下整个链路,你发现了什么?没错,动态代理!这里最终返回的是一个interface对应Mapper的一个代理对象。
由于上述的代码只是我截取的部分源码片段,其中有几个Field可能大家感觉比较晕。这里简单描述一下:
- Configuration 中的 mapperRegistry :可以把它理解为Mybatis的一个全局Mapper注册中心,它是在Mybaits启动时候解析配置文件的时候生成的;Mybatis会对解析到的每个mapper xml文件中的信息在mapperRegistry中进行注册。
- MapperRegistry 中的 knownMappers:它在 MapperRegistry 中是一个HashMap类型,如下所示,它的key是每个mapper xml文件中namespace属性值对应的Interface Mapper(如上述示例中的com.jorg.mybatis.mappers.StudentMapper),它的value对应的就是一个MapperProxyFactory工厂,从名字上看就可以知道,该工厂是用来生产MapperProxy的,对应value的初始化也是在Mybatis解析配置文件阶段。
public class MapperRegistry {
//....
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
//.....
}
回到 MapperProxyFactory.java,我们继续看下它生成代理对象的过程,不难发现 MapperProxy 类封装了具体的代理逻辑,跟进其源码:
public class MapperProxy<T> implements InvocationHandler, Serializable {
//...
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//如果方法是定义在 Object 类中的,则直接调用
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);
}
//从缓存中获取 MapperMethod 对象,若缓存未命中,则创建 MapperMethod 对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
//....
}
其中invoke方法体是其代理逻辑,可以看到它最终的调用的是 mapperMethod.execute 方法。mapperMethod获取通过调用一个cachedMapperMethod方法,该方法执行逻辑涉及到二级缓存相关的内容,后面会有时间的情况下会单独写篇文章分析一下。在这里,可以把MapperMethod理解为 Mybatis对mapper文件中的每个执行逻辑的一个封装(如<select></select>,<update></update>等等),而每个MapperMethod的生成也是在Mybatis启动时解析文件时进行的。
下面跟进下 mapperMethod.execute 执行逻辑:
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
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;
}
}
看着代码有点多,有点懵逼?莫慌兄弟!没那么复杂。方法体的执行逻辑有一个switch语句,而每个case是啥?INSERT、UPDATE、DELETE、SELECT,没错这对应的就是mapper里的标签啊。回想一下咱们一开始的测试代码的执行的是update操作对吧,那对应的case就是UPDATE了。So,看下UPDATE中的执行逻辑:sqlSession.update(command.getName(), param),像不像在“使用SqlSessionTemplate完成update操作”部分的测试代码里的sqlSessionTemplate.update(“update”,paramMaps)这个逻辑。有点像???又不太像。不像的地方是之前用的是sqlSessionTemplate,而这里是sqlSession;之前update传参是“update”,而这里是command.getName()。那到底是不是呢??
好了,也不卖关子了。其实这里完全就是一回事,这个sqlSession是怎么来的呢,回过去再顺着之前一路代码跟进的过程看(为了防止你失去信心,我又在下面梳理了一下代码片段):
SqlSessionTemplate.java
@Override
public <T> T getMapper(Class<T> type) {
/*注意看,这里传入的是this*/
return getConfiguration().getMapper(type, this);
}
|
|
V
Configuration.java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
|
|
V
MapperRegistry.java
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);
}
}
|
|
V
MapperProxyFactory<T>.java
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
|
|
V
//此处省略MapperProxy.java
好了,看明白了没?MapperProxy里面的sqlSession是谁?对,就是SqlSessionTemplate!那command.getName()是呢?这里直接告诉你吧,它的值就是“update”,它是什么时候给command这个对象设进去的?也是在Mybatis启动时候解析配置文件的时候设置的。
So,sqlSession.update(command.getName(), param)是完全等于sqlSessionTemplate.update(“update”,paramMaps)。
结尾
至此,我对SqlSessionTemplate这个类理解也阐述完了。如果看后觉得有哪些不合理和或者疏漏的,希望大家能够帮助矫正。
原文地址:https://www.jianshu.com/p/7cb4777a539e?from=singlemessage
参考文章1:https://www.cnblogs.com/daxin/p/3544188.html
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/138275.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...