主要内容
提升mybatis的性能
任何对数据库的操作都无可避免地出现性能问题,任何频繁读写数据库的操作都是有问题的,为了尽可能提升我们的数据库读写速度,mybatis提供了赖加载和缓存的机制。
使用懒加载
当程序中的JavaBean有关联关系的时候(一对一或者一对多),使用懒加载可以实现关联属性的按需加载,按需加载的意思是当属性在使用的时候才会被加载数据。例如当查询部门员工信息的时候,员工职位并不一定是必须的。又如顾客和订单之间的关系:有时候只需要查询顾客信息,订单信息是非必需的。
开启懒加载
通过在mybatis-conf中的<settings>配置两个属性就可以开启懒加载,这两个属性是:
1 2 3 4 |
<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false" /> </settings> |
第一个属性很好理解,当ture的时候就是使用懒加载,反则关闭懒加载。当使用懒加载的时候,第二个属性必须是false,否则无法开启懒加载。这个属性在mybatis<=3.4.1的时候默认是true的,新版本默认false。
添加了这两个配置之后,懒加载机制就已经生效,不过需要注意的是,之前的文章中介绍了两种配置一对多关系的方式,其中方式二是无法享受懒加载的好处的。
使用缓存
缓存的好处是让我们尽量减少对数据库的读写操作,从而减少数据库的IO和提高程序的性能。mybatis中的缓存方式有两种:一级缓存和二级缓存。一级缓存由mybatis内部实现;二级缓存让我们可以利用第三方库提供的缓存方式,例如EhCache或者Redis。 默认情况下,一级缓存和二级缓存都是开启的,但是,二级缓存还需要额外的配置。
缓存的作用域
一级缓存的作用域是session隔离的,即是说,相同的sql查询语句的缓存效果只对同一个session对象起作用。
二级缓存的作用域是基于命名空间的(namespace),二级缓存可以跨越多个session共享数据,只要它们处于同一个命名空间之中。
开启二级缓存
mybatis-config配置文件中缓存的全局开关:
1 2 3 |
<settings> <setting name="cacheEnabled" value="true" /> </settings> |
如果value等于false,那么无论一级缓存还是二级缓存都不会生效。另外,要二级缓存生效,还需要在Mapper配置文件中打开缓存开关:
1 2 3 4 5 6 7 8 9 10 |
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="cn.sharpcode.mapper.StaffMapper"> <!-- 打开StaffMapper的缓存功能 --> <cache /> <!-- other mapper setting --> </mapper> |
两个步骤,二级缓存就可以生效。<cache>可用的参数如下:
1 2 3 4 5 |
<cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true" /> |
参数解释:
eviction:缓存的过期淘汰算法,即按照什么规则去清除缓存,可选值有:”LRU”、”FIFO”、”SOFT”、”WEAK”,默认值是LRU(最近最少使用)
flashInterval:过期时间,单位为毫秒,10000即为10秒,默认值为空,即只要容量足够,缓存永不过期
size:可缓存对象的个数,默认值为1024
readOnly:是否只读,值为true,则所有相同的sql查询语句返回同一个对象(会有线程安全问题),如果值为false,则相同的查询语句访问的是cache的clone副本。
两种缓存的关系
是不是开启二级缓存后,一级缓存就没有用呢?其实不是,mybatis优先使用的是一级缓存,二级缓存是需要被显式写入的(通过commit方法)。先看一段简单的代码来说明问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class Main { private static SqlSession sqlSession; private static SqlSessionFactory sqlSessionFactory; @Before public void setup(){ String resource = "mybatis-config.xml"; try { InputStream resourceAsStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); sqlSession = sqlSessionFactory.openSession(); } catch (IOException e) { e.printStackTrace(); } } @Test public void testCache() throws IOException { StaffMapper mapper = sqlSession.getMapper(StaffMapper.class); Staff staff = mapper.selectStaff(1); System.out.println(staff.getName()); //第二次查询从缓存中取得结果 staff = mapper.selectStaff(1); System.out.println(staff.getName()); sqlSession.close(); } } |
我们关注的是testCache()
方法。方法中发出了两次sql查询语句,实际上,运行代码的过程中会发现,sql语句只被执行了一次,也就是说,第二次的结果是从一级缓存中获取的。
什么时候缓存会被更新
如果使用了缓存,我们面对的问题常常是:这些数据是最新的吗?换句话说,这些被缓存的数据会不会是脏数据。为了解决这个问题,mybatis的做法是,当数据有可能发生变更的时候,就会主动清除缓存。一般来说,会对数据产生影响的sql语句无非就是插入(insert)、删除(delete)、更新(update)。
下面是测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@Test public void testCache() throws IOException { StaffMapper mapper = sqlSession.getMapper(StaffMapper.class); Staff staff = mapper.selectStaff(1); System.out.println(staff.getName()); //更新staff的名称,导致缓存被清除 staff.setName("Cat"); mapper.updateStaff(staff); staff = mapper.selectStaff(1); System.out.println(staff.getName()); sqlSession.close(); } |
从控制台日输出可见,sql语句发送了两次(二级缓存未开启之前),印证了我们的说法。
什么时候会使用二级缓存
这个“使用”需要分开两个部分来说,即读取缓存和写入缓存。从控制台的输出来看,当Mapper的二级缓存开启的时候,每次执行sql查询语句之前,都会先从二级缓存中尝试获取数据,二级缓存没有匹配再查询一级缓存:
1 2 3 4 5 6 7 8 9 |
DEBUG [main] - Cache Hit Ratio [cn.sharpcode.mapper.StaffMapper]: 0.0 DEBUG [main] - ==> Preparing: SELECT * FROM staff WHERE id = ? DEBUG [main] - ==> Parameters: 1(Integer) TRACE [main] - <== Columns: id, name TRACE [main] - <== Row: 1, Jack DEBUG [main] - <== Total: 1 Jack DEBUG [main] - Cache Hit Ratio [cn.sharpcode.mapper.StaffMapper]: 0.0 Jack |
如果控制台中看到Cache Hit Ratio(缓存命中率)字样,证明二级缓存已经开启。
二级缓存中的数据是在sqlSession.commit()方法被调用的时候写入的,当然,sqlSession.close()方法也会触发二级缓存的发生,原因在于close之前会先调用commit方法。
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
@Test public void testCache() throws IOException { StaffMapper mapper = sqlSession.getMapper(StaffMapper.class); Staff staff = mapper.selectStaff(1); System.out.println(staff.getName()); //执行update方法,会清空缓存 staff.setName("Jack"); mapper.updateStaff(staff); //获取更新后的数据 staff = mapper.selectStaff(1); System.out.println(staff.getName()); sqlSession.commit(); //写入二级缓存 //从二级缓存中取出数据 Staff staff1 = mapper.selectStaff(1); System.out.println(staff1.getName()); //跨session共享缓存数据 SqlSession sqlSession1 = sqlSessionFactory.openSession(); StaffMapper mapper1 = sqlSession1.getMapper(StaffMapper.class); Staff staff2 = mapper1.selectStaff(1); System.out.println(staff2.getName()); sqlSession.close(); } |
控制台输出日志:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
DEBUG [main] - Cache Hit Ratio [cn.sharpcode.mapper.StaffMapper]: 0.0 DEBUG [main] - ==> Preparing: SELECT * FROM staff WHERE id = ? DEBUG [main] - ==> Parameters: 1(Integer) TRACE [main] - <== Columns: id, name TRACE [main] - <== Row: 1, Jack DEBUG [main] - <== Total: 1 Jack DEBUG [main] - ==> Preparing: UPDATE staff SET name = ? WHERE id = ? DEBUG [main] - ==> Parameters: Jack(String), 1(Integer) DEBUG [main] - <== Updates: 1 DEBUG [main] - Cache Hit Ratio [cn.sharpcode.mapper.StaffMapper]: 0.0 DEBUG [main] - ==> Preparing: SELECT * FROM staff WHERE id = ? DEBUG [main] - ==> Parameters: 1(Integer) TRACE [main] - <== Columns: id, name TRACE [main] - <== Row: 1, Jack DEBUG [main] - <== Total: 1 Jack DEBUG [main] - Cache Hit Ratio [cn.sharpcode.mapper.StaffMapper]: 0.3333333333333333 Jack DEBUG [main] - Cache Hit Ratio [cn.sharpcode.mapper.StaffMapper]: 0.5 Jack |
Cache Hit Ratio只要不为0,就证明数据是从二级缓存中获取的。
使用EHCACHE缓存库
Mapper配置文件中的<cache>元素有一个属性type
,type属性用于指定我们用于缓存的具体对象类型。
在mybatis中整合ehcache需要另外导入mybatis-ehcache包。该包中有两个类:LoggingEhcache和EhcacheCache,只需要在type属性中指定这两个类中的任意一个即可。
1 2 3 |
<cache type="org.mybatis.caches.ehcache.LoggingEhcache" /> <!-- 或者 --> <cache type="org.mybatis.caches.ehcache.EhcacheCache" /> |
转载请注明:Pure nonsense » mybatis懒加载和缓存