目录
一.一个核心问题
为什么通过mapper接口就可以执行数据库操作(mapper接口没有实现类型)。
Ans: 通过mapper接口动态代理的增强+ 配置文件解读。
动态代理增强就是binding模块的功能。
从表现来讲,bingding的主要功能是将面向mapper接口编程转换成session中对应的方法执行。
二 、binding模块分析
2.1、binging 模块核心组件关系如下图:
- MapperRegistry : mapper 接口和对应动态代理工厂 的注册中心。
- MapperProxyFactory : 用于生成mapper接口动态代理的实例对象;
- MapperProxy:实现了InvocationHandler接口,它是增强mapper接口的实现;
- MapperMethod:封装了Mapper接口中对应方法的信息,以及对应的sql语句的信息;它是mapper接口与映射配置文件中sql语句的桥梁;
2.2、MapperRegistry解析
MapperRegistry是 Mapper 接口及其对应的代理对象工厂的注册中心。
MapperRegistry中字段的含义和功能如下:
private final Configuration config;//Configuration对象,mybatis全局唯一的
//记录了mapper接口与对应MapperProxyFactory之间的关系
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
public MapperRegistry(Configuration config) {
this.config = config;
}
在Mybatis初始化过程中会读取配置文件以及mapper接口中的注解信息,并调用MapperRegistry.addMapper()方法将mapper接口的工厂类添加到mapper注册中心(MapperRegistry.knownMappers 集合 )该集合的 key 是 Mapper 接口对应的 Class 对象, value 为 MapperProxyFactory 工厂对象,可以为 Mapper 接口创建代理对象, MapperProxyFactory 的实现马上就会分析到。 MapperRegistry.addMapper()方法的 部分实现如下:
//将mapper接口的工厂类添加到mapper注册中心
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {//监测是否为接口
if (hasMapper(type)) {//判断是否已经初始化过了
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//实例化Mapper接口的代理工程类,并将信息添加至knownMappers
knownMappers.put(type, new MapperProxyFactory<T>(type));
//解析接口上的注解信息,并添加至configuration对象
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
在需要执行某sql语句时候,会先调用 MapperRegistry.getMapper()方法获得Mapper接口的代理对象,例如:
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class); mapper实际上是通过Jdk动态代理为TUserMapper接口生产代理对象。其代码如下所示:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//根据type获取 其动态代理工厂类
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//返回mapper接口的动态代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
2.3、MapperProxyFactory
MapperProxyFactory 主要负责创建代理对象, 其中核心字段的含义和功能如下:
//mapper接口的class对象
private final Class<T> mapperInterface;
//key是mapper接口中的某个方法的method对象,value是对应的MapperMethod,
// MapperMethod对象不记录任何状态信息,所以它可以在多个代理对象之间共享
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
MapperProxyFactory.newInstance(SqlSession sqlSession)方法实现了创建mapperInterface接口的代理对象的功能,其具体代码如下:
public T newInstance(SqlSession sqlSession) {
//每次调用都会创建新的MapperProxy对象
final MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
//创建实现了mapper接口的动态代理对象
/**
* jdk动态代理获得代理对象
* 参数一 被代理对象的类加载器
* 参数二 被代理对象的接口
* 参数三 代理对象
*/
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
2.4 MapperProxy
MapperProxy 实现了 lnvocationHandler 接 口,在介绍 JDK 动态代理,该接口的实现是代理对象的核心逻辑,这里不再重复描述。 MapperProxy 中核心宇段的含义和功能 如下:
private final SqlSession sqlSession;//当前查询的sqlSession对象
private final Class<T> mapperInterface;// Mapper接口对应的class对象
////key是mapper接口中的某个方法的method对象,value是对应的MapperMethod,
// MapperMethod对象不记录任何状态信息,所以它可以在多个代理对象之间共享
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;
}
MapperProxy. invoke()方法是代理对象执行的主要逻辑, 实现如下:
/**
*
* @param proxy 代理对象
* @param method 被代理对象中方法
* @param args 方法中饿入参
* @return 代理对象
* @throws Throwable
*/
@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 (method.isDefault()) {// ..针对 Java7 以上版本对动态类型语言的支持
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//methodCache 缓存中获取MapperMethod 对象,如采缓存中没有,则创建新的 MapperMethod 对象并添加到缓存中
final MapperMethod mapperMethod = cachedMapperMethod(method);
//调用 MapperMethod.execute()方法执行 SQL 语句
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method,
k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
2.5 MapperMethod
MapperMethod 中封装了Mapper接口中对应方法的信息,以及对应SQL语句的信息。我们可以把MapperMethod的看着连接Mapper接口以及映射配置文件中定义的SQL语句的桥梁。
MapperMethod中各个字段的信息如下:
private final SqlCommand command;//从configuration中获取方法的命名空间.方法名以及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);
}
2.5.1 SqlCommand
SqlCommand 是MapperMethod中定义的内部类,他使用name字段记录SQL语句的名称,使用 type 宇段( SqlCommandType 类型)记录了 SQL 语句的类型。 SqlCommandType 是枚举类 型,有效取值为 UNKNOWN、 INSERT、 UPDATE、 DELETE、 SELECT、 FLUSH。
public static class SqlCommand {
//sql的名称,命名空间+方法名称
private final String name;
//获取sql语句类型
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();//获取名称
final Class<?> declaringClass = method.getDeclaringClass();
//从configuration中获取mappedStatement
// 用于存储mapper.xml文件中的select、insert、update和delete节点,同时还包
//含了这些节点的很多重要属性;
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 {//如果如果mappedStatement不为空
name = ms.getId();//获取sql的名称,命名空间+方法名称
type = ms.getSqlCommandType();//获取sql语句的类型
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
//从configuration中获取mappedStatement
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
String statementId = mapperInterface.getName() + "." + methodName;//sql语句的id为命名空间+方法名字
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);//从configuration中获取mappedStatement
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
2.5.2 ParamNameResolver
ParamNameResolver(Resolver [riˈzɑlvər] 解析器; ) 解析mapper接口方法中的入参;
在 MethodSignature 中, 会使用 ParamNameResolver 处理 Mapper 接口中定义的方法的参数 列表。 ParamNameResolver 使用 name 宇段( SortedMap<Integer, String>类型)记录了参数在参 数列表中的位置索引与参数名称之间的对应关系,其中 key 表示参数在参数列表中的索引位置, value 表示参数名称,参数名称可以通过@Param 注解指定,如果没有指定@Param 注解,则使 用参数索引作为其名称。
2.5.3 MethodSignature
MethodSignature(Signature [ˈsɪɡnətʃər] 明显特征;)封装mapper接口方法的相关信息(入参,返回类型);
MethodSignature 中核心内容如下:
private final boolean returnsMany;//返回参数是否为集合类型或数组
private final boolean returnsMap;//返回参数是否为map
private final boolean returnsVoid;//返回值为空
private final boolean returnsCursor;//返回值是否为游标类型
private final boolean returnsOptional;//返回值是否为Optional
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.returnsOptional = Optional.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 语句对应的参数列表,它是通过上面介绍的
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
2.5.4 MapperMethod.execute()
介绍完 MapperMethod 中定义的内部类, 回到 MapperMethod 继续分析。 MapperMethod 中 最核心的方法是 execute()方法,它会根据 SQL 语句的类型调用 SqISession 对应的方法完成数据 库操作。 SqlSession 是 MyBatis 的核心组件之一,其具体实现后面会详细介绍,这里读者暂时只 需知道它负责完成数据库操作即可。
MapperMethod.execute()方法的具体实现如下:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//根据 SQL 语句的类型调用 SqlSession 对应的方法
switch (command.getType()) {
case INSERT: {
//使用 ParamNameResolver 处理 args[]数组(用户传入的实参列表),将用户传入的 实参与
// 指定参数名称关联起
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
}
case DELETE: {
}
case SELECT:
//处理返回值为 void 且 ResultSet 通过 ResultHandler 处理的方法
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {///处理返回值为集合或数组的方法
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {//处理返回值为 Map 的方法
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {//处理返回值为 Cursor 的方法
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;
}
三 、数据库执行流程分析
3.1 流程分析
@Test
public void testManyParamQuery(){
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
// 1.读取mybatis配置文件创SqlSessionFactory
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2.获取sqlSession 方法级别的
SqlSession sqlSession = sqlSessionFactory.openSession();
//// 3.获取对应mapper
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
String email = "qq.com";
Byte sex = 1;
// 第三种方式用对象
EmailSexBean esb = new EmailSexBean();
esb.setEmail(email);
esb.setSex(sex);
List<TUser> list3 = mapper.selectByEmailAndSex3(esb);
System.out.println("javabean:"+list3.size());
}
3.1.1 创建动态代理对象流程
//// 3.获取对应mapper
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
step1.SqlSession的唯一实现是DefaultSqlSession,getMapper方法如下:
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
step2.调用Configuration的getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
/*mapper接口的动态代理注册中心*/
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public MapperRegistry(Configuration config) {
this.config = config;
}
step3.调用MapperRegistry的getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//根据type获取 其动态代理工厂类
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//返回mapper接口的动态代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
step4.调用MapperProxyFactory的newInstance:
public T newInstance(SqlSession sqlSession) {
//每次调用都会创建新的MapperProxy对象
final MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
//创建实现了mapper接口的动态代理对象
/**
* jdk动态代理获得代理对象
* 参数一 被代理对象的类加载器
* 参数二 被代理对象的接口
* 参数三 代理对象
*/
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
最后DEBUG可以看出来得到的mapper实际上是 mapper接口class对象的动态代理对象
3.1.2 执行查询流程
List<TUser> list3 = mapper.selectByEmailAndSex3(esb);
step1.动态代理最终要调用的InvocationHandler——mapperProxy
/**
*
* @param proxy 代理对象
* @param method 被代理对象中方法
* @param args 方法中饿入参
* @return 代理对象
* @throws Throwable
*/
@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 (method.isDefault()) {// ..针对 Java7 以上版本对动态类型语言的支持
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//methodCache 缓存中获取MapperMethod 对象,如采缓存中没有,则创建新的 MapperMethod 对象并添加到缓存中
final MapperMethod mapperMethod = cachedMapperMethod(method);
//调用 MapperMethod.execute()方法执行 SQL 语句
return mapperMethod.execute(sqlSession, args);
}
step2.获取MapperMethod对象
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
MapperMethod封装了Mapper接口中对应方法的信息,以及对应的sql语句的信息,它是mapper接口与映射配置文件中sql语句的桥梁,MapperMethod对象不记录任何状态信息,所以它可以在多个代理对象之间共享。
- SqlCommand:从configuration中获取方法的命名空间、方法名以及SQL语句的类型;
- MethodSignature:封装mapper接口方法的相关信息(入参、返回类型);
- ParamNameResolver:解析mapper接口方法中的入参。
step2-1.获取SqlCommand
step2-2.获取MethodSignature
step3.调用MapperMethod.execute执行sql
return mapperMethod.execute(sqlSession, args);
四、总结
step1.通过动态代理创建了代理对象MapperProxy
step2.MapperProxy最后调用的是MapperMethod的方法执行
最后的核心落在了MapperMethod,其封装了Mapper接口中对应方法的信息,以及对应的sql语句的信息,它是mapper接口与映射配置文件中sql语句的桥梁。
参考
- 1)享学课堂Lison老师笔记