MyBatis延迟加载和缓存
Posted 迷茫王子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis延迟加载和缓存相关的知识,希望对你有一定的参考价值。
一、延迟加载
1.主对象的加载:
根本没有延迟的概念,都是直接加载。
2.关联对象的加载时机:
01.直接加载:
访问主对象,关联对象也要加载
02.侵入式延迟:
访问主对象,并不加载关联对象
访问主对象属性的属性的时候,关联对象会被加载
03.深度延迟
访问主对象,并不加载关联对象
访问主对象的属性的时候,关联对象也不会被加载
访问关联对象或关联对象的属性的时候,才会加载关联对象。
3.一对多延迟加载代码:
01.实体类代码:
package cn.pb.bean;
import java.util.Set;
/**
* 国家的实体类
*/
public class Country {
private Integer cId;//国家的编号
private String cName;//国家的名称
//关联省会的属性
private Set<Provincial> provincials;
public Integer getcId() {
return cId;
}
public void setcId(Integer cId) {
this.cId = cId;
}
public String getcName() {
return cName;
}
public void setcName(String cName) {
this.cName = cName;
}
public Set<Provincial> getProvincials() {
return provincials;
}
public void setProvincials(Set<Provincial> provincials) {
this.provincials = provincials;
}
}
package cn.pb.bean;
/**
* 省会对应的实体类
*/
public class Provincial {
private Integer pId; //省会的编号
private String pName; //省会名称
public Integer getpId() {
return pId;
}
public void setpId(Integer pId) {
this.pId = pId;
}
public String getpName() {
return pName;
}
public void setpName(String pName) {
this.pName = pName;
}
}
02.dao层代码:
public interface CountryDao {
/**
* 根据国家id 查询国家的信息 以及国家下面的省会
*/
Country selectCountryById(Integer id);
}
03.mapper.xml代码:
<mapper namespace="cn.pb.dao.CountryDao">
<!--01.先根据id查询出国家信息 多条sql的查询 可以使用延迟加载策略-->
<select id="selectCountryById" resultMap="countryMap">
select cid,cname from country where cid=#{xxx}
</select>
<!--03.根据国家id 查询出省份信息 -->
<select id="selectProvincialByCountryId" resultType="Provincial">
select pid,pname from provincial
where countryid=#{xxx}
<!--#{xxx} 对应的就是resultMap中 collection节点下面的column -->
</select>
<!--02.国家的映射信息-->
<resultMap id="countryMap" type="Country">
<id property="cId" column="cid"/>
<result property="cName" column="cname"/>
<!--设置关联的集合属性
select:需要关联的查询语句
column: select关联语句中需要的参数 -->
<collection property="provincials" ofType="Provincials"
select="selectProvincialByCountryId"
column="cid"/>
</resultMap>
</mapper>
04.测试代码:
public class CountryTest {
CountryDao dao=null;
SqlSession session=null;
Logger log= Logger.getLogger(CountryTest.class);
@Before
public void before(){
//获取session
session= SessionFactoryUtil.getSession();
//获取执行的类对象
dao=session.getMapper(CountryDao.class);
}
/**
* 在所有的test测试方法执行之后 都要执行的操作
*/
@After
public void after(){
if(session!=null){
session.close();
}
}
<!--只输出主加载对象 只会有一条sql语句-->
@Test
public void testSelectCountryById(){
Country country=dao.selectCountryById(1);
log.debug("根据id查询国家信息"+country);
}
<!--输出关联对象的信息 会有两条sql语句-->
@Test
public void testSelectCountryById(){
Country country=dao.selectCountryById(1);
log.debug("根据id查询国家信息"+country.getProvincials());
}
}
4.配置延迟加载:
在mybatis.xml文件中配置:
<settings>
<!-- 全局性地启用或禁用延迟加载。当禁用时,所有关联的配置都会立即加载。 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!--当启用后,一个有延迟加载属性的对象的任何一个延迟属性被加载时,该对象
的所有的属性都会被加载。否则,所有属性都是按需加载。 侵入式延迟 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
二、MyBatis缓存
1.查询缓存的作用:
查询缓存的使用,主要是为了提供查询访问速度。将用户对同一数据的重复查询过程简化,不再每次均从数据库查询获取结果数据,从而提高访问速度。
2.关于缓存的说明:
01.MyBatis查询缓存机制。根据缓存区的作用域与生命周期,可划分为两种:一级缓存和二级缓存。
02.MyBatis查询缓存的作用域是根据映射文件的namespace划分的,相同的namespace的mapper查询数据放在同一个缓存区域。不同namespace下的数据互不干扰。无论是一级缓存还是二级缓存,都是按照namespace进行分别存放的。
03.但是一级、二级缓存的不同之处在于,SqlSession一旦关闭,则SqlSession中的数据将不存在,即一级缓存就不复存在。而二级缓存的生命周期与整个应用同步,与SqlSession是否关闭无关。换句话说,一级缓存是在同一线程(同一SqlSession)间共享数据,而二级缓存是在不同线程(不同的SqlSession)间共享数据。
04. 一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其生命周期为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。
05. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。
06. 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。
3.一级缓存:
01.一级缓存存在的证明代码:
001.dao层代码:
/**
* 根据学生的编号查询对应的信息
* 验证一级缓存的存在
*/
Student selectStudentById(Integer sId);
002.mapper.xml代码:
<mapper namespace="cn.pb.dao.StudentDao">
<!-- 查询指定学生的信息 验证一级缓存的存在 -->
<select id="selectStudentById" resultType="Student">
select sid,sname from stu where sid=#{xxx}
</select>
</mapper>
003.测试代码:
package cn.pb;
import cn.pb.bean.Student;
import cn.pb.dao.StudentDao;
import cn.pb.util.SessionFactoryUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class StudentTest {
StudentDao dao;
SqlSession session;
Logger logger=Logger.getLogger(StudentDao.class);
@Before
public void before(){
session= SessionFactoryUtil.getSession();
dao=session.getMapper(StudentDao.class);
}
/**
* 在所有的test测试方法执行之后 都要执行的操作
*/
@After
public void after(){
if(session!=null){
session.close();
}
}
/**
* 验证一级缓存的存在
* myBatis的一级缓存是一直开启的,并且不能关闭!
*/
@Test
public void test1(){
Student student=dao.selectStudentById(1);
logger.debug("第一次查到的id为1的学生:"+student);
//再次查询相同的id对象
Student student2 = dao.selectStudentById(1);
logger.debug("第2次查到的id为1的学生:"+student2);
}
}
02.一级缓存--从缓存中查找数据的依据:
001.缓存的底层实现是一个Map,Map的value是查询结果
002.Map的key,即查询依据,使用的ORM架构不同,查询依据是不同的
003.MyBatis的查询依据是:Sql的id+SQL语句
004.Hibernate的查询依据是:查询结果对象的id
005.验证代码:
a.dao层代码:
/**
* 验证mybatis缓存查询的依据!
*/
Student selectStudentById2(Integer sId);
b.mapper.xml代码:
<mapper namespace="cn.pb.dao.StudentDao">
<!-- 查询指定学生的信息 验证mybatis缓存查询的依据!
两个查询语句的id不一致,但是sql语句一样-->
<select id="selectStudentById2" resultType="Student">
select sid,sname from stu where sid=#{xxx}
</select>
</mapper>
c.测试代码:
/**
* 验证查询的依据
* 两个查询都是查询id为1的学生对象,但是查询语句的id不一致
*/
@Test
public void test2() {
Student student = dao.selectStudentById(1);
logger.debug("第1次查到的id为1的学生:"+student);
//再次查询相同的id对象
Student student2 = dao.selectStudentById2(1);
logger.debug("第2次查到的id为1的学生:"+student2);
}
得到的结论是:
mybatis的查询依据是 : mapper文件中sql的id + sql语句!
hibernate底层查询的依据是: 查询对象的id!
其实缓存的底层是一个map,
map的key就是查询依据,value是查询的结果!
03.增、删、改对一级缓存的影响:
增删改会清空一级缓存:注意:必须使用insert标签,不能使用select,否则实验做不成功
001.dao层代码:
/**
* 验证增删改查对一级缓存的影响!
*/
void addStudent(Student student);
002.mapper.xml代码:
<mapper namespace="cn.pb.dao.StudentDao">
<!-- 查询指定学生的信息 验证一级缓存的存在 -->
<select id="selectStudentById" resultType="Student">
select sid,sname from stu where sid=#{xxx}
</select>
<!--新增一个学生-->
<insert id="addStudent">
<!--#{sId},#{sName} 对应的是实体类中的属性名 -->
insert into stu values(#{sId},#{sName})
</insert>
</mapper>
003.测试代码:
package cn.pb;
import cn.pb.bean.Student;
import cn.pb.dao.StudentDao;
import cn.pb.util.SessionFactoryUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class StudentTest {
StudentDao dao;
SqlSession session;
Logger logger=Logger.getLogger(StudentDao.class);
@Before
public void before(){
session= SessionFactoryUtil.getSession();
dao=session.getMapper(StudentDao.class);
}
/**
* 在所有的test测试方法执行之后 都要执行的操作
*/
@After
public void after(){
if(session!=null){
session.close();
}
}
/**
* 验证增删改对一级缓存的影响
* 之前是只有一条查询语句!
* 但是加上新增语句之后发现出现了再次查询!
*
* 因为增删改查操作都要清空缓存,把数据同步到数据库,
* 保证后续的查询得到正确的结果集!
*/
@Test
public void test3() {
Student student = dao.selectStudentById(1);
logger.debug("新增之前查询到的stuent:"+student);
dao.addStudent(new Student(55, "新增学生"));
session.commit();
//再次查询相同的id对象
Student student2 = dao.selectStudentById(1);
logger.debug("新增之后查询到的student:"+student2);
}
}
4.二级缓存:
01.内置二级缓存
001.由于MyBatis从缓存中读取数据的依据与SQL的id相关,而非查询出的对象。所以,使用二级缓存的目的,不是在多个查询间共享查询结果(所有查询中只要查询结果中存在该对象,就直接从缓存中读取,这是对查询结果的共享,Hibernate中的缓存就是为了在多个查询间共享查询结果,但MyBatis不是),而是为了防止同一查询(相同的Sql id,相同的sql语句)的反复执行。
002.MyBatis内置的二级缓存为
https://my.oschina.net/KingPan/blog/280167
以上是关于MyBatis延迟加载和缓存的主要内容,如果未能解决你的问题,请参考以下文章