一、SqlSession
上一篇分析了SqlSessionFactoryBuilder是如何解析mapper的,并且mapper的核心在于会将所有的mapper接口注册到MapperRegistry中(key-mapper接口Class类,value-mapper的代理工厂)。
当SqlsessionFactoryBuilder的build方法执行完毕后,如下图Mybatis文档中的mapper.xml所示:mapper信息、select信息、resultType信息、sql信息、resultMap信息、paraterMap信息已经被全部解析出来。
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select></mapper>
· SqlSession会话
BlogMapper mapper = session.getMapper(BlogMapper.class);Blog blog = mapper.selectBlog(101);
SqlSessionFactoryBuilder的build方法执行后,SqlSessionFactory就被创建了出来。如上图Mybatis文档的执行sql示例所示,我们需要使用SqlSessionFactory来打开sqlSession,通过sqlSession来获取mapper接口,执行数据库方法。
· openSession
public class DefaultSqlSessionFactory implements SqlSessionFactory {
@Override
public SqlSession openSession(boolean autoCommit) {
return openSessionFromDataSource (configuration.getDefaultExecutorType(), null, autoCommit);
} //...}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null;
try { //环境变量,存放着事物工厂、jdbc数据源信息 final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//根据execType创建执行器,默认为Simple(SimpleExecutor) final Executor executor = configuration.newExecutor(tx, execType);
//最后创建sqlSession return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
通过SqlSessionFactory的openSession方法来创建SqlSession,在方法中,会对解析后的environment、事物管理器进行赋值,并且创建executor执行器。
· getMapper
SqlSession创建成功后,需要通过sqlSession.getMapper来获取mapper接口。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { //从map中取出mapper对应的代理工厂
final MapperProxyFactory<T> mapperProxyFactory =(MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {//通过jdk动态代理创建mapper代理类,并且指定了sqlSession。return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}protected T newInstance(MapperProxy<T> mapperProxy) { //通过jdk动态代理生成mapper代理类 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
从上述getMapper的源码中可以看出,想要获取mapper接口类,实际上是调用了mapperRegistry.getMapper方法,从hashMap中取出mapper的代理工厂类,最后通过jdk动态代理来创建出mapper的代理类。
二、Mapper的动态代理类
· MapperProxy
我们先来看下生成的动态代理类中发生了什么:
public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try { //如果是类的基础方法,则invoke执行 return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} //否则对代理方法进行缓存
final MapperMethod mapperMethod = cachedMapperMethod(method); //执行数据库操作 return mapperMethod.execute(sqlSession, args);
}}
Blog blog = mapper.selectBlog(101);
当调用mapper.selectBlog(101)时,会先从mapper的注册中心中拿到对应的mapper动态代理类,在对非基础方法进行执行(即数据库操作方法)时,会先对代理方法进行缓存,然后再进行数据库操作。
(1)cachedMapperMethod
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;
}
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
· SqlCommand的封装
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
//name = 接口名+方法名 String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
//增删改成的方法,都会走到这步逻辑, //在sqlSessionFactoryBuilder中都被加载到configuration中了 if (configuration.hasStatement(statementName)) { //获取mappedStatement对象 ms = configuration.getMappedStatement(statementName);
} else if (!mapperInterface.equals(method.getDeclaringClass())) {
String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
if (configuration.hasStatement(parentStatementName)) {
ms = configuration.getMappedStatement(parentStatementName);
}
} if (ms == null) {
if(method.getAnnotation(Flush.class) != null){
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): " + statementName);
}
} else { //name为接口名+方法名 name = ms.getId();
//type为根据xml的sql信息解析后的select\insert\delete\update type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
在对SqlCommand进行封装时,会根据接口名+方法名来判断SqlSessionFactoryBuilder加载时是否存在对应的方法,如果存在设置name和type,name则为接口名+方法名,type则为根据xml的sql信息解析后的select\insert\delete\update标签。
· MethodSignature
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
//根据方法,接口解析returnType,里面主要包含了接口路径名、类加载器、返回类型等基础信息 Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
//设置returntype 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);
//是否存在mapKey注解 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);
}
在对MethodSignature进行封装时,主要是对方法的返回类型、返回参数等基础信息进行封装赋值。
最后全部封装完毕后,放入到methodCache缓存中。以便下一次相同接口路径名+方法名重复执行时,可以减少数据解析封装,提升性能。
三、总结
BlogMapper mapper = session.getMapper(BlogMapper.class);Blog blog = mapper.selectBlog(101);
以上述示例为例,在对mybatis进行加载时,会调用SqlSessionFatoryBuilder的build方法对mapper及其相关xml配置文件进行加载,mapper接口会被注册到mapperRegistry注册中心。需要使用mapper对数据库进行操作时,会通过sqlSession的getMapper方法,实际上mapper的底层也是调用了mapperRegistry.getMapper方法获取mapper接口,该注册中心的map的key为mapper的class对象,value为mapper接口的动态代理工厂。因此通过getMapper接口获取mapper时,也会对原来的mapper接口进行动态代理,生成一个mapper动态代理类进行返回。
当需要执行方法时,如selectBlog方法,会执行ProxyMapper中的增强方法,代理方法执行数据库操作前,会做一次缓存,缓存的数据结果的key为被执行的方法(即selectBolg方法对象),value为mapper解析后的基础信息,如:sql执行的标识id(即接口路径名+method)、增删改查的type、方法映射(返回集合、返回参数、返回类型)等基础信息,以便下次对同一个方法执行时,可以直接取缓存中的数据来提高性能。
最后通过executor开始执行数据库操作,下一篇分析。
问题一:接口Mapper内的方法能重载吗?
不能。在mapperProxy的动态代理方法中,会把sql执行的标识id等基础信息存放到缓存中来提高性能,而这里的标识id为接口路径名+执行方法,而这个标识id就是用作去xml中查找该执行的sql的唯一标识。如果出现了方法的重载,在使用标识id查询sql时就会出现多条需要执行的sql出现矛盾。
因此对于Mapper接口内的方法,是不允许重载的。