上篇文章我们介绍了SQL映射文件的解析,关于Mybatis使用的准备工作算是分析的差不多了,本篇文章我们就来分析一下Mybatis是如何实现select查询的。一般我们使用Mybatis查询可能通过如下两种方式:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
UserInfoMapper userInfoMapper = sqlSession.getMapper(UserInfoMapper.class);
System.out.println("query all user");
List<UserInfo> userInfoList = userInfoMapper.queryAllUser();
userInfoList.forEach(System.out::println);
System.out.println("---------------------");
System.out.println("queryUserInfoById id = 1");
System.out.println(userInfoMapper.queryUserInfoById(1L));
System.out.println("---------------------");
} finally {
sqlSession.close();
}
这种方式,使用看上去,好像在直接使用Mapper接口查询。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
String nameSpace = "com.zhuoli.service.mybatis.explore.mapper.UserInfoMapper" + ".";
System.out.println("query all user");
List<UserInfo> userInfos = sqlSession.selectList(nameSpace + "queryAllUser");
userInfos.forEach(System.out::println);
System.out.println("---------------------");
System.out.println("queryUserInfoById id = 1");
UserInfo userInfo = sqlSession.selectOne(nameSpace + "queryUserInfoById", 1L);
System.out.println(userInfo);
System.out.println("---------------------");
} finally {
sqlSession.close();
}
这种方式,我们直接使用了SqlSession中提供的接口查询。
上述这两种方式有什么区别,接下来我们就来详细介绍一下。第一种使用”Mapper接口 “查询的方式,本质上其实也是通过SqlSession中的接口(第二种)实现的。另外需要注意的是,无论使用哪种方式从查询,都不可避免地先创建SqlSession(update、insert、delete也是一样,也需要提前构建SqlSession),所以在介绍如何实现查询之前,我们先来看一下SqlSession的构建,由于篇幅原因,本篇文章介绍一下使用Mybatis使用Mapper接口的查询,后面的文章再介绍使用SqlSession的查询。
1. SqlSession构建
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession构建需要两步:
- 通过Mybatis配置文件文件流创建SqlSessionFactory
- 调用SqlSessionFactory的openSession方法构建SqlSession
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
关于SqlSessionFactory的构建,其实就是解析Mybatis配置文件获取Configuration对象,然后调用DefaultSqlSessionFactory的构造函数,返回一个DefaultSqlSessionFactory对象。
关于Mybatis配置文件解析,前两篇文章已经介绍过。接下来,我们重点来看一下DefaultSqlSessionFactory的openSession方法,是如何构建SqlSession的。
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
/**
* ExecutorType:枚举(SIMPLE, REUSE, BATCH),用于指定Executor的类型,默认类型为SIMPLE
* TransactionIsolationLevel:事务隔离级别,传参null,则表示使用数据库默认的事务隔离界别
* autoCommit:自动提交,true-自动提交,false-不自动提交
*/
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获取配置中的环境信息,环境中可以获取Mybatis配置文件中配置的数据源,事务等信息
final Environment environment = configuration.getEnvironment();
// 从环境中获取事务工厂TransactionFactory
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创建事务,配置事务属性(数据源、事务隔离级别、事务是否自动提交)
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建Executor,即执行器,执行器用于执行sql(事务 + 执行器类型)
final Executor executor = configuration.newExecutor(tx, execType);
// 创建DefaultSqlSession对象,构造函数中传参(Configuration + 执行器 + 是否自动提交)
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
上述openSessionFromDataSource方法主要包含如下几个核心步骤:
- 从configuration中获取Environment对象,Environment对象中主要包含了DataSource和TransactionFactory
- 通过TransactionFactory创建Transaction
- 通过Transaction和ExecutorType创建执行器Executor
- 创建DefaultSqlSession对象
1.1 获取Environment对象
这里需要使用到我们之前Mybatis配置文件解析得到的Environment,我们回顾一下Environment的配置,如下:
<!-- 环境配置 -->
<environments default="default">
<environment id="default">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${dataSource.driver}"/>
<property name="url" value="${dataSource.url}"/>
<property name="username" value="${dataSource.username}"/>
<property name="password" value="${dataSource.password}"/>
</dataSource>
</environment>
</environments>
可以看到,我们Environment中配置了<transactionManager>和<dataSource>,我们之前的文章介绍过我们会根据配置,得到TransactionFactory和DataSource并设置到Configuration中。
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
// 1. 获取默认的JDBC环境名称
if (environment == null) {
environment = context.getStringAttribute("default");
}
// 2. 遍历<environments>标签下的每一个<environment>标签
for (XNode child : context.getChildren()) {
// 2.1 获取<environment>标签id属性
String id = child.getStringAttribute("id");
// 2.2 判断当前的<environment>是不是默认的JDBC环境,如果是则解析该环境下的TransactionFactory和DataSource
if (isSpecifiedEnvironment(id)) {
// 解析当前环境下配置的TransactionFactory
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 解析当前环境下配置的DataSource
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
这里我们<transactionManager>类型配置的是”JDBC”,<dataSource>类型配置的是”POOLED”,所以最终会解析得到JdbcTransactionFactory和PooledDataSource。
1.2 创建Transaction
Transaction对象时候通过TransactionFactory调用newTransaction方法获取的,我们上面介绍过,TransactionFactory是在Environment中配置的,类型为JdbcTransactionFactory,所以我们来看一下JdbcTransactionFactory的newTransaction方法。
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit);
}
JdbcTransaction继承自Transaction接口,用于获取\关闭Sql连接(Connection),提交\回滚事务。
1.3 创建Executor执行器
Executor执行器通过调用Configuration类的newExecutor方法生成,该方法中会根据参数ExecutorType确定生成的Executor执行器类型,当参数ExecutorType为null时,默认生成SimpleExecutor。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
如果Mybatis配置文件<settings>中配置了”cacheEnabled”为true,则生成执行器时,会使用装饰器模式生成CachingExecutor执行器。
最后调用InterceptorChain的pluginAll方法,非常有意思。Mybatis配置文件中配置的所有的plugin,解析后会生成一个InterceptorChain(内部有一个Interceptor集合)。
<!-- 插件 -->
<plugins>
<!-- 可以通过plugin标签,添加拦截器,比如分页拦截器 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 分页合理化参数,默认文false;pageNum <= 0,查询第一页;pageNum > 总页数,查询最后一页-->
<property name="reasonable" value="true"/>
</plugin>
</plugins>
在pluginAll方法中,每个Interceptor,会通过代理模式生成一个代理对象。并且一个代理对象会作为下一个Interceptor处理生成代理对象场景下的委托类,所以InterceptorChain中所有的Interceptor处理结束,所有的Interceptor都会被织入代理链中,最原始的委托类对象其实就是我们生成的SimpleExecutor(如果应用了缓存就是CachingExecutor)。那么之后SimpleExecutor执行sql方法时,我们配置的Interceptor就可以生效了。我们之前使用的PageHelper就是一种Interceptor。
1.4 创建DefaultSqlSession对象
最后创建DefaultSqlSession,是通过调用DefaultSqlSession构造函数完成的。
new DefaultSqlSession(configuration, executor, autoCommit);
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
可以看到DefaultSqlSession构造函数中,我们传入了Configuration配置类和执行器Executor以及autoCommit(这里是false)。
可以看到DefaultSqlSession中定义了很多sql操作的方法。完成了SqlSession构建后,接下来来看一下是如何通过Mybatis完成查询操作的。
2. 使用”Mapper接口”查询
这里我们首先来看一下,使用Mybatis如何通过Mapper接口完成查询操作。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
UserInfoMapper userInfoMapper = sqlSession.getMapper(UserInfoMapper.class);
System.out.println("query all user");
List<UserInfo> userInfoList = userInfoMapper.queryAllUser();
userInfoList.forEach(System.out::println);
System.out.println("---------------------");
System.out.println("queryUserInfoById id = 1");
System.out.println(userInfoMapper.queryUserInfoById(1L));
System.out.println("---------------------");
} finally {
sqlSession.close();
}
这里我们通过SqlSession的getMapper接口,获取了一个UserInfoMapper实例。然后就使用该实例中定义的方法,完成了查询操作,到这里我们肯定有很多疑问,比如:
- 我们并没有对Mapper接口定义实现类,那么这个getMapper返回的实例实际类型是什么?
- Mybatis如何实现通过Mapper接口查询的
接下来我们就来看分析一下Mapper接口的查询。不难发现,核心逻辑肯定在getMapper中。
2.1 SqlSession.getMapper
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
DefaultSqlSession的getMapper方法比较简答,其实就是调用Configuration的getMapper方法。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
Configuration类中getMapper方法的实现是调用MapperRegistry的getMapper方法。
关于MapperRegistry是不是比较熟悉,好像在之前的文章中介绍过。对,其实就是在上篇文章Mybatis源码解读『SQL映射文件解析』中,SQL映射文件解析完成后,为nameSpace绑定Mapper接口,在bindMapperForNamespace方法中,会调用MapperRegistry的addMapper方法。
private void bindMapperForNamespace() {
// 获取映射文件的命名空间
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 根据命名空间解析 mapper 类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
}
if (boundType != null) {
// 检测当前 mapper 类是否被绑定过
if (!configuration.hasMapper(boundType)) {
configuration.addLoadedResource("namespace:" + namespace);
// 绑定 mapper 类
configuration.addMapper(boundType);
}
}
}
}
// Configuration
public <T> void addMapper(Class<T> type) {
// 通过 MapperRegistry 绑定 mapper 类
mapperRegistry.addMapper(type);
}
// MapperRegistry
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 {
/*
* 将 type 和 MapperProxyFactory 进行绑定,MapperProxyFactory 可为 mapper 接口生成代理类
*/
knownMappers.put(type, new MapperProxyFactory<T>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 解析注解中的信息
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
也就是说每个SQL映射XML文件解析结束,会调用一次Configuration.addMapper方法,进而向Configuration的成员mapperRegistry中添加一个type(等价于向MapperRegistry的knownMappers添加一个type -> MapperProxyFactory对)。
介绍完MapperRegistry中添加Mapper,我们来看一下getMapper方法,其实就是addMapper方法的反操作。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 获取Mapper接口type对应的MapperProxyFactory(MapperProxyFactory是在SQL映射文件解析结束后添加的)
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 调用MapperProxyFactory的newInstance方法生成Mapper接口实例对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
可以看到getMapper方法本质其实是调用MapperProxyFactory的newInstance方法,返回了一个Mapper接口的实例对象。
2.2 MapperProxyFactory
public class MapperProxyFactory<T> {
// Mapper接口Interface
private final Class<T> mapperInterface;
// Mapper接口内方法缓存
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
// 生成Mapper接口代理对象
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
到这,我们大致可以明白,为什么通过Mapper接口,不定义实现类,就可以使用接口中定义的方法,因为Mybatis帮我们定义的Mapper接口生成了代理对象,至于代理的实现细节,我们接下来再介绍。
2.2.1 JDK动态代理
上面Mapper接口的代理对象生成,使用了JDK动态代理。为了更方便理解,我们再回顾一下JDK动态代理,更详细的介绍可以去看之前的文章彻底搞懂动态代理。我们知道,JDK动态代理需要如下几步:
- 创建接口Subject及委托类RealSubject(实现了Subject接口)
- 创建一个实现InvocationHandler接口的类,实现invoke方法(定义代理逻辑)
- 通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)创建一个代理对象
- 使用代理对象调用方法
- 定义一个Subject接口和委托类RealSubject
public interface Subject {
String SayHello(String name);
String SayGoodBye();
}
public class RealSubject implements Subject {
@Override
public String SayHello(String name) {
return "hello " + name;
}
@Override
public String SayGoodBye() {
return "good bye!";
}
}
- 创建一个实现InvocationHandler接口的类
public class DynamicProxy implements InvocationHandler {
private Object object;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在代理方法执行前添加一些自己的操作
System.out.print("在调用之前,打印方法名称:");
System.out.println("Method:" + method);
//委托类方法执行
Object obj = method.invoke(object, args);
//在代理方法执行后添加一些自己的操作
System.out.println("方法执行结束");
return obj;
}
public DynamicProxy(Object object) {
this.object = object;
}
@SuppressWarnings("unchecked")
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(
object.getClass().getClassLoader(),
object.getClass().getInterfaces(),
this
);
}
}
实现的invoke方法,其实就是代理方法的执行逻辑。我们这invoke方法中对于委托类方法进行了“加强”,在委托类方法执行前打印方法名称,在委托类方法执行结束,打印一段文字”good bye!”。另外我们可以看到,委托类方法的执行是通过反射完成的,就是invoke方法中的method.invoke(object, args)。
这里我们再添加一个工厂类,来获取代理对象:
public class SubjectProxyFactory {
public static Subject newJdkProxy() {
//代理RealSubject
DynamicProxy dynamicProxy = new DynamicProxy(new RealSubject());
return dynamicProxy.getProxy();
}
}
- 测试代理功能
public static void main(String[] args) {
//获取代理对象
Subject subject = SubjectProxyFactory.newJdkProxy();
System.out.println("动态代理对象的类型:" + subject.getClass().getName());
String hello = subject.SayHello("zhuoli");
System.out.println(hello);
System.out.println("-------------------------");
String goodbye = subject.SayGoodBye();
System.out.println(goodbye);
}
输出结果:
动态代理对象的类型:com.sun.proxy.$Proxy0
在调用之前,打印方法名称:Method:public abstract java.lang.String com.zhuoli.service.thinking.java.proxy.jdkproxy.Subject.SayHello(java.lang.String)
方法执行结束
hello zhuoli
-------------------------
在调用之前,打印方法名称:Method:public abstract java.lang.String com.zhuoli.service.thinking.java.proxy.jdkproxy.Subject.SayGoodBye()
方法执行结束
good bye!
使用起来很简单,同时可以发现,我们上面Mapper接口使用JDK动态代理,有一点不符合常规JDK动态代理的要求,那就是——委托类没有实现类,只是个接口。那么在InvocationHandler中invoke方法,委托类接口的方法调用(Mapper接口中的定义的方法)如何调用?
其实仔细想想,也是可以走得通的,我们Mapper接口中定义的方法实现在什么地方?其实就是我们SQL映射文件中通过<select>|<update>|<insert>|<delete>的一些列sql语句。通过之前的文章,我们知道这些标签已经被我们解析为MappedStatement,并存入到Configuration中。只要这些MappedStatement能生效,那么其实Mapper接口中的方法就有”实现”了。
2.2.1 MapperProxy
接下来我们来看一下动态代理中,非常重要的一个模块InvocationHandler,这里其实是MapperProxy。
// 生成Mapper接口代理对象
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
由于MapperProxy类中,并没有一个成员变量是我们的Mapper接口实现类(实际上也没有实现类),那么代理逻辑是如何实现的,核心就在invoke方法中。
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 {
// 如果非Object类定义的方法(Mapper接口中定义的方法),则通过如下方式实现调用
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
如果调用的委托类的方法是Object类中的方法,则直接反射调用,不用做特殊处理。否则,则需要通过cachedInvoker方法获取MapperMethodInvoker,然后调用MapperMethodInvoker的invoke方法实现对Mapper接口中某个方法的调用。
// private final Map<Method, MapperMethodInvoker> methodCache;
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
// A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372
// It should be removed once the fix is backported to Java 8 or
// MyBatis drops Java 8 support. See gh-1929
// 尝试从缓存中获取方法对应的MapperMethodInvoker
MapperMethodInvoker invoker = methodCache.get(method);
// 如果缓存命中,直接返回
if (invoker != null) {
return invoker;
}
// 如果缓存未命中,则构建MapperMethodInvoker并添加到缓存中
return methodCache.computeIfAbsent(method, m -> {
// 如果方法是default方法
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
// 非default方法,生成PlainMethodInvoker
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
cachedInvoker方法,就跟它的方法名一样,就是从缓存中获取MapperMethodInvoker对象,这个缓存其实是MapperProxy类的成员变量methodCache,类型为Map(Method -> MapperMethodInvoker)。如果缓存未命中,会构建一个MapperMethodInvoker对象,并将Method -> MapperMethodInvoker对添加到缓存methodCache中。
关于构建MapperMethodInvoker对象的逻辑,分为两个分支,即方法是否为default方法,当方法不是default方法是,其实就会构建一个PlainMethodInvoker对象。
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
可以看到PlainMethodInvoker的核心其实是其成员变量MapperMethod,其invoke方法也是调用MapperMethod的invoke方法实现的。那么MapperMethod又是什么?
我们上面再生成PlainMethodInvoker时,通过调用MapperMethod的构造函数方法,生成了一个MapperMethod对象。
public class MapperMethod {
// Mapper接口的某个方法对应的sql信息,比如id,sql类型(INSERT, UPDATE, DELETE, SELECT等)
private final SqlCommand command;
// Mapper接口的某个方法签名,比如方法的返回值类型,以及参数解析器等
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);
}
}
所以MapperMethod包含了两部分信息:
- Mapper接口方法,底层sql的基本信息(SqlCommand)
- Mapper接口方法,方法签名(MethodSignature)
接下来分别看一下这两个类,首先是SqlCommand,它包含了Mapper接口底层方法的基础信息,即id + SqlCommandType,我们来看一下其构造函数。
public static class SqlCommand {
//name为MappedStatement的id,也就是namespace.methodName,
//也是我们之前SQL映射文件解析过程中获取的
// 比如com.zhuoli.service.mybatis.explore.mapper.UserInfoMapper.queryAllUser
private final String name;
//SQL的类型,如INSERT, UPDATE, DELETE, SELECT等
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
//拼接Mapper接口名和方法名,nameSpace(等价于mapperInterface) + "." + methodName
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
//检测configuration是否有key为com.zhuoli.service.mybatis.explore.mapper.UserInfoMapper.queryAllUser的MappedStatement
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);
}
}
// 检测当前方法是否有对应的MappedStatement
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 {
// 设置name和type 变量
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
}
逻辑也比较简单,其实就是根据接口和方法信息,从Configuration中获取方法对应的MappedStatement(非常重要,因为MappedStatement其实等价于我们Mapper接口的“实现类”),通过MappedStatement初始化SqlCommand。从这个方法,我们也可以得出一个结论,SQL映射文件中nameSpace必须为Mapper接口的interfaceName。
然后来看一下方法签名MethodSignature,它包含了Mapper接口某个方法的返回值类型,返回值信息,以及参数解析器等,来看一下其构造函数。
public static class MethodSignature {
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final boolean returnsOptional;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final 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();
}
// 检测返回值类型是否是 void、集合或数组、Cursor、Optional、Map等
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);
// 解析@MapKey注解,获取注解内容
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
// 获取RowBounds参数在参数列表中的位置,如果参数列表中包含多个RowBounds参数,会抛出异常
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
// 获取ResultHandler参数在参数列表中的位置
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
// 获取参数解析器
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
}
了解了SqlCommand和MethodSignature后,我们对MapperMethod所有成员的介绍就结束了。接下来介绍MapperMethod的核心逻辑,如果通过MapperMethod实现代理逻辑的。我们上面介绍过MapperProxy(InvocationHandler)中,重写的invoke方法其实是调用PlainMethodInvoker的invoke方法实现的,而PlainMethodInvoker的invoke方法其实是调用的MapperMethod的execute方法,如下:
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
所以接下来重点来看一下MapperMethod的execute方法。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 获取方法对应的SQL类型(INSERT, UPDATE, DELETE, SELECT等)
switch (command.getType()) {
case INSERT: {
// 使用参数解析器paramNameResolver解析方法参数
Object param = method.convertArgsToSqlCommandParam(args);
// 执行插入操作,rowCountResult方法用于处理返回值
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()) {
// 返回void的查询操作
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);
// 返回Optional的查询操作
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;
}
在execute方法中,最后都会通过调用SqlSession的方法,而SqlSession中的方法其实最终就是我们在SQL映射文件中定义的sql(MappedStatement)起作用的。所以为什么我们不用定义实现类,就能通过Mapper接口实现查询?
- Mapper接口中的任何方法,在SQL映射文件中都有定义其“实现”
- Mybatis帮我们的Mapper接口生成了代理对象,而代理逻辑其实就是执行SQL映射文件中定义的SQL
关于SqlSession的方法,比如上述sqlSession.selectOne方法中,MappedStatement是如何生效的,我们接下来再详细介绍。在本篇文章最开始介绍的使用Mybatis执行查询操作的第二种方式,其实就是调用SqlSession中的方法。
参考链接:
1. Mybatis源码