MyBatis(第3天) 动态SQL和多表关联
动态SQL的概念和环境搭建
之前我们编写SQL语句的时候都是将SQL语句固定写好的
SELECT * FROM product WHERE brand='小米' AND type='ai' AND size=55;
UPDATE user SET username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} WHERE id=#{id};
目标
学习动态SQL的概念
搭建动态SQL的环境
什么是动态SQL
动态SQL指的是:在程序运行时,根据传入的参数情况,拼接最终执行的sql语句。
搭建动态SQL的环境
模块名:mybatis_day02_04_dynamic_sql
配置mybatis核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"/>
<typeAliases>
<package name="com.itheima.entity"/>
</typeAliases>
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="dirver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.itheima.dao"/>
</mappers>
</configuration>
测试
public class TestApp {
// 省略其他sqlSession的创建
@Test
public void testFindUserBySex() {
List<User> list = userMapper.findUserByNameAndSex(new QueryVo("%王%", "女"));
for (User user : list) {
System.out.println("user = " + user);
}
}
}
小结
- 搭建mybatis环境
动态SQL:if标签
目标
用户名和性别查询用户
学习动态SQL语句:if标签的使用
if标签的格式
<if test="条件">
SQL语句
</if>
if标签的作用
如果满足条件,就会拼接这个SQL。
需求实现
定义QueryVo实体类
public class QueryVo {
private String username;
private String sex;
// 省略构造方法/getter/setter
}
声明UserMapper接口方法
package com.itheima.dao;
import com.itheima.entity.User;
import java.util.List;
public interface UserMapper {
/*
根据用户名称和性别查询用户
*/
List<User> findUserByNameAndSex(QueryVo vo);
}
配置mappr映射文件
<?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="com.itheima.dao.UserMapper">
<select id="findUserByNameAndSex" parameterType="queryvo" resultType="User">
SELECT * FROM user WHERE username LIKE #{username} AND sex=#{sex};
</select>
</mapper>
UserMapper.xml
- if:判断用户名称不为空,且不为空字符串,则用户名称作为查询条件
- if:判断用户性别不为空,且不为空字符串,则用户性别作为查询条件
<?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="com.itheima.dao.UserMapper">
<!--直接根据用户名和性别查询-->
<!-- <select id="findUserByNameAndSex" parameterType="queryvo" resultType="User">
SELECT * FROM user WHERE username LIKE #{username} AND sex=#{sex};
</select>-->
<!--
判断用户名称不为空,且不为空字符串,则用户名称作为查询条件
判断用户性别不为空,且不为空字符串,则用户性别作为查询条件
-->
<select id="findUserByNameAndSex" parameterType="User" resultType="User">
select * from user where
<if test="username!=null and username!=''">
username like '%${username}%'
</if>
<!-- &&表示与操作,要转义 -->
<if test="sex!=null && sex!=''">
and sex = #{sex}
</if>
</select>
</mapper>
测试
- 通过用户名和性别查询多个用户
- 只设置性别
- 名字和性别一个都不设置
@Test
public void testFindUserBySex() {
List<User> list = userMapper.findUserByNameAndSex(new QueryVo("", ""));
for (User user : list) {
System.out.println("user = " + user);
}
}
疑问:if标签如果第1个条件没有,会出现什么情况?如何解决这个问题?
因为SQL语句拼接语句不正确,出现问题。
小结
动态SQL中if标签
if标签的含义做判断如果条件为true就拼接SQL
if标签的格式:
<if test"条件">
SQL
</if>
动态SQL:where标签的作用
目标
学习where标签的使用
where标签作用
- 相当于where关键字,自动补全where这个关键字
- 去掉多余的and和or关键字
需求实现
UserMapper.xml
if标签写在where标签内部
- if:判断用户名称不为空,且不为空字符串,则用户名称作为查询条件
- if:判断用户性别不为空,且不为空字符串,则用户性别作为查询条件
<select id="findUserByNameAndSex" parameterType="queryvo" resultType="User">
SELECT * FROM user
<where>
<if test="username != null and username != ''">
AND username LIKE #{username}
</if>
<!--&&表示与操作,要转义-->
<if test="sex != null && sex != ''">
AND sex=#{sex};
</if>
</where>
</select>
小结
-
if标签的作用:
做判断,如果满足条件就拼接SQL
-
where标签的作用
- 替代WHERE关键字
- 如果有条件会自动添加WHERE关键字
- 如果没有条件就不添加WHERE关键字
- 去掉多余的AND OR
动态SQL:set标签
目标
使用set标签对修改字段拼接
编写修改SQL语句存在的问题
之前我们编写修改的SQL语句时是根据用户ID更新用户所有字段的数据
<!--根据用户ID更新用户所有字段的数据, 这样存在问题,没有值的字段也会被更新为null,最好是有数据的字段更新,没有数据的字段不更新-->
<update id="updateUser" parameterType="user">
UPDATE user SET username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address}
WHERE id=#{id};
</update>
更新数据的时候,有些为空则不用更新,怎么来处理呢?
set标签的作用
- 用在update语句中,相当于set关键字
- 去掉SQL代码片段中后面多余的逗号
需求
根据id修改用户部分数据
实现
声明mapper接口方法
/**
更新用户
*/
int updateUser(User user);
配置mapper映射文件:根据id修改用户部分数据
<!--这样存在问题,没有值的字段也会被更新为null,最好是有数据的字段更新,没有数据的字段不更新-->
<update id="updateUser" parameterType="user">
UPDATE user SET username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} WHERE id=#{id};
</update>
测试
@Test
public void testUpdateUser() throws IOException {
User u = new User();
u.setId(26);
u.setUsername("大王");
userMapper.updateUser(u);
}
这样存在问题,没有值的字段也会被更新为null,最好是有数据的字段更新,没有数据的字段不更新。
使用set标签进行判断,如果有值就更新,没有值就不更新。
<update id="updateUser" parameterType="user">
UPDATE user
<set>
<if test="username != null and username != ''">
username=#{username},
</if>
<if test="birthday != null">
birthday=#{birthday},
</if>
<if test="sex != null and sex != ''">
sex=#{sex},
</if>
<if test="address != null and address != ''">
address=#{address}
</if>
</set>
WHERE id=#{id};
</update>
set标签,可以按照条件拼接set后面的内容
小结
set标签的作用是?
相当于Set关键字,会自动去掉多余的,
foreach标签:遍历集合1
MyBatis得到数组中的数据动态拼接SQL
int row = userMapper.deleteUsers(new int[]{1, 3, 6, 8});
delete from user where id in (1, 3, 6, 8);
使用数组来封装要删除的所有的id
目标
foreach标签:遍历集合得到基本类型的数据
需求
遍历集合拼接SQL语句,实现批量删除用户
foreach标签介绍
foreach标签的属性 | 作用 |
---|---|
collection | 要遍历的集合,2个取值:list或array |
item | 设置变量名,代表每个遍历的元素 |
separator | 每次遍历完后添加一个分隔符 |
#{变量名.属性} | 来引用每个属性中值 |
实现
声明mapper接口方法
/**
批量删除用户
*/
int deleteUsers(int[] ids);
配置mapper映射文件
<delete id="deleteUsers" parameterType="list">
<!--DELETE FROM user WHERE id in (1, 2, 3);-->
DELETE FROM user WHERE id in
<!--
collection: 取值为array表示遍历的是数组
open: 遍历前添加的符号
close: 遍历最后添加的符号
separator:分隔符
item: 表示每个元素的变量名
-->
<foreach collection="array" open="(" close=");" separator="," item="temp">
#{temp}
</foreach>
</delete>
测试
@Test
public void testDeleteUser() throws IOException {
int row = userMapper.deleteUsers(new int[]{24, 25, 26});
System.out.println("row = " + row);
}
小结
foreach遍历数组格式
foreach标签:遍历集合2
目标
foreach标签:遍历集合得到自定义类型的数据
需求
使用list集合保存多个User对象,添加到数据库中
提问:一条insert语句插入多条记录的MySQL语句如何编写?
-- 同时插入多条记录
insert into user (username,birthday,sex,address) values
(#{username},#{birthday},#{sex},#{address}),
(#{username},#{birthday},#{sex},#{address}),
(#{username},#{birthday},#{sex},#{address});
/*
(#{username},#{birthday},#{sex},#{address}) 要循环的内容
*/
实现
mapper接口批量添加用户的方法
/**
批量添加用户
*/
int addUsers(List<User> users);
配置mapper映射文件
批量新增用户,参数类型是:list
<!--批量添加多条记录-->
<insert id="addUsers" parameterType="list">
insert into user (username,birthday,sex,address) values
<!--
collection 要遍历的集合使用list
item 设置变量名,代表每个遍历的元素
separator 每次遍历完后添加一个分隔符
#{变量名.属性} 来引用每个属性中值
-->
<foreach collection="list" item="user" separator=",">
(#{user.username},#{user.birthday},#{user.sex},#{user.address})
</foreach>
</insert>
测试
@Test
public void testAddUsers() throws IOException {
List<User> users = new ArrayList<>();
users.add(new User(null,"牛魔王", Date.valueOf("1980-01-30"),"男","火焰山"));
users.add(new User(null,"红孩儿", Date.valueOf("2009-05-08"),"男","火云洞"));
users.add(new User(null,"玉面狐狸", Date.valueOf("2005-11-01"),"女","狐狸洞"));
int row = userMapper.addUsers(users);
System.out.println("添加数据影响的行数 = " + row);
}
结果
小结
foreach遍历集合
<foreach collection="数组写array, List集合写list" open="在循环前要拼接的内容" close="在循环结束后要拼接的内容" separator="每次循环后要拼接的" item="循环得到的数据会放到这个变量名中">
取出数据进行拼接
</foreach>
sql和include标签
我们发现在接口映射文件中会出现很多相同的SQL语句,每个地方都写一遍有些麻烦。我们可以把相同的SQL语句抽取出来,在需要的地方引入即可。
目标
学习sql和include标签的使用
sql和include标签的作用
- sql标签:定义一段SQL语句,起个名字可以重用。
- include标签:引入上面定义的SQL代码段。
需求实现
UserMapper.xml
<!--抽取重复的SQL并取个名字-->
<sql id="commont">
INSERT INTO user (username,birthday,sex,address) VALUES
</sql>
<insert id="addUsers" parameterType="list">
<!--INSERT INTO user (username,birthday,sex,address) VALUES-->
<!--引入上面的的SQL-->
<include refid="commont"/>
<foreach collection="list" close=";" separator="," item="user">
(#{user.username},#{user.birthday},#{user.sex},#{user.address})
</foreach>
</insert>
小结
-
sql标签的作用:抽取一段SQL语句
-
include标签的作用:引入之前抽取的SQL语句,达到重复使用的目的
回顾表之间的关系
目标
回顾表之间的关系
表之间的关系分类
-
一对一关联关系
人和身份证的关系
夫妻关系
-
一对多关联关系
一个用户,有多个订单
一个老师,有多个学生
一个教室,有多个人
-
多对多关联关系
老师与学生: 一个老师教多个学生(一对多),一个学生由多个老师教学(一对多)
用户与角色: 一个用户有多个角色,一个角色有多个用户
在实际项目中,多对多关系通过中间表,看成两个一对多关联关系。
多表关联:一对一关联(重要)
目标
需求:查询1号用户和他的扩展信息,一对一关联
步骤
- 创建数据库表
- 创建模块
- 写实体类
- dao接口
- 接口映射
- 测试
创建数据库表
用户基本信息表与用户扩展信息表的关系
用户表与用户扩展信息表
DROP TABLE IF EXISTS USER;
-- 创建用户基本表
CREATE TABLE USER (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20) NOT NULL,
birthday DATE,
sex CHAR(1) DEFAULT '男',
address VARCHAR(50)
);
INSERT INTO USER VALUES (NULL, '孙悟空','1980-10-24','男','花果山水帘洞');
INSERT INTO USER VALUES (NULL, '白骨精','1992-11-12','女','白虎岭白骨洞');
INSERT INTO USER VALUES (NULL, '猪八戒','1983-05-20','男','福临山云栈洞');
INSERT INTO USER VALUES (NULL, '蜘蛛精','1995-03-22','女','盤丝洞');
SELECT * FROM USER;
-- 用户扩展信息表,一个用户对应一条用户扩展信息
CREATE TABLE user_info (
id INT PRIMARY KEY, -- 既是主键又是外键
height DOUBLE, -- 身高厘米
weight DOUBLE, -- 体重公斤
married TINYINT, -- 是否结婚,1为结婚,0为未婚
FOREIGN KEY (id) REFERENCES USER(id)
);
-- 插入用户扩展信息表
INSERT INTO user_info VALUES(1,185,90,1),(2,170,60,0);
SELECT * FROM user_info;
编写关联查询sql语句,查询1号用户和他的扩展信息
SELECT * FROM USER u INNER JOIN user_info i ON u.id = i.id WHERE u.id=1;
查询结果
创建模块
创建新的模块:mybatis_day03_01_multi_table
写实体类
类之间的关系
User类
// 用户基本信息类
public class User {
private Integer id; // 主键
private String username; // 用户名
private Date birthday; // 生日
private String sex; // 性别
private String address; // 地址
private UserInfo userInfo; // 用户扩展信息类
// 省略getter/setter
}
UserInfo类
// 用户扩展信息类
public class UserInfo {
private Integer id; // 主键
private Double height; // 身高
private Double weight; // 体重
private Boolean married; // 是否结婚
// 省略getter/setter
}
UserMapper接口
编写方法通过uid查找用户和信息
/**
通过uid查找用户和扩展信息
*/
User findUserAndInfo(int uid);
接口映射UserMapper.xml的配置
association标签的作用:用来指定类的关联关系
association标签的属性 | 说明 |
---|---|
property | 指定另一方对象的属性名字,如:userInfo |
resultMap |
配置步骤:
-
定义User的映射UserOneUserInfoMap,包含主键和所有的属性,无论属性名与列名是否相同。
-
使用association定义一对一关联映射,指定:property,column,javaType,子标签UserInfo字段的映射。
代码
<resultMap id="UserOneUserInfoMap" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<!--一对一关联关系,User类中包含一个UserInfo类,名称为userInfo-->
<association property="userInfo" javaType="com.itheima.entity.UserInfo">
<id property="id" column="id"/>
<result property="height" column="height"/>
<result property="weight" column="weight"/>
<result property="married" column="married"/>
</association>
</resultMap>
<select id="findUserAndInfo" parameterType="int" resultMap="UserOneUserInfoMap">
SELECT * FROM USER u INNER JOIN user_info i ON u.id = i.id WHERE u.id=#{id};
</select>
测试
public class TestUserMapper {
private static SqlSessionFactory factory; //会话工厂
private SqlSession session; //会话
// 创建工厂对象
// 创建会话对象,自动提交事务
@Before
public void begin() throws IOException {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
factory = builder.build(inputStream);
session = factory.openSession();
}
// 关闭会话
@After
public void end() {
session.commit();
session.close();
}
@Test
public void testFindUserAndInfo() {
userMapper = session.getMapper(UserMapper.class); // 得到代理对象
User user = userMapper.findUserAndInfo(1);
System.out.println("user = " + user);
}
}
注意
关联查询的时候使用resultMap封装结果时,即使字段名和类的属性名相同也需要指定。
小结
-
一对一关联查询实体类的关系?
-
在接口映射文件种使用哪个标签指定一对一关联?
多表关联:一对多关联(重要)
目标
需求:查询某个用户,并且查询关联用户的多个订单信息
分析用户订单数据模型
步骤
- 创建数据库表
- 写实体类
- dao接口
- 接口映射
- 测试
创建数据库表
-- 创建订单表
CREATE TABLE order_form(
oid INT PRIMARY KEY AUTO_INCREMENT , -- 主键
user_id INT NOT NULL, -- 用户id,外键
number VARCHAR(20), -- 订单编号
create_time DATETIME, -- 下单时间
note VARCHAR(100), -- 备注
FOREIGN KEY(user_id) REFERENCES USER(id) -- 外键约束,关联主表的主键
);
-- 添加订单数据
INSERT INTO order_form VALUES(NULL, 1,'10001001', NOW(), '小米9袋'),(NULL, 1,'10001002', NOW(), '9袋小米'),(NULL, 1,'10001003', NOW(), '小米9手机');
INSERT INTO order_form VALUES(NULL, 2,'10001004', NOW(), '逃生锤'),(NULL, 2,'10001005', NOW(), '安全带');
SELECT * FROM order_form;
表与表的关系
查询某个用户,并且查询关联用户的多个订单信息
select * from user u inner join order_form o on u.id = o.user_id where u.id=1;
查询结果
写实体类
修改用户订单类
- 添加一个用户类
- 生成get和set方法
package com.itheima.entity;
import java.sql.Timestamp;
/**
订单实体类
*/
public class OrderForm {
private Integer oid; //主键
private Integer userId; //外键
private String number; //订单号
private Timestamp createTime; //下单时间
private String note; //备注信息
// 省略getter/setter/toString
}
用户实体类对象
- 建立用户到订单的一对多关联关系,在User中创建List<OrderForm> orders
package com.itheima.entity;
import java.sql.Date;
import java.util.List;
/**
用户实体类对象 */
public class User {
private Integer id; //注:使用大写
private String username;
private Date birthday;
private String sex;
private String address;
private List<OrderForm> orders; //对应的所有订单信息
private UserInfo userInfo; //对应的用户信息
// 省略getter/setter/toString
}
声明UserMapper接口方法
/*
查询指定的用户数据
并且关联查询用户的所有订单数据
*/
User findUserAndOrders(int uid);
UserMapper.xml映射配置文件
collection的作用:一对多的关联映射
collection的属性 | 说明 |
---|---|
property | 多方属性的名字,指:orders |
javaType | 属性的类型 |
ofType | 每个元素的类型:OrderForm |
resultMap | 指定订单的映射 |
接口映射文件配置
-
定义订单的映射orderMap
-
collection:配置一对多关联关系,指定property,ofType,resultMap为orderMap
-
查询某个用户,并且查询关联的多个订单信息
UserMapper.xml
<?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="com.itheima.dao.UserMapper">
...
<resultMap id="useMapMore" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<!--一对多关系,一个User包含多个订单,使用collection-->
<collection property="orders" javaType="List" ofType="OrderForm">
<id property="oid" column="oid"/>
<result property="userId" column="user_id"/>
<result property="number" column="number"/>
<result property="createTime" column="create_time"/>
<result property="note" column="note"/>
</collection>
</resultMap>
<!--查询指定的用户数据,并且关联查询用户的所有订单数据-->
<select id="findUserAndOrders" parameterType="int" resultMap="useMapMore">
SELECT * FROM USER u INNER JOIN order_form o ON u.id = o.user_id WHERE u.id=#{uid};
</select>
</mapper>
测试
- 查询1号用户的所有订单
- 得到用户信息
- 输出这个用户所有的订单信息
@Test
public void testFindUserAndOrders() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.findUserAndOrders(1);
System.out.println("user = " + user);
List<OrderForm> orders = user.getOrders();
for (OrderForm order : orders) {
System.out.println(order);
}
}
执行效果
小结
-
实体类
-
查询的结果映射
多表关联:多对多关联
目标
多对多关联关系,可以通过中间表看成两个双向的一对多关联关系。
用户与角色多对多关系模型
一个用户对应多种角色
一种角色可以有多个用户
步骤
- 创建数据库表
- 写实体类
- dao接口
- 接口映射
- 测试
创建角色表和中间表
/*创建角色表*/
CREATE TABLE `role` (
role_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '角色 id(主键)',
role_name VARCHAR(32) NOT NULL COMMENT '角色名称',
role_detail VARCHAR(100) DEFAULT NULL COMMENT '角色描述'
);
-- 插入角色记录
INSERT INTO role(role_name,role_detail) VALUES('校长','全校的管理者');
INSERT INTO role(role_name,role_detail) VALUES('讲师','传道授业解惑');
INSERT INTO role(role_name,role_detail) VALUES('班主任','班级的灵魂');
INSERT INTO role(role_name,role_detail) VALUES('助教','大家的朋友');
SELECT * FROM role;
/*创建用户角色中间表*/
CREATE TABLE user_role (
user_id INT NOT NULL COMMENT '用户 id',
role_id INT NOT NULL COMMENT '角色 id',
PRIMARY KEY (user_id,role_id), -- 复合主键
FOREIGN KEY (user_id) REFERENCES `user`(id),
FOREIGN KEY (role_id) REFERENCES role(role_id)
);
INSERT INTO user_role(user_id,role_id) VALUES(1,1); -- 1 号用户对应 1 号角色
INSERT INTO user_role(user_id,role_id) VALUES(2,2);
INSERT INTO user_role(user_id,role_id) VALUES(6,2);
INSERT INTO user_role(user_id,role_id) VALUES(1,3);
INSERT INTO user_role(user_id,role_id) VALUES(2,1);
INSERT INTO user_role(user_id,role_id) VALUES(2,4);
SELECT * FROM user_role;
需求:查询 1 号用户有哪些角色
SELECT u.*, r.* FROM USER u INNER JOIN user_role ur ON u.id = ur.user_id INNER JOIN role r ON
ur.role_id = r.role_id WHERE u.id = 1;
类之间的关系
Role 角色实体类,对应多个用户
public class Role {
private Integer roleId;
private String roleName;
private String roleDetail;
// 一个角色对应多个用户
private List<User> users;
// 省略get/set/构造方法
}
public class User {
private Integer id; // 主键
private String username; // 用户名
private Date birthday; // 生日
private String sex; // 性别
private String address; // 地址
private UserInfo userInfo; // 用户扩展信息类
private List<OrderForm> orders; // 用户对应的所有订单信息
private List<Role> roles; // 一个用户对应多个角色
// 省略get/set/构造方法
}
声明 UserMapper 接口
/**
通过 uid 查找用户和他的所有角色
*/
User findRolesByUserId(int uid);
/**
通过 role_id 查找角色和他的所有用户
*/
Role findUsersByRoleId(int role_id);
配置 UserMapper.xml
<?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="com.itheima.dao.UserMapper">
<resultMap id="useRoleMapMore" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<collection property="roles" javaType="list" ofType="role">
<id property="roleId" column="role_id"/>
<result property="roleName" column="role_name"/>
<result property="roleDetail" column="role_detail"/>
</collection>
</resultMap>
<!--多对多,通过用户id查询到一个用户里面的多个角色-->
<select id="findRolesByUserId" parameterType="int" resultMap="useRoleMapMore">
SELECT u.*, r.* FROM USER u INNER JOIN user_role ur ON u.id = ur.user_id INNER JOIN role r ON ur.role_id = r.role_id WHERE u.id = #{id};
</select>
<resultMap id="useRoleMapMore2" type="role">
<id property="roleId" column="role_id"/>
<result property="roleName" column="role_name"/>
<result property="roleDetail" column="role_detail"/>
<collection property="users" javaType="list" ofType="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
</collection>
</resultMap>
<!--多对多,通过用户id查询到一个用户里面的多个角色-->
<select id="findUsersByRoleId" parameterType="int" resultMap="useRoleMapMore2">
SELECT u.*, r.* FROM USER u INNER JOIN user_role ur ON u.id = ur.user_id INNER JOIN role r ON ur.role_id = r.role_id WHERE r.role_id = #{role_id};
</select>
</mapper>
执行测试
@Test
public void test01() {
Role role = userMapper.findUsersByRoleId(1);
System.out.println("role = " + role);
for (User user : role.getUsers()) {
System.out.println(user);
}
}
小结
多对多在数据库里建立第三张表中间表
在实体类中配置,双向一对多
配置接口映射文件
关联映射小结
一对一
类之间的关系
配置关系
一对多
类之间的关系
配置关系
association:一对一的延迟加载(重要)
复制模块
- 复制项目为mybatis_day03_02_lazy
- 删除UserMapper,UserMapper.xml和TestUserMapper.java中多余的代码
刚才我们一对一使用表连接查询,直接查询出两张表中的数据
SELECT * FROM USER u INNER JOIN user_info i ON u.id = i.id WHERE u.id=1;
有时候查询用户信息,不需要他的扩展信息。但后面有可能又需要用到,这时候可以通过延迟加载来实现。
目标
学习1对1的延迟加载
延迟加载介绍
延迟加载概念:也叫懒加载。指的是按需加载,在实际用到数据的时候才加载。
如:查询用户信息,不需要他的扩展信息。但后面有可能又需要用到,这时候可以通过延迟加载来实现。当需要扩展信息的时候,再发送一条SQL语句来查询扩展信息。好处是,只有在需要的时候才查询相应数据。提升查询的效率。相当于每次只查询1张表,而不是一次使用表连接查询所有的信息。
一对一关联查询使用标签:association
一对多关联查询使用标签:collection
需求
-
通过id查询1号用户User的基本信息
-
使用延迟加载的方式,关联查询出对应的用户扩展信息UserInfo
SQL语句分析
SELECT * FROM USER WHERE id=1;
SELECT * FROM user_info WHERE id=1;
配置步骤
-
配置association的1对1关联查询
-
关联属性再发送一条SQL语句去查询从表中的数据
修改UserMapper接口中的方法
/**
持久层接口:UserMapper
*/
public interface UserMapper {
/**
通过id查询1个用户
*/
User findUserById(int id);
/**
通过id查询1个用户扩展信息
*/
UserInfo findUserInfoById(int id);
}
修改UserMapper.xml一对一关联查询配置延迟加载
<?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="com.itheima.dao.UserMapper">
<resultMap id="useMapOne" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<!--一对一关联关系,User类中包含一个UserInfo类,名称为userInfo-->
<association property="userInfo" column="id" javaType="UserInfo" select="findUserInfoById" fetchType="lazy"/>
</resultMap>
<select id="findUserById" parameterType="int" resultMap="useMapOne">
SELECT * FROM USER WHERE id=#{uid};
</select>
<select id="findUserInfoById" parameterType="int" resultType="UserInfo">
SELECT * FROM user_info WHERE id=#{uid};
</select>
</mapper>
只查询用户基本数据
- 通过id查询用户对象
- 输出用户名和性别的属性
注意:
不要使用断点调试,无法看到懒加载
不要直接输出user对象,因为会触发toString()方法,导致立即加载userInfo对象
@Test
public void testFindUserAndInfo() {
User user = userMapper.findUserById(1);
System.out.println(user.getUsername());
System.out.println("----------------");
System.out.println(user.getUserInfo());
}
测试延迟加载用户数据
查询用户之后,再查询用户扩展信息
执行效果
小结
- association标签
association标签的属性 | 说明 |
---|---|
property | 一对一对象的成员变量名 |
column | 这个字段的值会作为第二个SQL语句的参数 |
select | 要延迟执行的第二条SQL |
-
一对一延迟加载需要将一个SQL拆成两个SQL
-
配置映射的关系
- 配置延迟加载属性
collection:一对多的延迟加载(重要)
目标
一对多的延迟加载
需求
查询1号用户的数据,并且关联查询出这个用户所有的订单数据,使用延迟加载方式实现。
SQL语句分析
SELECT * FROM USER u INNER JOIN order_form o ON u.id = o.user_id WHERE u.id=1;
-- 查询1号用户数据
SELECT * FROM USER WHERE id=1;
-- 查询1号用户的订单表,使用延迟加载方式实现
SELECT * FROM order_form WHERE user_id=1;
查询结果
UserMapper接口
添加方法:通过userId查询所有的订单
/**
通过id查询1个用户
*/
User findUserById(int id);
/**
通过userId查询这个用户所有的订单信息
*/
List<OrderForm> findOrdersByUserId(int userId);
修改UserMapper.xml
<?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="com.itheima.dao.UserMapper">
<resultMap id="useMapOne" type="user">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<!--一对一关联关系,User类中包含一个UserInfo类,名称为userInfo-->
<association property="userInfo" column="id" javaType="UserInfo" select="findUserInfoById" fetchType="lazy"/>
<!--多对多,通过用户id查询到一个用户里面的多个角色-->
<collection property="orders" column="id" javaType="List" ofType="OrderForm" select="findOrdersByUserId" fetchType="lazy"/>
</resultMap>
<select id="findUserById" parameterType="int" resultMap="useMapOne">
SELECT * FROM USER WHERE id=#{uid};
</select>
<select id="findUserInfoById" parameterType="int" resultType="UserInfo">
SELECT * FROM user_info WHERE id=#{uid};
</select>
<select id="findOrdersByUserId" parameterType="int" resultType="OrderForm">
SELECT * FROM order_form WHERE user_id=#{uid};
</select>
</mapper>
测试
-
不查询订单数据,只输出用户的名字和性别
-
测试延迟加载订单数据,输出所有的订单
@Test
public void testFindUserAndOrder() {
User user = userMapper.findUserById(1);
System.out.println(user.getUsername());
System.out.println("----------------");
// System.out.println(user.getOrders());
for (OrderForm order : user.getOrders()) {
System.out.println(order);
}
}
效果
配置sqlMapConfig.xml,开启MyBatis延迟加载
如果有多个延迟加载需要配置。可以在sqlMapConfig.xml核心配置文件中统一配置一次即可
开启延迟加载的settings中的lazyLoadingEnabled
<!--全局设置-->
<settings>
<!--开启延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
小结
-
collection标签的属性
collection标签的属性 说明 property 类中多方的成员变量名 column 这个字段的值会作为延迟加载的SQL的参数 select 延迟加载的SQL
Mybatis一级缓存
现在我们每次执行相同的SQL语句都是去数据库中查询,存在效率问题。Mybatis提供了缓存方案可以提高重复查询数据的效率。
目标
学习Mybatis一级缓存
Mybatis缓存介绍
Mybatis 框架提供了缓存策略,通过缓存策略可以减少查询数据库的次数,提升系统性能。在 Mybatis 框架中
缓存分为一级缓存和二级缓存。
一级缓存概述
一级缓存是 sqlSession 范围的缓存,只能在同一个 sqlSession 内部有效。它本身已经存在,一级缓存可以直
接使用,不需要手动处理。
一级缓存测试
public class TestCache {
// 测试一级缓存
@Test
public void testPrimaryCache() throws IOException {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = builder.build(inputStream);
SqlSession session = factory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class); // 得到代理对象
/*
* 1.先根据id=1去一级缓存找
* 2.没有找到,再查询数据库
* 3.查询到结果后,放入一级缓存
*/
User user1 = userMapper.findUserById(1);
/*
* 1.先根据id=1去一级缓存找
* 2.找到,就直接返回,不查询数据库
*/
User user2 = userMapper.findUserById(1);
session.commit(); // 提交事务,清空一级缓存
session.close();
}
}
一级缓存分析
第一次查询数据时,会将查询的数据放入一级缓存中。后面的相同查询直接从缓存中获取。
一级缓存是 SqlSession 范围缓存。当调用 SqlSession 的修改、添加、删除、提交、关闭等方法时,一级缓存会被清空。
清空一级缓存
public class TestCache {
// 测试一级缓存
@Test
public void testPrimaryCache() throws IOException {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = builder.build(inputStream);
SqlSession session = factory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class); // 得到代理对象
/*
* 1.先根据id=1去一级缓存找
* 2.没有找到,再查询数据库
* 3.查询到结果后,放入一级缓存
*/
User user1 = userMapper.findUserById(1);
// 调用clearCache()方法,清空一级缓存的内容
session.clearCache();
// 当调用 SqlSession 的修改、添加、删除、提交、关闭等方法时,一级缓存会被清空。
// userMapper.deleteUserById(5);
/*
* 1.先根据id=1去一级缓存找
* 2.找到,就直接返回,不查询数据库
*/
User user2 = userMapper.findUserById(1);
session.commit(); // 提交事务,清空一级缓存
session.close();
}
}
小结
-
一级缓存的范围?
同一个SqlSession有效
-
一级缓存何时失效?
执行了增删改,提交事务,clearCache方法。清空一级缓存
Mybatis二级缓存
目标
学习Mybatis二级缓存
二级缓存概述
二级缓存是 mapper 映射级别缓存,作用范围跨越SqlSession,即可以在多个 SqlSession 之间共享二级缓存
数据。
二级缓存关键点
- 实体类需要实现Serializable接口
- 至少要准备2个SqlSession,再进行测试。
配置二级缓存
在 sqlMapConfig.xml 配置开启二级缓存,打开mybatis官方文档,找到settings配置:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
修改实体类实现Serializable接口
public class User implements Serializable {
private Integer id; // 主键
private String username; // 用户名
private Date birthday; // 生日
private String sex; // 性别
private String address; // 地址
// 省略其他
}
在 UserMapper.xml 开启二级缓存使用
二级缓存还需要在具体的 mapper 映射文件中明确开启,这样做的原因是缓存数据要消耗资源,只有在需要使
用的时候开启,可以避免资源的过度消耗。
<mapper namespace="com.itheima.dao.UserMapper">
<!--开启二级缓存,当前Mapper里的所有查询的数据都会放入二级缓存中-->
<cache/>
<select id="findUserById" parameterType="int" resultMap="useMapOne">
SELECT * FROM USER WHERE id=#{uid};
</select>
</mapper>
测试
// 测试二级缓存
@Test
public void testSecondCache() throws IOException {
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = builder.build(inputStream);
SqlSession session1 = factory.openSession();
UserMapper userMapper1 = session1.getMapper(UserMapper.class); // 得到代理对象
User user1 = userMapper1.findUserById(1);
session1.close(); // 需要先关掉第一个sqlsession
SqlSession session2 = factory.openSession();
UserMapper userMapper2 = session2.getMapper(UserMapper.class); // 得到代理对象
User user2 = userMapper2.findUserById(1);
session2.close();
}
小结
二级缓存范围,跨域多个SqlSession,只要是同一个Mapper就可以
二级缓存使用步骤:
-
类要实现Serializable接口
-
在Mybatis核心配置文件中添加
<settings> <setting name="cacheEnabled" value="true"/> </settings>
-
在接口的映射文件中配置
<cache/>
-
测试,需要准备2个SqlSession
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(TestMybatis.class.getResourceAsStream("/sqlMapConfig.xml")); SqlSession sqlSession1 = factory.openSession(); // 第一个SqlSession UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); User user1 = mapper1.findUserById(1); sqlSession1.close(); // 需要关闭sqlSqlsession,sqlSession里面的数据才会保存到二级缓存中 // 第二个SqlSession SqlSession sqlSession2 = factory.openSession(); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); User user2 = mapper2.findUserById(1); sqlSession2.close();
注解实现:一对一关联查询
目标
使用注解实现一对一关联查询
概述
MyBatis框架中除了使用XML配置文件实现关系映射之外,也可以使用注解实现复杂的关系映射(一对一关联查询,一对多关联查询)。
复杂关系映射注解介绍
注解 | 描述 | 对应xml配置标签 |
---|---|---|
@One | 用于一对一关联映射 | association |
@Many | 用于一对多的关联映射 | collection |
操作步骤
-
复制项目为mybatis_day03_03_ann_multi_table
-
删除UserMapper.xml文件,使用注解
-
修改sqlMapConfig.xml
保持延迟加载的配置
sqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--定义类的别名-->
<typeAliases>
<package name="com.itheia.entity"/>
</typeAliases>
<!--环境配置-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!--连接池的配置-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--实体类映射文件-->
<mappers>
<mapper class="com.itheima.dao.UserMapper"/>
</mappers>
</configuration>
UserMapper接口
package com.itheima.dao;
import com.itheima.entity.OrderForm;
import com.itheima.entity.User;
import com.itheima.entity.UserInfo;
import java.util.List;
/**
持久层接口:UserMapper */
public interface UserMapper {
/**
通过id查询1个用户
*/
User findUserById(int id);
/**
通过id查询1个用户扩展信息,1对1
*/
UserInfo findUserInfoById(int id);
/**
通过userId查询这个用户所有的订单信息,1对多
*/
List<OrderForm> findOrdersByUserId(int userId);
}
需求
查询1个用户数据,并且采用延迟加载关联查询出用户扩展数据
在UserMapper接口中增加查询方法
-
编写方法:通过id查询用户扩展信息
- 方法名:findUserInfoById
- 使用@Select注解编写SQL语句
-
编写方法:通过id查询1个用户
- 方法名:findUserById
- @Select编写查询
- @Results配置1对1关联映射
代码
public interface UserMapper {
// 通过uid查找用户和扩展信息
@Select("SELECT * FROM user WHERE id=#{uid}")
@Results({
@Result(property = "id", column = "id", id = true), // 主键映射
@Result(property = "userInfo", column = "id", javaType = UserInfo.class,
// select表示要查询的方法名 fetchType指定为LAZY表示延迟加载
one = @One(select = "findUserInfoById", fetchType = FetchType.LAZY))
})
User findUserById(int uid);
// 通过uid查找用户和扩展信息
@Select("SELECT * FROM user_info WHERE id=#{uid}")
UserInfo findUserInfoById(int uid);
}
测试
- 只查询用户名和性别
- 同时查询用户扩展信息
@Test
public void testFindUserAndUserInfo() {
User user = userMapper.findUserById(1);
System.out.println(user.getUsername());
System.out.println("----------------");
System.out.println(user);
System.out.println(user.getUserInfo());
}
小结
注解 | 作用 |
---|---|
@Select | 表示查询SQL语句 |
@Results | 对查询结果进行映射 |
@Result | column:表中字段的名字 property:类中的成员变量 one :表示一对一的关系 |
@One | select:延迟要执行的SQL语句 fetchType: LAZY, 延迟加载 |
注解实现:一对多关联查询
目标
- 查询1号用户数据
- 关联查询出1号用户全部订单数据
- 采用延迟加载方式实现
UserMapper接口
-
通过user_id查询当前用户订单的方法
- 编写findOrdersByUserId方法
- 使用@Select注解
-
修改findUserById()方法,增加1对多延迟加载配置
代码
package com.itheima.dao;
import com.itheima.entity.OrderForm;
import com.itheima.entity.User;
import com.itheima.entity.UserInfo;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.FetchType;
import java.util.List;
/**
持久层接口:UserMapper */
public interface UserMapper {
/**
通过id查询1个用户
*/
@Select("select * from user where id=#{id}")
@Results({
@Result(column = "id", property = "id", id = true), //主键映射
// 一对一的配置
@Result(column = "id", property = "userInfo",
//select表示要查询的方法名 fetchType指定为LAZY表示延迟加载
one = @One(select = "findUserInfoById", fetchType = FetchType.LAZY)),
//一对多的配置
@Result(column = "id", property = "orders",
many = @Many(select = "findOrdersByUserId", fetchType = FetchType.LAZY))
})
User findUserById(int id);
/**
通过id查询1个用户扩展信息,1对1
*/
@Select("select * from user_info where id=#{id}")
UserInfo findUserInfoById(int id);
/**
通过userId查询这个用户所有的订单信息,1对多
*/
@Select("select * from order_form where user_id=#{id}")
@Results({
@Result(column = "user_id", property = "userId")
})
List<OrderForm> findOrdersByUserId(int userId);
}
测试
- 只查用户信息
- 查询订单信息
@Test
public void testFindUserById() {
User user = userMapper.findUserById(1);
System.out.println("用户名:" + user.getUsername());
//延迟加载多方
List<OrderForm> orders = user.getOrders();
for (OrderForm order : orders) {
System.out.println(order);
}
//System.out.println(user); //toString()不会延迟加载,默认查询所有的信息
//得到扩展信息
//UserInfo userInfo = user.getUserInfo();
// System.out.println("身高:" + userInfo.getHeight());
}
小结
@Result注解属性 | 说明 |
---|---|
column | 字段名,这个字段的值会作为下一个SQL语句的参数 |
property | 类中的成员变量 |
many | 一对多的配置 |
@Many注解属性 | 说明 |
---|---|
select | 要执行的SQL |
fetchType | LAZY懒加载 |
学习总结
-
掌握 MyBatis 多表关联查询
一对一:association
一对多:collection
多对多: 就是两个一对多
-
MyBatis 的延迟加载
-
注解实现多表关联查询
-
掌握二级缓存的开启配置
-
核心配置文件
<settings> <setting name="cacheEnabled" value="true"/> </settings>
-
接口映射文件配置
<!--这个Mapper里面所有的查询出的对象都会放到二级缓存中--> <cache/>
//select表示要查询的方法名 fetchType指定为LAZY表示延迟加载 one = @One(select = "findUserInfoById", fetchType = FetchType.LAZY)), //一对多的配置 @Result(column = "id", property = "orders", many = @Many(select = "findOrdersByUserId", fetchType = FetchType.LAZY))
})
User findUserById(int id);/**
通过id查询1个用户扩展信息,1对1
*/
@Select(“select * from user_info where id=#{id}”)
UserInfo findUserInfoById(int id);/**
通过userId查询这个用户所有的订单信息,1对多
*/
@Select(“select * from order_form where user_id=#{id}”)
@Results({
@Result(column = “user_id”, property = “userId”)
})
List findOrdersByUserId(int userId);
} -
#### 测试
1. 只查用户信息
2. 查询订单信息
```java
@Test
public void testFindUserById() {
User user = userMapper.findUserById(1);
System.out.println("用户名:" + user.getUsername());
//延迟加载多方
List<OrderForm> orders = user.getOrders();
for (OrderForm order : orders) {
System.out.println(order);
}
//System.out.println(user); //toString()不会延迟加载,默认查询所有的信息
//得到扩展信息
//UserInfo userInfo = user.getUserInfo();
// System.out.println("身高:" + userInfo.getHeight());
}
小结
@Result注解属性 | 说明 |
---|---|
column | 字段名,这个字段的值会作为下一个SQL语句的参数 |
property | 类中的成员变量 |
many | 一对多的配置 |
@Many注解属性 | 说明 |
---|---|
select | 要执行的SQL |
fetchType | LAZY懒加载 |