MyBatis源码解析之基础模块—binding
binding未诞生之暗黑时代
在介绍MyBatis的binding之前,咱们先一段代码:
UserInfoDAO
package com.todobugs.study.dao;
import com.todobugs.study.domain.UserInfo;
import com.todobugs.study.query.UserInfoQuery;
public interface UserInfoDAO {
Long insert(UserInfo userInfo);
/**
* 根据用户名查找
* @param userName
* @return
*/
UserInfo selectUserInfoByName(String userName);
}
UserInfoDaoImpl
package com.todobugs.study.dao.impl;
import com.todobugs.study.dao.BaseDAO;
import com.todobugs.study.dao.UserInfoDAO;
import com.todobugs.study.domain.UserInfo;
import org.springframework.stereotype.Repository;
@Repository("userInfoDAO")
public class UserInfoDAOImpl extends BaseDAO implements UserInfoDAO {
private static final String SQLMAP_SPACE = "USER_INFO.";
public Long insert(UserInfo userInfo) {
return (Long)getSqlMapClientTemplate().insert(SQLMAP_SPACE + "insert", userInfo);
}
@Override
public UserInfo selectUserInfoByName(String userName) {
return (UserInfo) this.getSqlMapClientTemplate().queryForObject(SQLMAP_SPACE+"getByName", userName);
}
}
上述两份源码就是使用ibatis开发的dao,从中可以看出dao实现类其实没有什么业务逻辑处理,就是为了绑定namespace 及sql节点。
在ibatis时代,开发者在编写dao(即现在的mapper)时必须要实现该dao接口,其根本目的只是指定对应的namespace及操作节点。虽然实现内容很简单,这给开发者带来不必要且繁琐的编码,且在编译时并不能发现开发者是否存在异常,只有在运行时才能发现。
为解决这种操作方式的弊端,在mybatis版本中提供了binding模块。从而能够在编译期就能够发现问题。同时通过采用jdk动态代理模式,开发者只需要要编写对应的接口即可完成持久层的开发工作。即降低工作量,有大大降低出错概率。
接下来,我们将通过源码详细介绍binding的执行逻辑。
架构设计
binding模块所在包路径为org.apache.ibatis.binding
,类关系比较简单,总共就五个类:
- MapperRegistry:Mapper注册类
- MapperProxyFactory:Mapper代理工厂类
- MapperProxy:Mapper代理类
- MapperMethod:Mapper执行方法
- BindingException:绑定异常类()
其类之间的架构设计关系为:
接下来各类中主要方法依次介绍。
源码解读
MapperRegistry
老规矩,先上源码:
package org.apache.ibatis.binding;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder;
import org.apache.ibatis.io.ResolverUtil;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
public class MapperRegistry {
/** 全局配置类 */
private final Configuration config;
/** 已添加的mapper代理类工厂 */
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
/** 构造函数 */
public MapperRegistry(Configuration config) {
this.config = config;
}
/** 根据包名添加mapper */
public void addMappers(String packageName) {
//默认superType为Object.class,这样该包下的所有接口均会被添加到knownMappers中
addMappers(packageName, Object.class);
}
/** 根据指定包名及父类类型添加mapper */
public void addMappers(String packageName, Class<?> superType) {
/** 通过resolverUtil类判断查询packageName包下所有匹配superType的类型,并添加到一个set类型集合中 */
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
/** 循环遍历该集合,并将该mapperClass添加到knownMappers中 */
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
/**
* 判断是否为接口,是的话才会生成代理对象
* 后续会在运行时该代理对象会被拦截器进行拦截处理
*/
public <T> void addMapper(Class<T> type) {
/** 1、判断传入的type是否是一个接口
* 2、判断knownMappers是否已经存在,若存在则抛出已存在异常。
* 3、设置是否加载完成标识,final会根据是否加载完成来区别是否删除该type的设置
* 4、将该接口put到knownMappers中
* 5、调用MapperAnnotationBuilder构造方法,并进行解析。(具体处理逻辑会在builder模块中展开)
*/
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
/** 获取mapper代理对象 */
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
/**判断knownMappers中是否存在该类型的mapper代理工厂*/
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
/** 主要用于测试,无需关注 */
public Collection<Class<?>> getMappers() {
return Collections.unmodifiableCollection(knownMappers.keySet());
}
}
在Configuration实例化时,会通过如下方式进行实例化。
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
从源码中可以看出,MapperRegister
类只有两个属性,七个方法(包含一个构造方法)
- config:该属性里面包含各种mybatis的配置信息,此处不再赘述。
- knownMappers:该属性存放mapper接口并提供其代理对象(稍后介绍
MapperProxyFactory
)。 - MapperRegistry:该构造方法注入config配置信息。
- addMappers(String packageName):根据包名添加该包下的所有mapper接口类,调用下述重载方法
- addMappers(String packageName, Class<?> superType):重载方法,根据包名及父类类型添加该包下的所有mapper接口类
- getMapper:根据mapper类型及sqlsession获取对应mapper接口的代理对象。
- hasMapper:根据mapper类型判断knownMappers中是否已存在,主要用于addMapper时校验。
- addMapper:根据mapper类型将其添加到knownMappers中,该方法默认被
addMappers(String packageName, Class<?> superType)
循环调用,开发者亦可手动调用。
MapperProxyFactory
package org.apache.ibatis.binding;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.ibatis.binding.MapperProxy.MapperMethodInvoker;
import org.apache.ibatis.session.SqlSession;
public class MapperProxyFactory<T> {
/** mapper接口 */
private final Class<T> mapperInterface;
/** method缓存 */
private final Map<Method, MapperMethodInvoker> methodCache =