SQL映射文件
单词
ResultType 结果类型
ResultMap结果映射
Association协会
Cache缓存
parameterType参数
使用myBatis实现条件查询
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<User> 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<User> userList = new ArrayList<User>();
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<String, String> userMap = new HashMap<String, String>();
userMap.put("uName", "赵");
userMap.put("uRole", "3");
userList = sqlSession.getMapper(UserMapper.class).getUserListByMap(userMap);
改造UserMapper.java,把封装好的userMap作为参数传入接口方法,代码如下:
/**
* 查询用户列表
* @return
*/
public List<User> getUserListByMap(Map<String, String> 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()方法
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的自动映射级别
我们希望没有映射的字段是不能在后台查询并输出的,即使SQL语句中是查询所有字段(select * from ...)。
因为我们使用resultMap也是为了自由灵活地控制映射结果,达到只对关心的属性进行赋值填充的目的
为何age和address并没有在resultMap中做映射关联却能正常输出结果?
若更改需求为:没有在resultMap内映射的字段不能获取,那么又该如何实现?
这就跟resultMap的自动映射级别有关,默认的映射级别为PARTIAL。
若要满足需求,则需要设置MyBatis对于resultMap的自动映射级别(autoMappingBehavior)为NONE,即禁止自动匹配。
修改resources/mybatis-config.xml,代码如下:
<settings>
<!-- 设置resultMap的自动映射级别为NONE(禁止自动匹配) -->
<setting name="autoMappingBehavior" value="NONE"/>
</settings>
【注意:】
在MyBatis中,使用resultMap能够进行自动映射匹配的前提是字段名和属性名需要一致。
在默认映射级别(PARTIAL)情况下,若一致,即使没有做属性名和字段名的匹配,也可以在后台获取到未匹配过的属性值;
若不一致,且在resultMap里没有做映射,那么就无法在后台获取并输出。
使用MyBatis进行对数据表的增删改查操作
insert标签属性及意义:
【注意】
对于增删改(insert/update/dalete)这类数据库更新操作,需要注意两点:
- 该类型的操作本身默认返回执行sql影响的行数,所以dao层的接口方法的返回值一般设置为int类型,最好不要返回boolean类型
- Insert/update/delete元素中均没有resultType属性,只有查询操作需要对返回结果类型(resultType/resultMap)进行相应的指定
Factory.openSession(false);//自动提交事务
使用update完成修改操作
增加modify()方法
Public int modify(User user);
要修改的User对象作为入参,返回值为int类型,即返回执行收影响的行数(示例13)
使用@Param注解实现多参数入参
直接进行多参数入参,代码可读性高,可清晰的看出这个接口方法所需的参数是什么
public int updatePwd(@Param("id")Integer id, @Param("userPassword")String pwd);
如果不使用@Param注解提出使用多参数入参的方式进行订单列表的查询操作,则会报错!
【经验】
在MyBatis中参数入参,一般情况下,超过4个以上的参数最好封装成对象入参(特别是在常规的增加和修改操作时,字段较多,封装成对象比较方便)
对于参数固定的业务方法,最好使用多参数入参,原因是这种方法比较灵活,代码的可读性高,可以清晰的看出接口方法中所需的参数是什么。并且对于固定的接口方法,参数一般是固定的,所以直接多参数入参即可,无需封装对象。
使用delete完成删除操作
MyBatis实现删除操作,是使用delete元素来映射删除语句。
public int deleteUserById(@Param("id")Integer delId);
使用resultMap的基本配置
resultMap的基本配置项
- 属性
Id:resultMap的唯一标识
Type:表示该resultMap的映射结果类型(通常是java实体类)
- 子节点
Id:一般对应数据库中该行的主键id,设置此项可以提升mtBatis性能
字节的和result均实现最基本的结果集映射,将列映射到简单数据类型的属性。这两者唯一不同的是:在比较对象实例时id将作为结果集的标识属性。这就有助于提高总体性能,特别是应用缓存和嵌套结果映射的时候。而若要实现高级结果映射,就需要学习下面两个配置项:association和collection
Association
映射到javaBean的某个复杂属性,比如javaBean类,即内部嵌套一个复杂数据类型属性,这种情况就属于复杂类型的关联,但是注意:assoiction仅处理一对一的关系
Collection
Collection元素作用和assoication元素作用差不多一样,也是映射到javaBean的某个复杂类型属性,只不过这个属性是一个集合列表,即javaBean内部嵌套一个复杂数据类型(集合)
MyBatis缓存
- 一级缓存
基于PerpetualCache的hashMap本地缓存,作用范围为session域内。当session flush或者close之后,该session中所有的cache就会被清空
- 二级缓存
二级缓存就是global caching超出session范围之外,可以被所有sqlSession共享,开启它只需要在myBatis的核心配置文件(config.xml)setting中设置即可
- 二级缓存的配置
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
本章总结
- MyBaatis的SQL映射文件提供select/insert/update/delete等元素来实现sql语句的映射
- Sql映射文件的根节点是mapper元素.需要制定namespace来区别于其他的mapper,保证全局唯一,并且其名称必须要跟接口同名,作用是绑定dao接口,即面向接口编程
- Sql映射文件的select的返回结果类型的映射可以使用resultMap和resultType,但不能同时使用
- 关于myBatis的sql语句参数入参,对于基础数据类型的参数数据,使用@Param注解实现参数入参;复杂数据类型的参数直接入参即可
- resultMap的addociation和collection可以实现高级结果映射
内容就到这,
我是印书打总结的陈恰恰.77