Mapper 接口
在使用Mapper接口时,如果写错了在编译时候就可以发现,原来那种方式命名空间很容易写错,参数也容易传错,对比来原来 DAO 接口,可以自己不写实现类。
使用注意:
类似之前的 DAO,在接口中定义 CRUD 等操作方法。Mapper组件 = Mapper接口 + Mapper XML文件
- 接口的命名为实体名Mapper,一般和其对应 XML 文件放一起(只要编译之后字节码文件和 XML 文件在一起)
- XML 命名空间用其对应接口的全限定名
- Mapper 接口的方法名要和 Mapper XML 文件元素(select | update | delete | insert) id 值一样
- 方法的返回类型对应 SQL 元素中定义的 resultType / resultMap 类型
- 方法的参数类型对应 SQL 元素中定义的 paramterType 类型(一般不写)
定义和使用
定义:
public interface UserMapper {
void save(User user);
}
使用:
@Test
public void testNewSave() throws Exception {
User user = new User();
user.setUsername("李四");
user.setPassword("123456");
SqlSession session = MyBatisUtil.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.save(user);
session.commit();
session.close();
}
使用@Param注解处理多个参数
由于Mapper接口本质的底层还是用传命名空间的方法,也就只支持传一个参数。但是跟调用者需求不可能只传一个参数,如:实现一个登录需求,根据用户名和密码查询用户。其实我们可以把参数封装成对象或存到Map里这样就满足传一个参数了,但这样代码会很臃肿。
@Param注解的使用:
修改Mapper接口中的方法,在形参贴注解即可。
// 本质相当于构建一个Map对象,key为注解 @Param的值,value为参数的值。
User login(@Param("username")String username,@Param("password")String password);
本质相当于构建一个Map对象,key为注解 @Param的值,value为参数的值。
集合/数组参数
当传递一个 List 对象或数组对象参数给 MyBatis 时,MyBatis 会自动把它包装到一个 Map 中,此时:
List 对象会以 list 作为 key,数组对象会以 array 作为 key,也可以使用注解 @Param 设置 key 名。
MyBatis 的 # 和 $ 区别
相同点
都可以获取对象(Map 对象或者 JavaBean 对象)的信息。
不同点
使用 # 传递的参数会先转换为,无论传递是什么类型数据都会带一个单引号;
使用 $ 传递的参数,直接把值作为 SQL 语句的一部分;
使用 # 支持把简单类型(八大基本数据类型及其包装类、String、BigDecimal 等等)参数作为值;
使用 $ 不支持把简单类型参数作为值;
使用 # 好比使用 PrepareStatement,没有 SQL 注入的问题,相对比较安全;
使用 $ 好比使用 Statement,可能会有 SQL 注入的问题,相对不安全。
在什么情况使用:
在 group by 子句或者 order by 子句取值,都是取列名,就使用 $。其他子句获取参数值使用 #
动态SQL
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
if和where
if元素,用于判断,一般用作是否应该符合某一个查询条件
<if test="boolean表达式"></if>
<!--
xml 中<> 转义符
< : <
> : >:
> -->
where元素,只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。而且,若语句的开头为“AND”或“OR”,where 元素也会将它们去除。where 和 if的配合使用案例。
<select id="selectBy" resultType="Employee">
SELECT * FROM employee
<where>
<if test="name != null">
AND name=#{name}
</if>
<if test="minSalary != null">
AND salary >=#{minSalary}
</if>
<if test="maxSalary != null">
AND salary <=#{maxSalary}
</if>
</where>
</select>
set
set 元素会动态前置 SET 关键字,同时也会删掉无关的逗号,若里面条件都不成立,就会去除 SET 关键字。其用来解决更新时丢失数据的问题。
<update id="update">
UPDATE employee
<set>
<if test="name != null">
name=#{name},
</if>
<if test="salary != null">
salary=#{salary},
</if>
<if test="deptId != null">
deptId=#{deptId}
</if>
</set>
WHERE id=#{id}
</update>
foreach
动态 SQL 的另外一个常用的操作需求是对一个集合进行遍历,通常是在构建 IN 条件语句的时候,这里就会使用到foreach元素。
<delete id="delete">
DELETE FROM employee WHERE id in
<!--
collection:要迭代的数组或集合,集合的key默认使用list,数组默认使用array,但也可以通过注解@Param 修改
open:迭代开始符号
item:迭代元素遍量
separator:分隔符号
close:迭代结束符号
index:迭代索引变量
-->
<foreach collection="ids" open="(" item="id" separator="," close=")">
#{id}
</foreach>
</delete>
关系概述
生活中数据很多是存在关系的,就是把生活中有关系的数据通过 MyBatis 持久化到数据库,且存储的数据也能表示出来这种关系,再由数据库中把有关系的数据查询出来在页面展示。
保存:页面的数据 ----> 使用 Java 对象封装 —> 通过 MyBatis —> 数据库表的数据
查询:数据库表的数据 —> 通过 MyBatis —> 封装成 Java 对象 —> 页面展示数据
对象关系分类
- 泛化关系
- 实现关系
- 依赖关系
- 关联关系
- 聚合关系
- 组合关系
关联关系
A 对象依赖 B 对象,并且把 B 作为 A 的一个成员变量,则 A 和 B 存在关联关系。在 UML 中依赖通常使用实线箭头表示。
关联关系分类
-
按照导航性分
若通过 A 对象中的某一个属性可以访问到 B 对象,则说 A 可以导航到 B。
1. 单向:只能从 A 通过属性导航到 B,B 不能导航到 A。
2. 双向:A 可以通过属性导航到 B,B 也可以通过属性导航到 A。
-
按照多重性分
一对一,一对多,多对一,多对多。
判断对象的关系
判断都是从对象的实例上面来看的;
判断关系需要根据对象的属性;
判断关系必须确定具体需求。
单项多对一的查询保存
需求
- 保存一个部门和两个员工,且这两个员工都是这个部门的。
- 根据员工id查询员工,并知道该员工的所在的部门。
表设计
类设计
Employee
@Setter
@Getter
@ToString
public class Employee {
private Long id;
private String name;
private Department dept; // 关联部门
}
Department
@Setter
@Getter
@ToString
public class Department {
private Long id;
private String name;
}
Mapper接口和Mapper XML文件编写
Employee
public interface EmployeeMapper {
void save(Employee emp);
Employee get(Long id);
}
<insert id="save" useGeneratedKeys="true" keyProperty="id">
INSERT INTO employee(name,deptId) VALUES(#{name},#{dept.id})
</insert>
<resultMap type="Employee" id="baseResultMap">
<id column="id" property="id"/>
<result column="name" property="name"/>
<!--
使用额外SQL的方式:
association:针对关联属性的配置。非集合类型的
javaType:额外SQL查询结果集封装的类型
column:列的值传递额外SQL的参数值
select:发送的额外SQL
-->
<association column="deptId" property="dept" select="cn.wolfcode.crud.mapper.DepartmentMapper.get"/>
</resultMap>
<select id="get" resultMap="baseResultMap">
SELECT * FROM employee WHERE id = #{id}
</select>
Department
public interface DepartmentMapper {
void save(Department dept);
Department get(Long id);
}
<insert id="save" useGeneratedKeys="true" keyProperty="id">
INSERT INTO department(name) VALUES(#{name})
</insert>
<select id="get" resultType="Department">
SELECT * FROM department WHERE id = #{id}
</select>
编写单元测试
@Test
public void testSave() {
SqlSession session = MyBatisUtil.getSession();
DepartmentMapper departmentMapper = session.getMapper(DepartmentMapper.class);
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
// 先保存部门
Department dept = new Department();
dept.setName("开发部");
departmentMapper.save(dept);
// 再保存员工
Employee emp1 = new Employee();
emp1.setName("张老大");
emp1.setDept(dept);
employeeMapper.save(emp1);
Employee emp2 = new Employee();
emp2.setName("李老二");
emp2.setDept(dept);
employeeMapper.save(emp2);
session.commit();
session.close();
}
@Test
public void testGet() throws Exception {
SqlSession session = MyBatisUtil.getSession();
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.get(2L);
System.out.println(employee);
session.close();
}
注意:
保存:先保存部门后保存员工,执行代码是从上往下的
查询:手动编写mysql,再让MyBatis发送额外的SQL
多对多查询保存删除
需求
- 保存两个学生和两个老师,且这两个老师都教了这个两个学生。
- 根据id查询学生,并查询其老师。
- 根据id删除学生。
表设计
类设计
Teacher
@Getter
@Setter
public class Teacher {
private Long id;
private String name;
}
Student
@Getter
@Setter
@ToString
public class Student {
private Long id;
private String name;
// 关联属性
private List<Teacher> teacher = new ArrayList<Teacher>();
}
Mapper接口和Mapper XML文件编写
Teacher
public interface TeacherMapper {
void save(Teacher teacher);
List<Teacher> queryById(Long id);
}
<insert id="save" useGeneratedKeys="true" keyProperty="id">
INSERT
INTO teacher(name) VALUES(#{name})
</insert>
<select id="queryById" resultType="Teacher">
select id,name from teacher s
JOIN teacher_student s_t
ON s.id =s_t.teacher_id
where student_id=#{id}
</select>
Student
public interface StudentMapper {
void save(Student student);
// 维护关系
void insertRelationWithTeacher(@Param("studentId") Long studentId, @Param("teacherId") Long teacherId);
Student queryById(Long id);
void deleteById(Long id);
// 删掉关系
void deleteRelationById(Long id);
}
<insert id="save" useGeneratedKeys="true" keyProperty="id">
INSERT
INTO student(name) VALUES(#{name})
</insert>
<insert id="insertRelationWithTeacher">
INSERT INTO teacher_student(teacher_id,student_id)
VALUES(#{teacherId},#{studentId})
</insert>
<resultMap type="Student" id="baseResultMap">
<id column="id" property="id" />
<result column="name" property="name" />
<collection select="cn.wolfcode.crud.mapper.TeacherMapper.queryById"
column="id" property="teacher" />
</resultMap>
<select id="queryById" resultMap="baseResultMap">
select id,name from student where id=#{id}
</select>
<delete id="deleteById">
delete from student where id=#{id}
</delete>
<delete id="deleteRelationById">
delete from teacher_student where
student_id=#{student_id}
</delete>
编写单元测试
@Test
public void testSave() {
SqlSession session = MyBatisUtil.getSession();
TeacherMapper teacherMapper = session.getMapper(TeacherMapper.class);
StudentMapper studentMapper = session.getMapper(StudentMapper.class);
// 添加老师
Teacher teacher1 = new Teacher();
teacher1.setName("孔子");
Teacher teacher2 = new Teacher();
teacher2.setName("孟子");
teacherMapper.save(teacher1);
teacherMapper.save(teacher2);
// 添加学生
Student student1 = new Student();
student1.setName("张三");
student1.getTeacher().add(teacher1); // 添加关系
student1.getTeacher().add(teacher2);
Student student2 = new Student();
student2.setName("李四");
student2.getTeacher().add(teacher1); // 添加关系
student2.getTeacher().add(teacher2);
studentMapper.save(student1);
studentMapper.save(student2);
// 数据保存到中间表
for (Teacher teacher : student1.getTeacher()) {
studentMapper.insertRelationWithTeacher(student1.getId(), teacher.getId());
}
for (Teacher teacher : student2.getTeacher()) {
studentMapper.insertRelationWithTeacher(student2.getId(), teacher.getId());
}
// 提交 关闭
session.commit();
session.close();
}
@Test
public void testQuery() throws Exception {
SqlSession session = MyBatisUtil.getSession();
StudentMapper studentMapper = session.getMapper(StudentMapper.class);
Student stu = studentMapper.queryById(2L);
session.close();
System.out.println(stu);
}
@Test
public void testDelete() throws Exception {
SqlSession session = MyBatisUtil.getSession();
//获取mapper
StudentMapper studentMapper = session.getMapper(StudentMapper.class);
//调用方法先删掉关系的数据表中信息,防止有其他关联外键问题
studentMapper.deleteRelationById(1L);
//删掉学生用户的信息
studentMapper.deleteById(1L);
//提交事务和关闭session
session.commit();
session.close();
}
注意:
保存:在多对多表查询中,要设计一张中间表来表示之间的关系
查询:MyBatis发送额外的SQL
删除:先从中间表把关系数据删除,再删除要删除数据