原创不易,转载请注明出处
前言
我们都知道,在mybatis中,我们写的mapper接口,mybatis在初始化的时候会为其创建动态代理实现类,然后我们在service中调用dao操作mysql,其实都是调用的mybatis为我们创建的动态代理实现类,同时,我们在《mybatis执行原理精讲》一文介绍执行原理的时候,曾说到这个动态代理实现类会调用SqlSession执行我们的sql,接下来我们要探索下dao代理类是怎样找到sqlSession并调用的。
1. 代理类调用入口
比如说我们现在有个注册用户的需求,需要用户信息保存在mysql中,在项目中我们一般会这么写(这里都是demo级别)
@Mapper
public interface UserDao {
boolean addUser(UserEntity userEntity);
}
写一个用户相关的mapper接口,里面有一个addUser的方法。
<?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.example.testcanal.dao.UserDao" >
<insert id="addUser" useGeneratedKeys="true" keyProperty="id" parameterType="com.example.testcanal.model.UserEntity">
insert into user (username,password) values (#{userName},#{password})
</insert>
</mapper>
写一个UserMapper.xml ,里面就是insert语句。 这里service层的代码我们就不写了,在项目启动的时候,mybatis会进行初始化,扫描配置类,mapper类等等创建SqlSessionFactory,并且是为mapper 接口创建动态代理实现类,这些东西我们在《springboot整合mybatis原理分析》都有介绍到,基于这篇文章,我们看看执行一个addUser调用是怎样执行的。
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
这里我们把创建dao接口动态代理实现类的代码贴出来,使用的是jdk的动态代理api,所以说只需要关心那个invocationHandler的实现可以了,因为关于这个dao的所有方法调用会走到这里面的invoke方法。这里使用的invocationHandler 实现类是MapperProxy ,我们直接看下invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
判断调用的这个方法是不是Object类的方法,如果是的话,直接执行就ok了,我们这里肯定不是,获取一个invoker,执行invoke方法,看看cachedInvoker方法是怎样获取一个invoker的。
MapperMethodInvoker invoker = methodCache.get(method);
if (invoker != null) {
return invoker;
}
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
先从map缓存中获取这个方法对应的invoker,methodCache 是个map,缓存方法与invoker对应关系的,很显然,开始的时候我们并没有创建这个invoker,这个时候就会创建一个invoker 并放入缓存中。 我们普通方法是创建的PlainMethodInvoker ,构造方法中传入了一个MapperMethod 对象。 关于这个PlainMethodInvoker 其实没什么东西,就是个调用转发的作用,我们看看它代码
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
invoke方法中,就是直接调用的MapperMethod的execute方法,需要注意的是这里这个sqlSession 是SqlSessionTemplate,并不是真正执行意义上的SqlSession,这个是在springboot整合mybatis项目初始化的时候自动装配类创建的一个SqlSession模板类。 我们这里要看下MapperMethod的execute 方法了
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);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
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;
}
代码很长,逻辑很简单,其实就是将我们调用的方法转换成SqlSession的调用。
这里有一个问题,它是怎么知道执行sql的类型的? 其实创建MapperMethod对象的构造方法中,创建了一个SqlCommand对象,这个对象会根据你接口名+方法名去 configuration中获取项目初始化解析mapper xml 创建的MappedStatement。 关于MappedStatement是怎么被创建出来的可以阅读《mybatis源码解析之初始化(解析mapper文件)》这篇文章,在MappedStatement 有你这个sql类型,毕竟在创建MappedStatement 的时候是要解析你sql信息的,但是它是通过你标签判断出来的,就像<insert> <update> <delete> <select> 标签,拿到你标签就知道你这个方法是什么类型了。
我们这里是insert语句,所以执行的就是sqlSession的insert(command.getName(), param) 方法。其他的语句也同理。
command.getName() 是MappedStatement id ,也就是接口全类名.方法名
在进入sqlSession 的insert方法之前,我们要看下上面执行的convertArgsToSqlCommandParam 方法,这个方法很重要,处理你参数的。
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
判断你参数个数,如果是0的话,就返回null, 如果就一个参数 并且没有@Param注解的话,如果是集合/list/数组的话,封装到一个map中,如果是普通的bean,直接返回了 如果存在@Param注解或者是多个参数的话,就将参数名-》对应的值 塞到map中 比如说我有下面这么一个根据条件查询用户信息的方法
UserEntity selectByCondition(@Param("userName") String username, String password);
这个时候它解析到的参数map中就有这么几个kv: userName -> username值 (有@Param注解优先使用直接中的值,没有的话采用参数名) password -> password值 param1 -> username值 param2 -> password值 封装到map中并返回 好了,接下来就看下sqlSession的insert方法了。
2. SqlSession创建
上面我们有说到这个sqlSession其实SqlSessionTemplate 我们直接找到
@Override
public int insert(String statement, Object parameter) {
return this.sqlSessionProxy.insert(statement, parameter);
}
调用的是sqlSessionProxy 的insert方法,其实这个SqlSessionTemplate 什么活也不干的,都交给了sqlSessionProxy 这个代理类 创建SqlSessionTemplate对象的时候,会创建一个sqlSessionProxy 对象。
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
对应的invocationHandler的实现类是SqlSessionInterceptor(是SqlSessionTemplate 里面的一个内部类)。我们看下它的invoke方法 由于我们这篇文章是分析SqlSession的创建 ,这里它的invoke方法我们就不全部贴出来了
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
调用getSqlSession 方法获取sqlSession,将sqlSessionFactory 传入了进去
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
这里就是从spring的事务管理器中获取一个sqlSession,如果没有的话就创建一个,这个getResource 是从ThreadLocal中获取。 我们一开始肯定没有的,这个时候就会找sessionFactory 创建一个SqlSession,然后塞到这个spring事务管理器中。
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);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
可以看到创建了一个Executor ,然后创建一个DefaultSqlSession ,将Executor 组件传了进去。 看看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);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
上面一顿判断执行类型,我们这里是Simple。所以就创建一个SimpleExecutor 如果开启缓存的话(这里默认是开启的),就是使用CachingExecutor 装饰一下,包装一下,装饰者设计模式 最后,如果你有插件的话,在CachingExecutor 基础上进行装饰,然后返回。 SimpleExecutor与CachingExecutor 的创建都是很简单的,没有逻辑,我们这里就不看了
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
DefaultSqlSession 也有什么逻辑,就是赋值逻辑。 好了,到这里我们这个能干活的SqlSession就创建出来了。
总结
本文主要是找了找mapper 接口动态代理实现类执行入口,一步步的找到获取sqlSession逻辑与创建SqlSession的逻辑。
|