抛出问题
1、Mybatis 开发为何通过接口就可以执行sql?
首先、有一点我们很清楚,在Java中 接口是无法被实例化使用的。
既然我们没有提供实现类,那么我们可以很自然的猜测是Mybatis帮我们生成了实现类。
直接上源码看下,Mapper接口动态代理的处理器类
1、MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; //sql执行的顶层接口 private final SqlSession sqlSession; //mapper接口 private final Class<T> mapperInterface; //mapperMethod缓存 private final Map<Method, MapperMethod> methodCache; //动态代理的构造函数、同时代理多个对象,持有多个真实对象 public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //判断如果是调用Object的方法,如:toString()、equals()等执行反射执行。 if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } //缓存mapperMethod final MapperMethod mapperMethod = cachedMapperMethod(method); //通过mapperMethod,执行sql return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); //当前mapperMethod被缓存,则设置缓存。 if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; }}123456789101112131415161718192021222324252627282930313233343536373839404142
观察MapperProxy,我们可以看出这是一个典型的jdk动态代理实现,与上篇示例不同的是,MapperProxy是mapper接口的处理器类,内部持有多个真实对象,也就是说它基于多个对象进行了增强。查看invoke方法,可以认为它进行对增强操作就是mapperMethod缓存和sql执行。
它是被谁调用,在什么时候使用呢?
2、MapperProxyFactory
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>(); //构造函数接收一个mapper接口 public MapperProxyFactory(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } public Class<T> getMapperInterface() { return mapperInterface; } public Map<Method, MapperMethod> getMethodCache() { return methodCache; } //通过Proxy#newProxyInstance创建代理对象 @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { //最终返回一个动态代理生成的代理对象 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { //创建mapperProxy对象,这个就是我们的真实对象,也就是mapper接口的实现类 final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }}123456789101112131415161718192021222324252627282930
MapperProxyFactory类,见名知意,它就是MapperProxy的工厂类,负责生成Mapper接口的动态代理类,通过代理类执行sql。可以这样理解: 执行过程中生成的MapperProxy实例就是具体Mapper接口的实现类。因此,我们才可以通过接口直接执行sql.
2、SqlSessionTemplate 是如何实现线程安全的?
首先、mybatis原生提供的DefaultSqlSession是线程不安全的。
在Mybatis-Spring整合包中,为我们提供了线程安全的SqlSessionTemplate.
查看SqlSessionTemplate源码类注释可以看到,明确说明,它是被Spring管理的、线程安全的、单例类,可以被多个DAO共享。这是如何实现的呢?
这个问题在MapperFactoryBean源码解析查看,这次我们看下它关于动态代理的实现。
在SqlSessionTemplate中通过内部类,SqlSessionInterceptor实现InvocationHandler接口作为一个动态代理处理器类。
private class SqlSessionInterceptor implements InvocationHandler { //没有内部对象,但是可以调用外部SqlSessionTemplate的。成员变量 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //进行了spring事物相关的增强。 SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } catch (Throwable t) { //异常处理、释放资源。 Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }12345678910111213141516171819202122232425262728293031323334353637
我们知道它只是动态代理的处理器类,必然有调用者调用传入代理对象接口才能生效。
查看SqlSessionInterceptor的调用者:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; //Proxy#newProxyInstance创建类代理对象,对SqlSessionTemplate进行增强。 this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); }123456789101112131415
当SqlSessionTemplate相关方法被执行时,会被处理器进行增强处理,在这里即是对Spring事物相关的增强。
三、本篇总结
学习了在Mybatis中,两处关于动态代理的实现收获:
1、动态代理的使用:处理器类可以持有多个真实对象,进行增强。
2、动态代理的处理器类可以通过内部类方式实现。
3、为使用动态代理提供很好的范例,除了单一的直接创建对象,既可以通过工厂方式生成、也可以通过构造器使用。