目录
1、MyBatis缓存
1.1 一级缓存
MyBatis的一级缓存的作用域是session,当openSession()后,如果执行相同的SQL(相同语句和参数),MyBatis不再执行SQL,而是从缓存中命中返回。
原理:Mybatis执行查询时首先去缓存区命中,如果命中直接返回,没有命中则执行SQL,从数据库中查询。
- 使用session.clearCache()清空缓存,强制查询不缓存。
- 在执行insert、update、delete时会清空缓存。
测试正常的一级缓存:
调用clearCache()方法强制清空缓存:
执行update语句清空缓存数据:
1.2 二级缓存
1.2.1 MyBatis实现
MyBatis的二级缓存的作用域是一个mapper的namespace,同一个namespace中查询sql可以从缓存中命中。二级缓存是可以跨session的。即不同的sqlSession执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存,第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
开启二级缓存:在mapper.xml文件中加入 <cache /> 。
【注意】在mybatis-config中有缓存的全局控制配置(cacheEnabled),默认是开启的,所以无需手动开启。如果设置为不开启,则所有在mapper.xml文件中的缓存配置将失效。
测试配置:
在mapper.xml中加入<cache/>;之后测试如下:
【注意】二级缓存需要将缓存的数据写入缓冲区,所以对应的对象需要实现序列化接口。也即上面的User需要实现Serializable接口。
在mapper.xml中的cache元素,它还可以设置其它的属性如下:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会 导致冲突。
1.2.2 第三方实现
使用第三方缓存实现二级缓存,常见的有如下:
1)EHCache
mybatis-ehcache – MyBatis Ehcache | Reference Documentation
2)Memcache
mybatis-memcached – MyBatis Memcached | Reference Documentation
2、高级查询
2.1 数据模型
一个年级下有很多学生,一个学生只属于一个年级;
一个学生有且只有一个对应的地址;
一个学生可以选择多门课程,一个课程也可以被多个学生选择。
概念模型:
物理模型:
2.2 创建数据库表
导入《mybatis.sql》文件到数据库中,创建将要演示的数据库表及数据。
2.3 一对一查询
【需求】:根据学生ID查询学生信息包括学生的地址。
2.3.1 第一种方式
由于Studnet中没有任何关于地址的属性,但是需求中的查询将包含地址的所有属性;鉴于此,可以新创建一个StudentAddress继承于Student来解决查询 结果的映射。
1)StudentAddress类:
2)接口中方法:
3)在mapper.xml文件中SQL:
4)测试:
2.3.2 第二种方式
在第一种方式中,不好的是如果以后每次都有这样的关联查询都需要新建一个类,太繁琐。在MyBatis中其实可以在mapper.xml文件使用resultMap来映射这种高级查询。
在resultMap元素中有association元素可以映射有一个的情形。可以是一对一的关系,也可以是多对一中的多方使用。
关联元素association处理“有一个”类型的关系。比如,在我们的示例中,一个学生有一个地址。 关联映射就工作于这种结果之上。
1)改造Student类,添加address属性:
2)接口中方法:
3)在mapper.xml文件中SQL:
4)测试:
2.4 一对多查询
在MyBatis中对于返回结果是一对多的集合列表也有特定的集合元素collection来处理。集合元素的作用几乎和关联是相同的。
【需求】:根据年级ID查询该年级信息包括该年级的所有学生
1)改造年级类Grade,添加学生列表属性students:
2)接口中方法:
3)在mapper.xml文件中SQL:
4)测试:
2.5 多对多查询
多对多的返回结果中一般也包含列表或者关联,它的处理和上述的处理没有区别。主要不同是在mapper.xml文件中SQL语句的编写需要考虑多对多的表关系。
【需求】:根据学生ID查询学生信息包括学生的地址及其学习的课程
1)改造Student类,添加课程列表属性courses:
2)接口中方法:
3)在mapper.xml文件中SQL:
第一种写法:
或者使用第二种写法,继承已有的resultMap:
4) 测试:
2.6 延迟加载
在关联元素association查询中可以将关联的对象延迟加载;即如果使用到了关联对象的时候才需要到数据库进行查询,类似hibernate的懒加载。
延迟加载的意义在于,虽然是关联查询,但不是及时将关联的数据查询出来,而且在需要的时候进行查询。如果在MyBatis中要启用延迟加载需要:
1) 在总配置文件中设置开启延迟加载:
<!-- 开启延迟加载 --> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> |
【说明】
lazyLoadingEnabled:true使用延迟加载,false禁用延迟加载。默认为true
aggressiveLazyLoading:true启用时,当延迟加载开启时访问对象中一个懒对象属性时,将完全加载这个对象的所有懒对象属性。false,当延迟加载时,按需加载对象属性(即访问对象中一个懒对象属性,不会加载对象中其他的懒对象属性)。默认为true
- 在mybatis-xx.xx.xx目录中找到相关依赖包:asm-x.x.x.jar和cglib-x.x.x.jar将jar包加入到项目中;
- 在对应的mapper.xml文件中利用association元素实现。
【需求】根据学生ID查询学生信息包括延迟加载学生的地址;即调用学生对象中的获取地址的方法时才去执行查询地址的SQL。
1)改造Student,添加年级属性grade:
2) 接口中方法:
3) 在mapper.xml文件中SQL:
4) 测试:
3、分页插件
到目前为止;所使用的查询都是查询所有数据,没有进行分页;这样显然是不合理的。如果要在MyBatis里面实现查询的分页如何做?
解决方式一:可以在每个select语句中添加limit来实现分页,这样做是最简单的,但是这样做需要在太多地方添加比较麻烦。另外;还需要在原有的DAO层修改代码,添加查询记录总数的操作。
方式一改动量大并需要改动原有代码,不太合适;能否做到在不改变原有业务逻辑的情况下做到分页呢?也即如果可以在MyBatis执行时候拦截即将要执行的语句,然后加入分页逻辑的话,原有的所有代码都不需要改变。这也就是解决方式二,使用MyBatis的插件(拦截器)。
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定了想要拦截的方法签名即可。
方式二的解决方案正是使用了插件;这里引用一个开源的MyBatis分页插件PageHelper。PageHelper实现了通用的分页查询,其支持的数据有,mysql、Oracle、DB2、PostgreSQL等主流的数据库。
该插件托管于github: GitHub - pagehelper/Mybatis-PageHelper: Mybatis通用分页插件
详见《Mybatis分页插件-PageHelper》
在MyBatis配置文件中配置分页插件,然后在代码中添加分页参数进行分页是非常简单的。
1) 添加PageHelper的相关jar包;
2) 配置:
3) 运用: