一、SQL映射文件
MyBatis真正强大之处就在于SQL映射语句,也就是它的魅力所在。
相对于它强大的功能,SQL映射文件的配置却非常简单。
在前面的学习中,我们简单地对比了SQL映射配置和JDBC代码,发现使用SQL映射文件配置可减少50%以上的代码量。
并且MyBatis专注于SQL,对开发人员来说,也可极大限度地进行SQL调试,以保证性能。
下面是关于SQL映射文件的几个顶级元素配置:
1. mapper:映射文件的根元素节点,只有一个属性namespace(命名空间),作用如下:
1)用于区分不同的mapper,全局唯一;
2)绑定DAO接口,即面向接口编程。当namespace绑定某一接口之后,可以不用写该接口的实现类,MyBatis会通过接口的完整限定名查找到对应的mapper配置来执行SQL语句。因此namespace的命名必须要跟接口同名。
2. cache:配置给定命名空间的缓存;
3. cache-ref:从其他命名空间引用缓存配置;
4. resultMap:用来描述数据库结果集合对象的对应关系;
5. sql:可以重用的SQL块,也可以被其他语句引用;
6. insert:映射插入语句;
7. update:映射更新语句;
8. delete:映射删除语句;
9. select:映射查询语句。
【注意:】
关于MyBatis的SQL映射文件中的mapper元素的namespace属性有如下要求:
1. namespace的命名必须跟某个DAO接口同名,同属于DAO类,故代码结构上。
映射文件与该DAO接口应放置在同一package下(如com.smbms.dao.user),并且习惯上都是以Mapper结尾(UserMapper.java、UserMapper.xml);
2. 在不同的mapper文件中,子元素的id可以相同,MyBatis通过namespace和子元素的id联合区分。
接口中的方法与映射文件中SQL语句id应一一对应。
二、使用select完成单条件查询
与查询对应的select元素是使用MyBatis时最常用的元素。
在前面的学习中,我们实现了对用户表的简单查询。
现在升级需求,增加查询条件,那如何实现带参数和返回复杂类型的查询?
这就需要先详细了解select元素的属性。
以实现根据用户名模糊查询来获取用户列表信息为例,SQL映射语句如下:
<
select
id
=
"
getUserListByUserName
"
resultType
=
"
User
"
parameterType
=
"
User
"
>
select * from smbms_user
where userName like CONCAT('%', #{userName}, '%')
select
>
这是一个id为getUserListByUserName的映射语句:
参数类型为string,返回结果的类型是User。
为了使数据库查询的结果和返回类型中的属性都能自动匹配以便开发,对于MySQL数据库和JavaBean都会采用同一套命名规则,即Java驼峰命名规则,这样就不需要再做映射。
(注:数据库表的字段名和属性名不一致的情况下需要手动映射)
注意参数的传递使用#{参数名},它告诉MyBatis生成PreparedStatement参数)。对于JDBC,该参数会被标识为“?”。
若采用JDBC来实现,代码的形式如下:
String sql
=
"
select * from smbms_user where userName like CAONCAT('%', ?, '%')
"
;
PreparedStatement pstmt
= conn
.preparedStatement
(sql
);
pstmt
.setString
(
1
, userName
);
由此可以看出,MyBatis可以节省大量的代码。
如果想完成复杂一些的查询或者让配置文件更简洁些,还需要进一步了解select元素的属性和MyBatis配置文件的属性。
下面先介绍上述select元素配置中出现属性的含义:
1. id:
命名空间中唯一的标识符,可以被用来引用这条语句;
2. parameterType:
表示查询语句传入参数的类型的完全限定名或别名,它支持基础数据类型和负责数据类型。
在上面的实例中使用的是基础数据类型“string”,这是一个别名,代表String,属于一个内建的类型别名。
对于普通的Java类型,有许多内建的类型别名,并且它们对大小不敏感。
如下图所示只列出了部分别名和Java类型的映射,其他的别名映射请参考MyBatis的帮助文档:
除了内建的类型别名外,还可以为自定义的类设置别名。
在前面的学习中已经讲过关于别名(typeAliases)在mybatis-config.xml中的设置,在映射文件中可直接使用别名,以减少配置文件的代码。
3. resultType:
查询语句返回结果类型的完全限定名或别名。别名的使用方式与parameterType是一样的。
三、使用select完成多条件查询
上述示例是只通过一个条件对用户表进行查询操作,但是在实际应用中,数据查询会有多种条件,结果也会有各种类型,如图所示:
根据上图,查询条件包括:用户名(模糊查询)、用户角色,那对于多条件查询,该如何实现呢?
我们可以考虑将查询条件封装成对象进行入参。
改造UserMapper.java代码如下:
public
interface
UserMapper
{
/**
* 查询用户列表
*
@
param
user 包含:用户名称(模糊查询)、用户角色
*
@
return
*/
public
List
getUserList(
User
user)
;
}
改造UserMapper.xml代码如下:
<
select
id
=
"
getUserList
"
resultType
=
"
User
"
parameterType
=
"
User
"
>
select * from smbms_user
where
userName like CONCAT('%', #{userName}, '%') and
userRole=#{userRole}
select
>
改造测试类UserMapperTest.java代码如下:
@Test
public
void estGetUserList
(){
SqlSession sqlSession
=
null
;
List userList
=
new
ArrayList()
;
try
{
sqlSession
=
MyBatisUtil
.createSqlSession
();
// 封装条件对象
User user
=
new
User()
;
user
.setUserName
(
"
赵
"
);
user
.setUserRole
(
3
);
userList
= sqlSession
.getMapper
(
UserMapper
.class
).getUserList
(user
);
}
catch (
Exception e)
{
e
.printStackTrace
();
}
finally
{
MyBatisUtil
.closeSqlSession
(sqlSession
);
}
for(
User user
: userList)
{
logger
.debug
(
"
UserCode:
"
+ user
.getUserCode
()
+
"
, UserName:
"
+ user
.getUserName
());
}
}
在上述实例中,parameterType使用了复杂数据类型,把条件参数封装成User对象进行入参。
运行结果如图所示:
对User对象中userName和userRole两个属性分别进行赋值,在映射的查询语句中设置parameterType为User类型。
传入参数分别使用#{}userName和#{userRole}来表示,即#{属性名}(参数对象中的属性名)。
parameterType支持的复杂数据类型除了JavaBean之外,还包括Map类型。
改造上一示例,把用户名和用户角色封装成Map对象进行入参。
测试类UserMapperTest.java部分代码如下:
// 封装条件Map
Map
userMap
=
new
HashMap
()
;
userMap
.put
(
"
uName
"
,
"
赵
"
);
userMap
.put
(
"
uRole
"
,
"
3
"
);
userList
=
sqlSession
.
getMapper
(
UserMapper
.
class
).
getUserListByMap
(
userMap
);
改造UserMapper.java,把封装好的userMap作为参数传入接口方法,代码如下:
/**
* 查询用户列表
*
@
return
*/
public
List getUserListByMap
(
Map userMap
);
改造UserMapper.xml,parameterType设置为Map。
SQL语句中的参数值使用#{uName}和#{uRole}来表示,即#{Map的key},代码如下:
<
select
id
=
"
getUserListByMap
"
resultType
=
"
User
"
parameterType
=
"
Map
"
>
select * from smbms_user
where
userName like CONCAT('%', #{uName}, '%') and
userRole=#{uRole}
select
>
这种做法更加灵活,不管是什么类型的参数,或者多少个参数,我们都可以把它封装成Map数据结构进行入参,通过Map的key即可获取传入的值。
【注意:】
MyBatis传入参数类型可以是Java基础数据类型,但是只适用于一个参数的情况,通过#{参数名}即可获取传入的值。
若是多参数入参,需要复杂数据类型来支持,包括Java实体类、Map,通过#{属性名}或#{Map的key}来获取传入的参数值。
四、使用resultMap完成查询结果的展现
通过上面的学习完成了传入多条件的查询操作。
但是对于结果列的展现,只是展示出用户表(smbms_user)中所有字段的值,比如用户表中userRole字段记录的是角色id,而不是其对应的角色名称。
在实际应用中,作为列表页的展示,用户关注的往往是角色名称而不是角色id,那么应该如何解决这类问题?
有两种解决方案,简单介绍并分析如下:
1. 修改POJO(User.java),增加userRoleName属性和getAge()方法:
public
class
User
{
private
Integer id
;
//id
private
String userCode
;
//用户编码
private
String userName
;
//用户名称
private
String userPassword
;
//用户密码
private
Integer gender
;
//性别
private
Date birthday
;
//出生日期
private
String phone
;
//电话
private
String address
;
//地址
private
Integer userRole
;
//用户角色
private
Integer createdBy
;
//创建者
private
Date creationDate
;
//创建时间
private
Integer modifyBy
;
//更新者
private
Date modifyDate
;
//更新时间
private
String userRoleName
;
// 角色名称
// 根据生日计算年龄
public
Integer
getAge()
{
/*long time = System.currentTimeMillis()-birthday.getTime();
Integer age = Long.valueOf(time/365/24/60/60/1000).IntegerValue();*/
Date date
=
new
Date()
;
Integer age
= date
.getYear
()-birthday
.getYear
();
return age
;
}
// 省略getters & setters ...
}
并修改查询用户列表的SQL语句,对用户表(smbms_user)和角色表(smbms_role)进行联表查询,使用resultType做自动映射;
2. 通过resultMap来映射自定义结果。
我们推荐第二种方案,使用resultMap做自定义结果映射,字段名可以不一致,并且还可以指定要显示的列,比较灵活,应用也广泛。
【注意:】
MyBatis中使用resultType做自动映射,一定要注意:字段名和POJO的属性名必须一致。若不一致,则需要给字段起别名,保证别名与属性名一致。
下面通过示例来演示resultMap的用法,查询出用户信息列表所必须的显示字段(包括用户编码、用户名称、性别、年龄、电话、压迫那个花角色等字段信息)。
注意用户角色要显示角色名称而不是角色id。
首先需要在User类中加入userRoleName属性:
private
String
userRoleName
; 及其相应的getter和setter方法。
然后修改UserMapper接口中的查询用户列表的getUserList()方法。
在UserMapper.xml中,修改getUserList的SQL映射语句,并修改select的resultType属性为resultMap,其属性值为userList。
代码如下:
<
select
id
=
"
getUserList
"
resultMap
=
"
userList
"
parameterType
=
"
User
"
>
select
u.*, r.roleName
from smbms_user as u
inner join smbms_role as r on u.userRole=r.id
where
u.userName like CONCAT('%', #{userName}, '%') and
u.userRole=#{userRole}
select
>
通过getUserList的SQL语句,进行联表查询,可得到用户对应的角色的中文名称。
接下来在UserMaper.xml中增加id为userList的resultMap元素节点,代码如下:
<
resultMap
type
=
"
User
"
id
=
"
userList
"
>
<
result
property
=
"
id
"
column
=
"
id
"
/>
<
result
property
=
"
userCode
"
column
=
"
userCode
"
/>
<
result
property
=
"
userName
"
column
=
"
userName
"
/>
<
result
property
=
"
phone
"
column
=
"
phone
"
/>
<
result
property
=
"
birthday
"
column
=
"
birthday
"
/>
<
result
property
=
"
gender
"
column
=
"
gender
"
/>
<
result
property
=
"
userRole
"
column
=
"
userRole
"
/>
<
result
property
=
"
userRoleName
"
column
=
"
roleName
"
/>
resultMap
>
resultMap元素用来描述如何将结果集映射到Java对象,此处使用resultMap对列表展示所需的必要字段来进行自由映射。
特别是当数据库的字段名和POJO中的属性名不一致的情况下,比如角色名称,字段名column是roleName,而User对象的属性名则为userRoleName,此时就需要做映射。
resultMap元素的属性值和子节点:
1. id属性:唯一标识,此id值用于select元素resultMap属性的引用;
2. type属性:表示该resultMap的映射结果类型;
3. result子节点:用于标识一些简单属性,其中column属性表示从数据库中查询的字段名,property则表示查询出来的字段对应的值赋给实体对象的哪个属性。
最后在测试类中进行相关字段的输出,展示列表(用户编码、用户名称、性别、年龄、性别、电话、用户角色)。注意:用户角色不再是角色id,输出的是角色名称。
MyBatis中在对查询进行select映射的时候,返回类型可以用resultType,也可以用resultMap。那么resultType和resultMap到底有何关联和区别?应用场景又是什么?下面做详细讲解。
1. resultType
resultType直接表示返回类型,包括基础数据类型和复杂数据类型。
2. resultMap
resultMap则是对外部resultMap定义的引用,对应外部resultMap的id,表示返回结果映射到哪一个resultMap上。
它的应用场景一般是:数据库字段信息与对象属性不一致或者需要做复杂的联合查询以便自由控制映射结果。
3. resultType和resultMap的关联
在MyBatis进行查询映射的时候,其实查询出来的每个字段值都放在一个对应的Map里面,其中键是字段名,值则是其对应的值。
当select元素提供的返回类型属性是resultType的时候,MyBatis会讲Map里面的键值对取出赋给resultType所指定的对象对应的属性(即调用对应的对象里面的属性的setter方法进行填充)。正因为如此,当使用resultType的时候,直接在后台就能接收到其相应的对象属性值。
由此可以看出,其实MyBatis的每个查询映射的返回类型都是resultMap,只是当我们提供的返回类型属性是resultType的时候,MyBatis会自动把对应的值赋给resultType所指定对象的属性,而当我们提供的返回类型是resultMap的时候,因为Map不能很好地表示领域模型,我们就需要通过进一步的定义把它转化为对应的实体对象。
当返回类型是resultMap时,也是非常有用的,这主要用在进行复杂联合查询上,当然进行简单查询时是没有什么必要的,使用resultType足以。
【注意:】
在MyBatis的select元素中,resultType和resultMap本质上是一样的,都是Map数据结构。
但需要明确一点:resultType属性和resultMap属性绝对不能同时存在,只能二者选其一使用。
4. resultMap的自动映射级别
在上面的示例中,选择部分字段进行resultMap映射。
我们希望没有映射的字段是不能在后台查询并输出的,即使SQL语句中是查询所有字段(select * from ...)。
因为我们使用resultMap也是为了自由灵活地控制映射结果,达到只对关心的属性进行赋值填充的目的。
修改测试类(UserMapperTest.java)的输出项,示例如下:
@Test
public
void estGetUserList
(){
SqlSession sqlSession
=
null
;
List userList
=
new
ArrayList()
;
try
{
sqlSession
=
MyBatisUtil
.createSqlSession
();
// 封装条件User对象
User user
=
new
User()
;
user
.setUserName
(
"
赵
"
);
user
.setUserRole
(
3
);
userList
= sqlSession
.getMapper
(
UserMapper
.class
).getUserList
(user
);
}
catch (
Exception e)
{
e
.printStackTrace
();
}
finally
{
MyBatisUtil
.closeSqlSession
(sqlSession
);
}
for(
User user
: userList)
{
logger.debug("testGetUserList() userCode:"+user.getUserCode() +
" and userName:"+user.getUserName() +
" and userRole:"+user.getUserRole() +
" and userRoleName:"+user.getUserRoleName() +
" and age:"+user.getAge() +
" and address:"+user.getAddress());
}
}
在上面的实例代码中,对比之前设置的resultMap映射的属性,增加了address和age两个属性值的输出。
观察结果,发现address和age的值均可正常输出,输出结果如下:
Preparing: select u.*, r.roleName from smbms_user as u inner join smbms_role as r on u.userRole=r.id where u.userName like CONCAT('%', ?, '%') and u.userRole=?
Parameters: 赵(String), 3(Integer)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@7ce6a65d]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@7ce6a65d]
Returned connection 2095490653 to pool.
testGetUserList()
userCode:zhaoyan and userName:赵燕 and userRole:3 and userRoleName:普通员工 and age:31 and address:北京市海淀区回龙观小区10号楼
为何age和address并没有在resultMap中做映射关联却能正常输出结果?
若更改需求为:没有在resultMap内映射的字段不能获取,那么又该如何实现?
这就跟resultMap的自动映射级别有关,默认的映射级别为PARTIAL。
若要满足需求,则需要设置MyBatis对于resultMap的自动映射级别(autoMappingBehavior)为NONE,即禁止自动匹配。
修改resources/mybatis-config.xml,代码如下:
<
settings
>
<
setting
name
=
"
autoMappingBehavior
"
value
=
"
NONE
"
/>
settings
>
增加以上的设置之后,在进行结果的输出,结果如下:
Preparing: select u.*, r.roleName from smbms_user as u inner join smbms_role as r on u.userRole=r.id where u.userName like CONCAT('%', ?, '%') and u.userRole=?
Parameters: 赵(String), 3(Integer)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@7ce6a65d]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@7ce6a65d]
Returned connection 2095490653 to pool.
testGetUserList() userCode:zhaoyan and userName:赵燕 and userRole:3 and userRoleName:普通员工 and age:31 and
address:null
从上面的输出结果中发现address属性值为null,该属性没有进行自动setter赋值。
但是age的属性值仍为31,并非为空,这是因为age属性值并非直接取自数据表,而是在getAge()方法中通过birthday属性计算出来的,只要加载了birthday就可以计算出age。
【注意:】
在MyBatis中,使用resultMap能够进行自动映射匹配的前提是字段名和属性名需要一致。
在默认映射级别(PARTIAL)情况下,若一致,即使没有做属性名和字段名的匹配,也可以在后台获取到未匹配过的属性值;
若不一致,且在resultMap里没有做映射,那么就无法在后台获取并输出。