Mybatis源码学习(15)-binding模块之MapperMethod类
程序员文章站
2022-07-12 22:54:38
...
一、概述
1、Mybatis操作数据
在Mybatis中,进行数据操作时,有两种方式,分别是:1、使用传统的MyBatis提供的API;2、使用Mapper接口,即面向接口编程。
- 传统的Mybatis工作模式
示例:
@Test
public void testApi() {
SqlSession sqlSession = mySqlSessionFactory.openSession(true);//自动提交
Map<String, Object> param = new HashMap<String, Object>();
param.put("field1", "a1");
List<Map<String, Object>> list = sqlSession.selectList("TestMapper.queryList", param);
System.out.println(list.toString());
}
2. 面向接口编程
示例:
@Test
public void testMapper() {
SqlSession sqlSession = mySqlSessionFactory.openSession(true);//自动提交
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
Map<String, Object> param = new HashMap<String, Object>();
param.put("field1", "a1");
List<Map<String, Object>> list = testMapper.queryList(param);
System.out.println(list.toString());
}
2、面向接口编程简介
Mybatis的binding模块就是用来实现面向接口编程的功能。面向接口编程,其实就是通过建立Mapper接口与XML配置文件的一一对应关系,从而实现通过操作对应接口来实现操作数据库SQL语句实现增删改查功能。
二、binding模块结构
Mybatis的binding模块的包目录:org.apache.ibatis.binding。具体包结构如下图所示:
其中,Mybatis的binding模块就是用到了动态代理。
- MapperMethod
定义Mapper接口中对应方法的信息以及对应SQL语句的信息,用来完成参数转换以及SQL语句的执行功能,可以看成连接Mapper接口以及映射配置文件中定义的SQL语句的桥梁。 - MapperProxy
MapperProxy实现了lnvocationHandler接口,是代理对象。 - MapperProxyFactory
负责创建代理对象 - MapperRegistry
Mapper接口及其对应的代理对象工厂的注册中心
三、MapperMethod类
定义Mapper接口中对应方法的信息以及对应SQL语句的信息,用来完成参数转换以及SQL语句的执行功能,可以看成连接Mapper接口以及映射配置文件中定义的SQL语句的桥梁。
- 字段、构造函数
在MapperMethod类中,定义了command、method两个字段,这两个字段均是内部类对象。其中command定义SQL命令的概要信息,主要包括了SQL命令的名称和类型;method定义Mapper接口中方法的相关信息。构造函数主要是实现command、method两个对象的初始化。
private final SqlCommand command; //定义SQL命令的概要信息,主要包括了SQL命令的名称和类型
private final MethodSignature method;//定义Mapper接口中方法的相关信息
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
- 内部类 SqlCommand
MapperMethod内部类,用来定义SQL命令的概要信息,主要包括了SQL命令的名称和类型。
public static class SqlCommand {
private final String name;//定义SQL命令的名称,由Mapper接口的全限定类名与对应的方法名称组成的。
private final SqlCommandType type;//定义SQL命令的类型,SqlCommandType是枚举类型,可选值为:UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
/**
* 构造函数,完成对字段属性name、type的初始化。
* 同时验证了Configuration.mappedStatements中是否有对应的MappedStatement对象(在解析XML时初始化该对象)。
* @param configuration
* @param mapperInterface
* @param method
*/
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
/**
* 解析SqlCommand实例对应的MappedStatement对象,并返回。
* 对应的MappedStatement对象在解析Mapper对应的XML时,进行初始化,并存储在了Configuration.mappedStatements中。
* @param mapperInterface
* @param methodName
* @param declaringClass
* @param configuration
* @return
*/
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {//当declaringClass==mapperInterface时,说明methodName对应方法的MappedStatement对象不存在,且不可能是在在超类中,所以直接返回null。
return null;
}
//下面循环是处理当methodName对应的方法在超类中的情况
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {//判断declaringClass是superInterface的父类,如果是继续查找对应的父类,直到查找到对应的MappedStatement或者检索完所有的父类
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
- 内部类 MethodSignature
定义Mapper接口中方法的相关信息。其中ParamNameResolver类后续再详细分析,现在只需要了解是用来处理方法参数即可。
public static class MethodSignature {
private final boolean returnsMany;//返回值类型是否为Collection类型或是数组类型
private final boolean returnsMap;//返回值类型是否为Map类型
private final boolean returnsVoid;//返回值类型是否为void类型
private final boolean returnsCursor;//返回值是否为Cursor类型
private final Class<?> returnType;//返回值类型
private final String mapKey;//如果返回值类型是Map,则该字段记录了作为key的列名
private final Integer resultHandlerIndex;//用来标记该方法参数列表中ResultHandler类型参数的位置
private final Integer rowBoundsIndex;//用来标记该方法参数列表中RowBounds类型参数的位置
private final ParamNameResolver paramNameResolver;//该方法对应的ParamNameResolver对象,主要用来处理方法的参数
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
/**
* 负责将args[]数组(用户传入的实参列表)转换成SQL语句对应的参数列表
* @param args
* @return
*/
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
public boolean hasRowBounds() {
return rowBoundsIndex != null;
}
public RowBounds extractRowBounds(Object[] args) {
return hasRowBounds() ? (RowBounds) args[rowBoundsIndex] : null;
}
public boolean hasResultHandler() {
return resultHandlerIndex != null;
}
public ResultHandler extractResultHandler(Object[] args) {
return hasResultHandler() ? (ResultHandler) args[resultHandlerIndex] : null;
}
public String getMapKey() {
return mapKey;
}
public Class<?> getReturnType() {
return returnType;
}
public boolean returnsMany() {
return returnsMany;
}
public boolean returnsMap() {
return returnsMap;
}
public boolean returnsVoid() {
return returnsVoid;
}
public boolean returnsCursor() {
return returnsCursor;
}
/**
* 查找指定类型的参数在参数列表中的位置,且该paramType在参数中只能有一个,否则会抛出BindingException异常。
* @param method
* @param paramType
* @return
*/
private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
Integer index = null;
final Class<?>[] argTypes = method.getParameterTypes();
for (int i = 0; i < argTypes.length; i++) {
if (paramType.isAssignableFrom(argTypes[i])) {
if (index == null) {
index = i;
} else {
throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
}
}
}
return index;
}
private String getMapKey(Method method) {
String mapKey = null;
if (Map.class.isAssignableFrom(method.getReturnType())) {
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
if (mapKeyAnnotation != null) {
mapKey = mapKeyAnnotation.value();
}
}
return mapKey;
}
}
- 内部类 ParamMap
重写了HashMap类,主要重写了get()方法,在原来的基础上,添加了当获取不存在的key值时,直接保存BindingException异常。
public static class ParamMap<V> extends HashMap<String, V> {
private static final long serialVersionUID = -2212268410512043556L;
@Override
public V get(Object key) {
if (!super.containsKey(key)) {
throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
}
return super.get(key);
}
}
- execute()方法
代理对象调用invoke()方法时,最终是调用了execute()方法,是最核心的代码,是实现操作SQL语句的逻辑的地方。这个方法主要用来处理参数、处理返回结果,真正操作数据的操作还是交给了sqlSession对应的方法。
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;
}
- convertArgsToSqlCommandParam()方法
负责将args[]数组(用户传入的实参列表)转换成SQL语句对应的参数列表。内部是通过paramNameResolver的getNamedParams()方法实现。
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
- 返回结果处理
/**
* 对数字或布尔类型的返回结果进行转换
* @param rowCount
* @return
*/
private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = (long)rowCount;
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = rowCount > 0;
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
/**
* 处理需要通过回调ResultHandler处理结果集的情况
* @param sqlSession
* @param args
*/
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
if (!StatementType.CALLABLE.equals(ms.getStatementType())
&& void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException("method " + command.getName()
+ " needs either a @ResultMap annotation, a @ResultType annotation,"
+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
}
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
/**
* 处理对应方法的返回值为数组或是Collection接口实现类
* @param <E>
* @param sqlSession
* @param args
* @return
*/
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
/**
* 处理返回值为Cursor的方法
* @param <T>
* @param sqlSession
* @param args
* @return
*/
private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
Cursor<T> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<T>selectCursor(command.getName(), param, rowBounds);
} else {
result = sqlSession.<T>selectCursor(command.getName(), param);
}
return result;
}
/**
* 主要负责将结果对象转换成Collection集合对象和数组对象
* @param <E>
* @param config
* @param list
* @return
*/
private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
Object collection = config.getObjectFactory().create(method.getReturnType());
MetaObject metaObject = config.newMetaObject(collection);
metaObject.addAll(list);
return collection;
}
/**
* 主要负责将结果对象转换成Collection集合对象和数组对象
* @param <E>
* @param list
* @return
*/
@SuppressWarnings("unchecked")
private <E> Object convertToArray(List<E> list) {
Class<?> arrayComponentType = method.getReturnType().getComponentType();
Object array = Array.newInstance(arrayComponentType, list.size());
if (arrayComponentType.isPrimitive()) {
for (int i = 0; i < list.size(); i++) {
Array.set(array, i, list.get(i));
}
return array;
} else {
return list.toArray((E[])array);
}
}
/**
* 主要负责将结果对象转换成Map对象
* @param <K>
* @param <V>
* @param sqlSession
* @param args
* @return
*/
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
}
return result;
}
下一篇: Canvas贝塞尔曲线和二次贝塞尔曲线