2021-1-02----ssm之第2章 SQL映射文件

第2章 SQL映射文件

  • 任务1 实现条件查询
    • 2.1.1 SQL映射文件
    • 2.1.2 单条件查询
    • 2.1.3 多条件查询
      • 2.1.4 自定义查询结果映射
  • 任务2 实现增删改操作
    • 2.2.1 增加操作
    • 2.2.2 修改操作
    • 2.2.3 多参数入参
    • 2.2.4 删除操作
  • 任务3 实现高级结果映射
    • 2.3.1 resultMap的配置
    • 2.3.2 使用association处理一对一关联关系
    • 2.3.3 使用collection处理一对多关联关系
  • 任务4 配置resultMap自动映射级别和MyBatis缓存
    • 2.4.1 resultMap自动映射级别
    • 2.4.2 MyBatis缓存
  • 本章总结

❖ 掌握通过SQL映射文件进行增、删、改、查的方法

❖ 掌握参数的使用方法

❖ 掌握resultMap

❖ 了解Cache的使用方法

任务1:实现条件查询任务

2:实现增删改操作任务

3:实现高级结果映射任务

4:配置resultMap自动映射级别和MyBatis缓存

2021-1-02----ssm之第2章 SQL映射文件_第1张图片

任务1 实现条件查询

关键步骤如下。

➢ 使用select元素实现根据用户名模糊查询用户列表信息。

➢ 使用select元素实现多条件查询用户列表信息。

➢ 使用resultMap元素映射自定义查询结果。

2.1.1 SQL映射文件

➢ 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应一一对应。

2.1.2 单条件查询

与查询对应的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());
   }
}

2.1.3 多条件查询

通过一个条件对用户表进行查询操作,但在实际应用中,数据查询会有多种条件,结果也会有各种类型

查询条件包括:用户名(模糊查询)、用户角色。那对于多条件查询,该如何实现?我们可以考虑将查询条件封装成对象进行入参,改造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}来获取传入的参数值。

2.1.4 自定义查询结果映射

(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里没有做映射,那么就无法在后台获取并输出。

任务2 实现增删改操作

关键步骤如下。

➢ 使用insert元素实现用户表的增加。

➢ 使用update元素实现根据用户id修改用户信息。

➢ 使用@Param注解实现多参数入参。

➢ 使用delete元素实现根据用户id删除用户。

2.2.1 增加操作

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。

2.2.2 修改操作

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);
}

2.2.3 多参数入参

实现的是根据用户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注解来进行参数的传递。

2.2.4 删除操作

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);
}

任务3 实现高级结果映射

➢ 使用association实现根据用户角色id获取该角色下的用户列表。

➢ 使用collection实现获取指定用户的相关信息和地址列表。

2.3.1 resultMap的配置

1.属性

➢ id:resultMap的唯一标识。

➢ type:表示该resultMap的映射结果类型(通常是Java实体类)。

2.子节点

➢ id:一般对应数据库中该行的主键id,设置此项可以提升MyBatis性能。

➢ result:映射到JavaBean的某个“简单类型”属性,如基础数据类型、包装类等。

子节点id和result均可实现最基本的结果集映射,将列映射到简单数据类型的属性。这两者的唯一不同是:在比较对象实例时id将作为结果集的标识属性。这有助于提高总体性能,特别是在应用缓存和嵌套结果映射的时候。而若要实现高级结果映射,就需要学习下面两个配置项:association和collection。

2.3.2 使用association处理一对一关联关系

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元素来实现。

2.3.3 使用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类,增加地址列表属性(List
addressList),并增加相应的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)]

任务4 配置resultMap自动映射级别和MyBatis缓存

关键步骤如下。

➢ 配置resultMap的自动映射级别。

➢ 了解MyBatis缓存的配置方法。

2.4.1 resultMap自动映射级别


   
   
   
   



   
   
   
   
   



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:自动匹配所有属性。

2.4.2 MyBatis缓存

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可以实现高级结果映射。

你可能感兴趣的:(java,java)