Dawn's Blogs

分享技术 记录成长

0%

SSM学习之MyBatis (4) 多表查询和缓存

多表查询

假设有两张表,分别是员工表和部门表。

多对一

查询员工以及员工所在的部门(需要在 User 实体类中定义一个 Dept 类型的属性 dept),可以有三种方法:

  • 级联方式
  • 使用 association
  • 分步查询

级联

使用级联方式,只需要在 resultMap 中定义 result 标签即可,dept 部门属性表示为 dept.xxx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<resultMap id="empDeptMap" type="Emp">
<id column="eid" property="eid"></id>
<result column="ename" property="ename"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<result column="did" property="dept.did"></result>
<result column="dname" property="dept.dname"></result>
</resultMap>

<!--Emp getEmpAndDeptByEid(@Param("eid") int eid);-->
<select id="getEmpAndDeptByEid" resultMap="empDeptMap">
select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did =
dept.did where emp.eid = #{eid}
</select>

association

使用 association 标签,可以指明属性 dept 和属性类型 Dept,在 association 标签中定义部门的映射关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<resultMap id="empDeptMap" type="Emp">
<id column="eid" property="eid"></id>
<result column="ename" property="ename"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<association property="dept" javaType="Dept">
<id column="did" property="did"></id>
<result column="dname" property="dname"></result>
</association>
</resultMap>

<!--Emp getEmpAndDeptByEid(@Param("eid") int eid);-->
<select id="getEmpAndDeptByEid" resultMap="empDeptMap">
select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did =
dept.did where emp.eid = #{eid}
</select>

分步查询

分步查询就是首先查询员工信息,然后根据员工信息查询部门信息。

具体就是使用 association 的 select 属性和 column 属性

  • select 属性:设置分步查询,查询某个属性的值的 sql 的标识。
  • column 属性:将第一步中的查询结果中的字段,设置为下一步查询的输入值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<resultMap id="empDeptStepMap" type="Emp">
<id column="eid" property="eid"></id>
<result column="ename" property="ename"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<!--
select:设置分步查询,查询某个属性的值的sql的标识(namespace.sqlId)
column:将sql以及查询结果中的某个字段设置为分步查询的条件
-->
<association property="dept"
select="com.atguigu.MyBatis.mapper.DeptMapper.getEmpDeptByStep" column="did">
</association>
</resultMap>

<!--Emp getEmpByStep(@Param("eid") int eid);-->
<select id="getEmpByStep" resultMap="empDeptStepMap">
select * from t_emp where eid = #{eid}
</select>

<!--Dept getEmpDeptByStep(@Param("did") int did);-->
<select id="getEmpDeptByStep" resultType="Dept">
select * from t_dept where did = #{did}
</select>

一对多

查询部门下的所有员工(需要在 Dept 实体类中定义 User List),可以有以下几种方法:

  • collection
  • 分步查询

collection

使用 collection 标签可以表示一对多的关系,ofType 属性指明集合中存储的数据类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<resultMap id="deptEmpMap" type="Dept">
<id property="did" column="did"></id>
<result property="dname" column="dname"></result>
<!--
ofType:设置collection标签所处理的集合属性中存储数据的类型
-->
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"></id>
<result property="ename" column="ename"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
</collection>
</resultMap>

<!--Dept getDeptEmpByDid(@Param("did") int did);-->
<select id="getDeptEmpByDid" resultMap="deptEmpMap">
select dept.*,emp.* from t_dept dept left join t_emp emp on dept.did =
emp.did where dept.did = #{did}
</select>

分步查询

即先查询部门,再查询部门下的所有员工。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<resultMap id="deptEmpStep" type="Dept">
<id property="did" column="did"></id>
<result property="dname" column="dname"></result>
<collection property="emps" fetchType="eager"
select="com.atguigu.MyBatis.mapper.EmpMapper.getEmpListByDid" column="did">
</collection>
</resultMap>

<!--Dept getDeptByStep(@Param("did") int did);-->
<select id="getDeptByStep" resultMap="deptEmpStep">
select * from t_dept where did = #{did}
</select>

<!--List<Emp> getEmpListByDid(@Param("did") int did);-->
<select id="getEmpListByDid" resultType="Emp">
select * from t_emp where did = #{did}
</select>

延迟加载

分步查询的好处就是可以实现延迟加载

MyBatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载。在 MyBatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false,然后通过设置 association 和 collection 中的 fetchType 属性设置当前分步查询是否延迟加载(lazy 延迟加载,eager 立即加载)。

原理

MyBatis 延迟加载实现的原理是,使用 CGLIB 创建目标对象的代理对象

当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName() ,拦截器 invoke() 方法发现 a.getB() 是 null 值,那么就会执行查询关联 B 对象的 sql 语句去查询,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName() 方法的调用。

缓存

MyBatis 有缓存的功能,将 select 语句缓存到内存中,下⼀次还是这条相同 select 语句的话,直接从缓存中取,不再查数据库。MyBatis 的缓存包括:

  • 一级缓存:将查询到的结果存储到 SqlSession 中。
  • 二级缓存:将查询到的数据存储到 SqlSessionFactory 中。

还有一些第三方缓存,如 EHCache、Memcache 等。

一级缓存

一级缓存是 SqlSession 级别的,同一个 SqlSession 下查询的数据会被缓存。一级缓存失效的原因:

  • 使用不同的 SqlSession。
  • 查询语句发生变化。
  • 查询之间有增删改操作。
  • 手动调用了 clearCache() 方法,手动清空一级缓存。

二级缓存

二级缓存是 SqlSessionFactory 级别的,二级缓存需要手动开启:

  1. 在配置文件中设置 <setting name="cacheEnabled" value="true"> 开启缓存,默认是 true。
  2. 在需要使⽤二级缓存的映射文件中添加一个标签:<catche />
  3. 使用二级缓存的实体类必须是可序列化的,也就是实现 java.io.Serializable 接口。
  4. SqlSession 对象关闭或者提交后,一级缓存中的数据才会被写入到二级缓存中。

配置

二级缓存在 <cache /> 标签中配置,可选的配置项:

  • eviction:淘汰算法。
    • LRU
    • FIFO
    • SOFT:淘汰软引用指向的对象。
    • WEEK:淘汰弱引用指向的对象。
  • flushInterval:刷新间隔,单位毫秒。默认是不刷新。
  • readOnly:只读属性。
    • true:缓存返回相同的实例,可能存在并发问题。
    • false:通过序列化返回对象的拷贝,线程安全,默认为 false。
  • size:缓存的对象数,代表缓存最多可以存储多少个对象,太大容易导致内存溢出。