MyBatis接口绑定原理
获取代理对象
public class App1 {
public static void main(String[] args) {
// 加载 MyBatis 配置文件
InputStream is = App1.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
// 获取 SqlSession, 代表和数据库的一次会话, 用完需要关闭
// SqlSession 和 Connection, 都是非线程安全的, 每次使用都应该去获取新的对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取实现接口的代理对象
// UserMapper 并没有实现类, 但是mybatis会为这个接口生成一个代理对象(将接口和xml绑定)
// 这里返回的是一个 MapperProxy 代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
sqlSession.close();
}
}
从上面可以看出,通过SqlSession
来获取代理类,SqlSession
对象表示MyBaits框架与数据库建立的会话,我们可以通过SqlSession
实例完成对数据库的增删改查操作。SqlSession
是一个接口
// SqlSession.java
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
List<BatchResult> flushStatements();
@Override
void close();
void clearCache();
Configuration getConfiguration();
// 根据接口类型获取接口对应的代理
<T> T getMapper(Class<T> type);
Connection getConnection();
}
SqlSession
的默认实现为DefaultSqlSession
// DefaultSqlSession.java
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
}
@Override
public <T> T getMapper(Class<T> type) {
// MyBatis初始化时已经把Mapper接口对应的代理信息保存到了 Configuration 中
// 这里直接从 Configuration 中获取代理类
// 这里返回的是一个 MapperProxy
// 找到Configuration类的getMapper方法,该方法比较简单
// 主要发生如下几个步骤
// Map<Class<?>, MapperProxyFactory<?>> knownMappers
// 1. 从 Configuration 获取 Mapper 接口对应的 MapperProxyFactory
// 2. MapperProxyFactory 为 Mapper 接口生成 MapperProxy 代理对象并返回
return configuration.<T>getMapper(type, this);
}
MapperProxy
MyBatis中通过MapperProxy
类实现动态代理。下面是MapperProxy类的关键代码:
// MapperProxy.java
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
// Mapper 接口
private final Class<T> mapperInterface;
// Mapper 接口中的每个方法都会生成一个MapperMethod对象, methodCache维护着他们的对应关系
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中定义的方法,直接执行。如toString(),hashCode()等
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);
}
// 将 Method 转换成 MapperMethod 并保存到 methodCache 中
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 这里面将会执行 Mapper接口对应的 SQL
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
// 通过 Configuration 获取对应的 MappedStatement
// 通过 MappedStatement 获取 MapperMethod 需要的各种信息
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
MapperProxy
使用的是JDK内置的动态代理,实现了InvocationHandler
接口,invoke()方法中为通用的拦截逻辑。当我们调用自己Mapper接口中的方法时,其实就是在调用MapperProxy
的invoke()方法。
由上面的分析可以知道,通过MapperMethod
来执行代理方法。
MapperMethod
public class MapperMethod {
// 里面有两个属性
// name: 要执行的方法名,如com.example.demo.UserMapper.getUserByUserName
// type: SQL标签的类型 insert update delete select
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;
// 根据insert、update、delete、select调用不同的方法
switch (command.getType()) {
case INSERT: {
/**
* args是用户 Mapper 所传递的方法参数列表
* 如果方法只包含一个参数并且不包含命名参数, 则返回传递的参数值。
* 如果包含多个参数或包含 @Param 注解修饰的参数,则返回包含名字和对应值的Map对象
*/
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;
}
}
从上面可以看出,数据的增删改查都是通过SqlSession
来实现的。
MethodSignature
MethodSignature
是MapperMethod
的内部类。MethodSignature
为MapperMethod
类提供了三个作用。
- 获取待执行方法中的参数和
@Param
注解标注的参数名 - 获取标注有
@MapKey
的值 - 获取SELECT操作时必要的标志位。
public static class MethodSignature {
// 是否多值查询
private final boolean returnsMany;
// 是否map查询
private final boolean returnsMap;
// 是否void查询
private final boolean returnsVoid;
// 是否游标查询
private final boolean returnsCursor;
// 返回类型
private final Class<?> returnType;
// 获取mapKey的值
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
// 参数解析器
private final ParamNameResolver paramNameResolver;
/**
* args是用户 Mapper 所传递的方法参数列表
* 如果方法只包含一个参数并且不包含命名参数, 则返回传递的参数值。
* 如果包含多个参数或包含命名参数,则返回包含名字和对应值的Map对象
*/
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
}
ParamNameResolver
public class ParamNameResolver {
private static final String GENERIC_NAME_PREFIX = "param";
// 保存方法参数列表的参数名
// 例如方法:getUser(String userName, Integer age)
// names.get(0) = arg0,names.get(1) = arg1
// 例如方法:getUser(@Param(value = "userName") String userName, Integer age)
// names.get(0) = 'userName',names.get(1) = arg1
// key存储参数所在位置的下标
// value为参数名,如果存在@Param,则使用@Param注解里面的值,否则 value = 'arg' + 参数下标索引值
private final SortedMap<Integer, String> names;
// 方法参数列表是否存在 @Param 注解的参数
private boolean hasParamAnnotation;
/**
* args是用户 Mapper 所传递的方法参数列表
* 如果方法只包含一个参数并且不包含命名参数, 则返回传递的参数值。
* 如果包含多个参数或包含 @Param 注解修饰的参数,则返回包含名字和对应值的Map对象
*/
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
// 如果方法参数为空,则直接放回 null
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
// 如果方法参数没有使用 @Param 注解修饰的参数,并且只有一个参数
// 这里直接返回参数值,相当于是返回args[0]
return args[names.firstKey()];
} else {
// 创建一个Map对象
// key 为参数名,value为参数值
final Map<String, Object> param = new ParamMap<Object>();
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 + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
本作品采用《CC 协议》,转载必须注明作者和本文链接