Mybatis深入

Mybatis的代理详解

Java中动态代理主要是JDK动态代理(接口),CGLib动态代理(类继承 )

JDK动态代理:

  • 1.首先存在接口(studentMapper)
  • 2.必须存在该接口的实现类(sqlSession.selectOne)
  • 3. 实现invocationHandler辅助类
  • 4.通过Proxy类来产生代理对象 

mybatis是否使用了JDK动态代理相关的接口和类?产生的代理对象如何组织

mybatis中代理对象的获取是通过如下方法:

sqlSession.getMapper(StudentMapper.class);

mapper接口是如何添加进去?

通过Java代码形式来进行数据源、映射文件的配置形式如下:

Configuration configuration = new Configuration();
        PooledDataSourceFactory pooledDataSourceFactory = new PooledDataSourceFactory();
        DataSource dataSource = pooledDataSourceFactory.getDataSource();
        JdbcTransactionFactory jdbcTransactionFactory = new JdbcTransactionFactory();
        //设置环境
        org.apache.ibatis.mapping.Environment environment = new org.apache.ibatis.mapping.Environment("test", jdbcTransactionFactory, dataSource);//名称、会话工厂、数据源
        configuration.setEnvironment(environment);
        new SqlSessionFactoryBuilder().build(configuration);

        //设置Mapper接口
        configuration.addMapper(StudentMapper.class);
        new SqlSessionFactoryBuilder().build(configuration);

重点在于接口的添加形式

configuration.addMapper(StudentMapper.class);

在Configuration类中

 public <T> void addMapper(Class<T> type) {
        this.mapperRegistry.addMapper(type);
    }

mapper在Configuration类中没有做任何事,添加到了mapperRegistry类中

public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {//只允许添加接口
            if (this.hasMapper(type)) {//不能重复添加
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }

            boolean loadCompleted = false;

            try {
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

    }

执行的configuration.addMapper(StudentMapper.class),实际上最终被放入到HashMap中,其命名为knownMappers,他是mapperRegistry类中的私有属性,是一个hashmap对象,key是接口class对象,value是MapperProxyFactory的对象实例

通过getMapper来获取代理对象

DefaultSqlSession类:

public <T> T getMapper(Class<T> type) {
        return this.configuration.getMapper(type, this);
    }

configuration类中存放所有的mybatis全局配置文件信息,从参数上可以知道是class类型

configuration类:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

configuration类中getMapper中调用了mapperRegistry,mapperRegistry中存放一个hashmap

mapperRegistry类:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

 调用mapperRegistry类中的getMapper方法,该方法回到hashmap中通过类名获取对应的value值,获取到的值是MapperProxyFactory对象,然后调用newInstance方法,该方法创建了一个对象

public class MapperProxyFactory<T> {//映射代理工厂
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }

    public Map<Method, MapperMethod> getMethodCache() {
        return this.methodCache;
    }

    protected T newInstance(MapperProxy<T> mapperProxy) {
    //使用的是JDK自带的动态代理对象生成代理类对象
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
public class MapperProxy<T> implements InvocationHandler, Serializable {
//实现了InvocationHandler接口

    //代理之后,所有的mapper的方法调用,都会调用invoke方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                return method.invoke(this, args);//该位置就使用了原始的调用方式
            } catch (Throwable var5) {
                throw ExceptionUtil.unwrapThrowable(var5);
            }
        } else {
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            //执行CURD操作
            return mapperMethod.execute(this.sqlSession, args);
        }
    }

}

mybatis中getMapper的代理对象的获取,使用的是JDK的动态代理,在mapperRegistry类中的HashMap中存放了所有的mapper接口,key是接口的类名信息,value是MapperProxyFactory类型实例,getMapper操作会使当前对象调用newInstance操作,该操作会调用Proxy.newProxyInstance,核心在于调用了InvocationHandler接口,实现了invoke方法,除了调用原始的接口,还对调用进行了增强,进行了方法缓存,且最终会执行增删改查操作

总结Mapper接口方法执行的几个点:

1.Mapper接口的初始在SqlSessionFactory注册

2.Mapper接口注册在mapperRegistry类的HashMap中,key是接口的类名信息,value是创建代理工厂

3.Mapper注册之后可以通过SqlSession获取对象

4.SqlSession.getMapper运用了JDK的动态代理,产生了目标Mapper接口的代理对象,动态代理的代理类是MapperProxy,实现了增删改查调用

mybatis的缓存机制

缓存介绍

mybatis提供了查询缓存,用于减轻数据库的压力,提高数据库的性能

mybatis提供了一级缓存和二级缓存

一级缓存是sqlsession级别缓存,在操作数据库时都需要构造sqlsession对象,在对象中有一个数据结构hashmap用于存储缓存数据,不同的sqlsession之间的缓存数据区域是互不影响的

一级缓存的作用域是在同一个sqlsession中,在同一个sqlsession执行相同的SQL,第一次执行完毕后就将数据写入到缓存中,第二次执行SQL就直接从缓存中获取数据,从而提高了效率

当sqlsession结束后,该sqlsession中的以及缓存就不存在,mybatis默认开启一级缓存

二级缓存是mapper级别的缓存,多个sqlsession去操作同一个mapper的SQL语句,多个sqlsession操作都会存在于二级缓存中,多个sqlsession共用二级缓存,二级缓存是跨sqlsession的

二级缓存是多个sqlsession共享的,作用域是mapper下的同一个nameSpace,不同的sqlsession两次执行相同的nameSpace下的SQL最终能获取相同的SQL结果

mybatis不是默认开启一级缓存,需要在全局配置文件中配合开启二级缓存

一级缓存

一级缓存介绍

每个sqlsession中持有一个执行器Executor,每个执行器中有一个local cache,当用户发起查询时,mybatis根据当前执行的语句生成mapperStatement,,在local cache中进行查询,如果缓存命中,直接返回给用户,如果没有命中,查询数据库,结果写入local cache中,最后最后返回给用户

缓存生效失效的时机:

如果是连续的查询同一个数据操作,在第一次查询之后,后续查询都能命中缓存

如果在查询之后,紧接着的是进行变更操作,就会导致缓存失效

一级缓存测试

mybatis默认支持一级缓存,不需要做其他配置

测试1:同一个sqlsession下连续进行查询操作

//同一个sqlsession下连续进行查询操作
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        //第一次查询操作
        System.out.println("第一次查询开始");
        Student student = studentMapper.selectStudentById(3);
        System.out.println("第一次查询结束");
        //第二次查询操作
        System.out.println("第二次查询开始");
        Student student1 = studentMapper.selectStudentById(3);
        System.out.println("第二次查询开始");

通过执行结果可知:第一次查询会查询数据库,第二次查询是通过缓存查询到结果缓存

测试2:第一次查询操作结束后,进行数据变更,在进行查询

//同一个sqlsession下连续进行查询操作
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        //第一次查询操作
        System.out.println("第一次查询开始");
        Student student = studentMapper.selectStudentById(3);
        System.out.println(student);
        System.out.println("第一次查询结束");
        //对数据进行变更操作
        System.out.println("变更操作开始");
        studentMapper.updateNameById(3,"yy");
        sqlSession.commit();
        System.out.println("变更操作结束");

        //第二次查询操作
        System.out.println("第二次查询开始");
        Student student1 = studentMapper.selectStudentById(3);
        System.out.println(student1);
        System.out.println("第二次查询结束");

在查询结束后,如果对于数据变更操作,会删除掉缓存,导致第二次查询依然需要进入到数据库中去查询

测试3:不同的sqlsession下的同一个操作是否后命中缓存

        SqlSession sqlSession = sqlSessionFactory.openSession();
       
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        //第一次查询操作
        System.out.println("第一次查询开始");
        Student student = studentMapper.selectStudentById(3);
        System.out.println(student);
        System.out.println("第一次查询结束");

        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        StudentMapper sqlSession1Mapper = sqlSession1.getMapper(StudentMapper.class);
        //第二次查询操作
        System.out.println("第二次查询开始");
        Student student1 = sqlSession1Mapper.selectStudentById(3);
        System.out.println(student1);
        System.out.println("第二次查询结束");

不同的sqlsession的一级缓存是无法共享的

 

二级缓存

mybatis二级缓存是mapper级别的缓存,默认是关闭的

对于同一个mapper下不同的sqlsession可以共享二级缓存,不同的mapper是相互隔离的

二级缓存的特点是需要打开二级缓存的配置,并且映射的java类需要实现序列化

二级缓存原理

 二级缓存和一级缓存的区别:

二级缓存范围更大,多个SQLsession可以共享一个mapper级别的二级缓存,数据类型依然是hashmap来存储二级缓存内容,mapper是按照nameSpace来划分,如果namespace相同则使用同一个二级缓存

一级缓存范围较小,是一个sqlsession级别

二级缓存使用步骤

1.在mybatis的全局配置文件中开启二级缓存

<settings>
        <!--cacheEnabled:开启mybatis的二级缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

2.将映射的pojo类序列化

public class Student implements Serializable

3.在mapper配置文件中使用cache标签

<!--
    cache是和二级缓存相关的标签、
    eviction属性:代表缓存的回收策略,mybatis提供了回收策略如下:
                  LRU:最近最少使用,用来回收最长时间不被使用的对象
                  FIFO:先进新出,按照对象进入缓存的顺序来进行回收
                  SOFT:软引用,移除基于垃圾回收器和软引用规则作用的对象(内存不足时会回收)
                  WEAK:弱引用,移除基于垃圾回收器和弱引用规则作用的对象(只要发生GC操作,无论内存是否充足都会回收)
    flushInterval属性:刷新间隔时间,单位是毫秒
    size属性:引用数目,代表缓存最多可以存储对象的个数  
    readOnly属性:只读,意味着缓存数据只能读取不能修改            
    -->
    <cache eviction="FIFO" flushInterval="1000" size="1024" readOnly="false" />

注:在select查询标签上使用属性:

useCache="true":缓存的使用和静止

flushCache="true":刷新缓存

 二级缓存测试:

 public void selectStudentById(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
        //第一次查询操作
        System.out.println("第一次查询开始");
        Student student = studentMapper.selectStudentById(3);
        System.out.println(student);
        System.out.println("第一次查询结束");
        sqlSession.close();//才会触发缓存被记录

        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        StudentMapper sqlSession1Mapper = sqlSession1.getMapper(StudentMapper.class);
        //第二次查询操作
        System.out.println("第二次查询开始");
        Student student1 = sqlSession1Mapper.selectStudentById(3);
        System.out.println(student1);
        System.out.println("第二次查询结束");
        sqlSession1.close();

    }

命中缓存的概率为0.5

表名不同的sqlsession可以命中同一个二级缓存

底层:二级缓存是Cache接口的实现类,一级缓存是LocalCache(是一个hashmap的属性),执行过程先经过二级缓存,在二级缓存未命中时才会走一级缓存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值