7. 关联查询
7.1 准备工作:
数据库表:
#订单表:
create table tb_order
(
id INT AUTO_INCREMENT PRIMARY KEY,
userid INT,
createtime DATETIME,
state VARCHAR(20)
) ENGINE=InnoDB AUTO_INCREMENT=1;
#订单详情表
CREATE TABLE tb_orderdetail(
id INT AUTO_INCREMENT PRIMARY KEY,
productId INT,
ordereId INT,
num INT,
price DOUBLE(8,2)
) ENGINE=InnoDB AUTO_INCREMENT=1;
#商品表
CREATE TABLE tb_product (
id INT AUTO_INCREMENT PRIMARY KEY,
name varchar(100),
price DOUBLE(8,2),
description varchar(500)
) ENGINE=InnoDB AUTO_INCREMENT=1 ;
#用户表
CREATE TABLE tb_user(
ID INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(20),
PASSWORD VARCHAR(50),
sex VARCHAR(2),
brithday DATE,
address VARCHAR(200)
) ENGINE=INNODB AUTO_INCREMENT=1 ;
表与表之间的关联:
7.2 一对一查询
需求:
查询订单信息,以及关联的用户信息
注意:因为一个订单信息只会是一个人下的订单,所以从查询订单信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的订单信息则为一对多查询,因为一个用户可以下多个订单。
实体类:
Sql映射文件:
使用resultMap定义输出参数,
<!-- 定义一个包含用户信息的Order的ResultMap -->
<resultMap type="Order" id="orderMap">
<id column="id" property="id"/>
<result column="createTime" property="createTime"/>
<result column="state" property="state"/>
<!-- 配置关联属性User的信息 -->
<!-- association:用于映射关联单个对象信息
property:关联的属性名,就是将用户对象关联到Order的那个属性
javaType: 属性的类型
-->
<association property="user" javaType="User">
<id column="id" property="id"></id>
<result column="username" property="name"/>
<result column="sex" property="sex"/>
<result column="password" property="password"/>
<result column="address" property="address"/>
<result column="brithday" property="brithday"/>
</association>
</resultMap>
SQL语句
<!-- 查询订单以及该订单所关联的用户 -->
<select id="findOrders" resultMap="orderMap">
SELECT o.* , u.username,u.address FROM tb_order o , tb_user u WHERE o.userId = u.id
</select>
Mapper接口:
测试代码:
7.3 一对多查询
需求:
查询用户,及用户下的所有订单信息
User类:
需要在User类添加一个订单的关联属性,但是一个用户是可以有多个订单的,所以该属性是一个集合属性.
定义一个包含Order信息的User的ResultMap:
<!-- 定义一个包含Order信息的User的ResultMap -->
<resultMap type="User" id="userOrderMap">
<id column="id" property="id"></id>
<result column="username" property="name"/>
<result column="sex" property="sex"/>
<result column="password" property="password"/>
<result column="address" property="address"/>
<result column="brithday" property="brithday"/>
<!-- 订单信息 -->
<!--
collection:对关联查询到的多条记录映射到集合对象中
property:将查询到的多条记录映射到User类的那个属性中
ofType: 指明集合中的元素的类型
-->
<collection property="orders" ofType="Order">
<id column="oid" property="id"></id>
<result column="createTime" property="createTime"/>
<result column="state" property="state"/>
</collection>
</resultMap>
SQL语句定义:
<!-- 查询用户以及该用户的订单信息 -->
<select id="findUserOrder" resultMap="userOrderMap">
select u.* ,o.id oid,o.createtime,o.state from tb_user u , tb_order o where u.id = o.userId
</select>
级联查询的时候,主表和从表有一样的字段名的时候,在mysql上命令查询是没问题的。但在mybatis中主从表需要为相同字段名设置别名
Mapper 接口:
测试代码:
7.4 多对多查询
需求:
查询用户,及用户的订单信息和订单详情信息
SQL语句:
查询主表是:用户表
关联表:由于用户和订单详情表没有直接关联,通过订单进行关联
SELECT u.*, o.id oid , o.createTime, o.state, d.id orderdetailId, d.price,d.num
FROM tb_user u, tb_order o, tb_orderdetail d
WHERE u.id = o.userId AND o.id =d.orderId
映射思路:
将用户信息映射到user中。
在user类中添加订单列表属性List orders,将用户创建的订单映射到orders
在Orders中添加订单明细列表属性Listorderdetials,将订单的明细映射到orderdetials
resultMap:
<!-- 定义一个包含Order信息以及OrderDetail信息的User的ResultMap -->
<resultMap type="User" id="userOrderOrderDetailMap">
<id column="id" property="id"></id>
<result column="username" property="name"/>
<result column="sex" property="sex"/>
<result column="password" property="password"/>
<result column="address" property="address"/>
<result column="brithday" property="brithday"/>
<!-- 订单信息 -->
<!--
collection:对关联查询到的多条记录映射到集合对象中
property:将查询到的多条记录映射到User类的那个属性中
ofType: 指明集合中的元素的类型
-->
<collection property="orders" ofType="Order">
<id column="oid" property="id"></id>
<result column="createTime" property="createTime"/>
<result column="state" property="state"/>
<!-- 订单详情信息 -->
<collection property="orderDetails" ofType="OrderDetail">
<id column="orderDetailId" property="id"></id>
<result column="price" property="price"/>
<result column="num" property="num"/>
</collection>
</collection>
</resultMap>
SQL映射:
<!-- 查询用户以及该用户的订单及订单详情 -->
<select id="findUserOrderOrderDetail" resultMap="userOrderOrderDetailMap">
SELECT u.*, o.id oid , o.createTime, o.state, d.id orderdetailId, d.price,d.num
FROM tb_user u, tb_order o, tb_orderdetail d
WHERE u.id = o.userId AND o.id =d.orderId
</select>
Mapper接口:
测试代码:
8. 缓存
8.1 MyBatis的缓存简介
如下图,是mybatis一级缓存和二级缓存的区别图解:
Mybatis一级缓存的作用域是同一个SqlSession,在同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了。Mybatis默认开启一级缓存。
Mybatis二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis默认没有开启二级缓存需要在setting全局参数中配置开启二级缓存。
8.2 一级缓存
下图是根据id查询用户的一级缓存图解:
一级缓存区域是根据SqlSession为单位划分的。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。
Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象,sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域。
测试:
@Test
public void testCache1() throws Exception{
SqlSession sqlSession = sqlSessionFactory.openSession();//创建代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//下边查询使用一个SqlSession
//第一次发起请求,查询id为1的用户
User user1 = userMapper.findUserById(1);
System.out.println(user1);
//如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
//更新user1的信息
user1.setUsername("习大大");
userMapper.updateUser(user1);
//执行commit操作去清空缓存
sqlSession.commit();
//第二次发起请求,查询id为1的用户
User user2 = userMapper.findUserById(1);
System.out.println(user2);
sqlSession.close();
}
分析:
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
8.3 二级缓存
下图是多个sqlSession请求UserMapper的二级缓存图解。
二级缓存区域是根据mapper的namespace划分的,相同namespace的mapper查询数据放在同一个区域,如果使用mapper代理方法每个mapper的namespace都不同,此时可以理解为二级缓存区域是根据mapper划分。
每次查询会先从缓存区域找,如果找不到从数据库查询,查询到数据将数据写入缓存。
Mybatis内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象
sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域。
开启二级缓存:
在核心配置文件mybatis-config.xml中加入
<setting name="cacheEnabled" value="true"/>
配置项 | 描述 | 允许值 | 默认值 |
---|---|---|---|
cacheEnabled | 对在此配置文件下的所有cache 进行全局性开/关设置。 | true false | true |
要在你的Mapper映射文件中添加一行: <cache />
,表示此mapper开启二级缓存。
实体类要实现序列化
二级缓存需要查询结果映射的pojo对象实现java.io.Serializable接口实现序列化和反序列化操作,注意如果存在父类、成员pojo都需要实现序列化接口。
public class Order implements Serializable
public class User implements Serializable
...
测试
//获取session1
SqlSession session1 = sqlSessionFactory.openSession();
UserMapper userMapper = session1.getMapper(UserMapper.class);
//使用session1执行第一次查询
User user1 = userMapper.findUserById(1);
System.out.println(user1);
//关闭session1
session1.close();
//获取session2
SqlSession session2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
//使用session2执行第二次查询,由于开启了二级缓存这里从缓存中获取数据不再向数据库发出sql
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
//关闭session2
session2.close();
8.4 禁用二级缓存
在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
is内部存储缓存使用一个HashMap,key为hashCode+sqlId+Sql语句。value为从查询出来映射生成的java对象
sqlSession执行insert、update、delete等操作commit提交后会清空缓存区域。
开启二级缓存:
在核心配置文件mybatis-config.xml中加入
<setting name="cacheEnabled" value="true"/>
配置项 | 描述 | 允许值 | 默认值 |
---|---|---|---|
cacheEnabled | 对在此配置文件下的所有cache 进行全局性开/关设置。 | true false | true |
要在你的Mapper映射文件中添加一行: <cache />
,表示此mapper开启二级缓存。
实体类要实现序列化
二级缓存需要查询结果映射的pojo对象实现java.io.Serializable接口实现序列化和反序列化操作,注意如果存在父类、成员pojo都需要实现序列化接口。
public class Order implements Serializable
public class User implements Serializable
...
测试
//获取session1
SqlSession session1 = sqlSessionFactory.openSession();
UserMapper userMapper = session1.getMapper(UserMapper.class);
//使用session1执行第一次查询
User user1 = userMapper.findUserById(1);
System.out.println(user1);
//关闭session1
session1.close();
//获取session2
SqlSession session2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
//使用session2执行第二次查询,由于开启了二级缓存这里从缓存中获取数据不再向数据库发出sql
User user2 = userMapper2.findUserById(1);
System.out.println(user2);
//关闭session2
session2.close();
8.4 禁用二级缓存
在statement中设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">