• Home

  • 归档

  • 分类

  • 标签
Keep Coding
Keep Coding

05月
16
Hibernate

Hibernate 三种数据状态

发表于 2018-05-16 • 分类于 Hibernate

Hibernate 中的三种状态

瞬时状态:刚创建的对象还没有被 Session 持久化、缓存中不存在这个对象的数据并且数据库中没有这个对象对应的数据为瞬时状态这个时候是没有 OID。   

持久状态:对象经过 Session 持久化操作,缓存中存在这个对象的数据为持久状态并且数据库中存在这个对象对应的数据为持久状态这个时候有 OID。

游离状态:当 Session 关闭,缓存中不存在这个对象数据而数据库中有这个对象的数据并且有 OID 为游离状态。

注:OID 为了在系统中能够找到所需对象,我们需要为每一个对象分配一个唯一的表示号。在关系数据库中我们称之为关键字,而在对象术语中,则叫做对象标识

(Object identifier-OID).通常OID在内部都使用一个或多个大整数表示,而在应用程序中则提供一个完整的类为其他类提供获取、操作。

Hibernate 数据状态图:
Hibernate 数据状态图

需要注意的是:
当对象的临时状态将变为持久化状态。当对象在持久化状态时,它一直位于 Session 的缓存中,对它的任何操作在事务提交时都将同步到数据库,因此,对一个已经持久的对象调用 save() 或 update() 方法是没有意义的。

阅读全文 »
05月
16
Java

Java Array、List、Set 的互相转换

发表于 2018-05-16 • 分类于 Java

Array、List、Set 互转实例

Array、List 互转

Array 转 List

1
2
String[] s = new String[]{"A", "B", "C", "D", "E"};
List<String> list = Arrays.asList(s);

注意这里 list 里面的元素直接是 s 里面的元素(list backed by the specified array),换句话就是说:对 s 的修改,直接影响 list。

1
2
s[0] = "AA";
System.out.println("list: " + list);

输出结果

1
list: [AA, B, C, D, E]

List 转 Array

1
2
String[] dest = list.toArray(new String[0]); // new String[0]是指定返回数组的类型
System.out.println("dest: " + Arrays.toString(dest));

输出结果

1
dest: [AA, B, C, D, E]

注意这里的 dest 里面的元素不是 list 里面的元素,换句话就是说:对 list 中关于元素的修改,不会影响 dest。

1
2
3
list.set(0, "Z");
System.out.println("modified list: " + list);
System.out.println("dest: " + Arrays.toString(dest));

输出结果

1
2
modified list: [Z, B, C, D, E]
dest: [AA, B, C, D, E]

可以看到 list 虽然被修改了,但是 dest 数组没有没修改。

阅读全文 »
03月
29
JS/JQuery

JavaScript 以 POST 方式打开新页面

发表于 2018-03-29 • 分类于 JS/JQuery

JavaScript 以 POST 方式打开新页面

场景:前置的查询页面,选择查询条件后提交到另一个页面。

方式很多,列出我知道的几种

1.window.open.

2.Response.Redirect.

3.Server.Transfer.

方法一和方法二都存在同样的问题,因为是get方式提交的,所以提交的数据都会显示URL中,一个是安全问题,另外一个是URL长度限制,在IE中,URL最大长度为2083.所以数据量过多时会导致数据丢失。

于是考虑到通过POST方式传递参数。

阅读全文 »
03月
20
JS/JQuery

如何阻止 jquery 事件触发多次的问题

发表于 2018-03-20 • 分类于 JS/JQuery

如何阻止 jquery 事件触发多次的问题

有时候jquery的事件,如click事件会触发多次,到时ga统计错误,或其他问题,怎么办呢?

最简单的做法是阻止冒泡,但是stopPropagation不一定有用,这时候可以试试stopImmediatePropagation

还有其他方法:
参考: http://www.gajotres.net/prevent-jquery-multiple-event-triggering/

我比较喜欢 solution4:

1
2
3
4
5
6
7
8
9
$(document).on('pagebeforeshow', '#index', function(){ 
$(document).on('click', '#test-button',function(e) {
if(e.handled !== true) // This will prevent event triggering more then once
{
alert('Clicked');
e.handled = true;
}
});
});
阅读全文 »
02月
08
MySQL

MySQL 学习和使用

发表于 2018-02-08 • 分类于 MySQL

常用命令

MySQL教程:MySQL数据库学习宝典(从入门到精通)

创建数据库

CREATE DATABASE IF NOT EXISTS <数据库名称> DEFAULT CHARACTER SET utf8;

查看已有库

1
> SHOW CREATE DATABASE <数据库名称>;

示例:

1
2
3
4
5
6
SHOW CREATE DATABASE webproject;

Name |Value |
---------------|-------------------------------------------------------------------|
Database |webproject |
Create Database|CREATE DATABASE `webproject` /*!40100 DEFAULT CHARACTER SET utf8 */|

查看建表语句

1
> SHOW CREATE TABLE <表名>;

示例:

1
2
3
4
5
SHOW CREATE TABLE user_info;

Table |Create Table | |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
user_info|CREATE TABLE `user_info` (`id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(20) NOT NULL, `nickname` varchar(20) NOT NULL, `password` varchar(60) DEFAULT NULL, `email` varchar(100) DEFAULT NULL, `activated` tinyint(1) NOT NULL DEFAU|

查看表结构

1
> DESC <表名>;

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> DESC user_info;

Field |Type |Null|Key|Default |Extra |
------------------|------------|----|---|-----------------|--------------|
id |bigint(20) |NO |PRI| |auto_increment|
username |varchar(20) |NO |UNI| | |
nickname |varchar(20) |NO | | | |
password |varchar(60) |YES | | | |
email |varchar(100)|YES |UNI| | |
activated |tinyint(1) |NO | |0 | |
created_by |bigint(20) |NO | | | |
created_date |datetime |NO | |CURRENT_TIMESTAMP| |
last_modified_by |bigint(20) |YES | | | |
last_modified_date|datetime |YES | | | |

修改表

常用的语法格式如下:

1
> ALTER TABLE <表名> [修改选项];

修改选项的语法格式如下:

1
2
3
4
5
6
RENAME TO <新表名>
ADD COLUMN <列名> <类型>
CHANGE COLUMN <旧列名> <新列名> <新列类型>
ALTER COLUMN <列名> { SET DEFAULT <默认值> | DROP DEFAULT }
MODIFY COLUMN <列名> <类型>
DROP COLUMN <列名>

数据库设计

MySQL 规范

数据库设计规范

  • 所有数据库对象名称必须使用小写字母并用下划线分割。

  • 所有数据库对象名称禁止使用 MySQL 保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来)。

  • 临时库表必须以 tmp_ 为前缀并以日期为后缀,备份表必须以 bak_ 为前缀并以日期为后缀。

  • 所有存储相同数据的列名和列类型必须一致(一般作为关联列,如果查询时关联列类型不一致会自动进行数据类型隐式转换,会造成列上的索 引失效,导致查询效率降低)。

  • 数据库表名和列表的最大字符数都控制在 64 个字符以内。

    oracle 数据库表名和列表的最大字符数都控制在 30 个字符以内。

    mssql 数据库表名和列表的最大字符数都控制在 128 个字符以内。

  • 尽量控制单表数据量的大小,建议控制在 500 万以内。

    500 万并不是 MySQL 数据库的限制,过大会造成修改表结构,备份,恢复都会有很大的问题。

    可以用历史数据归档(应用于日志数据),分库分表(应用于业务数据)等手段来控制数据量大小。

  • 谨慎使用 MySQL 分区表。

    分区表在物理上表现为多个文件,在逻辑上表现为一个表。 谨慎选择分区键,跨分区查询效率可能更低,建议采用物理分表的方式管理大数据。

  • 对于非负型的数据(如自增 ID、整型 IP)来说,要优先使用无符号整型来存储。

    因为无符号相对于有符号可以多出一倍的存储空间。

  • VARCHAR(N) 中的 N 代表的是字符数,而不是字节数。使用 UTF8 存储 255 个汉字 VARCHAR(255) = 765 个字节。过大的长度会消耗更多的内存。

  • 使用 TIMESTAMP(4 个字节)或 DATETIME 类型(8 个字节)存储时间。

    TIMESTAMP 存储的时间范围 1970-01-01 00:00:01 ~ 2038-01-19 03:14:07。

    TIMESTAMP 占用 4 字节和 INT 相同,但比 INT 可读性高。

    超出 TIMESTAMP 取值范围的使用 DATETIME 类型存储。

  • 需要精确计算的数据必须使用 decimal 类型。

    。非精准浮点:float, double

    。精准浮点:decimal

  • 尽量避免使用外键约束。

    。不建议使用外键约束(foreign key),但一定要在表与表之间的关联键上建立索引。

    。外键可用于保证数据的参照完整性,但建议在业务端实现。

    。外键会影响父表和子表的写操作从而降低性能。

  • 对于表中类似 is_deleted、is_activated 这类只有几种结果的属性,尽量在添加列时申明默认值。

阅读全文 »
01月
29
JS/JQuery

Bootstrap 模态框

发表于 2018-01-29 • 分类于 JS/JQuery

判断页面某个模态框是否显示或隐藏:

var modal_status = 0;//默认0, 0表示隐藏

$('#myModal').on('show.bs.modal', function (e) {
`modal_status = 1; // 1表示显示
})

$('#myModal').on('hidden.bs.modal', function (e) {
`modal_status = 0;
})
阅读全文 »
01月
29
Mybatis

Mybatis 语句

发表于 2018-01-29 • 分类于 Mybatis

批量操作:

Mybatis 插入和删除批处理操作

springMVC 接收数组参数,mybatis 接收数组参数,mybatis批量插入/批量删除案例

SQL中的case when then else end用法

批量 insert:

1
2
3
4
5
6
7
8
9
10
11
12
<insert id="batchSaveOrUpdate" useGeneratedKeys="true" keyProperty="id">
INSERT INTO favorite_group(id, name, user_id) VALUES
<foreach collection="list" item="item" index="index" separator=",">
(#{item.id}, #{item.name}, #{item.userId})
</foreach>
</insert>
```

最终的语句类似:

```
insert into favorite_group(id, name, user_id) values (1, 1, 1), (2, 2, 2), (3, 3, 3)
阅读全文 »
01月
17
JS/JQuery

JS 中的 map

发表于 2018-01-17 • 分类于 JS/JQuery

js 中使用map和java HashMap 对比

方式一

1
2
3
4
5
6
7
8
9
10
11
var map = {}; // Map map = new HashMap();
map[key] = value; // map.put(key, value);
var value = map[key]; // Object value = map.get(key);
var has = key in map; // boolean has = map.containsKey(key);
delete map[key]; // map.remove(key);
// 遍历
for(key in map){
alert(key + map[key]);
}
//长度判断
var length = Object.keys(map).length ;

方式二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var map={};

map.name='张三';

map.age=34;

var name=map.name;

var has = key in map;

delete map[key];

$.each(m,function(key,val){
alert(key+' '+val);
});
阅读全文 »
01月
08
Mybatis

Mybatis - 使用注解配置 SQL 映射器

发表于 2018-01-08 • 分类于 Mybatis

在映射器 Mapper 接口上使用 @Mapper 注解

映射语句

MyBatis 提供了多种注解来支持不同类型的语句(statement)如 SELECT,INSERT,UPDATE,DELETE。

@Insert

我们可以使用 @Insert 注解来定义一个 INSERT 映射语句:

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
package com.mybatis3.mappers;  
public interface StudentMapper {
@Insert("INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,ADDR_ID, PHONE)
VALUES(#{studId},#{name},#{email},#{address.addrId},#{phone})")
int insertStudent(Student student);
}
```

使用了 `@Insert` 注解的 insertStudent() 方法将返回 insert 语句执行后影响的行数。

<!--more-->

#### 自动生成主键
我们可以使用 `@Options` 注解的 `userGeneratedKeys` 和 `keyProperty` 属性让数据库产生 auto_increment(自增长)列的值,然后将生成的值设置到输入参数对象的属性中。

```
@Insert("INSERT INTO STUDENTS(NAME,EMAIL,ADDR_ID, PHONE)
VALUES(#{name},#{email},#{address.addrId},#{phone})")
@Options(useGeneratedKeys = true, keyProperty = "studId")
int insertStudent(Student student);
```

这里 STUD_ID 列值将会通过 MySQL 数据库自动生成。并且生成的值将会被设置到 student 对象的 studId 属性中。

```
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
mapper.insertStudent(student);
long studentId = student.getStudId();
```

有一些数据库如 Oracle,并不支持 AUTO_INCREMENT 列属性,它使用序列(SEQUENCE)来产生主键的值。
我们可以使用 `@SelectKey` 注解来为任意 SQL 语句来指定主键值,作为主键列的值。

假设我们有一个名为 `STUD_ID_SEQ` 的序列来生成 `STUD_ID` 主键值。

```
@Insert("INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,ADDR_ID, PHONE)
VALUES(#{studId},#{name},#{email},#{address.addrId},#{phone})")
@SelectKey(statement="SELECT STUD_ID_SEQ.NEXTVAL FROM DUAL",
keyProperty="studId", resultType=long.class, before=true)
int insertStudent(Student student);
```

这里我们使用了 `@SelectKey` 来生成主键值,并且存储到了 student 对象的 studId 属性上。由于我们设置了 `before=true`,该语句将会在执行 INSERT 语句之前执行。

如果你使用序列作为触发器来设置主键值,我们可以在 INSERT 语句执行后,从 `sequence_name.currval` 获取数据库产生的主键值。

```
@Insert("INSERT INTO STUDENTS(NAME,EMAIL,ADDR_ID, PHONE)
VALUES(#{name},#{email},#{address.addrId},#{phone})")
@SelectKey(statement="SELECT STUD_ID_SEQ.CURRVAL FROM DUAL",
keyProperty="studId", resultType=long.class, before=false)
int insertStudent(Student student);
```

### @Update
我们可以使用 `@Update` 注解来定义一个 UPDATE 映射语句,如下所示:

```
@Update("UPDATE STUDENTS SET NAME=#{name}, EMAIL=#{email},
PHONE=#{phone} WHERE STUD_ID=#{studId}")
int updateStudent(Student student);
```

使用了 `@Update` 的 `updateStudent()` 方法将会返回执行了 update 语句后影响的行数。

```
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
int noOfRowsUpdated = mapper.updateStudent(student);
```

### @Delete
我们可以使用 `@Delete` 注解来定义一个 DELETE 映射语句,如下所示:

```
@Delete("DELETE FROM STUDENTS WHERE STUD_ID=#{studId}")
int deleteStudent(long studId);
```

使用了 `@Delete` 的 `deleteStudent()` 方法将会返回执行了 delete 语句后影响的行数。

### @Select
我们可以使用 `@Select` 注解来定义一个 SELECT 映射语句,如下所示:

```
package com.mybatis3.mappers;
public interface StudentMapper
{
@Select("SELECT STUD_ID AS STUDID, NAME, EMAIL, PHONE FROM
STUDENTS WHERE STUD_ID=#{studId}")
Student findStudentById(long studId);
}
```

为了将列名和 Student bean 属性名匹配,我们为 stud_id 起了一个 studId 的别名。如果返回了多行结果,将抛出 `TooManyResultsException` 异常。

## 结果映射
我们可以将查询结果通过别名或者是 `@Results` 注解与 JavaBean 属性映射起来。
现在让我们看看怎样使用 `@Results` 注解将指定列与指定 JavaBean 属性映射器来,执行 SELECT 查询的:

```
package com.mybatis3.mappers;
public interface StudentMapper
{
@Select("SELECT * FROM STUDENTS")
@Results(
{
@Result(id = true, column = "stud_id", property = "studId"),
@Result(column = "name", property = "name"),
@Result(column = "email", property = "email"),
@Result(column = "addr_id", property = "address.addrId")
})
List<Student> findAllStudents();
}
```

> `@Results` 注解和映射器 XML 配置文件元素 `<resultMap>` 相对应,然而,`Mybatis3.2.2` 不能为 `@Results` 注解赋予一个 ID。所以,不像 `<resultMap>` 元素,我们不应该在不同的映射语句中重用 `@Results` 声明。
> 这意味着即使 `@Results` 注解完全相同,我们也需要(在不同的映射接口中)重复 `@Results` 声明。

例如,看下面的 findStudentById() 和 findAllStudents() 方法:

```
@Select("SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}")
@Results(
{
@Result(id = true, column = "stud_id", property = "studId"),
@Result(column = "name", property = "name"),
@Result(column = "email", property = "email"),
@Result(column = "addr_id", property = "address.addrId")
})
Student findStudentById(int studId);

@Select("SELECT * FROM STUDENTS")
@Results(
{
@Result(id = true, column = "stud_id", property = "studId"),
@Result(column = "name", property = "name"),
@Result(column = "email", property = "email"),
@Result(column = "addr_id", property = "address.addrId")
})
List<Student> findAllStudents();
```

这里两个语句的` @Results` 配置完全相同,但是我必须得重复它。这里有一个解决方法。我们可以创建一个映射器 Mapper 配置文件, 然后配置 `<resultMap>` 元素,然后使用 `@ResultMap` 注解引用此 `<resultMap>`。

在 StudentMapper.xml 中定义一个 ID 为 StudentResult 的 `<resultMap>` :

```
<mapper namespace="com.mybatis3.mappers.StudentMapper">
<resultMap type="Student" id="StudentResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<result property="phone" column="phone" />
</resultMap>
</mapper>
```

在 StudentMapper.java 中,使用 `@ResultMap` 引用名为 StudentResult 的 resultMap:

```
public interface StudentMapper {
@Select("SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}")
@ResultMap("com.mybatis3.mappers.StudentMapper.StudentResult")
Student findStudentById(long studId);

@Select("SELECT * FROM STUDENTS")
@ResultMap("com.mybatis3.mappers.StudentMapper.StudentResult")
List<Student> findAllStudents();
}
```

### 一对一映射
MyBatis 提供了 `@One` 注解来使用嵌套 select 语句(Nested-Select)加载一对一关联查询数据。让我们看看怎样使 `用@One` 注解获取学生及其地址信息。

```
public interface StudentMapper {
@Select("SELECT ADDR_ID AS ADDRID, STREET, CITY, STATE, ZIP, COUNTRY
FROM ADDRESSES WHERE ADDR_ID=#{id}")
Address findAddressById(long id);

@Select("SELECT * FROM STUDENTS WHERE STUD_ID=#{studId} ")
@Results(
{
@Result(id = true, column = "stud_id", property = "studId"),
@Result(column = "name", property = "name"),
@Result(column = "email", property = "email"),
@Result(property = "address", column = "addr_id",
one = @One(select = "com.mybatis3.mappers.StudentMapper.findAddressById"))
})
Student selectStudentWithAddress(long studId);
}
```

这里我们使用了 `@One` 注解的 select 属性来指定一个使用了完全限定名的方法上,该方法会返回一个 Address 对象。使用 `column=”addr_id”` ,则 STUDENTS 表中列 addr_id 的值将会作为输入参数传递给 findAddressById() 方法。如果 `@One` SELECT 查询返回了多行结果,则会抛出 `TooManyResultsException` 异常。

```
long studId = 1;
StudentMapper studentMapper =
sqlSession.getMapper(StudentMapper.class);
Student student = studentMapper.selectStudentWithAddress(studId);
System.out.println("Student :"+student);
System.out.println("Address :"+student.getAddress());
```

在使用 XML 配置 SQL 映射器中,我们可以通过基于 XML 的映射器配置,使用嵌套结果 ResultMap 来加载一对一关联的查询。而 `MyBatis3.2.2` 版本,并没有对应的注解支持。但是我们可以在映射器 Mapper 配置文件中配置 `<resultMap>` 并且使用 `@ResultMap` 注解来引用它。
在 StudentMapper.xml 中配置 `<resultMap>` ,如下所示:

```
<mapper namespace="com.mybatis3.mappers.StudentMapper">
<resultMap type="Address" id="AddressResult">
<id property="addrId" column="addr_id" />
<result property="street" column="street" />
<result property="city" column="city" />
<result property="state" column="state" />
<result property="zip" column="zip" />
<result property="country" column="country" />
</resultMap>
<resultMap type="Student" id="StudentWithAddressResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<association property="address" resultMap="AddressResult" />
</resultMap>
</mapper>
```

```
public interface StudentMapper
{
@Select("select stud_id, name, email, a.addr_id, street, city,
state, zip, country" + " FROM students s left outer join addresses a
on s.addr_id=a.addr_id" + " where stud_id=#{studId} ")
@ResultMap("com.mybatis3.mappers.StudentMapper.
StudentWithAddressResult")
Student selectStudentWithAddress(long id);
}
```

### 一对多映射
MyBatis 提供了 `@Many` 注解,用来使用嵌套 Select 语句加载一对多关联查询。
现在让我们看一下如何使用 `@Many` 注解获取一个讲师及其教授课程列表信息:

```
public interface TutorMapper
{
@Select("select addr_id as addrId, street, city, state, zip,
country from addresses where addr_id=#{id}")
Address findAddressById(int id);

@Select("select * from courses where tutor_id=#{tutorId}")
@Results(
{
@Result(id = true, column = "course_id", property = "courseId"),
@Result(column = "name", property = "name"),
@Result(column = "description", property = "description"),
@Result(column = "start_date" property = "startDate"),
@Result(column = "end_date" property = "endDate")
})
List<Course> findCoursesByTutorId(int tutorId);

@Select("SELECT tutor_id, name as tutor_name, email, addr_id
FROM tutors where tutor_id=#{tutorId}")
@Results(
{
@Result(id = true, column = "tutor_id", property = "tutorId"),
@Result(column = "tutor_name", property = "name"),
@Result(column = "email", property = "email"),
@Result(property = "address", column = "addr_id",
one = @One(select = " com.mybatis3.
mappers.TutorMapper.findAddressById")),
@Result(property = "courses", column = "tutor_id",
many = @Many(select = "com.mybatis3.mappers.TutorMapper.
findCoursesByTutorId"))
})
Tutor findTutorById(int tutorId);
}

这里我们使用了 @Many 注解的 select 属性来指向一个完全限定名称的方法,该方法将返回一个 List 对象。使用 column=”tutor_id” ,TUTORS 表中的 tutor_id 列值将会作为输入参数传递给 findCoursesByTutorId() 方法。

使用 XML 配置 SQL 映射器中,我们可以通过基于 XML 的映射器配置,使用嵌套结果 ResultMap 来加载一对多关联的查询。而 MyBatis3.2.2 版本,并没有对应的注解支持。但是我们可以在映射器 Mapper 配置文件中配置 <resultMap> 并且使用 @ResultMap 注解来引用它。
在 TutorMapper.xml 中配置 <resultMap> ,如下所示:

<mapper namespace="com.mybatis3.mappers.TutorMapper">  
  <resultMap type="Address" id="AddressResult">  
    <id property="addrId" column="addr_id" />  
    <result property="street" column="street" />  
    <result property="city" column="city" />  
    <result property="state" column="state" />  
    <result property="zip" column="zip" />  
    <result property="country" column="country" />  
  </resultMap>  
  <resultMap type="Course" id="CourseResult">  
    <id column="course_id" property="courseId" />  
    <result column="name" property="name" />  
    <result column="description" property="description" />  
    <result column="start_date" property="startDate" />  
    <result column="end_date" property="endDate" />  
  </resultMap>  
  <resultMap type="Tutor" id="TutorResult">  
    <id column="tutor_id" property="tutorId" />  
    <result column="tutor_name" property="name" />  
    <result column="email" property="email" />  
    <association property="address" resultMap="AddressResult" />  
    <collection property="courses" resultMap="CourseResult" />  
  </resultMap>  
</mapper> 
public interface TutorMapper  
{  
    @Select("SELECT T.TUTOR_ID, T.NAME AS TUTOR_NAME, EMAIL,  
            A.ADDR_ID, STREET, CITY, STATE, ZIP, COUNTRY, COURSE_ID, C.NAME,  
            DESCRIPTION, START_DATE, END_DATE  FROM TUTORS T LEFT OUTER  
            JOIN ADDRESSES A ON T.ADDR_ID=A.ADDR_ID LEFT OUTER JOIN COURSES  
            C ON T.TUTOR_ID=C.TUTOR_ID WHERE T.TUTOR_ID=#{tutorId}")  
    @ResultMap("com.mybatis3.mappers.TutorMapper.TutorResult")  
    Tutor selectTutorById(int tutorId);  
} 

动态 SQL

有时候我们需要根据输入条件动态地构建 SQL 语句。MyBatis 提供了各种注解如 @InsertProvider , @UpdateProvider , @DeleteProvider 和 @SelectProvider ,来帮助构建动态 SQL 语句,然后让 MyBatis 执行这些 SQL 语句。

@SelectProvider

现在让我们来看一个使用 @SelectProvider 注解来创建一个简单的 SELECT 映射语句的例子。
创建一个 TutorDynaSqlProvider.java 类,以及 findTutorByIdSql() 方法,如下所示:

package com.mybatis3.sqlproviders;  
import org.apache.ibatis.jdbc.SQL;  
public class TutorDynaSqlProvider {  
    public String findTutorByIdSql(long tutorId) {  
        return "SELECT TUTOR_ID AS tutorId, NAME, EMAIL FROM TUTORS  
               WHERE TUTOR_ID=" + tutorId;  
    }  
}  

在 TutorMapper.java 接口中创建一个映射语句,如下:

@SelectProvider(type=TutorDynaSqlProvider.class, method="findTutorByIdSql")  
Tutor findTutorById(long tutorId);  

这里我们使用了 @SelectProvider 来指定了一个类,及其内部的方法,用来提供需要执行的 SQL 语句。

但是使用字符串拼接的方法来构建 SQL 语句是非常困难的,并且容易出错。所以MyBaits提供了一个SQL工具类不使用字符串拼接的方式,简化构造动态SQL语句。

现在,让我们看看如何使用 org.apache.ibatis.jdbc.SQL 工具类来准备相同的 SQL 语句:

package com.mybatis3.sqlproviders;  
import org.apache.ibatis.jdbc.SQL;  
public class TutorDynaSqlProvider {  
    public String findTutorByIdSql(final long tutorId) {  
        return new SQL() 
        {  
            {  
                SELECT("tutor_id as tutorId, name, email");  
                FROM("tutors");  
                WHERE("tutor_id=" + tutorId);  
            }  
        }.toString();  
    }  
}

SQ L工具类会处理以合适的空格前缀和后缀来构造 SQL 语句。

动态 SQL provider 方法可以接收以下其中一种参数:

  • 无参数
  • 和映射器 Mapper 接口的方法同类型的参数
  • java.util.Map

使用不带参数的 SQL Provider

public String findTutorByIdSql()  
{  
    return new SQL()  
    {  
        {  
            SELECT("tutor_id as tutorId, name, email");  
            FROM("tutors");  
            WHERE("tutor_id = #{tutorId}");  
        }  
    }.toString();  
}  

这里我们没有使用输入参数构造 SQL 语句,所以它可以是一个无参方法。

使用和映射器 Mapper 接口的方法同类型的参数

如果映射器 Mapper 接口方法只有一个参数,那么可以定义 SQL Provider 方法,它接受一个与 Mapper 接口方法相同类型的参数。
例如映射器 Mapper 接口有如下定义:

Tutor findTutorById(long tutorId); 

这里 findTutorById(int) 方法只有一个 long 类型的参数。我们可以定义 findTutorByIdSql(long) 方法作为 SQL provider 方法:

public String findTutorByIdSql(final long tutorId)  
{  
    return new SQL()  
    {  
        {  
            SELECT("tutor_id as tutorId, name, email");  
            FROM("tutors");  
            WHERE("tutor_id=" + tutorId);  
        }  
    }.toString();  
} 

使用 java.util.Map 参数类型的 SQL Provider

如果映射器 Mapper 接口有多个输入参数,我们可以使用参数类型为 java.util.Map 的方法作为 SQL Provider 方法。然后映射器 Mapper 接口方法所有的输入参数将会被放到 map 中,以 param1,param2 等等作为 key,将输入参数按序作为 value。你也可以使用 0,1,2 等作为 key 值来取的输入参数:

@SelectProvider(type = TutorDynaSqlProvider.class,  
                method = "findTutorByNameAndEmailSql")  
Tutor findTutorByNameAndEmail(String name, String email);  

public String findTutorByNameAndEmailSql(Map<String, Object> map) {  
    String name = (String) map.get("param1");  
    String email = (String) map.get("param2");  
    //you can also get those values using 0,1 keys  
    //String name = (String) map.get("0");  
    //String email = (String) map.get("1");  
    return new SQL()  
    {  
        {  
            SELECT("tutor_id as tutorId, name, email");  
            FROM("tutors");  
            WHERE("name=#{name} AND email=#{email}");  
        }  
    }.toString();  
} 

SQL 工具类也提供了其他的方法来表示 JOINS , ORDER_BY , GROUP_BY 等等。
让我们看一个使用 LEFT_OUTER_JOIN 的例子:

public class TutorDynaSqlProvider {  
    public String selectTutorById() {  
        return new SQL()  
        {  
            {  
                SELECT("t.tutor_id, t.name as tutor_name, email");  
                SELECT("a.addr_id, street, city, state, zip, country");  
                SELECT("course_id, c.name as course_name, description,  
                       start_date, end_date");  
                FROM("TUTORS t");  
                LEFT_OUTER_JOIN("addresses a on t.addr_id=a.addr_id");  
                LEFT_OUTER_JOIN("courses c on t.tutor_id=c.tutor_id");  
                WHERE("t.TUTOR_ID = #{id}");  
            }  
        }.toString();  
    }  
}  

public interface TutorMapper {  
    @SelectProvider(type = TutorDynaSqlProvider.class,  
                    method = "selectTutorById")  
    @ResultMap("com.mybatis3.mappers.TutorMapper.TutorResult")  
    Tutor selectTutorById(int tutorId);  
} 

由于没有支持使用内嵌结果 ResultMap 的一对多关联映射的注解支持,我们可以使用基于 XML 的 <resultMap> 配置,然后与 @ResultMap 映射:

<mapper namespace="com.mybatis3.mappers.TutorMapper">  
  <resultMap type="Address" id="AddressResult">  
    <id property="id" column="addr_id" />  
    <result property="street" column="street" />  
    <result property="city" column="city" />  
    <result property="state" column="state" />  
    <result property="zip" column="zip" />  
    <result property="country" column="country" />  
  </resultMap>  
  <resultMap type="Course" id="CourseResult">  
    <id column="course_id" property="id" />  
    <result column="course_name" property="name" />  
    <result column="description" property="description" />  
    <result column="start_date" property="startDate" />  
    <result column="end_date" property="endDate" />  
  </resultMap>  
  <resultMap type="Tutor" id="TutorResult">  
    <id column="tutor_id" property="id" />  
    <result column="tutor_name" property="name" />  
    <result column="email" property="email" />  
    <association property="address" resultMap="AddressResult" />  
    <collection property="courses" resultMap="CourseResult"></collection>  
  </resultMap>  
</mapper>  

@InsertProvider

我们可以使用 @InsertProvider 注解创建动态的 INSERT 语句,如下所示:

public class TutorDynaSqlProvider {  
    public String insertTutor(final Tutor tutor) {  
        return new SQL()  
        {  
            {  
                INSERT_INTO("TUTORS");  
                if (tutor.getName() != null)  
                {  
                    VALUES("NAME", "#{name}");  
                }  
                if (tutor.getEmail() != null)  
                {  
                    VALUES("EMAIL", "#{email}");  
                }  
            }  
        }.toString();  
    }  
}   

public interface TutorMapper {  
    @InsertProvider(type = TutorDynaSqlProvider.class,  
                    method = "insertTutor")  
    @Options(useGeneratedKeys = true, keyProperty = "tutorId")  
    int insertTutor(Tutor tutor);  
} 

@UpdateProvider

我们可以通过 @UpdateProvider 注解创建 UPDATE 语句,如下所示:

public class TutorDynaSqlProvider {  
    public String updateTutor(final Tutor tutor) {  
        return new SQL()  
        {  
            {  
                UPDATE("TUTORS");  
                if (tutor.getName() != null)  
                {  
                    SET("NAME = #{name}");  
                }  
                if (tutor.getEmail() != null)  
                {  
                    SET("EMAIL = #{email}");  
                }  
                WHERE("TUTOR_ID = #{tutorId}");  
            }  
        }.toString();  
    }  
}   

public interface TutorMapper {  
    @UpdateProvider(type = TutorDynaSqlProvider.class,  
                    method = "updateTutor")  
    int updateTutor(Tutor tutor);  
}

@DeleteProvider

我们可以使用 @DeleteProvider 注解创建动态地 DELETE 语句,如下所示:

public class TutorDynaSqlProvider {  
    public String deleteTutor(long tutorId) {  
        return new SQL()  
        {  
            {  
                DELETE_FROM("TUTORS");  
                WHERE("TUTOR_ID = #{tutorId}");  
            }  
        } .toString();  
    }  
}  

public interface TutorMapper {  
    @DeleteProvider(type = TutorDynaSqlProvider.class,  
                    method = "deleteTutor")  
    int deleteTutor(long tutorId);  
}  

Java Persistence with MyBatis 3(中文版) 第四章 使用注解配置SQL映射器

阅读全文 »
01月
08
Mybatis

Mybatis - 使用 XML 配置 SQL 映射器

发表于 2018-01-08 • 分类于 Mybatis

映射器配置文件和映射器接口

在 com.mybatis3.mappers 包中的 StudentMapper.xml 配置文件内,有个 id 为 ”findStudentById” 的 SQL 语句:

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
<?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.mybatis3.mappers.StudentMapper">
<select id="findStudentById" parameterType="long" resultType="Student">
select stud_id as studId, name, email, dob
from Students where stud_id=#{studId}
</select>
</mapper>
```

我们可以通过下列代码调用 findStudentById 映射的 SQL 语句:

```
public Student findStudentById(long studId) {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
try {
Student student =
sqlSession.selectOne("com.mybatis3.mappers.StudentMapper.findStudentById", studId);
return student;
} finally {
sqlSession.close();
}
}
```

我们可以通过字符串(字符串形式为:映射器配置文件所在的包名 namespace + 在文件内定义的语句 id,如上,即包名 com.mybatis3.mappers.StudentMapper 和语句 id (findStudentById) 组成调用映射的 SQL 语句,但是这种方式容易出错。你需要检查映射器配置文件中的定义,以保证你的输入参数类型和结果返回类型是有效的。
MyBatis 通过使用映射器 Mapper 接口提供了更好的调用映射语句的方法。**一旦我们通过映射器配置文件配置了映射语句,我们可以创建一个完全对应的一个映射器接口,接口名跟配置文件名相同,接口所在包名也跟配置文件所在包名完全一样(如 StudentMapper.xml 所在的包名是 com.mybatis3.mappers,对应的接口名就是 com.mybatis3.mappers.StudentMapper.java)**。映射器接口中的方法签名也跟映射器配置文件中完全对应:方法名为配置文件中 id 值;方法参数类型为 parameterType 对应值;方法返回值类型为 returnType 对应值。

<!--more-->

对于上述的 StudentMapper.xml 文件,我们可以创建一个映射器接口 StudentMapper.java 如下:

```
package com.mybatis3.mappers;
public interface StudentMapper {
Student findStudentById(long id);
}
```

在 StudentMapper.xml 映射器配置文件中,其名空间 namespace 应该跟 StudentMapper 接口的完全限定名保持一致。另外,StudentMapper.xml 中语句 id, parameterType, returnType 应该分别和 StudentMapper 接口中的方法名,参数类型,返回值相对应。

使用映射器接口我们可以以类型安全的形式调用调用映射语句:

```
public Student findStudentById(long studId) {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
try {
StudentMapper studentMapper =
sqlSession.getMapper(StudentMapper.class);
return studentMapper.findStudentById(studId);
} finally {
sqlSession.close();
}
}
```

## 映射语句
MyBatis 提供了多种元素来配置不同类型的语句,如 SELECT,INSERT,UPDATE,DELETE。接下来让我们看看如何具体配置映射语句。

### INSERT 语句
一个 INSERT SQL 语句可以在 `<insert>` 元素在映射器 XML 配置文件中配置,如下所示:

```
<insert id="insertStudent" parameterType="Student">
INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL, PHONE)
VALUES(#{studId},#{name},#{email},#{phone})
</insert>
```

这里我们使用一个 ID insertStudent,可以在名空间 com.mybatis3.mappers.StudentMapper.insertStudent 中唯一标识。parameterType 属性应该是一个完全限定类名或者是一个类型别名(alias)。

我们可以如下调用这个语句:

```
int count = sqlSession.insert("com.mybatis3.mappers.StudentMapper.insertStudent", student);
```

sqlSession.insert() 方法返回执行 INSERT 语句后所影响的行数。

如果不使用名空间(namespace)和语句id来调用映射语句,你可以通过创建一个映射器 Mapper 接口,并以类型安全的方式调用方法,如下所示:

```
package com.mybatis3.mappers;
public interface StudentMapper {
int insertStudent(Student student);
}
```

然后可以如下调用 insertStudent 映射语句:

```
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
int count = mapper.insertStudent(student);
```

#### 自动生成主键
在上述的 INSERT 语句中,我们为可以自动生成(auto-generated)主键的列 STUD_ID 插入值。我们可以使用 `useGeneratedKeys` 和 `keyProperty` 属性让数据库生成 auto_increment 列的值,并将生成的值设置到其中一个输入对象属性内,如下所示:

```
<insert id="insertStudent" parameterType="Student" useGeneratedKeys="true"
keyProperty="studId">
INSERT INTO STUDENTS(NAME, EMAIL, PHONE)
VALUES(#{name},#{email},#{phone})
</insert>
```

这里 STUD_ID 列值将会被 MySQL 数据库自动生成,并且生成的值会被设置到 student 对象的 studId 属性上。

```
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
mapper.insertStudent(student);
```

现在可以如下获取插入的 STUDENT 记录的 STUD_ID 的值:

```
long studentId = student.getStudId();
```

有些数据库如 Oracle 并不支持 AUTO_INCREMENT 列,其使用序列(SEQUENCE)来生成主键值。

假设我们有一个名为 STUD_ID_SEQ 的序列来生成 STUD_ID 主键值。使用如下代码来生成主键:

```
<insert id="insertStudent" parameterType="Student">
<selectKey keyProperty="studId" resultType="long" order="BEFORE">
SELECT ELEARNING.STUD_ID_SEQ.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL, PHONE)
VALUES(#{studId},#{name},#{email},#{phone})
</insert>
```

这里我们使用了 `selectKey` 子元素来生成主键值,并将值保存到 Student 对象的 studId 属性上。 属性 `order="before"` 表示 MyBatis 将取得序列的下一个值作为主键值,并且在执行 INSERT SQL 语句之前将值设置到 studId 属性上。

我们也可以在获取序列的下一个值时,使用触发器(trigger)来设置主键值,并且在执行 INSERT SQL 语句之前将值设置到主键列上。如果你采取这样的方式,则对应的 INSERT 映射语句如下所示:

```
<insert id="insertStudent" parameterType="Student">
INSERT INTO STUDENTS(NAME,EMAIL, PHONE)
VALUES(#{name},#{email},#{phone})
<selectKey keyProperty="studId" resultType="long" order="AFTER">
SELECT ELEARNING.STUD_ID_SEQ.CURRVAL FROM DUAL
</selectKey>
</insert>
```

### UPDATE 语句
一个 UPDATE SQL 语句可以在 `<update>` 元素在映射器 XML 配置文件中配置,如下所示:

```
<update id="updateStudent" parameterType="Student">
UPDATE STUDENTS SET NAME=#{name}, EMAIL=#{email}, PHONE=#{phone}
WHERE STUD_ID=#{studId}
</update>
```

我们可以如下调用此语句:

```
int noOfRowsUpdated =
sqlSession.update("com.mybatis3.mappers.StudentMapper.updateStudent", student);
```

sqlSession.update() 方法返回执行 UPDATE 语句之后影响的行数。

如果不使用名空间(namespace)和语句id来调用映射语句,你可以通过创建一个映射器 Mapper 接口,并以类型安全的方式调用方法,如下所示:

```
package com.mybatis3.mappers;
public interface StudentMapper {
int updateStudent(Student student);
}
```

然后可以使用映射器Mapper接口来调用 updateStudent 语句:

```
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
int noOfRowsUpdated = mapper.updateStudent(student);
```

### DELETE 语句
一个 UPDATE SQL 语句可以在 `<update>` 元素在映射器 XML 配置文件中配置,如下所示:

```
<delete id="deleteStudent" parameterType="long">
DELETE FROM STUDENTS WHERE STUD_ID=#{studId}
</delete>
```

我们可以如下调用此语句:

```
long studId = 1;
int noOfRowsDeleted =
sqlSession.delete("com.mybatis3.mappers.StudentMapper.deleteStudent", studId);
```

sqlSession.delete() 方法返回 delete 语句执行后影响的行数。

如果不使用名空间(namespace)和语句 id 来调用映射语句,你可以通过创建一个映射器 Mapper 接口,并以类型安全的方式调用方法,如下所示:

```
package com.mybatis3.mappers;
public interface StudentMapper {
int deleteStudent(long studId);
}
```

然后以使用映射器Mapper接口来调用 updateStudent 语句:

```
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
int noOfRowsDeleted = mapper.deleteStudent(studId);
```

### SELECT 语句
MyBatis 真正强大的功能,在于映射 SELECT 查询结果到 JavaBean 方面的极大灵活性。

让我们看看一个简单的 select 查询是如何(在MyBatis中)配置的,如下所示:

```
<select id="findStudentById" parameterType="long"
resultType="Student">
SELECT STUD_ID, NAME, EMAIL, PHONE
FROM STUDENTS
WHERE STUD_ID=#{studId}
</select>
```

我们可以如下调用此语句:

```
long studId = 1;
Student student =
sqlSession.selectOne("com.mybatis3.mappers.StudentMapper.findStudentById", studId);
```

如果不使用名空间(namespace)和语句id来调用映射语句,你可以通过创建一个映射器 Mapper 接口,并以类型安全的方式调用方法,如下所示:

```
package com.mybatis3.mappers;
public interface StudentMapper {
Student findStudentById(long studId);
}
```

然后可以使用映射器 Mapper 接口来调用 updateStudent 语句,如下所示:

```
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.findStudentById(studId);
```

如果你检查 Student 对象的属性值,你会发现 studId 属性值并没有被 stud_id 列值填充。这是因为 MyBatis 自动对 JavaBean 中和列名匹配的属性进行填充。这就是为什么 name,email,和 phone 属性被填充,而 studId 属性没有被填充。

为了解决这一问题,我们可以为列名起一个可以与 JavaBean 中属性名匹配的别名,如下所示:

```
<select id="findStudentById" parameterType="long"
resultType="Student">
SELECT STUD_ID AS studId, NAME,EMAIL, PHONE
FROM STUDENTS
WHERE STUD_ID=#{studId}
</select>
```

现在,Student 这个 Bean 对象中的值将会恰当地被 stud_id, name, email, phone 列填充了。

现在,让我们看一下如何执行返回多条结果的 SELECT 语句查询,如下所示:

```
<select id="findAllStudents" resultType="Student">
SELECT STUD_ID AS studId, NAME,EMAIL, PHONE
FROM STUDENTS
</select>
```

我们可以如下调用此语句:

```
List<Student> students =
sqlSession.selectList("com.mybatis3.mappers.StudentMapper.findAllStudents");
```

映射器 Mapper 接口 StudentMapper 可以如下定义:

```
package com.mybatis3.mappers;
public interface StudentMapper {
List<Student> findAllStudents();
}
```

然后可以如下调用 findAllStudents 语句:

```
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.findAllStudents();

如果你注意到上述的 SELECT 映射定义,你可以看到,我们为所有的映射语句中的 stud_id 起了别名。

我们可以使用 ResultMap,来避免上述的到处重复别名,我们稍后会继续讨论。

除了 java.util.List,你也可以是由其他类型的集合类,如 Set,Map,以及 SortedSet。MyBatis 根据集合的类型,会采用适当的集合实现,如下所示:

  • 对于 List,Collection,Iterable 类型,MyBatis 将返回 java.util.ArrayList
  • 对于 Map 类型,MyBatis 将返回 java.util.HashMap
  • 对于 Set 类型,MyBatis 将返回 java.util.HashSet
  • 对于 SortedSet 类型,MyBatis 将返回 java.util.TreeSet

结果集映射 ResultMap

ResultMap 被用来将 SQL SELECT 语句的结果集映射到 JavaBean 的属性中。我们可以定义结果集映射 ResultMap 并且在一些 SELECT 语句上引用 resultMap。MyBatis 的结果集映射 ResultMap 特性非常强大,你可以使用它将简单的 SELECT 语句映射到复杂的一对一和一对多关系的 SELECT 语句上。

简单 ResultMap

一个映射了查询结果和 Student JavaBean 的简单的 resultMap 定义如下:

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
79
80
81
82
83
84
85
86
87
88
<resultMap id="StudentResult" type="com.mybatis3.domain.Student">  
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<result property="phone" column="phone" />
</resultMap>

<select id="findAllStudents" resultMap="StudentResult">
SELECT * FROM STUDENTS
</select>

<select id="findStudentById" parameterType="long" resultMap="StudentResult">
SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}
</select>
```

resultMap 的 id 值 StudentResult 应该在此名空间内是唯一的。并且 type 属性应该是完全限定类名或者是返回类型的别名。

`result` 子元素被用来将一个 resultset 列映射到 JavaBean 的一个属性中。

`id` 元素和 `result` 元素功能相同,不过它被用来映射到唯一标识属性,用来区分和比较对象(一般和主键列相对应)。

在 `<select>` 语句中,我们使用了 resultMap 属性,而不是 resultType 来引用 StudentResult 映射。当 `<select>` 语句中配置了 resultMap 属性,MyBatis 会使用此数据库列名与对象属性映射关系来填充 JavaBean 中的属性。

> resultType 和 resultMap 二者只能用其一,不能同时使用。
>
> resultType 指定一个实体类型,mybatis会自动将查询的结果填充到该实体对象中。
>
> resultMap 可以自定义将查询的结果填充到对象的属性,对象的关联对象和关联集合上。

让我们来看另外一个 `select` 映射语句定义的例子,怎样将查询结果填充到 HashMap 中。如下所示:

```
<select id="findStudentById" parameterType="long" resultType="map">
SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}
</select>
```

在上述的 `<select>` 语句中,我们将 resultType 配置成 map,即 `java.util.HashMap` 的别名。在这种情况下,结果集的列名将会作为 Map 中的 key 值,而列值将作为 Map 的 value 值。

```
HashMap<String,Object> studentMap =
sqlSession.selectOne("com.mybatis3.mappers.StudentMapper.findStudentById", studId);
System.out.println("stud_id :"+studentMap.get("stud_id"));
System.out.println("name :"+studentMap.get("name"));
System.out.println("email :"+studentMap.get("email"));
System.out.println("phone :"+studentMap.get("phone"));
```

让我们再看一个 使用 `resultType="map"` ,返回多行结果的例子:

```
<select id="findAllStudents" resultType="map">
SELECT STUD_ID, NAME, EMAIL, PHONE FROM STUDENTS
</select>
```

由于 `resultType=”map"` 和语句返回多行,则最终返回的数据类型应该是 `List<HashMap<String, Object>>` ,如下所示:

```
List<HashMap<String, Object>> studentMapList =
sqlSession.selectList("com.mybatis3.mappers.StudentMapper.findAllStudents");
for(HashMap<String, Object> studentMap : studentMapList) {
System.out.println("studId :" + studentMap.get("stud_id"));
System.out.println("name :" + studentMap.get("name"));
System.out.println("email :" + studentMap.get("email"));
System.out.println("phone :" + studentMap.get("phone"));
}
```

### 拓展 ResultMap
我们可以从另外一个 `resultMap` ,拓展出一个新的 `resultMap` ,这样,原先的属性映射可以继承过来。

```
<resultMap type="Student" id="StudentResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<result property="phone" column="phone" />
</resultMap>
<resultMap type="Student" id="StudentWithAddressResult" extends="StudentResult">
<result property="address.addrId" column="addr_id" />
<result property="address.street" column="street" />
<result property="address.city" column="city" />
<result property="address.state" column="state" />
<result property="address.zip" column="zip" />
<result property="address.country" column="country" />
</resultMap>

id 为 StudentWithAddressResult 的 resultMap 拓展了 id 为 StudentResult 的 resultMap。

如果你只想映射 Student 数据,你可以使用 id 为 StudentResult 的 resultMap,如下所示:

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
<select id="findStudentById" parameterType="long"   
resultMap="StudentResult">
SELECT * FROM STUDENTS WHERE STUD_ID=#{studId}
</select>
```

如果你想将映射 Student 数据和 Address 数据,你可以使用 id 为 StudentWithAddressResult 的 resultMap:

```
<select id="selectStudentWithAddress" parameterType="long"
resultMap="StudentWithAddressResult">
SELECT STUD_ID, NAME, EMAIL, PHONE, A.ADDR_ID, STREET, CITY,
STATE, ZIP, COUNTRY
FROM STUDENTS S LEFT OUTER JOIN ADDRESSES A ON
S.ADDR_ID=A.ADDR_ID
WHERE STUD_ID=#{studId}
</select>
```

## 一对一映射
在我们的域模型样例中,每一个学生都有一个与之关联的地址信息。表 STUDENTS 有一个 ADDR_ID 列,是 ADDRESSES 表的外键。

Student 和 Address 的 JavaBean 以及映射器 Mapper XML 文件定义如下所示:

```
public class Address {
private long addrId;
private String street;
private String city;
private String state;
private String zip;
private String country;
//setters & getters
}
public class Student {
private long studId;
private String name;
private String email;
private PhoneNumber phone;
private Address address;
//setters & getters
}
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
<resultMap type="Student" id="StudentWithAddressResult">  
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<result property="phone" column="phone" />
<result property="address.addrId" column="addr_id" />
<result property="address.street" column="street" />
<result property="address.city" column="city" />
<result property="address.state" column="state" />
<result property="address.zip" column="zip" />
<result property="address.country" column="country" />
</resultMap>

<select id="selectStudentWithAddress" parameterType="long"
resultMap="StudentWithAddressResult">
SELECT STUD_ID, NAME, EMAIL, A.ADDR_ID, STREET, CITY, STATE,
ZIP, COUNTRY
FROM STUDENTS S LEFT OUTER JOIN ADDRESSES A ON
S.ADDR_ID=A.ADDR_ID
WHERE STUD_ID=#{studId}
</select>
```

我们可以使用圆点记法为内嵌的对象的属性赋值。在上述的 resultMap 中,Student 的 address 属性使用了圆点记法被赋上了 address 对应列的值。同样地,我们可以访问任意深度的内嵌对象的属性。我们可以如下访问内嵌对象属性:

```
//接口定义
public interface StudentMapper{
Student selectStudentWithAddress(long studId);
}

//使用
long studId = 1;
StudentMapper studentMapper =
sqlSession.getMapper(StudentMapper.class);
Student student = studentMapper.selectStudentWithAddress(studId);
System.out.println("Student :" + student);
System.out.println("Address :" + student.getAddress());
```

上述样例展示了一对一关联映射的一种方法。然而,使用这种方式映射,如果 address 结果需要在其他的 SELECT 映射语句中映射成 Address 对象,我们需要为每一个语句重复这种映射关系。MyBatis 提供了更好地实现一对一关联映射的方法:嵌套结果 ResultMap 和嵌套 select 查询语句。

### 使用嵌套结果 ResultMap 实现一对一关系映射
我们可以使用一个嵌套结果 ResultMap 方式来获取 Student 及其 Address 信息,代码如下:

```

<resultMap type="Address" id="AddressResult">
<id property="addrId" column="addr_id" />
<result property="street" column="street" />
<result property="city" column="city" />
<result property="state" column="state" />
<result property="zip" column="zip" />
<result property="country" column="country" />
</resultMap>
<resultMap type="Student" id="StudentWithAddressResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<association property="address" resultMap="AddressResult" />
</resultMap>

<select id="findStudentWithAddress" parameterType="long"
resultMap="StudentWithAddressResult">
SELECT STUD_ID, NAME, EMAIL, A.ADDR_ID, STREET, CITY, STATE,
ZIP, COUNTRY
FROM STUDENTS S LEFT OUTER JOIN ADDRESSES A ON
S.ADDR_ID=A.ADDR_ID
WHERE STUD_ID=#{studId}
</select>
```

元素 `<association>` 被用来导入“有一个”(has-one) 类型的关联。在上述的例子中,我们使用了 `<association>` 元素引用了另外的在同一个 XML 文件中定义的 `<resultMap>` 。
我们也可以使用 `<association` 定义内联的 `resultMap`,代码如下所示:

```
<resultMap type="Student" id="StudentWithAddressResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<association property="address" javaType="Address">
<id property="addrId" column="addr_id" />
<result property="street" column="street" />
<result property="city" column="city" />
<result property="state" column="state" />
<result property="zip" column="zip" />
<result property="country" column="country" />
</association>
</resultMap>
```

使用嵌套结果 ResultMap 方式,关联的数据可以通过简单的查询语句(如果需要的话,需要与 joins 连接操作配合)进行加载。

### 使用嵌套查询实现一对一关系映射
我们可以通过使用嵌套 select 查询来获取 Student 及其 Address 信息,代码如下:

```
<resultMap type="Address" id="AddressResult">
<id property="addrId" column="addr_id" />
<result property="street" column="street" />
<result property="city" column="city" />
<result property="state" column="state" />
<result property="zip" column="zip" />
<result property="country" column="country" />
</resultMap>

<select id="findAddressById" parameterType="long"
resultMap="AddressResult">
SELECT * FROM ADDRESSES WHERE ADDR_ID=#{id}
</select>

<resultMap type="Student" id="StudentWithAddressResult">
<id property="studId" column="stud_id" />
<result property="name" column="name" />
<result property="email" column="email" />
<association property="address" column="addr_id" select="findAddressById" />
</resultMap>

<select id="findStudentWithAddress" parameterType="long"
resultMap="StudentWithAddressResult">
SELECT * FROM STUDENTS WHERE STUD_ID=#{Id}
</select>
```

在此方式中,`<association>` 元素的 select 属性被设置成了 id 为 findAddressById 的语句。这里,两个分开的 SQL 语句将会在数据库中执行,第一个调用 findStudentById 加载 student 信息,而第二个调用 findAddressById 来加载 address 信息。

Addr_id 列的值将会被作为输入参数传递给 selectAddressById 语句。

我们可以如下调用 findStudentWithAddress 映射语句:

```
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectStudentWithAddress(studId);
System.out.println(student);
System.out.println(student.getAddress());
```

## 一对多映射
在我们的域模型样例中,一个讲师可以教授一个或者多个课程。这意味着讲师和课程之间存在一对多的映射关系。
我们可以使用 `<collection>` 元素将 一对多类型的结果映射到一个对象集合上。

Course和Tutor的JavaBean定义如下:

```
public class Course {
private long courseId;
private String name;
private String description;
private Date startDate;
private Date endDate;
private long tutorId;
//setters & getters
}

public class Tutor {
private long tutorId;
private String name;
private String email;
private Address address;
private List<Course> courses;
//setters & getters
}
```

现在让我们看看如何获取讲师信息以及其所教授的课程列表信息。

`<collection>` 元素被用来将多行课程结果映射成一个课程 Course 对象的一个集合。和一对一映射一样,我们可以使用嵌套结果 ResultMap 和嵌套 Select 语句两种方式映射实现一对多映射。

### 使用内嵌结果 ResultMap 实现一对多映射
我们可以使用嵌套结果 resultMap 方式获得讲师及其课程信息,代码如下:

```
<resultMap type="Course" id="CourseResult">
<id column="course_id" property="courseId" />
<result column="name" property="name" />
<result column="description" property="description" />
<result column="start_date" property="startDate" />
<result column="end_date" property="endDate" />
</resultMap>

<resultMap type="Tutor" id="TutorResult">
<id column="tutor_id" property="tutorId" />
<result column="tutor_name" property="name" />
<result column="email" property="email" />
<collection property="courses" resultMap="CourseResult" />
</resultMap>

<select id="findTutorById" parameterType="long"
resultMap="TutorResult">
SELECT T.TUTOR_ID, T.NAME AS TUTOR_NAME, EMAIL, C.COURSE_ID,
C.NAME, DESCRIPTION, START_DATE, END_DATE
FROM TUTORS T LEFT OUTER JOIN ADDRESSES A ON T.ADDR_ID=A.ADDR_ID
LEFT OUTER JOIN COURSES C ON T.TUTOR_ID=C.TUTOR_ID
WHERE T.TUTOR_ID=#{tutorId}
</select>
```

这里我们使用了一个简单的使用了 JOINS 连接的 Select 语句获取讲师及其所教课程信息。`<collection>` 元素的 resultMap 属性设置成了 CourseResult,CourseResult 包含了 Course 对象属性与表列名之间的映射。

### 使用嵌套 Select 语句实现一对多映射
我们可以使用嵌套 Select 语句方式获得讲师及其课程信息,代码如下:

```
<resultMap type="Course" id="CourseResult">
<id column="course_id" property="courseId" />
<result column="name" property="name" />
<result column="description" property="description" />
<result column="start_date" property="startDate" />
<result column="end_date" property="endDate" />
</resultMap>

<resultMap type="Tutor" id="TutorResult">
<id column="tutor_id" property="tutorId" />
<result column="tutor_name" property="name" />
<result column="email" property="email" />
<association property="address" resultMap="AddressResult" />
<collection property="courses" column="tutor_id" select="findCoursesByTutor" />
</resultMap>

<select id="findTutorById" parameterType="long" resultMap="TutorResult">
SELECT T.TUTOR_ID, T.NAME AS TUTOR_NAME, EMAIL
FROM TUTORS T WHERE T.TUTOR_ID=#{tutorId}
</select>

<select id="findCoursesByTutor" parameterType="long" resultMap="CourseResult">
SELECT * FROM COURSES WHERE TUTOR_ID=#{tutorId}
</select>
```

在这种方式中,`<aossication>` 元素的 select 属性被设置为 id 为 findCourseByTutor 的语句,用来触发单独的 SQL 查询加载课程信息。tutor_id 这一列值将会作为输入参数传递给 findCouresByTutor 语句。

```
//接口定义
public interface TutorMapper {
Tutor findTutorById(long tutorId);
}

//使用
TutorMapper mapper = sqlSession.getMapper(TutorMapper.class);
Tutor tutor = mapper.findTutorById(tutorId);
System.out.println(tutor);
List<Course> courses = tutor.getCourses();
for (Course course : courses) {
System.out.println(course);
}
```

> 嵌套 Select 语句查询会导致 N + 1 问题。首先,主查询将会执行(1次),对于主查询返回的每一行,另外一个查询将会被执行(主查询 N 行,则此查询 N 次)。

## 动态 SQL
有时候,静态的 SQL 语句并不能满足应用程序的需求。我们可以根据一些条件,来动态地构建 SQL 语句。

MyBatis 通过使用 `<if>` , `<choose>` , `<where>`, `<foreach>`, `<trim>` 元素提供了对构造动态 SQL 语句的高级别支持。

### If 条件
`<if>` 元素被用来有条件地嵌入 SQL 片段,如果测试条件被赋值为 true,则相应地 SQL 片段将会被添加到 SQL 语句中。

假定我们有一个课程搜索界面,设置了 讲师(Tutor)下拉列表框,课程名称(CourseName)文本输入框,开始时间(StartDate)输入框,结束时间(EndDate)输入框,作为搜索条件。假定课讲师下拉列表是必须选的,其他的都是可选的。

当用户点击 搜索按钮时,我们需要显示符合以下条件的成列表:
- 特定讲师的课程
- 课程名 包含输入的课程名称关键字的课程;如果课程名称输入为空,则取所有课程
- 在开始时间和结束时间段内的课程

我们可以对应的映射语句,如下所示:

```
<resultMap type="Course" id="CourseResult">
<id column="course_id" property="courseId" />
<result column="name" property="name" />
<result column="description" property="description" />
<result column="start_date" property="startDate" />
<result column="end_date" property="endDate" />
</resultMap>

<select id="searchCourses" parameterType="hashmap" resultMap="CourseResult">
SELECT * FROM COURSES
WHERE TUTOR_ID= #{tutorId}
<if test="courseName != null">
AND NAME LIKE #{courseName}
</if>
<if test="startDate != null">
AND START_DATE >= #{startDate}
</if>
<if test="endDate != null">
AND END_DATE <= #{endDate}
</if>
</select>
```

```
public interface CourseMapper {
List<Course> searchCourses(Map<String, Object> map);
}

public void searchCourses() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("tutorId", 1);
map.put("courseName", "%java%");
map.put("startDate", new Date());
CourseMapper mapper = sqlSession.getMapper(CourseMapper.class);
List<Course> courses = mapper.searchCourses(map);
for (Course course : courses) {
System.out.println(course);
}
}
```

此处将生成查询语句 `SELECT * FROM COURSES WHERE TUTOR_ID= ? AND NAME like? AND START_DATE >= ?` 。

> Mybatis 是使用 ONGL (Object Graph Navigation Language) 表达式来构建动态 SQL 语句。

### choose,when 和 otherwise 条件
有时候,查询功能是以查询类别为基础的。首先,用户需要选择是否希望通过选择讲师,课程名称,开始时间,或结束时间作为查询条件类别来进行查询,然后根据选择的查询类别,输入相应的参数。在这样的情景中,我们需要只使用其中一种查询类别。

MyBatis 提供了 `<choose>` 元素支持此类型的 SQL 预处理。

现在让我们书写一个适用此情景的 SQL 映射语句。如果没有选择查询类别,则查询开始时间在今天之后的课程,代码如下:

```
<select id="searchCourses" parameterType="hashmap" resultMap="CourseResult">
SELECT * FROM COURSES
<choose>
<when test="searchBy == 'Tutor'">
WHERE TUTOR_ID= #{tutorId}
</when>
<when test="searchBy == 'CourseName'">
WHERE name like #{courseName}
</when>
<otherwise>
WHERE TUTOR start_date >= now()
</otherwise>
</choose>
</select>
```

MyBatis 计算 `<choose>` 测试条件的值,且使用第一个值为 TRUE 的子句。如果没有条件为 true,则使用 `<otherwise>` 内的子句。

### where 条件
有时候,所有的查询条件(criteria)应该是可选的。在需要使用至少一种查询条件的情况下,我们应该使用 WHERE子句。并且, 如果有多个条件,我们需要在条件中添加 AND 或 OR。MyBatis 提供了 `<where>` 元素支持这种类型的动态 SQL 语句。

在我们查询课程界面,我们假设所有的查询条件是可选的。进而,当需要提供一个或多个查询条件时,应该改使用 WHERE 子句。

```
<select id="searchCourses" parameterType="hashmap"
resultMap="CourseResult">
SELECT * FROM COURSES
<where>
<if test=" tutorId != null ">
TUTOR_ID= #{tutorId}
</if>
<if test="courseName != null">
AND name like #{courseName}
</if>
<if test="startDate != null">
AND start_date >= #{startDate}
</if>
<if test="endDate != null">
AND end_date <= #{endDate}
</if>
</where>
</select>
```

`<where>` 元素只有在其内部标签有返回内容时才会在动态语句上插入 WHERE 条件语句。并且,如果 WHERE 子句以 AND 或者 OR 打头,则打头的 AND 或 OR 将会被移除。

如果 tutor_id 参数值为 null,并且 courseName 参数值不为 null,则 `<where>` 标签会将 AND name like#{courseName} 中的 AND 移除掉,生成的 SQL WHERE 子句为:where name like#{courseName}。

### trim 条件
`<trim>` 元素和 `<where>` 元素类似,但是 `<trim>` 提供了在添加前缀/后缀 或者移除前缀/后缀方面提供更大的灵活性。

```
<select id="searchCourses" parameterType="hashmap"
resultMap="CourseResult">
SELECT * FROM COURSES
<trim prefix="WHERE" prefixOverrides="AND | OR">
<if test=" tutorId != null ">
TUTOR_ID= #{tutorId}
</if>
<if test="courseName != null">
AND name like #{courseName}
</if>
</trim>
</select>
```

这里如果任意一个 `<if>` 条件为 true, `<trim>` 元素会插入 WHERE,并且移除紧跟 WHERE 后面的 AND 或 OR。

### foreach 循环
另外一个强大的动态 SQL 语句构造标签即是 `<foreach>` 。它可以迭代遍历一个数组或者列表,构造 AND/OR 条件或一个 IN 子句。

假设我们想找到 tutor_id 为1,3,6的讲师所教授的课程,我们可以传递一个 tutor_id 组成的列表给映射语句,然后通过 `<foreach>` 遍历此列表构造动态 SQL。
```
<select id="searchCoursesByTutors" parameterType="map"
resultMap="CourseResult">
SELECT * FROM COURSES
<if test="tutorIds != null">
<where>
<foreach item="tutorId" collection="tutorIds">
OR tutor_id=#{tutorId}
</foreach>
</where>
</if>
</select>
```

```
public interface CourseMapper {
List<Course> searchCoursesByTutors(Map<String, Object> map);
}

public void searchCoursesByTutors() {
Map<String, Object> map = new HashMap<String, Object>();
List<Long> tutorIds = new ArrayList<Long>();
tutorIds.add(1);
tutorIds.add(3);
tutorIds.add(6);
map.put("tutorIds", tutorIds);
CourseMapper mapper =
sqlSession.getMapper(CourseMapper.class);
List<Course> courses = mapper.searchCoursesByTutors(map);
for (Course course : courses) {
System.out.println(course);
}
}
```

现在让我们来看一下怎样使用 `<foreach>` 生成 IN 子句:

```
<select id="searchCoursesByTutors" parameterType="map"
resultMap="CourseResult">
SELECT * FROM COURSES
<if test="tutorIds != null">
<where>
tutor_id IN
<foreach item="tutorId" collection="tutorIds"
open="(" separator="," close=")">
#{tutorId}
</foreach>
</where>
</if>
</select>

set 条件

<set> 元素和 <where> 元素类似,如果其内部条件判断有任何内容返回时,他会插入 SET SQL 片段。

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
<update id="updateStudent" parameterType="Student">  
update students
<set>
<if test="name != null">name=#{name},</if>
<if test="email != null">email=#{email},</if>
<if test="phone != null">phone=#{phone},</if>
</set>
where stud_id=#{id}
</update>
```

这里,如果 `<if>` 条件返回了任何文本内容,`<set>` 将会插入 set 关键字和其文本内容,并且会剔除将末尾的 “,”。
在上述的例子中,如果 phone!=null, `<set>` 将会让会移除 phone=#{phone} 后的逗号“,”,生成 set phone=#{phone}。

## Mybatis 补充说明
除了简化数据库编程外,MyBatis 还提供了各种功能,这些对实现一些常用任务非常有用,比如按页加载表数据,存取 CLOB/BLOB 类型的数据,处理枚举类型值,等等。

### 处理枚举类型
MyBatis 支持开箱方式持久化 enum 类型属性。假设 STUDENTS 表中有一列 gender(性别)类型为 varchar,存储 ”MALE” 或者 “FEMALE” 两种值。并且,Student 对象有一个 enum 类型的 gender 属性,如下所示:

```
public enum Gender {
FEMALE,
MALE
}
```

默认情况下,MyBatis 使用 EnumTypeHandler 来处理 enum 类型的 Java 属性,并且将其存储为 enum 值的名称。你不需要为此做任何额外的配置。你可以可以向使用基本数据类型属性一样使用 enum 类型属性,代码如下:

```
public class Student {
private Integer id;
private String name;
private String email;
private PhoneNumber phone;
private Address address;
private Gender gender;
//setters and getters
}
```

```
<insert id="insertStudent" parameterType="Student"
useGeneratedKeys="true" keyProperty="id">
insert into students(name,email,addr_id, phone,gender)
values(#{name},#{email},#{address.addrId},#{phone},#{gender})
</insert>
```

当你执行 insertStudent 语句的时候,MyBatis 会取 Gender 枚举(FEMALE/MALE)的名称,然后将其存储到 GENDER 列中。
如果你希望存储原enum的顺序位置,而不是enum名,你需要明确地配置它。

如果你想存储 FEMALE 为 0,MALE 为 1 到 gender 列中,你需要在 `mybatis-config.xml` 文件中配置 EnumOrdinalTypeHandler:

```
<typeHandler
handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
javaType="com.mybatis3.domain.Gender"/>
```

> 使用顺序位置为值存储到数据库时要当心。顺序值是根据 enum 中的生命顺序赋值的。如果你改变了 Gender enum 的声明顺序,则数据库存储的数据和顺序值就不匹配了。

### 处理 CLOB/BLOB 类型数据
MyBatis 提供了内建的对 CLOB/BLOB 类型列的映射处理支持。

假设我们有如下的表结构来存储学生和讲师的照片和简介信息:

```
CREATE TABLE USER_PICS
(
ID INT(11) NOT NULL AUTO_INCREMENT,
NAME VARCHAR(50) DEFAULT NULL,
PIC BLOB,
BIO LONGTEXT,
PRIMARY KEY (ID)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;
```

这里,照片可以是PNG,JPG或其他格式的。简介信息可以是学生或者讲师的漫长的人生经历。默认情况下,MyBatis 将 CLOB 类型的列映射到 `java.lang.String` 类型上、而把 BLOB 列映射到 byte[] 类型上。

```
public class UserPic {
private long id;
private String name;
private byte[] pic;
private String bio;
//setters & getters
}
```

创建 UserPicMapper.xml 文件,配置映射语句,代码如下:

```
<insert id="insertUserPic" parameterType="UserPic">
INSERT INTO USER_PICS(NAME, PIC,BIO)
VALUES(#{name},#{pic},#{bio})
</insert>
<select id="getUserPic" parameterType="long" resultType="UserPic">
SELECT * FROM USER_PICS WHERE ID=#{id}
</select>
```

下列的 insertUserPic() 展示了如何将数据插入到 CLOB/BLOB 类型的列上:

```
public void insertUserPic() {
byte[] pic = null;
try {
File file = new File("C:\\Images\\UserImg.jpg");
InputStream is = new FileInputStream(file);
pic = new byte[is.available()];
is.read(pic);
is.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

String name = "UserName";
String bio = "put some lenghty bio here";
UserPic userPic = new UserPic(0, name, pic , bio);
SqlSession sqlSession = MyBatisUtil.openSession();

try {
UserPicMapper mapper =
sqlSession.getMapper(UserPicMapper.class);
mapper.insertUserPic(userPic);
sqlSession.commit();
} finally {
sqlSession.close();
}
}
```

下面的 getUserPic() 方法展示了怎样将 CLOB 类型数据读取到 String 类型,BLOB 类型数据读取成 byte[] 属性:

```
public void getUserPic() {
UserPic userPic = null;
SqlSession sqlSession = MyBatisUtil.openSession();
try {
UserPicMapper mapper =
sqlSession.getMapper(UserPicMapper.class);
userPic = mapper.getUserPic(1);
} finally {
sqlSession.close();
}

byte[] pic = userPic.getPic();
try {
OutputStream os =
new FileOutputStream(new File("C:\\Images\\UserImage_FromDB.jpg"));
os.write(pic);
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
```

### 传入多个输入参数
MyBatis 中的映射语句有一个 parameterType 属性来制定输入参数的类型。如果我们想给映射语句传入多个参数的话,我们可以将所有的输入参数放到 HashMap 中,将 HashMap 传递给映射语句。

MyBatis 还提供了另外一种传递多个输入参数给映射语句的方法。假设我们想通过给定的 name 和 email 信息查找学生信息,定义查询接口如下:

```
Public interface StudentMapper {
List<Student> findAllStudentsByNameEmail(String name, String email);
}

MyBatis 支持将多个输入参数传递给映射语句,并以 #{param} 的语法形式引用它们:

1
2
3
4
<select id="findAllStudentsByNameEmail" resultMap="StudentResult">  
select stud_id, name,email, phone from Students
where name=#{param1} and email=#{param2}
</select>

这里 #{param1} 引用第一个参数 name,而 #{param2} 引用了第二个参数 email。

1
2
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);  
studentMapper.findAllStudentsByNameEmail(name, email);

多行结果集映射成 Map

如果你有一个映射语句返回多行记录,并且你想以 HashMap 的形式存储记录的值,使用记录列名作为 key 值,而记录对应值或为 value 值。我们可以使用 sqlSession.selectMap(),如下所示:

<select id=" findAllStudents" resultMap="StudentResult">  
    select * from Students  
</select>  

查询:

Map<Long, Student> studentMap =   
sqlSession.selectMap("com.mybatis3.mappers.StudentMapper.findAllStudents", "studId");   

这里 studentMap 将会将 studId 作为 key 值,而 Student 对象作为 value 值。

使用 RowBounds 对结果集进行分页

有时候,我们会需要跟海量的数据打交道,比如一个有数百万条数据级别的表。由于计算机内存的现实我们不可能一次性加载这么多数据,我们可以获取到数据的一部分。特别是在 Web 应用程序中,分页机制被用来以一页一页的形式展示海量的数据。

MyBatis 可以使用 RowBounds 逐页加载表数据。RowBounds 对象可以使用 offset 和 limit 参数来构建。参数 offset 表示开始位置,而 limit 表示要取的记录的数目。

假设如果你想每页加载并显示 25 条学生的记录,你可以使用如下的代码:

<select id="findAllStudents" resultMap="StudentResult">  
    select * from Students  
</select> 

然后,你可以如下加载第一页数据(前25条):

int offset = 0 , limit = 25;  
RowBounds rowBounds = new RowBounds(offset, limit);  
List<Student> = studentMapper.getStudents(rowBounds);  

若要展示第二页,使用 offset = 25,limit = 25; 第三页,则为 offset = 50,limit = 25。

使用 ResultSetHandler 自定义处理结果集 resultSet

MyBatis 在将查询结果集映射到 JavaBean 方面提供了很大的选择性。但是,有时候我们会遇到由于特定的目的,需要我们自己处理 SQL 查询结果的情况。MyBatis 提供了 ResultHandler 插件形式允许我们以任何自己喜欢的方式处理结果集 ResultSet。

假设我们想从学生的 stud_id 被用作 key,而 name 被用作 value 的 HashMap 中获取到 student 信息。

mybatis-3.2.2 并不支持使用 resultMap 配置将查询的结果集映射成一个属性为 key,而另外属性为 value 的 HashMap。
sqlSession.selectMap() 则可以返回以给定列为 key,记录对象为 value 的 map。
我们不能将其配置成其中一个属性作为 key,而另外的属性作为 value。

对于 sqlSession.select() 方法,我们可以传递给它一个 ResultHandler 的实现,它会被调用来处理 ResultSet 的每一条记录。

public interface ResultHandler {   
    void handleResult(ResultContext context);  
} 

现在我们来看一下怎么使用 ResultHandler 来处理结果集 ResultSet,并返回自定义化的结果。

public Map<Long, String> getStudentIdNameMap() {  

    final Map<Long, String> map = new HashMap<Long, String>();  
    SqlSession sqlSession = MyBatisUtil.openSession();  

    try {  
        sqlSession.select("com.mybatis3.mappers.StudentMapper.findAllStudents"
            , new ResultHandler() {  
                @Override  
                public void handleResult(ResultContext context) {  
                    Student student = (Student) context.getResultObject();  
                    map.put(student.getStudId(), student.getName());  
                }  
            }  
    } finally {  
        sqlSession.close();  
    }  

    return map;  
}  

在上述的代码中,我们提供了匿名内部 ResultHandler 实现类,在 handleResult() 方法中,我们使用 context.getResultObject() 获取当前的 result 对象,即 Student 对象,因为我们定义了 findAllStudent 映射语句的 resultMap="studentResult" 。对查询返回的每一行都会调用 handleResult() 方法,并且我们从 Student 对象中取出 studId 和 name,将其放到 map 中。

缓存

将从数据库中加载的数据缓存到内存中,是很多应用程序为了提高性能而采取的一贯做法。MyBatis 对通过映射的 SELECT 语句加载的查询结果提供了内建的缓存支持。默认情况下,启用一级缓存,即,如果你使用同一个 SqlSession 接口对象调用了相同的 SELECT 语句,则直接会从缓存中返回结果,而不是再查询一次数据库。

我们可以在 SQL 映射器 XML 配置文件中使用 <cache /> 元素添加全局二级缓存。

当你加入了 <cache /> 元素,将会出现以下情况:

  • 所有的在映射语句文件定义的 select 语句的查询结果都会被缓存
  • 所有的在映射语句文件定义的 insert,update 和 delete 语句将会刷新缓存
  • 缓存根据最近最少被使用(Least Recently Used,LRU)算法管理
  • 缓存不会被任何形式的基于时间表的刷新(没有刷新时间间隔),即不支持定时刷新机制
  • 缓存将存储 1024 个查询方法返回的列表或者对象的引用
  • 缓存会被当作一个读/写缓存。这是指检索出的对象不会被共享,并且可以被调用者安全地修改,不会有其他潜在的调用者或者线程的潜在修改干扰,即,缓存是线程安全的。

你也可以通过复写默认属性来自定义缓存的行为,如下所示:

<cache eviction="FIFO" flushInterval="60000" size="512"   
readOnly="true"/>  

以下是对上述属性的描述:

  • eviction:此处定义缓存的移除机制。默认值是 LRU,其可能的值有:LRU(least recently used,最近最少使用),FIFO(first in first out,先进先出),SOFT(soft reference,软引用),WEAK(weak reference,弱引用)。
  • flushInterval:定义缓存刷新间隔,以毫秒计。默认情况下不设置。所以不使用刷新间隔时,缓存cache只有在调用语句的时候刷新。
  • size:表示缓存cache中能容纳的最大元素数。默认值是1024,你也可以设置成任意的正整数。
  • readOnly:一个只读的缓存 cache 会对所有的调用者返回被缓存对象的同一个实例(实际返回的是被返回对象的一份引用)。一个读/写缓存 cache 将会返回被返回对象的一分拷贝(通过序列化)。默认情况下设置为 false。可能的值有 false 和 true。

一个缓存的配置和缓存实例被绑定到映射器配置文件所在的名空间(namespace)上,所以在相同名空间内的所有语句被绑定到一个 cache 中。

默认的映射语句的 cache 配置如下:

<select... flushCache="false" useCache="true"/>  
<insert ... flushCache="true"/>  
<update ... flushCache="true"/>  
<delete ... flushCache="true"/> 

你可以为任意特定的映射语句复写默认的 cache 行为;例如,对一个 select 语句不使用缓存,可以设置 useCache=”false”。

除了内建的缓存支持,MyBatis 也提供了与第三方缓存类库如 Ehcache , OSCache , Hazelcast 的集成支持。你可以在 MyBatis官方网站 上找到关于继承第三方缓存类库的更多信息。

Java Persistence with MyBatis 3(中文版) 第三章 使用XML配置SQL映射器

阅读全文 »
123456…8
wuchao

72 日志
18 分类
Creative Commons

博客已萌萌哒运行(●'◡'●)ノ♥

© 2020 Keep Coding. 由 Hexo 强力驱动. Theme By Sagiri v0.0.4.

Made with by wuchao.