Mybatis源码分析

一、Mybatis源码调用过程

对于下面一段常用的代码,分析其调用过程

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
SqlSession sqlSession = factory.openSession(true);
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
System.out.println(studentMapper.getStudentById(1));
sqlSession.close();

在这里插入图片描述

  • 1、根据mybatis-config.xml构建SqlSessionFactory接口实例,默认是DefaultSqlSessionFactory实现类
  • 2、调用SqlSessionFactory.openSession方法创建SqlSession接口实例,默认是DefaultSqlSession实现类,该实例里面持有Executor对象
  • 3、调用SqlSession.getMapper方法,获取用户定义的Mapper接口代理类,通过操作代理类的用户定义方法,来操作底层数据库,Mapper代理类使用JDK动态代理生成,其中的MapperProxy作为InvocationHandler角色出现
  • 4、代理类中MapperProxy根据不同的SQL调用sqlSession不同的方法,比如selectOne,insert,update等等,也就是形如sqlSession.selectOne(statementId, params);
  • 5、调用执行器Executor的相应方法,比如query、update等,Executor中处理一级缓存和二级缓存相关的逻辑,如果从缓存中查询到了数据,就不用再查数据库,也就是不用再执行下面的步骤
  • 6、执行器创建StatementHandler实例,通过StatementHandler创建真正的JDBC标准的Statement对象
  • 7、执行StatementHandler的query或update方法,底层执行的是JDBC标准的方法
  • 8、使用ResultSetHandler处理结果集并返回
  • 9、关闭sqlSession,归还connection连接

二、Mybatis一级缓存和二级缓存

缓存原理

无论是一级缓存还是二级缓存,底层都是map数据结构的支持,根据key来获取value,这个key在mybatis中是个复合值,用CacheKey对象表示,里面包含statementId、分页大小、分页offset、原生sql、查询参数五个条件,当且仅当这五个条件都命中时,才会匹配到对应的key。

注意:这里的statementId形如mapper.StudentMapper.getStudentById

一级缓存

mybatis默认开启的是一级缓存,一级缓存范围默认是SESSION,也就是说一级缓存只在同一个SqlSession中生效,要想关闭一级缓存,在mybatis-config.xml中设置localCacheScope为STATEMENT即可

  • 关闭一级缓存

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <settings>
            <setting name="localCacheScope" value="STATEMENT"/>
        </settings>
    </configuration>
    
  • 开启一级缓存时的请求流程

在这里插入图片描述

一级缓存是sqlSession级别的,因为一级缓存保存在baseExecutor中,而baseExecutor生命周期同sqlSession

二级缓存

mybatis二级缓存的全局配置默认也是开启的,但是还需要在mapper映射文件中分别配置开启缓存才会最终生效,当然也可以在mybatis-config.xml文件中关闭二级缓存全局配置

  • 关闭二级缓存

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <settings>
            <setting name="cacheEnabled" value="false"/>
        </settings>
    </configuration>
    
  • 开启二级缓存时的请求流程

在这里插入图片描述

二级缓存是全局的,因为每个statementId对应的MappedStatement是单例,每个MappedStatement对象都持有一个Cache,相同namespace下的MappedStatement共享同一个Cache,CachingExecutor获取到MappedStatement对象的Cache后,根据cacheKey获取缓存中的值。

三、Mybatis拦截器

Mybatis支持对Executor、StatementHandler、PameterHandler和ResultSetHandler 接口进行拦截,也就是说会对这4种对象进行代理,其核心原理是暴露扩展接口(plugin方法)给用户,对上述4种对象进行动态代理,因此,plugin方法返回的对象必须是代理对象或者原始对象,可用使用Plugin.wrap方法生成该代理对象,代理对象中会调用拦截器的intercept方法,可以通过@Intercepts注解来告知哪些方法需要被拦截。当然,你也可以使用自己的方式去创建代理对象,并通过自己的方式去判断方法是否需要被拦截。

比如下面这个拦截器是我自定义的,目的是对SQL语句SELECT id, name, age FROM student WHERE id = ?进行分表,分表策略为id % 2

package interceptor;

import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;

import java.sql.Connection;
import java.util.Properties;

/**
 * mybatis分表拦截器
 *
 * @author debo
 */
@Intercepts({
        @Signature(
                type = StatementHandler.class,
                method = "prepare",
                args = {Connection.class, Integer.class}
        )
})
public class TableShardInterceptor implements Interceptor {

    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (invocation.getTarget() instanceof RoutingStatementHandler) {
            try {
                RoutingStatementHandler statementHandler = (RoutingStatementHandler) invocation.getTarget();
                BoundSql boundSql = statementHandler.getBoundSql();
                String originSql = boundSql.getSql();
                Object parameter = statementHandler.getParameterHandler().getParameterObject();
                // 计算分表后缀
                int tIdx = Integer.parseInt(parameter.toString()) % 2;
                String newSql = originSql.replace("student", "student_" + tIdx);
                // MetaObject是mybatis里面提供的一个工具类,类似反射的效果
                MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
                // 把新语句设置回去
                metaStatementHandler.setValue("delegate.boundSql.sql", newSql);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // 当目标类是RoutingStatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数
        return (target instanceof RoutingStatementHandler) ? Plugin.wrap(target, this) : target;
    }

    @Override
    public void setProperties(Properties properties) {
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值