mybatis进阶
接口代理方式实现 Dao 层
- 接口代理方式可以让我们只编写接口即可,而实现类对象由 MyBatis 生成。
- 实现规则
- 映射配置文件中的名称空间必须和 Dao 层接口的全类名相同。
- 映射配置文件中的增删改查标签的 id 属性必须和 Dao 层接口的方法名相同。
- 映射配置文件中的增删改查标签的 parameterType 属性必须和 Dao 层接口方法的参数相同。
- 映射配置文件中的增删改查标签的 resultType 属性必须和 Dao 层接口方法的返回值相同。
- 获取动态代理对象
- SqlSession 功能类中的 getMapper() 方法。
MyBatis映射配置文件 - 动态 SQL
动态SQL:if和where标签
目标
- 动态SQL语句if标签的使用
- 动态SQL语句where标签的使用
应用场景
如果使用一个实体类来封装所有的查询条件,如果这个实体类属性有值,则参与查询,否则就不做为查询条件。
if标签
作用
判断某个条件是否为真,如果为真则拼接标签体中的SQL语句
UserMapper.xml
- 根据用户名称和性别查询用户
- if:判断用户名称不为空,且不为空字符串,则作为查询条件
- if:判断用户性别不为空,且不为空字符串,则作为查询条件
<!--
根据用户名称和性别查询用户
if 标签的作用:判断某个条件是否为真,如果为真则拼接标签体中的SQL语句
-->
<select id="findUserByNameAndSex" resultType="user">
select * from user where
<if test="username!=null and username!=''">
username like "%"#{username}"%"
</if>
<if test="sex!=null and sex!=''">
and sex = #{sex}
</if>
</select>
测试代码
- 通过用户名和性别查询多个用户
- 同时设置名字和性别
- 只设置名字或只设置性别
- 名字和性别一个都不设置
@Test
public void testFindUserByNameAndSex() {
//封装查询条件
User user = new User();
user.setUsername("精");
user.setSex("女");
List<User> users = userMapper.findUserByNameAndSex(user);
System.out.println(users);
}
疑问:if标签如果第1个条件没有,会出现什么情况?如何解决这个问题?
如果单独使用if标签,有时会出现问题,因为拼接的SQL语句是不正确的
where标签
作用
- 相当于where关键字,在SQL语句中可以省略where关键字
- 根据拼接SQL字符串的情况,自动去掉多余的and, or , where关键字
需求
- 只查性别
- 同时查姓名和性别
- 一个条件都不查
UserMapper.xml
- 根据用户名称和性别查询用户
- if标签写在where标签内部
- if:判断用户名称不为空,且不为空字符串,则作为查询条件
- if:判断用户性别不为空,且不为空字符串,则作为查询条件
<select id="findUserByNameAndSex" resultType="user">
select * from user
<where>
<if test="username!=null and username!=''">
username like "%"#{username}"%"
</if>
<if test="sex!=null and sex!=''">
and sex = #{sex}
</if>
</where>
</select>
小结
- if标签的作用:如果判断条件为真,就拼接if标签体中的SQL语句
- where标签的作用:
- 相当于where关键字
- 去掉多余的and, or, where关键字
set标签
目标
set标签的使用
作用
用于update标签
- 相当于update中set关键字
- 同时修改多个字段,字段之间使用逗号分隔。可能拼接的时候会出现多余的逗号,set标签可以去掉多余的逗号
应用场景
更新用户信息的时候,有些实体类的属性为空,为空就不更新,不为空才更新这个字段。
需求
动态修改用户数据
- 如果username属性不为空,则更新这个字段。
- 如果sex不为空则更新这个字段
效果
声明mapper接口方法
编写更新用户的方法,参数是user对象
/**
* 修改姓名和性别
*/
int updateUser(User user);
配置mapper映射文件
通过id来更新用户名或性别
- update标签更新用户数据
- 如果username不为空而且不为空串,则更新
- 如果sex不为空而且不为空串,则更新
- 最后加上where条件
注:每个更新的字段后面都加上了逗号
<!--
1. 如果username属性不为空,则更新这个字段。
2. 如果sex不为空则更新这个字段
-->
<update id="updateUser">
update user
<set>
<if test="username!=null and username!=''">
username=#{username},
</if>
<if test="sex!=null and sex!=''">
sex=#{sex}
</if>
</set>
where id=#{id}
</update>
测试
@Test
public void testUpdateUser() {
//封装实体对象
User user = new User();
user.setUsername("黑熊精");
user.setSex("男");
user.setId(8);
//调用方法
int row = userMapper.updateUser(user);
System.out.println("更新了" + row + "行记录");
}
小结
set标签的作用是?
- 相当于更新语句中set关键字
- 可以去掉多余的逗号
foreach标签:参数类型是集合
目标
foreach标签的使用:参数类型是集合的情况
作用
遍历集合或数组,将集合或数组中每个元素都做为SQL语句的一部分进行拼接,拼接多次
需求
使用list集合封装多个User对象,添加到数据库中。
声明mapper接口方法
提问:一条insert语句插入多条记录的MySQL语句如何编写?
insert into 表名 values (全部列名),(全部列名),(全部列名)
批量添加用户的方法
- 参数是List,包含多个User对象
int addUsers(List<User> users);
配置mapper映射文件
UserMapper.xml
批量新增用户,参数类型是:list
<!--使用foreach标签遍历一个集合,生成SQL语句-->
<insert id="addUsers">
insert into user (username, birthday, sex, address) values
<!--
foreach标签的属性:
collection 指定是集合还是数组,如果是集合使用list,如果是数组使用array
item 表示集合中每个元素的变量名,这里是一个实体类对象
separator 每次拼接完以后,添加1个符号分隔
-->
<foreach collection="list" item="user" separator=",">
(#{user.username},#{user.birthday},#{user.sex},#{user.address})
</foreach>
</insert>
测试
- 创建ArrayList,每个元素是User
- 调用方法添加多条记录
/**
* 添加多个用户
*/
@Test
public void testAddUsers() {
//创建一个List集合,添加3个元素
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标签的属性 | 作用:用于遍历集合或数组,多次拼接生成SQL语句 |
---|---|
collection | 取值:如果是集合使用list,如果是数组使用array |
item | 代表集合中每个元素,给元素定义一个名字 |
separator | 每次遍历完成以后添加1个分隔符 |
#{变量名.属性} | 标签体中引用的时候,必须使用变量名.属性名 |
foreach标签:参数类型是数组
目标
foreach标签:参数类型是数组
需求
实现批量删除用户
分析
删除多条记录的SQL语句如何编写?
delete from 表名 where id in (1,3,5)
声明mapper接口方法
/**
批量删除用户
*/
int deleteUsers(int[] ids);
配置mapper映射文件
foreach标签
- 注:parameterType 因为内置别名中没有数组类型的参数,所以当参数传递的是list和数组,都写成list
UserMapper.xml
<!--批量删除用户 -->
<delete id="deleteUsers">
delete from user where id in
<!--
collection: 表示要遍历的集合类型,数组使用array
item: 表示其中每个元素的变量名
open:表示循环开始前添加一个符号,只会执行1次
separator 每次拼接完以后,添加1个符号分隔,执行多次
close:表示循环结果以后添加一个符号,只会执行1次
-->
<foreach collection="array" open="(" item="id" separator="," close=")">
#{id}
</foreach>
</delete>
测试代码
- 删除多个用户
- 返回影响的行数
//删除多个用户
@Test
public void testDeleteUsers() {
int [] ids = {2,4,6};
int row = userMapper.deleteUsers(ids);
System.out.println("删除了" + row + "条记录");
}
小结
foreach标签中的属性 | 说明 |
---|---|
collection | 如果遍历数组,取值是array |
open | 遍历开始前添加1次符号 |
close | 遍历结束后添加1次符号 |
item | 每次元素的变量名,通过#{变量名}来引用 |
separator | 每次遍历的时候添加分隔符 |
sql和include标签
目标
学习sql和include标签的作用
作用
- sql标签:定义一个SQL语句代码段,并且使用id设置唯一的标识
- include标签:包含上面定义的语句代码段
这2个标签是要配合使用的,对定义的SQL代码段进行重用
需求
应用场景:以前讲过分页的操作,至少要查询2条SQL语句
- 这一页的数据,同时满足生日在这个范围之类
- 总共有多少条记录,同时满足生日在这个范围之类
如果有多条SQL语句,它后面的where查询条件是一样的,我们就可以将这个查询条件定义成一个代码片段进行重用。
- 通过生日最小值,最大值的区间,组合查询多个用户。
select * from user where 条件
- 使用相同的查询条件,查询一共有多少条记录。
select count(*) from user where 条件
上面两条语句where后面的条件是一样的,可以考虑重用。
步骤
- 使用Map封装上面的条件: minDate,maxDate
- 在UserMapper.xml中使用sql标签定义查询条件代码块
- 在查询方法中,使用include引用上面的条件代码块
- 查询符合条件的用户信息,返回List<User>
- 查询符合条件的记录数,返回int
- 注:关于大于号小于号的处理。可以使用<(小于),>(大于)或者使用CDATA
UserMapper接口
/**
* 查询满足条件的所有用户
* Map封装多个查询条件
*/
List<User> findUsersByMap(Map<String, String> map);
/**
* 查询满足条件的记录有多少条
*/
int findCountByMap(Map<String, String> map);
UserMapper.xml
<!--
1. sql标签定义查询条件代码块
Map = {minDate='1999-9-9', maxDate='2000-1-1'}
在XML中<或>符号是有特殊含义的,通常不能直接写在XML中
有两种解决方案:
1) 使用转义:< >
2) 使用CDATA包裹起来的内容表示纯文本内容,XML解析器不对它进行解析
-->
<sql id="sqlCondition">
<where>
<if test="minDate!=null and minDate!=''">
birthday >= #{minDate}
</if>
<if test="maxDate!=null and maxDate!=''">
<![CDATA[
and birthday <= #{maxDate}
]]>
</if>
</where>
</sql>
<!--
include标签的作用:引用使用sql标签定义的代码段
属性:refid对应上面sql的id值
-->
<select id="findUsersByMap" resultType="user">
select * from user <include refid="sqlCondition"/>
</select>
<select id="findCountByMap" resultType="int">
select count(*) from user <include refid="sqlCondition"/>
</select>
测试代码
@Test
public void testFindUsersByMap() {
HashMap<String, String> map = new HashMap<>();
map.put("minDate","1980-01-01");
map.put("maxDate", "1984-01-01");
List<User> users = userMapper.findUsersByMap(map);
users.forEach(System.out::println);
}
@Test
public void testFindCountByMap() {
HashMap<String, String> map = new HashMap<>();
map.put("minDate","1980-01-01");
map.put("maxDate", "1984-01-01");
int count = userMapper.findCountByMap(map);
System.out.println("满足条件的有" + count + "条记录");
}
小结
-
sql标签的作用:定义SQL语句的代码段
id属性:定义代码块的标识
-
include标签的作用:引用上面代码段
refid属性:引用上面的id的值
MyBatis 核心配置文件 – 分页插件
在核心配置文件中添加
<!--集成分页助手插件-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
//通过分页助手来实现分页功能
// 第一页:显示3条数据
//PageHelper.startPage(1,3);
// 第二页:显示3条数据
//PageHelper.startPage(2,3);
// 第三页:显示3条数据
PageHelper.startPage(3,3);
//获取分页相关参数
PageInfo<Student> info = new PageInfo<>(list);
System.out.println("总条数:" + info.getTotal());
System.out.println("总页数:" + info.getPages());
System.out.println("当前页:" + info.getPageNum());
System.out.println("每页显示条数:" + info.getPageSize());
System.out.println("上一页:" + info.getPrePage());
System.out.println("下一页:" + info.getNextPage());
System.out.println("是否是第一页:" + info.isIsFirstPage());
System.out.println("是否是最后一页:" + info.isIsLastPage());
MyBatis 多表操作(配置文件方式实现)
一对一
案例:人与身份证
<?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.table01.OneToOneMapper">
<!--配置字段和实体对象属性的映射关系-->
<resultMap id="oneToOne" type="card">
<id column="cid" property="id" />
<result column="number" property="number" />
<!--
association:配置被包含对象的映射关系
property:被包含对象的变量名
javaType:被包含对象的数据类型
-->
<association property="p" javaType="person">
<id column="pid" property="id" />
<result column="name" property="name" />
<result column="age" property="age" />
</association>
</resultMap>
<select id="selectAll" resultMap="oneToOne">
SELECT c.id cid,number,pid,NAME,age FROM card c,person p WHERE c.pid=p.id
</select>
</mapper>
标签解释:
<resultMap>:配置字段和对象属性的映射关系标签。
id 属性:唯一标识
type 属性:实体对象类型
<id>:配置主键映射关系标签。
<result>:配置非主键映射关系标签。
column 属性:表中字段名称
property 属性: 实体对象变量名称
<association>:配置被包含对象的映射关系标签。
property 属性:被包含对象的变量名
javaType 属性:被包含对象的数据类型
一对多
案例:一个班级与多个学生
<?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.table02.OneToManyMapper">
<resultMap id="oneToMany" type="classes">
<id column="cid" property="id"/>
<result column="cname" property="name"/>
<!--
collection:配置被包含的集合对象映射关系
property:被包含对象的变量名
ofType:被包含对象的实际数据类型
-->
<collection property="students" ofType="student">
<id column="sid" property="id"/>
<result column="sname" property="name"/>
<result column="sage" property="age"/>
</collection>
</resultMap>
<select id="selectAll" resultMap="oneToMany">
SELECT c.id cid,c.name cname,s.id sid,s.name sname,s.age sage FROM classes c,student s WHERE c.id=s.cid
</select>
</mapper>
标签解释:
<resultMap>:配置字段和对象属性的映射关系标签。
id 属性:唯一标识
type 属性:实体对象类型
<id>:配置主键映射关系标签。
<result>:配置非主键映射关系标签。
column 属性:表中字段名称
property 属性: 实体对象变量名称
<collection>:配置被包含集合对象的映射关系标签。
property 属性:被包含集合对象的变量名
ofType 属性:集合中保存的对象数据类型
多对多
案例:学生和课程,一个学生可以选择多门课程、一个课程也可以被多个学生所选择。
<?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.table03.ManyToManyMapper">
<resultMap id="manyToMany" type="student">
<id column="sid" property="id"/>
<result column="sname" property="name"/>
<result column="sage" property="age"/>
<collection property="courses" ofType="course">
<id column="cid" property="id"/>
<result column="cname" property="name"/>
</collection>
</resultMap>
<select id="selectAll" resultMap="manyToMany">
SELECT sc.sid,s.name sname,s.age sage,sc.cid,c.name cname FROM student s,course c,stu_cr sc WHERE sc.sid=s.id AND sc.cid=c.id
</select>
</mapper>
标签解释:
<resultMap>:配置字段和对象属性的映射关系标签。
id 属性:唯一标识
type 属性:实体对象类型
<id>:配置主键映射关系标签。
<result>:配置非主键映射关系标签。
column 属性:表中字段名称
property 属性: 实体对象变量名称
<collection>:配置被包含集合对象的映射关系标签。
property 属性:被包含集合对象的变量名
ofType 属性:集合中保存的对象数据类型
MyBatis 多表操作(注解的方式来实现)
一对一
案例:人与身份证
public interface CardMapper {
//查询全部
@Select("SELECT * FROM card")
@Results({
@Result(column = "id",property = "id"),
@Result(column = "number",property = "number"),
@Result(
property = "p", // 被包含对象的变量名
javaType = Person.class, // 被包含对象的实际数据类型
column = "pid", // 根据查询出的card表中的pid字段来查询person表
/*
one、@One 一对一固定写法
select属性:指定调用哪个接口中的哪个方法
*/
one = @One(select = "com.itheima.one_to_one.PersonMapper.selectById")
)
})
public abstract List<Card> selectAll();
}
public interface PersonMapper {
//根据id查询
@Select("SELECT * FROM person WHERE id=#{id}")
public abstract Person selectById(Integer id);
}
标签解释:
@Results:封装映射关系的父注解。
Result[] value():定义了 Result 数组
@Result:封装映射关系的子注解。
column 属性:查询出的表中字段名称
property 属性:实体对象中的属性名称
javaType 属性:被包含对象的数据类型
one 属性:一对一查询固定属性
@One:一对一查询的注解。
select 属性:指定调用某个接口中的方法
一对多
案例:一个班级与多个学生
public interface ClassesMapper {
//查询全部
@Select("SELECT * FROM classes")
@Results({
@Result(column = "id",property = "id"),
@Result(column = "name",property = "name"),
@Result(
property = "students", // 被包含对象的变量名
javaType = List.class, // 被包含对象的实际数据类型
column = "id", // 根据查询出的classes表的id字段来查询student表
/*
many、@Many 一对多查询的固定写法
select属性:指定调用哪个接口中的哪个查询方法
*/
many = @Many(select = "com.itheima.one_to_many.StudentMapper.selectByCid")
)
})
public abstract List<Classes> selectAll();
}
public interface StudentMapper {
//根据cid查询student表
@Select("SELECT * FROM student WHERE cid=#{cid}")
public abstract List<Student> selectByCid(Integer cid);
}
标签解释:
@Results:封装映射关系的父注解。
Result[] value():定义了 Result 数组
@Result:封装映射关系的子注解。
column 属性:查询出的表中字段名称
property 属性:实体对象中的属性名称
javaType 属性:被包含对象的数据类型
many 属性:一对多查询固定属性
@Many:一对多查询的注解。
select 属性:指定调用某个接口中的方法
多对多
案例:学生和课程,一个学生可以选择多门课程、一个课程也可以被多个学生所选择。
public interface StudentMapper {
//查询全部
@Select("SELECT DISTINCT s.id,s.name,s.age FROM student s,stu_cr sc WHERE sc.sid=s.id")
@Results({
@Result(column = "id",property = "id"),
@Result(column = "name",property = "name"),
@Result(column = "age",property = "age"),
@Result(
property = "courses", // 被包含对象的变量名
javaType = List.class, // 被包含对象的实际数据类型
column = "id", // 根据查询出student表的id来作为关联条件,去查询中间表和课程表
/*
many、@Many 一对多查询的固定写法
select属性:指定调用哪个接口中的哪个查询方法
*/
many = @Many(select = "com.itheima.many_to_many.CourseMapper.selectBySid")
)
})
public abstract List<Student> selectAll();
}
public interface CourseMapper {
//根据学生id查询所选课程
@Select("SELECT c.id,c.name FROM stu_cr sc,course c WHERE sc.cid=c.id AND sc.sid=#{id}")
public abstract List<Course> selectBySid(Integer id);
}
标签解释:
@Results:封装映射关系的父注解。
Result[] value():定义了 Result 数组
@Result:封装映射关系的子注解。
column 属性:查询出的表中字段名称
property 属性:实体对象中的属性名称
javaType 属性:被包含对象的数据类型
many 属性:一对多查询固定属性
@Many:一对多查询的注解。
select 属性:指定调用某个接口中的方法
column = "id", // 根据查询出student表的id来作为关联条件,去查询中间表和课程表
/*
many、@Many 一对多查询的固定写法
select属性:指定调用哪个接口中的哪个查询方法
*/
many = @Many(select = "com.itheima.many_to_many.CourseMapper.selectBySid")
)
})
public abstract List<Student> selectAll();
}
public interface CourseMapper {
//根据学生id查询所选课程
@Select(“SELECT c.id,c.name FROM stu_cr sc,course c WHERE sc.cid=c.id AND sc.sid=#{id}”)
public abstract List selectBySid(Integer id);
}
标签解释:
@Results:封装映射关系的父注解。
Result[] value():定义了 Result 数组
@Result:封装映射关系的子注解。
column 属性:查询出的表中字段名称
property 属性:实体对象中的属性名称
javaType 属性:被包含对象的数据类型
many 属性:一对多查询固定属性
@Many:一对多查询的注解。
select 属性:指定调用某个接口中的方法