Mybatis的架构可以大致分为以下三层: 1、API接口层:对外提供增删改查接口,使用传统的MyBatis提供的API或者使用Mapper代理的方式; 2、数据处理层:完成参数映射、SQL解析与执行、结果处理等; 3、框架支撑层:支持基于XML或者注解定义SQL、事务管理、连接池管理、缓存管理等;
Mybatis作为一个ORM框架,实际上底层还是通过JDBC来操作数据库,完成增删改查的。
想要详细了解底层的原理,首先需要知道Mybatis的九大核心的概念:
组件作用SqlSession作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成数据库增删改查功能ExecutorMyBatis执行器,是MyBatis调度的核心,负责SQL语句的生成和查询缓存的维护StatementHandler封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参数、将Statement结果集转换成List集合ParameterHandler负责对用户传递的参数转换成JDBC Statement所需要的参数ResultSetHandler负责将JDBC返回的ResultSet结果集对象转换成List类型的集合TypeHandler负责java数据类型和jdbc数据类型之间的映射和转换MappedStatementMappedStatement维护了一条<select | update | delete | insert>节点的封装SqlSource负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回BoundSql表示动态生成的SQL语句以及相应的参数信息Mybatis完成一次查询或者其他操作的大致流程如下图所示:
简单描述Mybatis执行过程如下:
加载核心配置文件,得到数据库连接信息,创建核心配置对象保存生成的连接池等信息;加载映射配置文件,将namespace和SQL语句标签的id,组合作为id,最终封装成为一个MappedStatement对象,存放到内存中;调用OpenSession方法获得会话,然后调用相应的增删改查方法,方法参数为namespace+id;接收到处理请求,由Executor的默认实现类BaseExecutor来处理,首先封装BoundSql对象,并根据分页等信息生成缓存的Key;判断缓存中是否有,如果有则返回,如果没有进行数据库的查询;进入SimpleExecutor中进行数据库的查询,调用StatementHandler来处理,而它先通过ParameterHandler进行参数设置,通过TypeHandler将java类型转换成为数据库类型;最终调用JDBC的操作,完成查询,得到ResultSet;然后通过ResultSetHandler处理器将结果进行处理,最终返回指定的结果类型;首先,通过传统模式调用mybatis的代码如下:
@Test public void test() throws IOException { //1.加载核心配置文件为字节流 InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); //2.解析配置文件,并创建了sqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //3.开启一个sqlSession会话,此处可以传参boolean类型,参数表示事务是否自动提交,目前是需要手动提交 SqlSession sqlSession = sqlSessionFactory.openSession(); //4.sqlSession调用方法,传入namespace+id List<User> users = sqlSession.selectList("user.findAll"); for (User user : users) { System.out.println(user); } // 最终关闭会话 sqlSession.close(); }这种传统方式的缺点也很明显,namespace+id是硬编码,如果很多地方使用时,发生变化,则需要修改所有的地方。 针对这个问题,Mybatis还支持动态代理的方式,就是通过getMapper的方式,无需指定:
@Test public void test() throws IOException { InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList= mapper.findAllUser(); for (User user : userList) { System.out.println(user); } }这种代理方式是如何实现的呢? 下面跟随源码进行查看,通过一步一步的进入,发现从SqlSession的实现类DefaultSqlSession一直到MapperProxyFactory最终找到具体的实现方式:
protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy); } //MapperProxyFactory类中的newInstance方法 public T newInstance(SqlSession sqlSession) { // 创建了JDK动态代理的invocationHandler接口的实现类mapperProxy final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache); // 调用了重载方法 return newInstance(mapperProxy); }到此处我们可以确定Mybatis是通过JDK动态代理的方式进行代理对象的产生。
同时,这里也可以解释为什么Mybatis规定Mapper的namespace要和mapper的全限定名一致,为什么SQL语句的id要与方法名一致。 因为,动态代理中只能获取到要执行哪个类下的哪个方法,无法获取xml里面的信息,所以让两者保持一致,可以直接定位到具体要执行的是哪个SQL。
通过JDK动态代理的参数可知,最终是由MapperProxy来进行的代理增强,进入其中查看invoke发现:
@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 if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }所以最终通过method参数获取到要执行的是哪个SQL,最终交给mapperMethod完成执行,进行上面讲的步骤,最终得到结果。
插件对mybatis来说就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的。
Mybatis对持久层的操作就是借助于四大核心对象,Executor、StatementHandler、ParameterHandler、ResultSetHandler,在上面分析源码时,会发现这四大核心对象都有拦截判断,我们就是通过拦截这四大对象来进行插件的开发。
进入org.apache.ibatis.session.Configuration核心配置类中,可以看到针对Executor的拦截器判断:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { // 获得执行器类型 executorType = executorType == null ? defaultExecutorType : executorType; // 使用默认 executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 使用 ExecutorType.SIMPLE // 创建对应实现的 Executor 对象 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); } // 如果开启缓存,创建 CachingExecutor 对象,进行包装 if (cacheEnabled) { executor = new CachingExecutor(executor); } // 此处是加载所有的针对Executor的插件 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }针对ParameterHandler的插件加载:
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { // 创建 ParameterHandler 对象 ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); // 此处是加载所有的针对ParameterHandler的插件 parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; }针对StatementHandler插件加载:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 创建 RoutingStatementHandler 对象 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); // 此处是加载所有的针对StatementHandler的插件 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }针对ResultSetHandler插件加载:
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { // 创建 DefaultResultSetHandler 对象 ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); // 此处是加载所有的针对ResultSetHandler的插件 resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; }通过上面的源码我们会发现,在这四大对象创建的时候会进行插件的加载,而且加载的方法是相同的,都是interceptorChain.pluginAll(),所以我们进入该方法,查看具体的加载逻辑:
public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; }在深入进入会发现如下逻辑:
public static Object wrap(Object target, Interceptor interceptor) { // 获得拦截的方法映射 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); // 获得目标类的类型 Class<?> type = target.getClass(); // 获得目标类的接口集合 Class<?>[] interfaces = getAllInterfaces(type, signatureMap); // 若有接口,则创建目标对象的 JDK Proxy 对象 if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); // 因为 Plugin 实现了 InvocationHandler 接口,所以可以作为 JDK 动态代理的调用处理器 } // 如果没有,则返回原始的目标对象 return target; }通过上面的源码,我们发现最终拦截器或者插件的实现原理就是通过JDK动态代理来实现的。
interceptorChain保存了所有的拦截器(interceptors),在Mybatis初始化的时候创建。 当创建四大对象时,调用拦截器链中的拦截器依次的对目标进行拦截或增强。 interceptor.plugin(target)中的target就可以理解为要被拦截的四大对象。返回的target就是被重重代理后的对象。
这时候如果我们想要自定义一个插件,只需要实现Mybatis的Interceptor接口,重写其中的方法就行了,示例如下:
@Intercepts({ @Signature(type= StatementHandler.class, method = "prepare", args = {Connection.class,Integer.class}) }) public class MyPlugin implements Interceptor { // 只要被拦截的目标对象的目标方法被执行时,每次都会执行intercept方法 @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("对方法进行了增强...."); // 增强逻辑执行完毕后,原方法执行 return invocation.proceed(); } // 主要为了把当前的拦截器生成代理存到拦截器链中 @Override public Object plugin(Object target) { Object wrap = Plugin.wrap(target, this); return wrap; } // 获取配置文件的参数 @Override public void setProperties(Properties properties) { System.out.println("获取到的配置文件的参数是:"+properties); } }通过@Intercepts和@Signature注解,可以指定自定义的拦截器(插件)是在哪个类的哪个方法执行时进行拦截,可以定义多个@Signature,同时对多个方法进行拦截。
当然,插件开发完毕后,也不是直接就会生效,还需要将其配置到Mybatis的核心配置文件中:
<plugins> <plugin interceptor="com.jfl.test.plugin.MyPlugin"></plugin> </plugins>配置之后,在Mybatis启动时就会将这个插件放入interceptorChain中,最终生成代理对象完成增强的方法的执行。
