0%

MyBatis映射文件

MyBatis映射文件

1 增删改查概述

1.1 Mapper接口定义

  • 在Mapper接口中定义增删改查方法的参数和返回值类型,
1
2
3
4
5
6
public interface EmployeeMapper {
Employee getEmployeeById(Integer id);
void addEmployee(Employee employee);
void updateEmployee(Employee employee);
void deleteEmployeeById(Integer id);
}

1.2 Mapper配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<mapper namespace="com.lnhoo.dao.EmployeeMapper">
<select id="getEmployeeById" resultType="com.lnhoo.Employee">
select * from employees where id = #{id}
</select>

<insert id="addEmployee" parameterType="com.lnhoo.Employee">
insert into employees (id, last_name, gender, email) values (#{id}, #{lastName}, #{gender}, #{email})
</insert>

<update id="updateEmployee" parameterType="com.lnhoo.Employee">
update employees set last_name=#{lastName}, gender=#{gender}, email=#{email} where id=#{id}
</update>

<delete id="deleteEmployeeById" parameterType="int">
delete from employees where id=#{id}
</delete>
</mapper>
  • 在Mapper配置文件中将接口中的方法和sql语句绑定
  • mapper标签的namespace属性填上接口的全类名
  • 增(insert)、删(delete)、改(update)、查(select)标签的id属性填上接口中的方法名
  • 参数提供的字段、sql语句执行的结果以“#{名字}”的形式捕获

1.3 执行sql

1
2
3
4
5
6
7
8
9
10
@Test
public void testModify() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Employee jack = new Employee(0, "jack", '1', "xxx@163.com");
employeeMapper.addEmployee(jack);
session.commit();
}
}
  • 先获取到SqlSessionFactory对象,然后调用openSession方法开启一次会话,从会话中获取MyBatis自动创建的动态代理对象,之后就可以调用事先在EmployeeMapper接口中定义的方法实现对数据库的增删改查
  • 调用openSession方法如果不提供参数,此时的会话将不自动提交更改,需要在合适的时候手动调用commit方法提交更改;如果调用重载方法,则可以提供一个布尔类型的参数,表示是否自动commit

1.4 获取增、删、改执行结果

  • MyBatis支持将接口中增、删、改方法的返回值类型定义成void、Integer、Long、Boolean,
1
2
3
4
5
6
public interface EmployeeMapper {
Employee getEmployeeById(Integer id);
Integer addEmployee(Employee employee);
Long updateEmployee(Employee employee);
Boolean deleteEmployeeById(Integer id);
}
  • 返回值类型为整数时,表示sql语句执行影响的记录数
  • 如果返回值类型是布尔值,表示sql语句执行是否对数据库表内容造成影响

1.5 获取自增主键的值

  • useGeneratedKeys=”true”:使用自增主键获取主键值的策略
  • keyProperty:指定对应的主键属性,也就是MyBatis获取到主键值后,将这个值封装给Java Bean的哪个属性
1
2
3
<insert id="addEmployee" parameterType="com.lnhoo.Employee" useGeneratedKeys="true" keyProperty="id">
insert into employees (id, last_name, gender, email) values (#{id}, #{lastName}, #{gender}, #{email})
</insert>

2 参数处理

2.1 单个参数

  • MyBatis不作特殊处理,直接#{参数名}取出参数值
1
2
3
4
Employee getEmployeeById(Integer id);
<select id="getEmployeeById" resultType="com.lnhoo.Employee">
select * from employees where id = #{id}
</select>

2.2 多个参数

2.2.1 通过索引取出参数值

  • 多个参数会被封装成一个Map,索引为:param1、param2、…、paramN,
1
2
3
4
Employee getEmployeeByIdAndLastName(Integer id, String lastName);
<select id="getEmployeeByIdAndLastName" resultType="com.lnhoo.Employee">
select * from employees where id=#{param1} and last_name=#{param2}
</select>

2.2.2 命名参数

  • 使用@Param注解为参数命名,
1
2
3
4
Employee getEmployeeByIdAndLastName(@Param(value = "id") Integer id, @Param(value = "lastName") String lastName);
<select id="getEmployeeByIdAndLastName" resultType="com.lnhoo.Employee">
select * from employees where id=#{id} and last_name=#{lastName}
</select>

2.2.3 POJO

  • 如果多个参数正好是业务逻辑的数据模型,就可以直接传入POJO,#{属性名}取出传入的POJO的属性值,
1
2
3
4
Integer addEmployee(Employee employee);
<insert id="addEmployee" parameterType="com.lnhoo.Employee">
insert into employees (id, last_name, gender, email) values (#{id}, #{lastName}, #{gender}, #{email})
</insert>

2.2.4 Map

  • 如果多个参数不是业务逻辑的数据模型,没有对应的POJO,也可以传入Map,#{key}取出参数值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Employee getEmployeeByMap(Map<String, Object> map);
<select id="getEmployeeByMap" resultType="com.lnhoo.Employee">
select * from employees where id=#{id} and last_name=#{lastName};
</select>
@Test
public void testSelect() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
try (SqlSession session = sqlSessionFactory.openSession()) {
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Map queryMap = new HashMap<String, Object>();
queryMap.put("id", 1);
queryMap.put("lastName", "tom");
Employee employee = employeeMapper.getEmployeeByMap(queryMap);
System.out.println(employee);
}
}

2.2.5 DTO

  • 如果多个参数不是业务模型中的数据,但是经常使用,可以考虑定义DTO(Data Transfer Object 数据传输对象)

2.2.6 #{}和${}取值的区别

  • #{}是以预编译的形式将参数设置到sql语句中,可以防止sql注入
  • ${}取出的值直接拼装在sql语句中,会有安全问题
  • 大多数情况下,取参数的值应该使用#{}的方式,在原生JDBC不支持占位符的地方就可以使用${}进行取值

2.2.7 #{}取值扩展用法

2.2.7.1 jdbcType

  • 当提供的参数为null的时候,有些数据库不能识别MyBatis对null的默认处理,比如Oracle就会报错
  • 默认情况下,MyBatis运行时设置项jdbcTypeForNull=OTHER,此时MyBatis对所有null都映射的是原生JDBC的OTHER类型,而oracle不认识
  • 可以在#{}取参数值的时候指定jdbcType,
1
2
3
<select id="getEmployeeByMap" resultType="com.lnhoo.Employee">
select * from employees where id=#{id} and last_name=#{lastName, jdbcType=NULL};
</select>
  • 也可以在全局配置文件中修改MyBatis运行时设置项,
1
2
3
<settings>
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>

3 select

3.1 查询返回list

  • 在Mapper接口定义一方法,表示根据lastName查询数据表中符合条件的记录,
1
List<Employee> getEmployeeByLastName(String lastName);
  • Mapper xml配置文件中将sql语句和接口方法绑定,
1
2
3
<select id="getEmployeeByLastName" resultType="com.lnhoo.Employee">
select * from employees where last_name like "%${lastName}%"
</select>

这里虽然查询返回的结果集被封装成List集合,但是select标签的resultType属性要填集合中对象的类型,不能写成集合的类型

3.2 resultMap

3.2.1 自定义结果集映射规则

  • 可以使用resultMap标签自定义结果集的映射规则,
1
2
3
4
5
6
<resultMap id="Employee" type="com.lnhoo.Employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="email" property="email"></result>
<result column="gender" property="gender"></result>
</resultMap>
  • resultMap标签的id属性是resultMap定义的唯一标识,方便后续引用,type是Java Bean的全类名
  • resultMap标签内部的id标签用于定义数据库表主键和Java Bean中属性的映射关系,此处也可以使用result标签替代
  • result标签表明数据库表中的列和Java Bean属性的映射关系
  • 在select标签中引用resultMap来使用自定义的映射规则,
1
2
3
<select id="getEmployeeById" resultMap="Employee">
select * from employees where id=#{id}
</select>

3.2.2 关联查询

  • 创建部门表,然后往里头加一些数据,
1
2
3
4
5
6
7
8
9
10
11
12
13
create table departments (
id int(11) primary key auto_increment,
dept_name varchar(255)
)

insert into departments values (0, "Human Resource");
insert into departments values (0, "Infomation Technology")

select * from departments;
id|dept_name |
--+---------------------+
1|Human Resource |
2|Infomation Technology|
  • 为员工表加一个dept_id字段,
1
2
3
4
5
6
7
8
9
alter table employees add column dept_id int(11);
describe employees
Field |Type |Null|Key|Default|Extra |
---------+------------+----+---+-------+--------------+
id |int(11) |NO |PRI| |auto_increment|
last_name|varchar(255)|YES | | | |
gender |char(1) |YES | | | |
email |varchar(255)|YES | | | |
dept_id |int(11) |YES | | | |
  • 添加部门Department类,修改Employee类,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class Department {
private int id;
private String name;

public Department() {

}

public Department(int id, String name) {
this.id = id;
this.name = name;
}

public void setId(int id) {
this.id = id;
}

public void setName(String name) {
this.name = name;
}

@Override
public String toString() {
return "Department{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public class Employee {
private Integer id;
private String lastName;
private char gender;
private String email;
private Department dept;

public Employee() {

}

public Employee(Integer id, String lastName, char gender, String email) {
this.id = id;
this.lastName = lastName;
this.gender = gender;
this.email = email;
}

public void setId(Integer id) {
this.id = id;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public void setGender(char gender) {
this.gender = gender;
}

public void setEmail(String email) {
this.email = email;
}

public void setDept(Department dept) {
this.dept = dept;
}

@Override
public String toString() {
return "Employee{" +
"id=" + id +
", lastName='" + lastName + '\'' +
", gender=" + gender +
", email='" + email + '\'' +
", dept=" + dept +
'}';
}
}
  • 在Mapper xml配置文件中将接口方法和连接查询的sql语句绑定,
1
2
3
<select id="getEmployeeById" resultMap="Employee">
select e.id id, last_name, gender, email, d.id did, dept_name from employees e, departments d where e.dept_id=d.id and e.id=#{id}
</select>
  • 自定义查询结果集字段和Java Bean属性之间的映射关系,
1
2
3
4
5
6
7
8
<resultMap id="Employee" type="com.lnhoo.Employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="email" property="email"></result>
<result column="gender" property="gender"></result>
<result column="did" property="dept.id"></result>
<result column="dept_name" property="dept.name"></result>
</resultMap>

使用”.”操作符取出对象类型成员的属性,比如”dept.id”、”dept.name”

3.2.3 定义关联对象的封装规则

  • 在resultMap中使用association标签定义关联对象的封装规则,
1
2
3
4
5
6
7
8
9
10
<resultMap id="Employee" type="com.lnhoo.Employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="email" property="email"></result>
<result column="gender" property="gender"></result>
<association property="dept" javaType="com.lnhoo.Department">
<id column="did" property="id"></id>
<result column="dept_name" property="name"></result>
</association>
</resultMap>
  • association标签的property属性填成员变量名称,javaType填该成员的全类名

3.2.4 分步查询

  • 使用association进行分步查询,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<resultMap id="MyEmpByStep" type="com.lnhoo.Employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="gender" property="gender"></result>
<result column="email" property="email"></result>
<association property="dept" select="com.lnhoo.dao.DepartmentMapper.getDepartmentById" column="dept_id">
<id column="id" property="id"></id>
<result column="dept_name" property="name"></result>
</association>
</resultMap>

<select id="getEmpByStep" resultMap="MyEmpByStep">
select * from employees where id=#{id};
</select>

association标签的select属性可支持分步查询,参数由column属性提供

3.2.5 延迟加载

  • 两个运行时设置项,
设置 功能 默认值
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态 false
aggressiveLazyLoading 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。 false(在 3.4.1 及之前的版本中默认为 true)
  • 所以要配置延迟加载,只需要在全局配置文件中设置两个开关,
1
2
3
4
5
<settings>
......
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
  • association、collection标签均有fetchType属性,可能值为“lazy”(延迟加载)、“eager”(立即加载)

3.2.6 定义关联集合封装规则

  • 通常员工和部门是多对一的关系,即一个部门拥有多个员工,一个员工只属于一个部门,
1
2
3
4
5
public class Department {
private int id;
private String name;
private List<Employee> employeeList;
............

假设Department对象包含一个员工列表employeeList

  • 使用collection标签定义关联集合封装规则,
1
2
3
4
5
6
7
8
9
10
<resultMap id="Department" type="com.lnhoo.Department">
<id column="id" property="id"></id>
<result column="dept_name" property="name"></result>
<collection property="employeeList" ofType="com.lnhoo.Employee">
<id column="eid" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="gender" property="gender"></result>
<result column="email" property="email"></result>
</collection>
</resultMap>
  • Mapper接口定义,
1
2
3
public interface DepartmentMapper {
Department getDepartmentById(Integer id);
}
  • 此时与接口绑定的查询sql配置为,
1
2
3
4
5
<select id="getDepartmentById" resultMap="Department">
select d.id id, dept_name, e.id eid, last_name, gender, email, dept_id from departments d
left join employees e on d.id=e.dept_id
where d.id=#{id}
</select>

3.2.7 关联集合分步查询

  • 假设要根据dept_id查询某个部门,Department对象有一employeeList字段,实现思路可以是:先根据部门id查询departments表得到部门名称,然后再根据部门id查询employees表得到属于该部门的所有员工填充到Department对象的employeeList属性中
  • 父查询:根据部门id查询对应的部门,
1
2
3
<select id="getDepartmentById" resultMap="Department">
select * from departments where id=#{id}
</select>
  • 子查询:根据部门id查询所属于该部门的所有员工,
1
2
3
<select id="getEmployeeByDeptId" resultType="com.lnhoo.Employee">
select * from employees where dept_id=#{id}
</select>
  • resultMap使用collection标签关联父、子查询,
1
2
3
4
5
6
7
8
9
10
<resultMap id="MyEmpByStep" type="com.lnhoo.Employee">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="gender" property="gender"></result>
<result column="email" property="email"></result>
<association property="dept" select="com.lnhoo.dao.DepartmentMapper.getDepartmentById" column="dept_id">
<id column="id" property="id"></id>
<result column="dept_name" property="name"></result>
</association>
</resultMap>

3.2.8 分步查询传递多列值

  • association、collection标签分步查询如果要为select语句传递多个参数,需要封装成一个map,
1
column="{key1: column1, key2: column2}"

3.2.9 鉴别器

  • discriminator可以根据查询结果集的某个列决定封装的对象
  • 假如有个需求,要求:

(1)只有女生才查询所属的部门

(2)男生的email被赋值成last_name

可以使用鉴别器实现如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<resultMap id="MyEmpByStep" type="com.lnhoo.Employee">
<discriminator javaType="string" column="gender">
<case value="0">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="gender" property="gender"></result>
<result column="email" property="email"></result>
<association property="dept" select="com.lnhoo.dao.DepartmentMapper.getDepartmentById" column="dept_id">
<id column="id" property="id"></id>
<result column="dept_name" property="name"></result>
</association>
</case>
<case value="1">
<id column="id" property="id"></id>
<result column="last_name" property="lastName"></result>
<result column="gender" property="gender"></result>
<result column="last_name" property="email"></result>
</case>
</discriminator>
</resultMap>