❖ 掌握参数的使用方法
❖ 掌握resultMap
❖ 了解Cache的使用方法
任务1:实现条件查询任务
2:实现增删改操作任务
3:实现高级结果映射任务
4:配置resultMap自动映射级别和MyBatis缓存
关键步骤如下。
➢ 使用select元素实现根据用户名模糊查询用户列表信息。
➢ 使用select元素实现多条件查询用户列表信息。
➢ 使用resultMap元素映射自定义查询结果。
➢ mapper:映射文件的根元素节点,只有一个属性namespace(命名空间),其作用如下。♦ 用于区分不同的mapper,全局唯一。♦ 绑定DAO接口,即面向接口编程。当namespace绑定某一接口之后,可以不用写该接口的实现类,MyBatis会通过接口的完整限定名查找到对应的mapper配置来执行SQL语句。因此namespace的命名必须跟接口同名。
➢ cache:配置给定命名空间的缓存。
➢ cache-ref:从其他命名空间引用缓存配置。
➢ resultMap:用来描述数据库结果集和对象的对应关系。
➢ sql:可以重用的SQL块,也可以被其他语句引用。
➢ insert:映射插入语句。
➢ update:映射更新语句。
➢ delete:映射删除语句。
➢ select:映射查询语句。
注意
MyBatis的SQL映射文件中mapper元素的namespace属性有如下要求:(1)namespace的命名必须跟某个DAO接口同名,同属于DAO层,故在代码结构上,映射文件与该DAO接口应放置在同一package下(如cn.smbms.dao. user),并且习惯上都是以Mapper结尾(如UserMapper.java、UserMapper.xml)。
(2)在不同的mapper文件中,子元素的id可以相同,MyBatis通过namespace和子元素的id联合区分。接口中的方法与映射文件中SQL语句id应一一对应。
与查询对应的select元素是使用MyBatis时最常用的。在上一章,我们实现了对用户表的简单查询,现在升级需求,增加查询条件,那么如何实现带参数和返回复杂类型的查询?这就需要先详细了解select元素的属性。以根据用户名模糊查询来获取用户列表信息为例,
➢ id:命名空间中唯一的标识符,可以被用来引用这条语句。
➢ parameterType:表示查询语句传入参数的类型的完全限定名或别名。它支持基础数据类型和复杂数据类型。在示例1中使用的是基础数据类型“string”,这是一个别名,代表String,属于一个内建的类型别名。对于普通的Java类型,有许多内建的类型别名,并且它们对大小写不敏感。
除了内建的类型别名外,还可以为自定义的类设置别名。
别名(typeAliases)在mybatis-config.xml中的设置,在映射文件中可直接使用别名,以减少配置文件的代码。
➢ resultType:查询语句返回结果类型的完全限定名或别名。别名的使用方式与parameterType相同。
这是一个id为getUserListByUserName的映射语句,参数类型为string,返回结果的类型是User。为了使数据库查询的结果和返回类型中的属性能够自动匹配、以便于开发,对于MySQL数据库和JavaBean都会采用同一套命名规则,即Java命名驼峰规则,这样就不需要再做映射(注:如果数据库表的字段名和属性名不一致则需要手动映射)。参数的传递使用#{参数名},它告诉MyBatis生成PreparedStatement参数。
@Test
public void testGetUserListByUserName(){
SqlSession sqlSession = null;
List userList = new ArrayList();
try {
sqlSession = MyBatisUtil.createSqlSession();
//第一种方式:调用selectList方法执行查询操作
//userList = sqlSession.selectList("cn.smbms.dao.user.UserMapper.getUserListByUserName","赵");
//第二种方式:调用getMapper(Mapper.class)执行dao接口方法来实现对数据库的查询操作
userList = sqlSession.getMapper(UserMapper.class).getUserListByUserName("赵");
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally{
MyBatisUtil.closeSqlSession(sqlSession);
}
for(User user: userList){
logger.debug("testGetUserListByUserName userCode: " + user.getUserCode() + " and userName: " + user.getUserName());
}
}
通过一个条件对用户表进行查询操作,但在实际应用中,数据查询会有多种条件,结果也会有各种类型
查询条件包括:用户名(模糊查询)、用户角色。那对于多条件查询,该如何实现?我们可以考虑将查询条件封装成对象进行入参,改造UserMapper.java
public List getUserList(User user);
@Test
public void testGetUserList(){
SqlSession sqlSession = null;
List userList = new ArrayList();
try {
sqlSession = MyBatisUtil.createSqlSession();
User user = new User();
user.setUserName("赵");
user.setUserRole(3);
//第一种方式:调用selectList方法执行查询操作
//userList = sqlSession.selectList("cn.smbms.dao.user.UserMapper.getUserList",user);
//第二种方式:调用getMapper(Mapper.class)执行dao接口方法来实现对数据库的查询操作
userList = sqlSession.getMapper(UserMapper.class).getUserList(user);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally{
MyBatisUtil.closeSqlSession(sqlSession);
}
for(User user: userList){
logger.debug("testGetUserList userCode: " + user.getUserCode() + " and userName: " + user.getUserName());
}
}
parameterType使用了复杂数据类型,把条件参数封装成User对象进行入参。对User对象中userName和userRole两个属性分别进行赋值,在映射的查询语句中设置parameterType为User类型,传入参数分别使用#{userName}和#{userRole}来表示,即#{属性名}(参数对象中的属性名)。
parameterType支持的复杂数据类型除了JavaBean之外,还有Map类型,改造上一示例,把用户名和用户角色封装成Map对象进行入参,测试类UserMapperTest.java的关键代码如示例5所示。
public List getUserListByMap(Map userMap);
改造UserMapper.xml,将parameterType设置为Map, SQL语句中的参数值使用#{uName}和#{uRole}来表示,即#{Map的key}
这种做法更加灵活,不管是什么类型的参数、有多少个参数,我们都可以把它们封装成Map数据结构进行入参,通过Map的key即可获取传入的值。
@Test
public void testGetUserListByMap(){
SqlSession sqlSession = null;
List userList = new ArrayList();
try {
sqlSession = MyBatisUtil.createSqlSession();
Map userMap = new HashMap();
userMap.put("uName", "赵");
userMap.put("uRole", "3");
//第一种方式:调用selectList方法执行查询操作
//userList = sqlSession.selectList("cn.smbms.dao.user.UserMapper.getUserListByMap",userMap);
//第二种方式:调用getMapper(Mapper.class)执行dao接口方法来实现对数据库的查询操作
userList = sqlSession.getMapper(UserMapper.class).getUserListByMap(userMap);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally{
MyBatisUtil.closeSqlSession(sqlSession);
}
for(User user: userList){
logger.debug("testGetUserListByMap userCode: " + user.getUserCode() + " and userName: " + user.getUserName());
}
}
注意
MyBatis传入参数类型可以是Java基础数据类型,但是只适用于一个参数的情况,通过#{参数名}即可获取传入的值。若是多参数入参,则需要复杂数据类型来支持,包括Java实体类、Map,通过#{属性名}或#{Map的key}来获取传入的参数值。
(1)修改POJO:User.java增加userRoleName属性,并修改查询用户列表的SQL语句,对用户表(smbms_user)和角色表(smbms_role)进行联表查询,使用resultType自动映射。
(2)通过resultMap映射自定义结果。
注意
MyBatis中使用resultType做自动映射时,要注意字段名和POJO的属性名必须一致。若不一致,则需要给字段起别名,保证别名与属性名一致。
查询出用户信息列表所必需的显示字段(包括用户编码、用户名称、性别、年龄、电话、用户角色等字段信息),注意用户角色要显示角色名称而不是角色id。首先需要在User类中加入userRoleName属性:private StringuserRoleName,及其相应的getter和setter方法。然后修改UserMapper接口中的查询用户列表的getUserList()方法,在UserMapper. xml中,修改getUserList的SQL映射语句,并修改select的resultType属性为resultMap,其属性值为userList,
public List getUserListByMap(Map userMap);
通过getUserList的SQL语句,进行联表查询,可得到用户对应角色的中文名称。接下来在UserMapper.xml中增加id为userList的resultMap元素节点
resultMap元素用来描述如何将结果集映射到Java对象,此处使用resultMap对列表展示所需的必要字段进行自由映射,特别是当数据库的字段名和POJO中的属性名不一致的情况下,比如角色名称,字段名column是roleName,而User对象的属性名为userRoleName,此时就需要做映射。
resultMap元素的属性值和子节点:
➢ id属性:唯一标识,此id值用于对select元素resultMap属性的引用。
➢ type属性:表示该resultMap的映射结果类型。
➢ result子节点:用于标识一些简单属性,其中column属性表示从数据库中查询的字段名,property则表示查询出来的字段对应的值赋给实体对象的哪个属性。
最后在测试类中进行相关字段的输出,展示列表(用户编码、用户名称、性别、年龄、电话、用户角色)。注意:用户角色不再是角色id,输出的是角色名称。MyBatis中在对查询进行select映射的时候,返回类型可以用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不能很好地表示领域模型,就需要通过进一步的定义把它转化为对应的实体对象。
注意
在MyBatis的select元素中,resultType和resultMap本质上是一样的,都是Map数据结构。但需要明确一点:resultType属性和resultMap属性绝对不能同时存在,只能二者选其一使用。
4.resultMap的自动映射级别
在上面的示例中,选择部分字段进行resultMap映射,我们希望没有映射的字段不能在后台查询并输出,即使SQL语句中是查询所有字段(select * from……)。因为我们使用resultMap也是为了自由灵活地控制映射结果,达到只对关心的属性进行赋值填充的目的。修改测试类(UserMapperTest.java)的输出项
@Test
public void testGetUserList(){
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) {
// TODO: handle exception
e.printStackTrace();
}finally{
MyBatisUtil.closeSqlSession(sqlSession);
}
/**
* 若设置resultMap的自动映射级别为NONE,
* 那么没有进行映射匹配的属性(比如:address等)则输出为null
* 若不设置resultMap的自动映射级别,则不管是否进行了映射,所有的属性值均可输出
*/
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的值均可正常输出
为何没有在resultMap中做映射关联的age和address却能正常输出结果?若需求为没有在resultMap内映射的字段不能获取,那么又该如何实现?
这跟resultMap的自动映射级别有关,默认的映射级别为PARTIAL。要满足新需求,则需要设置MyBatis对于resultMap的自动映射级别(autoMappingBehavior)为NONE,即禁止自动匹配。修改mybatis-config.xml
增加以上的设置之后,再进行结果的输出,发现address属性值为null,即该属性没有进行自动setter赋值,但是age的属性值仍为30,并非为空。这是因为age属性值并非直接取自数据表,而是在getAge()方法中通过birthday属性计算得出,只要加载了birthday就可以计算出age
注意
在MyBatis中,使用resultMap能够进行自动映射匹配的前提是字段名和属性名必须一致,在默认映射级别(PARTIAL)情况下,若字段名和属性名一致,即使没有做属性名和字段名的匹配,也可以在后台获取到未匹配过的属性值;若字段名和属性名不一致,且在resultMap里没有做映射,那么就无法在后台获取并输出。
关键步骤如下。
➢ 使用insert元素实现用户表的增加。
➢ 使用update元素实现根据用户id修改用户信息。
➢ 使用@Param注解实现多参数入参。
➢ 使用delete元素实现根据用户id删除用户。
MyBatis实现增加操作,使用的是insert元素来映射插入语句。具体的用法很简单,下面通过用户表的增加操作示例来演示具体用法,首先在UserMapper接口里增加add()方法。
public int add(User user);
要插入的User对象作为入参,返回值为int类型,即返回执行SQL语句影响的行数。修改UserMapper.xml,增加插入语句
insert into smbms_user (userCode,userName,userPassword,gender,birthday,phone,
address,userRole,createdBy,creationDate)
values (#{userCode},#{userName},#{userPassword},#{gender},#{birthday},#{phone},
#{address},#{userRole},#{createdBy},#{creationDate})
insert元素的属性:
➢ id:与select元素的id一样,是命名空间中唯一的标识符,可以被用来引用该条语句。
➢ parameterType:与select元素的parameterType一样,是传入参数的类型的完全限定名或别名。别名的含义和用法见select元素中的解释。
public int add(User user);
注意
对于增删改(insert、update、delete)这类数据库更新操作,需要注意两点:(1)该类型的操作本身默认返回执行SQL语句影响的行数,所以DAO层的接口方法的返回值一般设置为int类型。最好不要返回boolean类型。
(2)insert、update、delete元素中均没有resultType属性,只有查询操作需要对返回结果的类型(resultType/resultMap)进行相应的指定。
接下来修改测试类UserMapperTest.java,增加testAdd()方法,进行插入数据测试,并开启事务控制,模拟异常,若发生异常则回滚以测试事务。
@Test
public void testAdd(){
logger.debug("testAdd !===================");
SqlSession sqlSession = null;
int count = 0;
try {
sqlSession = MyBatisUtil.createSqlSession();
User user = new User();
user.setUserCode("test001");
user.setUserName("测试用户001");
user.setUserPassword("1234567");
Date birthday = new SimpleDateFormat("yyyy-MM-dd").parse("1984-12-12");
user.setBirthday(birthday);
user.setCreationDate(new Date());
user.setAddress("地址测试");
user.setGender(1);
user.setPhone("13688783697");
user.setUserRole(1);
user.setCreatedBy(1);
user.setCreationDate(new Date());
count = sqlSession.getMapper(UserMapper.class).add(user);
//模拟异常,进行回滚
//int i = 2/0;
sqlSession.commit();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
sqlSession.rollback();
count = 0;
}finally{
MyBatisUtil.closeSqlSession(sqlSession);
}
logger.debug("testAdd count: " + count);
}
那么在此处测试中,当sqlSession执行add()方法之后需要进行commit,完成数据的插入操作。若在执行的过程中抛出异常,那么就必须在catch中进行回滚,以此来保证数据的一致性,同时设置count为0。
MyBatis实现修改操作,使用的是update元素来映射修改语句。具体的用法与insert类似,下面通过根据用户id修改用户信息的操作实例来演示具体用法。首先在UserMapper接口里增加modify()方法。
public int modify(User user);
要修改的User对象作为入参,返回值为int类型,
即返回执行SQL语句影响的行数。修改UserMapper.xml,增加修改语句,
update smbms_user set userCode=#{userCode},userName=#{userName},userPassword=#{userPassword},
gender=#{gender},birthday=#{birthday},phone=#{phone},address=#{address},
userRole=#{userRole},modifyBy=#{modifyBy},modifyDate=#{modifyDate}
where id = #{id}
update元素的属性id和parameterType的含义和用法等同于insert元素中的属性用法,此处不再赘述。另外,由于是修改操作,因此更新的字段中需更新modifyBy和modifyDate,而不需要更新createBy和creationDate。
接下来修改测试类UserMapperTest.java,增加testModify方法进行修改数据测试,并开启事务控制,模拟异常,若发生异常则回滚以测试事务。
@Test
public void testModify(){
logger.debug("testModify !===================");
SqlSession sqlSession = null;
int count = 0;
try {
User user = new User();
user.setId(25);
user.setUserCode("testmodify");
user.setUserName("测试用户修改");
user.setUserPassword("0000000");
Date birthday = new SimpleDateFormat("yyyy-MM-dd").parse("1980-10-10");
user.setBirthday(birthday);
user.setCreationDate(new Date());
user.setAddress("地址测试修改");
user.setGender(2);
user.setPhone("13600002222");
user.setUserRole(2);
user.setModifyBy(1);
user.setModifyDate(new Date());
sqlSession = MyBatisUtil.createSqlSession();
count = sqlSession.getMapper(UserMapper.class).modify(user);
//模拟异常,进行回滚
//int i = 2/0;
sqlSession.commit();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
sqlSession.rollback();
count = 0;
}finally{
MyBatisUtil.closeSqlSession(sqlSession);
}
logger.debug("testModify count: " + count);
}
实现的是根据用户id修改用户信息操作,超市订单管理系统还有一个需求:修改个人密码。此需求也是修改操作,但是传入参数有两个:用户id和新密码。若按照之前封装成User对象的方式进行参数传递,并不是很合适,这里可以用更灵活的方式处理,即直接进行多参数入参,代码可读性高,可清晰地看出这个接口方法所需的参数是什么。
具体示例代码如下,修改UserMapper接口,增加修改个人密码的方法,当方法参数有多个时,每个参数前都需增加@Param注解:
public int updatePwd(@Param("id")Integer id, @Param("userPassword")String pwd);
使用注解@Param来传入多个参数,如@Param(“userPassword”)String pwd,相当于将该参数pwd重命名为userPassword,在映射的SQL中需要使用#{注解名称},如#{userPassword}。
下面继续修改UserMapper.xml,增加id为updatePwd的SQL映射
update smbms_user set userPassword=#{userPassword} where id=#{id}
若不使用@Param注解,则会报错,报错信息类似于Parameter ’参数名’ not found。探究原因,需要深入MyBatis源码,MyBatis的参数类型为Map,如果使用@Param注解参数,那么就会记录指定的参数名为key;如果参数前没有加@Param,那么就会使用“param”+它的序号作为Map的key。所以在进行多参数入参时,如果没有使用@Param指定参数,那么在映射的SQL语句中将获取不到#{参数名},从而报错。
最后修改测试类UserMapperTest.java,增加testUpdatePwd方法进行个人密码修改测试
@Test
public void testUpdatePwd() {
logger.debug("testUpdatePwd !===================");
SqlSession sqlSession = null;
String pwd = "8888888";
Integer id = 1;
int count = 0;
try {
sqlSession = MyBatisUtil.createSqlSession();
count = sqlSession.getMapper(UserMapper.class).updatePwd(id, pwd);
sqlSession.commit();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
sqlSession.rollback();
count = 0;
}finally{
MyBatisUtil.closeSqlSession(sqlSession);
}
logger.debug("testUpdatePwd count: " + count);
}
在该测试方法中,不需要再封装User对象,直接进行两个参数的入参即可,清晰明了。
经验
在MyBatis中使用参数入参,何时需要封装成对象入参,何时又需要使用多参数入参?
一般情况下,超过4个以上的参数最好封装成对象入参(特别是在常规的增加和修改操作时,字段较多,封装成对象比较方便)。
对于参数固定的业务方法,最好使用多参数入参。因为这种方法比较灵活,代码的可读性高,可以清晰地看出接口方法中所需的参数是什么。并且对于固定的接口方法,参数一般是固定的,所以直接采用多参数入参,无需封装对象。比如修改个人密码、根据用户id删除用户、根据用户id查看用户明细,都可以采取这种入参方式。
需要注意的是,当参数为基础数据类型时,不管是多参数入参,还是单独的一个参数入参,都需要使用@Param注解来进行参数的传递。
MyBatis实现删除操作是使用delete元素来映射删除语句。具体的用法与insert、update类似,下面通过根据用户id删除用户的操作示例来演示具体用法,首先在UserMapper接口中增加delete方法:
public int deleteUserById(@Param(“id”)Integer delId);
参数:delId(用户id),使用@Param注解来指定参数名为id,返回值为int类型,即返回执行SQL语句影响的行数。
修改UserMapper.xml,增加删除语句
delete from smbms_user where id=#{id}
接下来修改测试类UserMapperTest.java,增加testDeleteUserById方法,进行删除数据测试,并开启事务控制,模拟异常,若发生异常则回滚以测试事务。
Test
public void testDeleteUserById() {
logger.debug("testDeleteUserById !===================");
SqlSession sqlSession = null;
Integer delId = 25;
int count = 0;
try {
sqlSession = MyBatisUtil.createSqlSession();
count = sqlSession.getMapper(UserMapper.class).deleteUserById(delId);
sqlSession.commit();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
sqlSession.rollback();
count = 0;
}finally{
MyBatisUtil.closeSqlSession(sqlSession);
}
logger.debug("testDeleteUserById count: " + count);
}
➢ 使用association实现根据用户角色id获取该角色下的用户列表。
➢ 使用collection实现获取指定用户的相关信息和地址列表。
1.属性
➢ id:resultMap的唯一标识。
➢ type:表示该resultMap的映射结果类型(通常是Java实体类)。
2.子节点
➢ id:一般对应数据库中该行的主键id,设置此项可以提升MyBatis性能。
➢ result:映射到JavaBean的某个“简单类型”属性,如基础数据类型、包装类等。
子节点id和result均可实现最基本的结果集映射,将列映射到简单数据类型的属性。这两者的唯一不同是:在比较对象实例时id将作为结果集的标识属性。这有助于提高总体性能,特别是在应用缓存和嵌套结果映射的时候。而若要实现高级结果映射,就需要学习下面两个配置项:association和collection。
association:映射到JavaBean的某个“复杂类型”属性,比如JavaBean类,即JavaBean内部嵌套一个复杂数据类型(JavaBean)属性,这种情况就属于复杂类型的关联。需要注意:association仅处理一对一的关联关系。
下面通过一个示例来演示association的具体应用,示例需求:根据用户角色id获取该角色下的用户列表。
首先修改User类,增加角色属性(Role role),并增加其相应的getter和setter方法;注释掉用户角色名称属性(StringuserRoleName),并注释掉其getter和setter方法
package cn.smbms.pojo;
import java.util.Date;
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; //用户角色ID
private Integer createdBy; //创建者
private Date creationDate; //创建时间
private Integer modifyBy; //更新者
private Date modifyDate; //更新时间
private Integer age;//年龄
//private String userRoleName; //用户角色名称
//association
private Role role; //用户角色
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
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;
}
/* public String getUserRoleName() {
return userRoleName;
}
public void setUserRoleName(String userRoleName) {
this.userRoleName = userRoleName;
}*/
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserCode() {
return userCode;
}
public void setUserCode(String userCode) {
this.userCode = userCode;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPassword() {
return userPassword;
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Integer getUserRole() {
return userRole;
}
public void setUserRole(Integer userRole) {
this.userRole = userRole;
}
public Integer getCreatedBy() {
return createdBy;
}
public void setCreatedBy(Integer createdBy) {
this.createdBy = createdBy;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
public Integer getModifyBy() {
return modifyBy;
}
public void setModifyBy(Integer modifyBy) {
this.modifyBy = modifyBy;
}
public Date getModifyDate() {
return modifyDate;
}
public void setModifyDate(Date modifyDate) {
this.modifyDate = modifyDate;
}
}
通过以上改造,我们的JavaBean:User对象内部嵌套了一个复杂数据类型的属性:role。接下来在UserMapper接口里增加根据角色id获取用户列表的方法,
public List getUserListByRoleId(@Param("userRole")Integer roleId);
修改对应UserMapper.xml,增加getUserListByRoleId,该select查询语句返回类型为resultMap,外部引用的resultMap的类型为User。由于User对象内嵌JavaBean对象(Role),因此需要使用association来实现结果映射。
<!-- 根据roleId获取用户列表 association start-->
<resultMap type="User" id="userRoleResult">
<id property="id" column="id"/>
<result property="userCode" column="userCode" />
<result property="userName" column="userName" />
<result property="userRole" column="userRole" />
<association property="role" javaType="Role" >
<id property="id" column="r_id"/>
<result property="roleCode" column="roleCode"/>
<result property="roleName" column="roleName"/>
</association>
</resultMap>
<select id="getUserListByRoleId" parameterType="Integer" resultMap="userRoleResult">
select u.*,r.id as r_id,r.roleCode,r.roleName from smbms_user u,smbms_role r
where u.userRole = #{
userRole} and u.userRole = r.id
</select>
从上述代码来简单分析association的属性。
➢ javaType:完整Java类名或者别名。若映射到一个JavaBean,则MyBatis会自行检测到其类型;若映射到一个HashMap,则应该明确指定javaType,来确保所需行为。此处为Role。
➢ property:映射数据库列的实体对象的属性。此处为在User里定义的属性:role。association的子元素如下。
➢ result
♦ property:映射数据库列的实体对象的属性。此处为Role的属性。
♦ column:数据库列名或别名。在做结果映射的过程中,要确保所有的列名都是唯一且无歧义的。
注意
id子元素在嵌套结果映射中扮演了非常重要的角色,应该指定一个或者多个属性来唯一标识这个结果集。实际上,即便没有指定id, MyBatis也会工作,但是会导致严重的性能开销,所以选择尽量少的属性来唯一标识结果,使用主键或者联合主键均可。
最后修改测试类UserMapperTest.java,增加测试方法
@Test
public void getUserListByRoleIdTest(){
SqlSession sqlSession = null;
List userList = new ArrayList();
Integer roleId = 3;
try {
sqlSession = MyBatisUtil.createSqlSession();
userList = sqlSession.getMapper(UserMapper.class).getUserListByRoleId(roleId);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally{
MyBatisUtil.closeSqlSession(sqlSession);
}
logger.debug("getUserListByRoleIdTest userList.size : " + userList.size());
for(User user:userList){
logger.debug("userList =====> userName: " + user.getUserName() + ", Role: "
+ user.getRole().getId() + " --- " + user.getRole().getRoleCode()
+" --- " + user.getRole().getRoleName());
}
}
在测试方法中调用getUserListByRoleId()方法获取userList,并进行结果输出,关键是映射的用户角色相关信息。
通过上面的示例,我们了解了association的基本用法以及适用场景,现在再思考一个问题:上一个例子中使用“userRoleResult”联合一个association的结果映射来加载User实例,那么association的role结果映射是否可复用?
答案是肯定的,association提供了另一个属性:resultMap。通过这个属性可以扩展一个resultMap来进行联合映射,这样就可以使role结果映射重复使用。当然,若不需要复用,也可按照之前的写法,直接嵌套这个联合结果映射,如何使用要根据具体业务而定。下面就来改造刚才的示例,使用resultMap完成association的role映射结果的复用。
修改UserMapper.xml,增加resultMap来完成role的结果映射,association增加属性resultMap来引用外部的“roleResult”
在上述代码中,把之前的角色结果映射代码抽取出来放在一个resultMap中,然后设置了association的resultMap属性来引用外部的“roleResult”。这样做就可以达到复用的效果,并且整体的结构较为清晰明了,特别适合association的结果映射比较多的情况。
association用于处理一对一的关联关系,对于一对多的关联关系的处理,则需要collection元素来实现。
collection元素的作用和association元素的作用差不多。事实上,它们非常类似,collection也是映射到JavaBean的某个“复杂类型”属性,只不过这个属性是一个集合列表,即JavaBean内部嵌套一个复杂数据类型(集合)属性。和association元素一样,我们使用嵌套查询,或者从连接中嵌套结果集。
下面通过一个示例来演示collection的具体应用,示例需求:获取指定用户的相关信息和地址列表。
首先需要创建POJO:Address.java,根据数据库表(smbms_address)设计相应的属性,并增加getter和setter方法
package cn.smbms.pojo;
import java.util.Date;
import java.util.List;
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; //用户角色ID
private Integer createdBy; //创建者
private Date creationDate; //创建时间
private Integer modifyBy; //更新者
private Date modifyDate; //更新时间
private Integer age;//年龄
//private String userRoleName; //用户角色名称
//association
private Role role; //用户角色
//然后修改User类,增加地址列表属性(ListaddressList),并增加相应的getter和setter方法,
//collection
private List addressList;//用户地址列表
public List getAddressList() {
return addressList;
}
public void setAddressList(List addressList) {
this.addressList = addressList;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
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;
}
/* public String getUserRoleName() {
return userRoleName;
}
public void setUserRoleName(String userRoleName) {
this.userRoleName = userRoleName;
}*/
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserCode() {
return userCode;
}
public void setUserCode(String userCode) {
this.userCode = userCode;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPassword() {
return userPassword;
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Integer getUserRole() {
return userRole;
}
public void setUserRole(Integer userRole) {
this.userRole = userRole;
}
public Integer getCreatedBy() {
return createdBy;
}
public void setCreatedBy(Integer createdBy) {
this.createdBy = createdBy;
}
public Date getCreationDate() {
return creationDate;
}
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate;
}
public Integer getModifyBy() {
return modifyBy;
}
public void setModifyBy(Integer modifyBy) {
this.modifyBy = modifyBy;
}
public Date getModifyDate() {
return modifyDate;
}
public void setModifyDate(Date modifyDate) {
this.modifyDate = modifyDate;
}
}
我们的JavaBean:User对象内部嵌套了一个复杂数据类型的属性addressList。接下来在UserMapper接口中增加根据用户id获取用户信息以及地址列表的方法
public List getAddressListByUserId(@Param("id")Integer userId);
对应修改UserMapper.xml,增加getAddressListByUserId,该select查询语句返回类型为resultMap,并且引用外部的resultMap类型为User。由于User对象内嵌集合对象(addressList),因此需要使用collection来实现结果映射。
<!-- 获取指定用户的地址列表(user表-address表:1对多关系) collection start-->
<resultMap type="User" id="userAddressResult">
<id property="id" column="id"/>
<result property="userCode" column="userCode"/>
<result property="userName" column="userName"/>
//可以理解为:一个名为addressList、元素类型为Address的ArrayList集合。
<collection property="addressList" ofType="Address">
<id property="id" column="a_id"/>
<result property="postCode" column="postCode"/>
<result property="tel" column="tel"/>
<result property="contact" column="contact"/>
<result property="addressDesc" column="addressDesc"/>
</collection>
</resultMap>
<select id="getAddressListByUserId" parameterType="Integer" resultMap="userAddressResult">
select u.*,a.id as a_id,a.contact,a.addressDesc,a.postCode,a.tel
from smbms_user u,smbms_address a where u.id = a.userId and u.id=#{
id}
</select>
简单分析collection的属性。
➢ ofType:完整Java类名或者别名,即集合所包含的类型。此处为Address。
➢ property:映射数据库列的实体对象的属性。此处为User里定义的属性:addressList。
最后修改测试类UserMapperTest.java,增加测试方法
@Test
public void getAddressListByUserIdTest(){
SqlSession sqlSession = null;
List userList = new ArrayList();
Integer userId = 1;
try {
sqlSession = MyBatisUtil.createSqlSession();
userList = sqlSession.getMapper(UserMapper.class).getAddressListByUserId(userId);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally{
MyBatisUtil.closeSqlSession(sqlSession);
}
for(User user:userList){
logger.debug("userList(include:addresslist) =====> userCode: " + user.getUserCode() + ", userName: " + user.getUserName());
for(Address address : user.getAddressList()){
logger.debug("address ----> id: " + address.getId() + ", contact: " + address.getContact()
+ ", addressDesc: " + address.getAddressDesc() + ", tel: " + address.getTel()
+ ", postCode: " + address.getPostCode());
}
}
}
在测试方法中调用getAddressListByUserId()方法获取userList,并进行结果输出,关键是映射的用户地址列表的相关信息,需要进一步循环addressList输出。当然,通过之前学习association,我们就可以想到例子中的collection结果映射可以复用。提取相应代码到一个resultMap中,给collection增加resultMap属性进行外部引用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hUNTR4j6-1609551381902)(C:\Users\pcy\AppData\Roaming\Typora\typora-user-images\image-20201216213129163.png)]
关键步骤如下。
➢ 配置resultMap的自动映射级别。
➢ 了解MyBatis缓存的配置方法。
User类中的userPassword属性和Address类中的userId属性均未在resultMap中进行匹配映射。现修改测试方法getAddressListByUserIdTest的代码,输出未做匹配映射的属性值
@Test
public void getAddressListByUserIdTest(){
SqlSession sqlSession = null;
User user = null;
Integer userId = 1;
try {
sqlSession = MyBatisUtil.createSqlSession();
user = sqlSession.getMapper(UserMapper.class).getAddressListByUserId(userId);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally{
MyBatisUtil.closeSqlSession(sqlSession);
}
if(null != user){
logger.debug("userList(include:addresslist) =====> userCode: " + user.getUserCode() + ", userName: " + user.getUserName());
if(user.getAddressList().size() > 0){
for(Address address : user.getAddressList()){
logger.debug("address ----> id: " + address.getId() + ", contact: " + address.getContact()
+ ", addressDesc: " + address.getAddressDesc() + ", tel: " + address.getTel()
+ ", postCode: " + address.getPostCode());
}
}else{
logger.debug("该用户下无地址列表!");
}
}else{
logger.debug("查无此用户!");
}
}
观察输出结果,发现当没有设置autoMappingBehavior的时候,也就是默认情况下(PARTIAL),若是普通数据类型的属性,会自动匹配,这在示例10中已经提到过。但若是有内部嵌套(association或者collection),那么输出结果就是null(如图中的userPassword和userId),也就是说它不会自动匹配,除非手工设置autoMappingBehavior的value为FULL(自动匹配所有)。修改mybatis-config.xml代码如下:
观察输出结果,发现autoMappingBehavior的value设置为FULL(自动匹配所有属性)之后,未作映射的字段userPassword和userId均有值输出。
可以认识到MyBatis对resultMap自动映射的三个匹配级别:
➢ NONE:禁止自动匹配。
➢ PARTIAL(默认):自动匹配所有属性,有内部嵌套(association、collection)的除外。
➢ FULL:自动匹配所有属性。
1.一级缓存一级缓存是基于PerpetualCache(MyBatis自带)的HashMap本地缓存,作用范围为session域内,当session flush或者close之后,该session中所有的cache就会被清空。
2.二级缓存二级缓存就是global caching,它超出session范围之外,可以被所有SqlSession共享,开启它只需要在MyBatis的核心配置文件(mybatis-config.xml)的settings中设置即可。
一级缓存缓存的是SQL语句,二级缓存缓存的是结果对象。
3.二级缓存的配置
(1)MyBatis的全局cache配置,需要在mybatis-config.xml的settings中设置
(2)在mapper文件(如UserMapper.xml)中设置缓存,默认情况下是未开启缓存的。需要注意的是,global caching的作用域是针对mapper的namespace而言的,即只有在此namespace内(cn.smbms.dao.user.UserMapper)的查询才能共享这个cache,代码如下:
(3)在mapper文件配置支持cache后,如果需要对个别查询进行调整,可以单独设置cache,代码如下:
对于MyBatis缓存的内容仅做了解即可,因为面对一定规模的数据量,内置的Cache方式就派不上用场了;况且对查询结果集做缓存并不是MyBatis框架擅长的,它擅长做的应该是SQL映射。所以采用OSCache、Memcached等专门的缓存服务器来处理缓存更为合理。
➢ MyBatis的SQL映射文件提供select、insert、update、delete等元素来实现SQL语句的映射。
➢ SQL映射文件的根节点是mapper元素,需要指定namespace来区别于其他的mapper,保证全局唯一;并且其名称必须跟接口同名,作用是绑定DAO接口,即面向接口编程。
➢ SQL映射文件的select返回结果类型的映射可以使用resultMap或resultType,但不能同时使用。
➢ MyBatis的SQL语句参数入参,对于基础数据类型的参数数据,使用@Param注解实现参数入参;对于复杂数据类型的参数数据,直接入参即可。
➢ resultMap的association和collection可以实现高级结果映射。