接上文,前面说到使用Mybatis进行数据库操作,要先获取SqlSession
,从而Mybatis会为当前的数据库操作分配一个执行器Executor
和事务工厂TransactionFactory
之后通过SqlSession
就可以获取Mapper接口的代理对象,从而调用接口方法进行数据库操作
public class MybatisDemo {
public static void main(String[] args) {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
try (SqlSession session = sqlSessionFactory.openSession()) {
// 添加Mapper接口,并解析对应的XML文件
session.getConfiguration().addMapper(BlogMapper.class);
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
// 执行数据库查询,ID为101的记录
blogMapper.selectBlog(101);
}
}
/**
* 获取数据源
*/
private static DataSource getDataSource() { // ... }
/**
* 获取Mybatis的SessionFactory
*/
private static SqlSessionFactory getSqlSessionFactory() { // ... }
}
既然使用Mybatis时仅仅提供了Mapper接口以及对应的XML文件,那通过SqlSession
获取接口代理对象时,它又做了什么?
在上面的Demo中,可以看到如下代码:
// 通过当前session获取到Mybatis全局配置对象configration,
// 为其添加一个Mapper接口
session.getConfiguration().addMapper(BlogMapper.class);
通过Configuration
可以往Mybatis注册接口,跟进Configuration#addMapper
,来到MapperRegistery
,这是Myabtis的Mapper注册中心,Mybatis采用HashMap保存Mapper接口及其代理对象生成工厂的映射关系
public class MapperRegistry {
// 保存Mapper接口映射关系
// key : Mapper接口 value : 代理对象生成工厂
private final Map, MapperProxyFactory>> knownMappers = new HashMap<>();
/**
* 注册Mapper接口
*/
public void addMapper(Class type) {
// 必须是接口才会处理,否则直接不理会,相当于这个方法执行
if (type.isInterface()) {
// 这其实就是校验knownMappers是否contains当前key
if (hasMapper(type)) {
// 省略异常抛出 ...
}
boolean loadCompleted = false;
try {
// 添加Mapper接口和代理对象工厂映射关系
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 解析Mapper的XML配置
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
上述代码中,向Mybatis注册Mapper的时候,它使用一个Map集合保存接口以及接口代理工厂的映射关系,同时使用MapperAnnotationBuilder#parse
,解析接口对应的XML文件
public class MapperAnnotationBuilder {
// Mapper接口类型
private final Class> type;
/**
* 解析XML配置
*/
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
// 加载XML资源
loadXmlResource();
configuration.addLoadedResource(resource);
// ...
}
parsePendingMethods();
}
/**
* 加载XML资源并解析
*/
private void loadXmlResource() {
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
// 根据Mapper接口的类名.xml作为名称加载XML
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
try {
inputStream = Resources
.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
}
}
if (inputStream != null) {
// 解析XML生成MappedStatement添加到Configuration,后续执行SQL操作会用到
XMLMapperBuilder xmlParser
= new XMLMapperBuilder(inputStream, assistant.getConfiguration(),
xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
}
MapperAnnotationBuilder
通过Mapper的包路径尝试加载XML资源,加载成功之后,利用XMLMapperBuilder
进一步解析XML配置信息,最终会将XML内的信息解析成多个Map保存到全局配置对象Configuration
的几个Map集合中,其中mappedStatements
会保存配置文件中select
、update
、delete
、insert
标签信息,这个属性将会在后续执行数据库操作中起到至关重要的作用
public class Configuration {
// 缓存XML文件中各个数据库操作标签的信息
protected final Map mappedStatements
= new StrictMap("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue)
-> ". please check " + savedValue.getResource()
+ " and " + targetValue.getResource());
// 保存XML文件中标签的信息
protected final Map resultMaps = new StrictMap<>("Result Maps collection");
// 保存XML文件中标签的信息
protected final Map parameterMaps = new StrictMap<>("Parameter Maps collection");
}
到此为止,向Mybatis注册Mapper接口的过程结束,接着再看通过SqlSession
获取Mapper接口代理对象时Mybatis做了什么?
回到文章开头的代码块,可以看到如下方法:
BlogMapper blogMapper = session.getMapper(BlogMapper.class);
跟进这个方法,又来到了Mapper注册中心MapperRegistry
,它通过之前注册Mapper时保存的MapperProxyFactory
为当前的Mapper接口创建代理对象
public class MapperRegistry {
private final Map, MapperProxyFactory>> knownMappers = new HashMap<>();
/**
* 从注册的Mapper集合中查找对应的代理对象生成工厂
*/
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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);
}
}
}
MapperProxyFactory
创建代理对象的逻辑如下,底层采用JDK的动态代理技术生成代理对象
public class MapperProxyFactory {
// Mapper接口的类型
private final Class mapperInterface;
// 接口中的方法信息
private final Map methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class getMapperInterface() {
return mapperInterface;
}
public Map getMethodCache() {
return methodCache;
}
/**
* Mybatis最终通过JDK动态代理技术,为Mapper接口生成代理对象
*/
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
// MapperProxy实现了InvocationHandler
final MapperProxy mapperProxy = new MapperProxy<>(sqlSession,
mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
既然使用JDK动态代理技术,必然有一个实现InvocationHandler
接口的方法,也就是上面的MapperProxy
,它封装了当前SqlSession
、接口类型、接口类方法信息,当通过代理对象调用接口方法的时候,其实就是调用了MapperProxy
的invoke
方法
public class MapperProxy implements InvocationHandler, Serializable {
// 当前会话对象
private final SqlSession sqlSession;
// 接口方法的缓存信息
private final Map methodCache;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ...
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
/**
* 获取接口方法的缓存信息,如果没有那就创建一个缓存起来,将其返回
*/
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method,
k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
}
到此为止,Mybatis生成代理对象过程结束,简单来说,就是注册Mapper接口的同时,会为当前接口生成一个代理对象生成工厂并扫描它的XML配置文件,将解析好的信息放在Configuration
中,当通过SqlSession
获取接口的实现类时,会根据之前保存好的代理对象工厂生成代理对象,从Configuration
获取事先解析好的信息执行数据库操作