前面说了Mybatis的核心对象,那Mybatis的核心对象如何与SqlSession建立联系呢?
Mybatis的核心对象与SqlSession之间的联系
首先我们知道,用Mybatis操作数据库获取结果有两只方式
// 指定配置文件
String resource = "org/mybatis/example/mybatis-config.xml";
// 记在配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//方式一
try (SqlSession session = sqlSessionFactory.openSession()) {
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}
//方式二
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
}
由上面的代码我们都知道,第二种方式更加优雅和美观,原因是可以更好更清楚的知道我们要做什么操作,干什么。而不是像方法一样写一个字符串(该字符串是dao的全限定类名 + "." + xml中id)。
方法二是Mybatis现在的版本我们最常用的方式。单其实,方法二的底层就是用的第一种进行操作的。
接下来我们一步步追一下方式一源码:
在DefaultSqlSession中,我们看到了executor.query()方法,再往下:
在SimpleExecutor中,先获取StatementHandler,然后在执行update操作:
终于看到熟悉的代码了,statement.execute()不就是JDBC中的操作吗?
现在终于把之前的都串起来,首先SqlSession内部封装了Executor,而Executor咋操作增删改查的时候,调用了StatementHandler,StatementHandler内部封装了Statement,直接调用操作,完成数据操作。
那么问题来呢?
我们一般常用的是方法二,那方法二又是怎么封装转换的呢?
我们观察方法二发现,session.getMapper(BlogMapper.class)返回了一个BlogMapper,我们都知道,BlogMapper是一个借口,是没有办法进行实例化的。那么只能说明一个,BlogMapper是BlogMapper接口实现类的对象。
那么问题又来了?
BlogMapper的接口实现类在哪呢?按照我们前面的7步开发步骤,没有BlogMapper实现类这一步啊?
其实啊,MyBatis在这里用了动态字节码技术,即类在JVM运行时创建,当JVM运行结束后,就消失不见了。
好了,我们知道是用什么技术创建的。那么问题又又又来了?
- 如何创建BlogMapper接口的实现类呢?
- BlogMapper接口的实现类如何实现的呢?
我们先来看问题二,实现接口还不简单,就是创建一个类,来实现BlogMapper,那实现类的具体功能怎么写呢?根据前面的内容我们知道,Mybatis就是通过SqlSession来进行各种select、update、delete操作的,那实现类的内容就是SqlSession.select()。
好了,问题二我们分析好了,那问题一呢?我们的项目并没有创建实现类,但是Mybatis怎么就已经做出了操作呢?
要看这个问题,我们要了解一下动态代理的几种应用场景:
- 为原始目标增强,就是spring中常用的功能增强
- 远程代理,一些rpc中网络通信或者数据传输
- 实现接口类,虽然看不见,但运行的时候可以体现出来
看到这里,我们已经知道用什么了吧。对,就是动态代理,通过动态代理来实现接口的实现类,从而实现对应的功能。
那下面,我们也来写一个来模拟一下吧:
String resource = "org/mybatis/example/mybatis-config.xml";
// 记在配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
// BlogMapper mapper = session.getMapper(BlogMapper.class);
// 通过动态代理模拟上面的一句代码,进而获取到BlogMapper
Class[] interfalces = new Class []{BlogDAO. class};
BlogDAO mapper = (BlogDAO) Proxy.newProxyInstance(this.class.getClassLoader() , interfaces
, new MyMapperProxy(sqLSession, BlogDAO.class));
Blog blog = mapper.selectBlog(101);
}
定义一个proxy来实现InvocationHandler
public class MyMapperProxy impLements InvocationHandler {
private SqLSession sqlSession;
private Class daoClass;
public MyMapperProxy (SqLSession sqlSession, Class daoClass) {
this.sqLSession = sqLSession;
this.daoCLass = daoCLass;
}
@Override
public Object invoke (Object proxy, Method method, Object[] args) throws Throwable {
return sqLSession.select/update/delete(daoClass.getName() + "." + method.getName() ) ;
}
}
看到这里,我想大家对Mybatis代理的核心思想都已经比较清楚了。
Mybatis完成代理创建的核心类型
Mybatis完成代理创建的核心类型其实就是MapperProxy和MapperProxyFactory。
MapperProxy就相当于上面的MyMapperProxy,主要就是实现了InvocationHandler接口,并重写invoke的方法,在里面用Sqlsession进行了insert|update|delete|select等操作。
MapperProxyFactory在底层就是做了刚才增强的操作:Proxy.newProxyInstance()。
接下来,我们来追一下源码来看看:
public class MapperProxyFactory<T> {
// 需要代理的接口
private final Class<T> mapperInterface;
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);
}
// 传入了mapperProxy,即实现了InvocationHandler的类
public T newInstance(SqlSession sqlSession) {
// 创建mapperProxy 参数:sqlSession,需要代理的接口,..
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
我们发现,MapperProxyFactory确实需要一个mapperInterface的参数,而且这个参数就是需要代理的接口,即各种xxxMapper。而且构造方法中Proxy.newProxyInstance()不就和我们分析的一致吗?
然后在看参数:mapperProxy,这个就是实现了InvocationHandler的那个类。
然后再来看mapperProxy,这个肯定比我们写的那个要难得多:
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -4724728412955527868L;
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
private static final Constructor<Lookup> lookupConstructor;
private static final Method privateLookupInMethod;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
static {
Method privateLookupIn;
try {
privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
} catch (NoSuchMethodException e) {
privateLookupIn = null;
}
privateLookupInMethod = privateLookupIn;
Constructor<Lookup> lookup = null;
if (privateLookupInMethod == null) {
// JDK 1.8
try {
lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
lookup.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
"There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
e);
} catch (Exception e) {
lookup = null;
}
}
lookupConstructor = lookup;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return methodCache.computeIfAbsent(method, m -> {
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 {
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
private MethodHandle getMethodHandleJava9(Method method)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
declaringClass);
}
private MethodHandle getMethodHandleJava8(Method method)
throws IllegalAccessException, InstantiationException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
}
interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
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);
}
}
private static class DefaultMethodInvoker implements MapperMethodInvoker {
private final MethodHandle methodHandle;
public DefaultMethodInvoker(MethodHandle methodHandle) {
super();
this.methodHandle = methodHandle;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return methodHandle.bindTo(proxy).invokeWithArguments(args);
}
}
}
首先,我们可以很清楚的看到两次参数:sqlSession,mapperInterface,其次该方法实现了InvocationHandler。这个和我们分析的是一致的。然后我们来读一下代码:
- 静态代码代块,给一些属性变量进行赋值操作
- 然后invoke操作,首先判断是否是Object自带的一些方法,如果不是,则执行invoke
- 先往methodCache中存入值,存的过程中,判断当前方法是否是公共非抽象实例方法
- 然后我们会调用PlainMethodInvoker的构造方法,MapperMethod里面有两个特别的属性,我们需要注意下:MapperMethod:1、获取namespace + id 2、判断是什么操作, insert|update|delete|select等操作 MethodSignature:封装了返回值类型,分页,参数等
- PlainMethodInvoker的invoke方法,执行sql
- 判断是否查询进行分别操作
好了,到这里,前面的内容基本都讲清楚。
配置文件读取和工厂的创建
接下来我们就来看看前半部分:
String resource = "org/mybatis/example/mybatis-config.xml";
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建sqlSessionFactory工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession()
那这一部分说了些什么呢?
- 读取配置文件,之后通过解析config.xml成Configuration对象和解析xxMapper.xml成MappedStatement
通过xPath解析成对应的对象。
- 通过解析的Configuration对象创建DefaultSqlSessionFactory
- 通过DefaultSqlSessionFactory#openSessionFromDataSource()创建SqlSession对象
至此,Mybatis的前半部分基本清晰明了。
总结
最后我们来总结一下整个Mybatis的主要流程:
1、读取配置文件
2、解析config.xml --> Configuration(通过xPathParser解析成XNode实现的)
bulid() -> parseConfiguration(parser.evalNode("/configuration"))
parseConfiguration -> mapperElement(root.evalNode("mappers"))
|- XMLMapperBuilder mapperParser = new XMLMapperBuilder();
|- mapperParser.parse()
|-configurationElement(parser.evalNode("/mapper"))
|- statementParser.parseStatementNode()
|- MappedStatement statement = statementBuilder.build()
|-configuration.addMappedStatement(statement)
3、通过DefaultSqlSessionFactory#openSessionFromDataSource()创建SqlSession对象
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
4、slSession.getMapper()通过动态代理实现xxDAO接口的实现
MapperProxyFactory通过jdk创建代理
|- InvocationHandler
|- SqlCommoand
|- type:操作类型inset|update|datele|select
|- name:namespace.id
|- MethodSignature
|- 方法的参数、返回值、分页等信息
|- 通过类型不同执行sqlSession.update|delete|select
|-Executor(SimpleExecutor ReuserExecutor BatchExecutor)
|-StatementHandler
|- ParameterHadler ResultSetHandler
TypeHandler
5、获取结果集
小弟也是第一次分析,如有纰漏,欢迎各位大佬指出。
评论区