前面已经看完了整个MapperProxy
生成的过程,我们知道了当实际调用XXXMapper
方法的时候会调用MapperMethod.execute
方法进行数据库操作,那么这些具体是怎么实现的呢?还有Mybatis里面的@Param注解是如何实现的?如果没有写注解那么默认值是啥呢?带着这些问题我们继续往下看。
Executor
Mybatis
里面所有和数据库交互都是交给Executor
去执行的,它是一个接口,里面有各种和数据库相关的操作定义。
它的实现对应了你在配置mybatis
时候的executeType
,这个是一个枚举,主要有三个值,默认是simple,batch是批处理的方式,这个在大数据量处理的时候是非常常用的方式,比如上千W的数据插入和更新等。
注意批处理和mybatis
里面的<foreach>
标签有本质差异,foreach是将多个参数放到一起传输,但是还是一条一条sql执行,当数据量很大的时候,执行效率会变低,因为网络连接建立断开的代价太大,但是批处理是将一批量数据打包一起发送,减少了网络消耗,所以效率得到了很大提升(个人观点,欢迎指正)。
public enum ExecutorType {
SIMPLE,
REUSE,
BATCH
}
BaseExecutor
是一个基础实现,这三个都是基于它实现的。cachingExecutor
是缓存用的。是否开启你可以进行配置。
这个Executor
是在获取sqlsession
的时候创建的,你也可以指定对应的类型获取sqlsession
通过使用SqlSessionFactory
的openSession(ExecutorType execType)
方法。类似于下面这种写法:
、、、、、、省略
SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
Mapper mapper = sqlSession.getMapper(Mapper.class);
、、、、、、省略
这种写法实际调用的是下面这个方法:
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();
}
}
在这里会根据传入的execType
和事务信息,是否自动提交等配置去生成对应的Executor,最后创建出DefaultSqlSession
供我们使用。
ParamNameResolver
ParamNameResolver
这个类是解析所有关于param的相关操作的。比如@Param
参数的读取。
它里面有一个名为names
的SortedMap
,它会按照顺序存储mapper
接口的参数,如果是你使用了@Param
参数,那么会使用设置的名称作为value,否则使用序号作为value,这个值和参数的需要不一定完全对的上,如果你使用了RowBounds
和ResultHandler
作为参数将会跳过。参考下面的官方示例:
aMethod(@Param("M") int a, @Param("N") int b) -> {{0, "M"}, {1, "N"}}
aMethod(int a, int b) -> {{0, "0"}, {1, "1"}}
aMethod(int a, RowBounds rb, int b) -> {{0, "0"}, {2, "1"}}
它解析的时候会使用getNamedParams
方法,除了使用你设置的@Param
作为key之外,还会使用param+i
的方式作为参数,比如param1,param2
等,所以你在xml的sql语句里面直接用param+i也可以找到对应参数。
mybatis
里面的所有参数映射会进行两步操作,你可以在MapperMethod.execute
方法的param
变量查看第一步的映射关系,第二步则是在executor.update(ms, wrapCollection(parameter));
具体执行的时候会将集合类的参数再去封装一次,利用特殊的key去区分,比如collection,list,array
等,具体看你的类型。产生最后的param
。下面是源码:
public static Object wrapToMapIfCollection(Object object, String actualParamName) {
if (object instanceof Collection) {
ParamMap<Object> map = new ParamMap<>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
return map;
}
if (object != null && object.getClass().isArray()) {
ParamMap<Object> map = new ParamMap<>();
map.put("array", object);
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
return map;
}
return object;
}
这里如果是结合会用collection标识,如果是List会用list标识,如果是数组会用array标识,当然,你自己配置了@Param注解之后会具体的注解作为key也会放到map里面。比如你传的一个list,但是没有自己配置@Param,你可以在xml文件里面直接使用{list[0]}这种使用里面对象。
最终数据库操作都是用的这个返回的param
值。
在解析接口参数的时候,如果没有用注解且只有一个参数,是直接返回的整个类,没有用map,所以直接可以用类里面的属性,而不需要再指定传过来的参数名。比如我参数是一个Person,里面有name属性,我接口是addPerson(Person person);
这时候再xml里面直接可以用#{name}
而不需要用#{person.name}
。如果是多个参数或者集合的话还是返回的map。
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
}
if (!hasParamAnnotation && paramCount == 1) {
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(names.firstKey()) : 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()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
总结一下,如果是一个类型参数,可以直接用属性拿到值,如果是多个的话,需要用类型参数名.属性拿到值,Mybatis
里面默认开启了useActualParamName
配置的,它会直接用你传的参数作为key。基本属性直接拿来用就行。
比如对于
addPerson(Person person,String aa);
你在xml里面使用就不能直接用#{name}
了,而是必须使用#{person.name}
和#{aa}
才能拿到值。
总结
今天介绍了Executor
和ParamNameResolver
,他们在Mybatis
里面使用的地方很多,也是很重要的东西,后续还有一个MetaObject
类,这个是Mybatis
里面反射的主要实现类,它可以十分方便快捷的对数据进行赋值和取值,而且不限定类型,我这两天也在看这个类,看着有点复杂,等我研究差不多在来写。