SSM Chapter 02 SQL映射文件

SSM Chapter 02 SQL映射文件 笔记

本章目标:

1 . 使用MyBatis实现条件查询

1.1 SQL映射文件

MyBatis 的真正强大在于它的映射语句,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 为聚焦于 SQL 而构建,以尽可能地为你减少麻烦。

SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):

  • cache – 对给定命名空间的缓存配置。
  • cache-ref – 对其他命名空间缓存配置的引用。
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
  • sql – 可被其他语句引用的可重用语句块。
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句

注意: 关于MyBatis的SQL映射文件中的mapper元素的namespace 属性 有如下要求 :

(1) namespace 的命名 必须跟某个DAO接口同名,同属于DAO层,故代码结构上,映射文件与该DAO接口应防放置在同一package下(如 cn.smbms.dao),并且习惯上是以mapper结尾(如UserMapper)

(2) 在不同的mapper文件中,子元素的id可以相同,MyBatis通过namespace和子元素的id联合区分 , 接口中的方法与映射文件中的SQL语句id应一一对应

提示 对命名空间的一点补充

在之前版本的 MyBatis 中,命名空间(Namespaces)的作用并不大,是可选的。 但现在,随着命名空间越发重要,你必须指定命名空间。

命名空间的作用有两个,一个是利用更长的全限定名来将不同的语句隔离开来,同时也实现了你上面见到的接口绑定。就算你觉得暂时用不到接口绑定,你也应该遵循这里的规定,以防哪天你改变了主意。 长远来看,只要将命名空间置于合适的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis。

下一部分将从语句本身开始来描述每个元素的细节。

1.2 使用select完成单条件查询

查询语句是 MyBatis 中最常用的元素之一,只能把数据存到数据库中价值并不大,还必须重新取出来才有用,多数应用也都是查询比修改要频繁。对每个插入、更新或删除操作,通常间隔多个查询操作。这是 MyBatis 的基本原则之一,也是将焦点和努力放在查询和结果映射的原因。

简单查询的 select 元素是非常简单的。比如 示例 1:

 <!-- 根据id查询用户信息 返回HashMap 其中的键是列名,值便是结果行中的对应值-->
 <!-- #{}:占位符号,可以防止sql注入(替换结果会增加单引号‘’)-->
  <select id="selectUser" resultType="hashmap" parameterType="int">
  	select * from smbms_user where id=#{id}
  </select>

这个语句被称作 selectUser,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。

在MyBatis中的映射配置文件中,动态传递参数有两种方式:

  1. #{ } 占位符
  2. ${ } 拼接符
> #{ } 占位符:

#{ } 为参数占位符 ?,即sql 静态预编译

注意参数符号:

#{id}

这就告诉 MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:

// 近似的 JDBC 代码,非 MyBatis 代码...
String selectUser = "SELECT * FROM smbms_user WHERE ID=?";
PreparedStatement ps = conn.prepareStatement(selectUser);
ps.setInt(1,id);
//省略其他代码

UserMapper接口中增加方法:

/**
* 根据id 查询 用户信息 
* @param id
* @return
*/
Map<String,Object> selectUser(int id);

测试类增加测试方法如下:

@Test
public void testSelectUser() throws IOException {
    SqlSession sqlSession = MyBatisUtil.createSqlSession();
    try {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        Map<String,Object> map = userMapper.selectUser(1);
        map.forEach((x,y)->logger.info(x+":"+y));
    } finally {
   		MyBatisUtil.close(sqlSession);
    }
}
> ${ } 拼接符:

${} 为字符串替换,即 sql 动态拼接

使用${ }传参方式解决根据id查询用户信息的问题,代码如下:

 <!-- 根据id查询用户信息 返回HashMap 其中的键是列名,值便是结果行中的对应值-->
 <!--${}:sql拼接符号(替换结果不会增加单引号‘’,like和order by后使用,存在sql注入问题,需手动代码中过滤)-->
  <select id="selectUser" resultType="hashmap" parameterType="int">
  	select * from smbms_user where id='${id}'
  </select>

注意参数符号:

'${id}'

这就告诉 MyBatis使用拼接方式编写SQL语句,就像这样:

// 近似的 JDBC 代码,非 MyBatis 代码...
String selectUser = "SELECT * FROM smbms_user WHERE ID='"+id+"'";
Statement statement = conn.createStatement();
ResultSet rs = statement.executeQuery(selectUser);
//省略其他代码

至于接口中的方法以及测试方法与使用#{ }传参方法一致,此处不再赘述!

> 总结:

使用#{}传参跟使用${}传参的区别:

  1. #{} 为参数占位符 ?,即sql 静态预编译;${} 为字符串替换,即 sql 动态拼接
  2. 变量替换后,#{} 对应的变量自动加上单引号 ’ ';
    变量替换后,${} 对应的变量不会加上单引号 ’ ’
  3. #{} 能防止sql 注入;${} 不能防止sql 注入
> 接着示例1:

根据用户名模糊查询获取用户列表信息

SQL映射文件中增加如下代码:

<!--根据用户名称查询用户列表(模糊查询)like concat('%','${userName}','%')  -->
  <select id="getUserListByUserName" resultType="u" parameterType="string">
  	select * from smbms_user where username like concat ('%',#{userName},'%')
  </select>

测试类中增加方法,如下:

@Test//测试根据用户名模糊查询用户信息
public void testGetUserListByUserName() throws IOException {
    SqlSession sqlSession = MyBatisUtil.createSqlSession();
    try {
    	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    	List<User> list = userMapper.getUserListByUserName("孙");
    	list.forEach(logger :: info);
    } finally {
    	MyBatisUtil.close(sqlSession);
    }
}

总结示例1,使用 JDBC 意味着需要更多的代码来提取结果并将它们映射到对象实例中,而这就是 MyBatis 节省你时间的地方。参数和结果映射还有更深入的细节。这些细节会分别在后面单独的小节中呈现。

select 元素允许你配置很多属性来配置每条语句的作用细节。

<select
  id="selectPerson"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">
属性描述
id在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置(unset)。
resultType从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。可以使用 resultType 或 resultMap,但不能同时使用。
resultMap外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。可以使用 resultMap 或 resultType,但不能同时使用。
flushCache将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
fetchSize这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)。
statementTypeSTATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetTypeFORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖驱动)。
databaseId如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。
resultOrdered这个设置仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。 这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false
resultSets这个设置仅对多结果集的情况适用。它将列出语句执行后返回的结果集并给每个结果集一个名称,名称是逗号分隔的。

备注:parameterType resultType 中的类型名 与 上一章所讲的关于别名(typeAliases) 在mybatis-config.xml中的设置一样,也有MyBatis为常见的Java类型内建了一些对应的别名,它们都是不区分大小写的,注意对基本类型名称重复采取的特殊命名风格。

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

1.3 使用select完成多条件查询

上述示例是通过一个条件对用户表进行查询操作,但是在实际应用中,数据查询会有多种条件,结果也会各种类型,比如按条件查询用户表,若多条件情况下如何处理?

查询条件包括:用户名(模糊查询),用户角色.对于多条件查询,我们可以考虑将查询条件封装成对象入参,改造UserMapper.java,增加方法如下:

/**
* 查询用户列表(参数:对象入参)
* @param id
* @return
*/
List<User> getUserListByUser(User user);

示例2 : UserMapper.xml文件,增加代码如下:

  <!--查询用户列表  -->	
  <select id="getUserListByUser" resultType="u">
    select * from smbms_user 
    where userName like concat ('%',#{userName},'%')
    and userRole=#{userRole}
  </select>

改造测试类UserMapperTest.java,增加代码如下:

@Test
public void testGetUserListByUser() throws IOException {
    SqlSession sqlSession = MyBatisUtil.createSqlSession();
    try {
        User user = new User();
        user.setUserName("张");
        user.setUserRole(3);
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    	List<User> list = userMapper.getUserListByUser(user);
   	 	list.forEach(logger :: info);
    } finally {
    	MyBatisUtil.close(sqlSession);
    }
}

上述示例中,parameterType使用了复杂数据类型,把条件参数封装成了User对象入参.对User对象中userName和userRole分别进行赋值,在映射语句中设置parameterType为User类型,传入的参数分别使用#{userName} 和 #{userRole} 来表示,即#{属性名}(对应参数对象中的属性名)

parameterType支持的复杂数据类型除了JavaBean之外,还包括Map类型,改造上一示例,把用户名和用户角色封装成Map对象入参,改造UserMapper.java,将封装好的userMap作为参数 传入接口方法,代码如下:

/**
* 查询用户列表(参数:map入参)
* @param id
* @return
*/
List<User> getUserListByMap(Map<String,Object> map);

改造UserMapper.xml,代码如下:

<!--查询用户列表  map 入参 map中的key分别为userName,userRole -->	
  <select id="getUserListByMap" resultType="u">
    select * from smbms_user 
    where userName like concat ('%',#{userName},'%')
    and userRole=#{userRole}
  </select>

测试类中增加方法 对此功能进行测试,代码如下:

@Test //测试map入参
public void testGetUserListByMap() throws IOException {
    SqlSession sqlSession = MyBatisUtil.createSqlSession();
    try {
        Map<String,String> map = new HashMap<>();
        map.put("userName", "张");
        map.put("userRole", "3");
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> list = userMapper.getUserListByMap(map);
        list.forEach(logger :: info);
    } finally {
    	MyBatisUtil.close(sqlSession);
    }
}

总结 第二种封装参数的方法更加灵活,不管是什么类型的参数,或者多个参数,我们都可以把它封装成Map数据结构进行入参,通过Map的key 可以获取传入的值

注意:

MyBatis传入的参数类型 可以是Java的基本数据类型,但是只适用于一个(而且必须是一个)参数的情况,通过#{参数名}即可获取传入的值.若是多个参数入参,需要复杂数据类型来支持,包括Java实体类,Map,通过 #{属性名} 或者 #{Map的key} 来获取传入的参数

1.4 使用 resultMap 完成查询结果的展现

通过上面的小节完成了 传入多条件的查询操作,但是对于结果列的展现,只是展示出用户列表(smbms_user)中的所有字段的值,比如,用户表中的userRole字段记录的是角色id,而不是对应的角色名称.在实际应用中,作为列表页的展示,用户关注的往往是角色名称 而不是角色id,该如何解决此类问题呢?

1.4.1 有三种解决方案,简单介绍并分析如下:

(1) 增加用户角色类 Role,修改查询的SQL语句,对用户表(smbms_user) 和角色表(smbms_role) 进行查询连表查询,使用resultType做映射.思路如下:

  • 1 . 在pojo包下增加实体类,代码如下:

    package cn.smbms.pojo;
    import java.util.Date;
    /**
    * 用户角色类 对应表 smbms_role
    */
    public class Role {
    	private Integer id;//主键角色ID
    	private String roleCode;//角色编码
    	private String roleName;//角色名称
    	private Integer createdBy;//创建者
    	private Date creationDate;//创建时间
    	private Integer modifyBy;//修改者
    	private Date modifyDate;//修改时间
    	//省略getter和setter 以及 toString
    }
    
  • 2 . UserMapper.java中增加方法,代码如下:

    /**
    * 查询用户 以及角色 列表(参数:user入参 根据userName模糊查询)
    * @param id
    * @return
    */
    List<Map<String,Object>> getUserAndRoleListByUser(User user);
    
  • 3 . 改造UserMapper.xml,增加如下代码:

    <!--查询用户 以及 角色 列表  user 入参-->	
    <select id="getUserAndRoleListByUser" resultType="hashmap" parameterType="u">
        select * from smbms_user 
        inner join smbms_role on smbms_user.userRole=smbms_role.id
        where userName like concat ('%',#{userName},'%')
        and userRole=#{userRole}
    </select>
    
  • 4 . 测试类中增加方法 对此功能进行测试,代码如下:

    @Test //测试user 入参 查询用户以及角色信息
    public void testGetUserAndRoleListByUser() throws IOException {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();
        try {
            User user = new User();
            user.setUserName("张");
            user.setUserRole(3);
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            List<Map<String,Object>> list = userMapper.getUserAndRoleListByUser(user);
            list.forEach(logger :: info);
        } finally {
        	MyBatisUtil.close(sqlSession);
        }
    }
    

(2) 修改POJO:User.java,增加userRoleName属性,并修改查询用户列表的SQL语句,对用户表(smbms_user) 和角色表(smbms_role) 进行查询连表查询,使用resultType做映射.思路如下:

  • 1 . 在User类中加入userRoleName属性:private String userRoleName,并生成对应的getter和setter方法 重新生成toString方法

  • 2 . UserMapper.java中增加方法,代码如下:

    /**
    * 查询用户 以及角色 列表(参数:user入参)
    * @param id
    * @return
    */
    List<User> getUserRoleNameListByUser(User user);
    
  • 3 . 改造UserMapper.xml,增加如下代码:

    <!--查询用户 角色 列表  user 入参 为roleName 起别名 -->	
    <select id="getUserRoleNameListByUser" resultType="u" parameterType="u">
        select u.*,r.roleName as userRoleName from smbms_user u
        inner join smbms_role r on u.userRole=r.id
        where userName like concat ('%',#{userName},'%')
        and userRole=#{userRole}
    </select>
    
  • 4 . 测试类中增加方法 对此功能进行测试,代码如下:

    @Test //测试user 入参 查询用户以及角色信息
    public void testGetUserRoleListByUser() throws IOException {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();
        try {
            User user = new User();
            user.setUserName("张");
            user.setUserRole(3);
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        	List<User> list = userMapper.getUserRoleNameListByUser(user);
        	list.forEach(logger :: info);
        } finally {
       		MyBatisUtil.close(sqlSession);
        }
    }
    

(3) 通过resultMap来映射自定义结果 (推荐使用第三种)

使用resultMap做自定义结果映射,字段名可以不一致,并且还可以指定要显示的列,比较灵活,应用也广泛

注意:

MyBatis中使用resultType做自动映射时,一定要注意:字段名和POJO属性名必须一致,若不一致,则需要给字段起别名,保证别名与属性一致

通过刚才的示例来演示resultMap的用法,查询用户信息列表所必须的显示字段(包括用户编号,用户名称,性别,年龄,电话,用户角色等字段信息),注意用户角色要显示角色名称而不是角色id

  • 1 . 在User类中加入userRoleName属性:private String userRoleName,并生成对应的getter和setter方法 重新生成toString方法

  • 2 . UserMapper.java中增加方法,代码如下:

    /**
    * 查询用户 以及角色 列表(参数:user入参)
    * @param id
    * @return
    */
    List<User> getUserRoleNameInfoByUser(User user);
    
  • 3 . 改造UserMapper.xml,增加如下代码:

     <!--查询用户 以及 角色 列表  user 入参 使用resultMap映射自定义结果-->	
      <select id="getUserRoleNameInfoByUser" resultMap="userList" parameterType="u">
        select u.*,r.roleName  from smbms_user u
        inner join smbms_role r on u.userRole=r.id
        where userName like concat ('%',#{userName},'%')
        and userRole=#{userRole}
      </select>
      <!-- 定义resultMap 元素 id与getUserRoleNameInfoByUser方法中的属性resultMap的值一致 
      	type为该方法的返回值  -->
      <resultMap type="u" id="userList">
      	<!-- 注意 result属性中的column必须对应的SQL语句中的列名一致,
      	property 必须与type类型中的属性名一致  -->
      	<result column="id" property="id"/>
      	<result column="userName" property="userName"/>
      	<result column="userCode" property="userCode" />
      	<result column="phone" property="phone" />
      	<result column="birthday" property="birthday" />
      	<result column="gender" property="gender" />
      	<result column="userRole" property="userRole"/>
      	<result column="roleName" property="userRoleName" />
      </resultMap>
    
  • 4 . 测试类中增加方法 对此功能进行测试,代码如下:

    @Test //测试user 入参 查询用户以及角色信息 使用resultMap映射自定义结果
    public void testGetUserRoleInfoByUser() throws IOException {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();
        try {
            User user = new User();
            user.setUserName("张");
            user.setUserRole(3);
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            List<User> list = userMapper.getUserRoleNameInfoByUser(user);
        	list.forEach(logger :: info);
        } finally {
        	MyBatisUtil.close(sqlSession);
        }
    }
    

通过上述示例,resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。

实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的长达数千行的代码。ResultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系就行了。

resultMap 元素有很多子元素和一个值得深入探讨的结构。 下面是resultMap 元素的概念视图。

1.4.2 结果映射(resultMap)的子节点
  • constructor - 用于在实例化类时,注入结果到构造方法中
    • idArg - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
    • arg - 将被注入到构造方法的一个普通结果
  • id – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能
  • result – 注入到字段或 JavaBean 属性的普通结果
  • association – 一个复杂类型的关联;许多结果将包装成这种类型
    • 嵌套结果映射 – 关联本身可以是一个 resultMap 元素,或者从别处引用一个
  • collection – 一个复杂类型的集合
    • 嵌套结果映射 – 集合本身可以是一个 resultMap 元素,或者从别处引用一个
  • discriminator – 使用结果值来决定使用哪个
    • case– 基于某些值的结果映射
      • 嵌套结果映射 – case 本身可以是一个 resultMap 元素,因此可以具有相同的结构和元素,或者从别处引用一个
属性描述
id当前命名空间中的一个唯一标识,用于标识一个结果映射。
type类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考上面的表格)。
autoMapping如果设置这个属性,MyBatis将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。

最佳实践 最好一步步地建立结果映射。单元测试可以在这个过程中起到很大帮助。 如果你尝试一次创建一个像上面示例那样的巨大的结果映射,那么很可能会出现错误而且很难去使用它来完成工作。 从最简单的形态开始,逐步迭代。而且别忘了单元测试!

使用框架的缺点是有时候它们看上去像黑盒子(无论源代码是否可见)。 为了确保你实现的行为和想要的一致,最好的选择是编写单元测试。提交 bug 的时候它也能起到很大的作用。

下一部分将详细说明每个元素。

id & result
<id property="id" column="id"/>
<result property="userName" column="userName"/>

这些是结果映射最基本的内容。id 和 result 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。

这两者之间的唯一不同是,id 元素表示的结果将是对象的标识属性,这会在比较对象实例时用到。 这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。

两个元素都有一些属性:

属性描述
property映射到列结果的字段或属性。如果用来匹配的 JavaBean 存在给定名字的属性,那么它将会被使用。否则 MyBatis 将会寻找给定名称的字段。 无论是哪一种情形,你都可以使用通常的点式分隔形式进行复杂属性导航。 比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number”。
column数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样。
javaType一个 Java 类的完全限定名,或一个类型别名(关于内置的类型别名,可以参考上面的表格)。 如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。然而,如果你映射到的是 HashMap,那么你应该明确地指定 javaType 来保证行为与期望的相一致。
jdbcType(了解即可)JDBC 类型,所支持的 JDBC 类型参见这个表格之后的“支持的 JDBC 类型”。 只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型。这是 JDBC 的要求而非 MyBatis 的要求。如果你直接面向 JDBC 编程,你需要对可能存在空值的列指定这个类型。
typeHandler(了解即可)我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的完全限定名,或者是类型别名。
1.4.3 resultType 和 resultMap的关联和区别

MyBatis中在对查询进行select映射的时候,返回类型可以用resultType,也可以用resultMap,那么resultType和resultMap到底有何关联和区别?应用场景又是什么?下面做详细解释

1 . resultType

resultType直接表示返回类型,包括基础数据类型和复杂数据类型

2 . resultMap

resultMap则是对于外部的resultMap的定义的引用,对应外部resultMap属性的id,表示返回结果映射到哪一个resultMap上.它的应用场景一般是:数据库字段信息 与 对象属性不一致 或者 需要做复杂的联合查询以便自由控制映射结果

3 . resultType 和 resultMap的关联

在MyBatis 进行查询映射的时候,其实查询出来的每个字段值都放在一个对应的Map里面,其中key是字段名,value则是其对应的值.当select 元素提供的返回值类型属性是resultType的时候,MyBatis会将Map中的key value 取出 赋给 resultType所指定的对象对应的属性(即调用对应的对象里的属性的setter方法填充).

正因为如此,当使用resultType的时候,直接在后台就能接收到其相应的对象属性值.

由此可见:其实MyBatis的每个查询映射的返回值类型都是resultMap,只是当我们将提供的返回值类型属性是resultType的时候,MyBatis会自动把对应的值赋给resultType所指定对象的属性,而当我们提供的返回值类型是resultMap的时候,因为Map不能很好的表示领域模型,所以我们就需要通过进一步的定义把它转话成对应的实体对象.

当返回类型是resultMap时,也是非常有用的,这主要用在进行复杂联合查询上,当然在进行简单查询时是没有什么必要的,使用resultType足以

注意:在MyBatis的select元素中,resultType和resultMap本质上是一样的,都是Map数据结构,单需要明确一点,resultType和resultMap属性绝对不能同时存在,只能二者选其一使用

4 . resultMap 的自动映射级别

通过上面的示例演示,当我们使用resultMap只对部分字段进行映射时,结果却发现,没有被映射的字段也正常输出了,比如:userPassword和address.但是,我们希望没有被映射的字段是不能在后台查询并输出的,即使SQL语句中用了select*查询所有的字段.或者说 我们将需求改为:没有在resultMap内映射的字段不能获取.那么又该如何实现呢?

分析原因: 这跟resultMap的自动映射级别有关,默认的映射级别是PARTIAL(可参考MyBatis源码org.apache.ibatis.session.Configuration) , 而PARTIAL的意思是:只会自动映射没有定义嵌套结果集映射的结果集.

这话有点拗口,说人话就是映射文件中,对于resultMap标签,如果没有显式定义result标签,mybatis会自动帮你把结果映射到model(pojo)上 . 定义了result标签的例外,但是请注意:内部嵌套的也除外(比如 association collection)

而在上述的示例中,我们在resultMap标签中定义了result标签,所有才会自动映射没有指定的字段,若要满足需求,则需要设置MyBatis对于resultMap的自动映射级别(autoMappingBehavior) 为NONE,即禁止启动自动匹配,修改mybatis-config.xml文件,代码如下:

<settings>
    <!--设置resultMap的自动映射级别  为NONE(禁止自动匹配) -->
    <setting name="autoMappingBehavior" value="NONE"/>
</settings>

增加以上的设置之后,运行测试代码,发现 没有在resultMap中映射的字段 都为默认值

此为第一种设置resultMap的自动映射级别,除此之外 MyBatis还提供了第二种关于resultMap的自动映射级别,可参照手册resultMap元素属性中的autoMapping,官方给出的解释为:如果设置这个属性,MyBatis将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)

同样的需求修改上个示例:

  • (1) 去掉 mybatis-config.xml文件 中settings元素下的关于resultMap的自动级别的设置

  • (2) 修改UserMapper.xml文件,将对应resultMap中的autoMapping属性设置为false,true为自动映射,false为不自动映射.代码如下:

      <!-- 定义resultMap 元素 id与getUserRoleNameInfoByUser方法中的resultMap的值一致 
      	tyupe该方法的返回值 autoMapping可以设置自动映射级别 true为自动映射,false为不自动映射 -->
      <resultMap type="u" id="userList" autoMapping="false">
      	<!-- 注意 result属性中的column必须对应的SQL语句中的列名一致,
      	property 必须与返回值类型中的属性名一致  -->
      	<result column="id" property="id"/>
      	<result column="userName" property="userName"/>
      	<result column="userCode" property="userCode" />
      	<result column="phone" property="phone" />
      	<result column="birthday" property="birthday" />
      	<result column="gender" property="gender" />
      	<result column="userRole" property="userRole"/>
      	<result column="roleName" property="userRoleName" />
      </resultMap>
    

注意:

在MyBatis中,使用resultMap能够进行自动映射匹配的前提是字段名与属性名必须一致,否则就无法在后台中获取相应的数据

返回顶部

2. 使用MyBatis实现增删改操作

MyBatis 的真正强大在于它的映射语句,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 为聚焦于 SQL 而构建,以尽可能地为你减少麻烦。

SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):

  • cache – 对给定命名空间的缓存配置。
  • cache-ref – 对其他命名空间缓存配置的引用。
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
  • sql – 可被其他语句引用的可重用语句块。
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句

2.1 使用 insert 完成增加操作

MyBatis实现增加操作,使用的是insert元素来映射插入语句.具体的用法很简单,下面通过一个示例–实现用户表的增加操作来演示具体用法,首先在UserMapper接口里增加add()方法,代码如下:

/**
* 新增一条用户信息
* @param user
* @return
*/
int add(User user);

修改UserMapper.xml,增加插入语句,代码如下:

<!-- 新增一条用户信息  -->
  <insert id="add" parameterType="u">
	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>

修改测试类UserMapperTest.java,增加testAdd() 方法, 进行插入数据测试,代码如下:

@Test //测试user 入参 查询用户以及角色信息
public void testAdd() throws IOException {
    int count = 0;
    SqlSession sqlSession = MyBatisUtil.createSqlSession();
    try {
        User user = new User();
        user.setUserCode("zhang");
        user.setUserName("张三");
        user.setUserPassword("123456");
        Date date = new SimpleDateFormat("yyyy-MM-dd").parse("1990-09-09");
        user.setBirthday(date);
        user.setAddress("北京市 海淀区");
        user.setGender(1);
        user.setPhone("13511111111");
        user.setCreatedBy(1);
        user.setCreationDate(new Date());
        user.setUserRole(3);
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        count = userMapper.add(user);
    } catch (Exception e) {
    	e.printStackTrace();
    }finally {
   	 	MyBatisUtil.close(sqlSession);
    }
    logger.info("count====>"+count);
}
> 注意:

运行测试结果,输出count 为 1,但执行查询语句,发现数据库并没有该条记录,通过分析源码,得出MyBatis在使用SqlSessionFactory对象的openSession()方法,创建SqlSession对象时, 对于事务的管理默认值是false,也就说对于增删改操作是不自动提交的,因此可能出现一个奇怪的问题:在select数据库是没有问题, insert、delete、update也可以成功但是数据没有发生变化,就是因为这个调皮的commit默认机制导致的

解决办法:需要在insert、delete、update后手动提交,或者在创建sqlSession时使用factory.openSession(true),参数设置为true即可解决.

修改上面的测试方法,并开启事务控制,模拟异常,若发生异常,则进入回滚操作,测试事务,代码如下:

@Test //测试user 入参 查询用户以及角色信息
public void testAdd() throws IOException {
    int count = 0;
    SqlSession sqlSession = MyBatisUtil.createSqlSession();
    try {
        User user = new User();
        user.setUserCode("zhang");
        user.setUserName("张三");
        user.setUserPassword("123456");
        Date date = new SimpleDateFormat("yyyy-MM-dd").parse("1990-09-09");
        user.setBirthday(date);
        user.setAddress("北京市 海淀区");
        user.setGender(1);
        user.setPhone("13511111111");
        user.setCreatedBy(1);
        user.setCreationDate(new Date());
        user.setUserRole(3);
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        count = userMapper.add(user);
        //模拟异常,进行回滚操作
        //int i = 2/0;
        sqlSession.commit();
    } catch (Exception e) {
    	e.printStackTrace();
    	sqlSession.rollback();
		count = 0;
    }finally {
   	 	MyBatisUtil.close(sqlSession);
    }
    logger.info("count====>"+count);
}

数据变更语句 insert,update 和 delete 的实现非常接近:

属性描述
id命名空间中的唯一标识符,可被用来代表这条语句。
parameterType将要传入语句的参数的完全限定类名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器推断出具体传入语句的参数,默认值为未设置(unset)。
flushCache将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:true(对于 insert、update 和 delete 语句)。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
statementTypeSTATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys(仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。
keyProperty(仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认值:未设置(unset)。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。
keyColumn(仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望使用多个生成的列,也可以设置为逗号分隔的属性名称列表。
databaseId如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有的不带 databaseId 或匹配当前 databaseId 的语句;如果带或者不带的语句都有,则不带的会被忽略。

> 扩展需求:

在插入user对象时,需将自动生成的主键也查询出来.而MyBatis 插入语句的配置规则更加丰富,在插入语句里面有一些额外的属性和子元素用来处理主键的生成,而且有多种生成方式。

首先,如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性上就 OK 了。例如,如果上面的 User表已经对 id 使用了自动生成的列类型,那么语句可以修改为:

<!-- 新增一条用户信息   并查询出新增用户生成的id-->
  <insert id="addUser"  useGeneratedKeys="true" keyProperty="id" parameterType="u">
	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>

其次,在UserMapper.java接口中增加addUser方法,代码如下:

/**
* 新增一条用户信息 并查询出生成这条信息的用户的主键
* @param user
* @return
*/
int addUser(User user);

修改测试类UserMapperTest.java,增加testAddUser() 方法, 进行插入数据测试,并查询出主键,代码如下:

@Test //测试user 入参 新增用户信息 并 查询出 生成的主键
public void testAddUser() throws IOException {
    int count = 0;
    SqlSession sqlSession = MyBatisUtil.createSqlSession();
    User user = new User();
    try {
        user.setUserCode("zhaoliu");
        user.setUserName("赵六");
        user.setUserPassword("123456");
        Date date = new SimpleDateFormat("yyyy-MM-dd").parse("1989-09-09");
        user.setBirthday(date);
        user.setAddress("北京市 朝阳区");
        user.setGender(1);
        user.setPhone("13611111111");
        user.setCreatedBy(1);
        user.setCreationDate(new Date());
        user.setUserRole(3);
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        count = userMapper.addUser(user);
        //模拟异常,进行回滚操作
        //int i = 2/0;
        sqlSession.commit();
    } catch (Exception e) {
        e.printStackTrace();
        sqlSession.rollback();
        count = 0;
    }finally {
    	MyBatisUtil.close(sqlSession);
    }
    logger.info("新增用户信息成功, 并生成的主键是:"+ user.getId());
    logger.info("count====>"+count);
}

2.2 使用 update 完成修改操作

MyBatis实现修改操作,使用的update元素映射修改语句.具体的用法与insert类似,下面通过一个示例 实现根据用户id修改用户信息的操作来演示具体用法,首先在UserMapper.java中增加modify()方法:

/**
* 根据id修改用户信息
* @param user
* @return
*/
int modify(User user);

其次,修改UserMapper.xml文件,增加修改语句,示例代码如下:

<!-- 修改用户信息 -->
<update id="modify" parameterType="User">
    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>

接下来修改测试类UserMapperTest.java,增加testModify()方法进行修改数据测试,并开启事务,模拟异常,若发生异常则进入回滚,测试事务,代码如下:

@Test //测试user 入参 根据id修改用户信息
public void testModify(){
	int count = 0;
	SqlSession sqlSession = MyBatisUtil.createSqlSession();
	User user = new User();
	try {
		user.setId(24);
		user.setUserCode("zhaoliu");
		user.setUserName("Jack");
		user.setUserPassword("123456");
		Date date = new SimpleDateFormat("yyyy-MM-dd").parse("1992-09-09");
		user.setBirthday(date);
		user.setAddress("北京市 通州区");
		user.setGender(1);
		user.setPhone("13611111111");
		user.setCreatedBy(1);
		user.setCreationDate(new Date());
		user.setUserRole(3);
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		count = userMapper.modify(user);
		//模拟异常,进行回滚操作
		//int i = 2/0;
		sqlSession.commit();
	} catch (Exception e) {
		e.printStackTrace();
		sqlSession.rollback();
		count = 0;
	}finally {
		MyBatisUtil.close(sqlSession);
	}
	logger.info("count====>"+count);
}

返回顶部

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

在上述示例中 实现的是根据用户id修改用户信息操作,超市订单管理系统还有一个需求:修改个人密码.对于此需求,也是修改操作,但是可以明确方法的传入参数只有两个:用户id和新密码.若按照之前封装的User对象的方式进行传参,并不是很合适,可以用更灵活的方式处理,直接进行多参数入参即可,代码可读性高,可清晰的看出这个接口方法所需的参数是什么.

具体示例代码如下,修改UserMapper.java,增加修改个人密码的方法,当方法参数有多个时,每个参数前 都需要增加@Param注解:

/**
* 根据id 修改 用户密码
* @param id
* @param pwd
* @return
*/
int updatePwd(@Param("id") Integer id,@Param("userPassword") String pwd);

使用注解@Param 来传入多个参数,如@Param(“userPassword”) String pwd,相当于将该参数重命名为userPassword,在映射的SQL中需要使用#{注解名称},如 #{userPassword}.

继续修改UserMapper.xml,增加id为updatePwd的SQL映射,代码如下:

<!-- 修改当前用户密码 -->
<update id="updatePwd">
    update smbms_user 
    	set userPassword=#{userPassword}
    where id = #{id}
</update>
> 知识扩展:

在上个练习中,提出使用多参数入参的方式进行用户密码的修改,若不使用@Param,则程序会报错,报错信息类似于Parameter ‘参数名’ not found。

探究原因,需要深入MyBatis源码,MyBatis的参数类型为Map,若使用@Param 注解参数,那么MyBatis就会记录指定的参数名为key;若没有@Param,那么就会使用"param"+它的序号 作为Map的key . 例如:#{param1}, #{param2},这个是默认值, 故而在进行多参数入参时,若没有使用@Param 指定参数,那么在映射的SQL语句中就获取不到 #{参数名},从而报错

修改测试类UserMapperTest.java,增加testUpdatePwd方法进行个人密码修改测试,实例代码如下:

@Test //测试多个参数 单独入参 根据id修改用户密码
public void testUpdatePwd(){
	int count = 0;
	SqlSession sqlSession = MyBatisUtil.createSqlSession();
	try {
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		count = userMapper.updatePwd(24, "112233");
		//模拟异常,进行回滚操作
		//int i = 2/0;
		sqlSession.commit();
	} catch (Exception e) {
		e.printStackTrace();
		sqlSession.rollback();
		count = 0;
	}finally {
		MyBatisUtil.close(sqlSession);
	}
	logger.info("count====>"+count);
}

在该测试方法中,不需要再封装User对象,直接进行两个参数的入参即可,清晰明了.

> 经验:

在MyBatis中参数入参,何时需要封装成对象入参,何时有需要使用多参数入参?

一般情况下,超过4个以上的参数最好封装成对象入参(特别是在常规的增加和修改操作时,字段过多,封装成对象比较方便).

对于参数固定的业务方法,最好使用多参数入参,原因是这种方法比较灵活,代码可读性高,可以清晰的看出接口方法中所需要的参数是什么.并且对于固定的接口方法,参数一般是固定的,所以可以直接多参入参即可,无须封装对象.比如修改个人密码,根据用户id删除用户,根据id查看用户明细,都可以采用这种方式.

需要注意的是:当参数为基础数据类型时,不管是多参数入参,还是单独一个参数入参,都需要@Param 注解 来进行参数传递.

返回顶部

2.4 使用 delete 完成删除操作

MyBatis实现删除操作,是使用delete元素来映射删除语句.具体的用法与insert,update类似,下面通过一个示例 实现根据id删除用户的操作 演示具体用法,首先在UserMapper接口中增加delete方法:

/**
 * 根据id 删除用户记录
 * @param id
 * @return
 */
int deleteUserById(@Param("id") int id);

修改UserMapper.xml,增加删除语句,示例代码如下:

<!-- 根据id删除对应的用户记录 -->
<delete id="deleteUserById">
	delete from smbms_user
	where id = #{id}
</delete>

delete 元素的属性id和parameterType 的含义 和用法等同于 insert,update元素中属性用法,此处不再赘述.

修改测试类UserMapperTest.java,增加测试删除的方法,进行数据的删除操作,代码如下:

@Test //测试根据id删除对应的用户信息 
public void testDeleteUserById(){
	int count = 0;
	SqlSession sqlSession = MyBatisUtil.createSqlSession();
	try {
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		count = userMapper.deleteUserById(25);
		//模拟异常,进行回滚操作
		//int i = 2/0;
		sqlSession.commit();
	} catch (Exception e) {
		e.printStackTrace();
		sqlSession.rollback();
		count = 0;
	}finally {
		MyBatisUtil.close(sqlSession);
	}
	logger.info("count====>"+count);
}
> 映射器注解(增删改查)
注解使用对象相对应的XML描述
@Insert @Update @Delete @Select方法<insert> <update> <delete> <select>这四个注解分别代表将会被执行的 SQL 语句。它们用字符串数组(或单个字符串)作为参数。如果传递的是字符串数组,字符串之间先会被填充一个空格再连接成单个完整的字符串。这有效避免了以 Java 代码构建 SQL 语句时的“丢失空格”的问题。然而,你也可以提前手动连接好字符串。属性有:value,填入的值是用来组成单个 SQL 语句的字符串数组。
> 映射器注解说明

因为最初设计时,MyBatis 是一个 XML 驱动的框架。配置信息是基于 XML 的,而且映射语句也是定义在 XML 中的。而到了 MyBatis 3,在构建全面且强大的基于 Java 语言的配置 API 之上,就有新选择了。这个行的选择是:使用注解方式实现SQL语句的映射,并且不会引入大量的开销。

注意:不幸的是,==Java 注解的表达力和灵活性十分有限。==尽管很多时间都花在调查、设计和试验上,最强大的 MyBatis 映射并不能用注解来构建——并不是在开玩笑,的确是这样。因为基于注解的方式对于复杂的SQL语句的映射,就显得力不从心了,并且会显得更加混乱,因此,如果你需要完成很复杂的查询操作,那么最好使用 XML 来映射语句

2.5 知识扩展:

使用Sql元素 实现Mapper文件中Sql语句的复用

这个元素可以被用来定义可重用的 SQL 代码段,这些 SQL 代码可以被包含在其他语句中。它可以(在加载的时候)被静态地设置参数。比如:

<!-- 修改当前用户密码 -->
<update id="updatePwd">
	update smbms_user set
		userPassword=#{userPassword}
	<include refid="userId"/>
</update>
<!-- 根据id删除对应的用户记录 -->
<delete id="deleteUserById">
	delete from smbms_user
	<include refid="userId"/>
</delete>
<!-- 定义可重用的Sql代码段 -->
<sql id="userId">
	where id = #{id}
</sql>

关于Sql元素更详细的用法 可查看手册

3 . 使用 resultMap 实现高级结果映射

3.1 resultMap 的基本配置项

在讲解使用resultMap实现高级结果映射之前,先回顾之前学习的resultMap元素的基本配置项.

3.1.1 属性:

id : resultMap的唯一标识.

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

3.1.2 子节点:

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

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

子节点 id 和 result 均可实现最基本的结果集映射,将列映射到简单数据类型的属性,这两者唯一不同的是:在比较对象实例时 id 将作为 结果集的标识属性.这有助于提高总体性能,特别是应用缓存和嵌套结果映射的时候.

result : 映射到JavaBean的某个"简单类型"属性,比如

如果世界总是这么简单就好了。但其实在实际开发中,经常需要实现高级结果映射,这就需要学习下面连个配置项:association 和 collection.

3.2 association :

association:映射到JavaBean的某个"复杂类型" 属性,比如 JavaBean类,即JavaBean内部嵌套一个复杂数据类型(JavaBean) 属性,这种情况就属于复杂类型的关联.但是需要注意的是:association仅处理一对一的关联关系,比如一个博客有一个用户

下面通过一个示例 演示 association具体的应用,示例需求:根据用户角色id获取该角色下的用户列表.

首先修改User类,增加角色属性(Role role),并增加相应的getter和setter方法,注视掉用户角色名称属性(String userRoleName),注视掉其getter和setter方法,重新生成toString方法,示例代码如下:

/**
 * 用户信息类
 * @author Administrator
 *
 */
@Alias("u")
public class User implements Serializable{
	private static final long serialVersionUID = 8107300065594787676L;
	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 Role role;			//用户角色
	//private String userRoleName;//用户角色名称
	//省略getter和setter 以及重写生成 toSring方法
}	

通过以上的改造,我们的JavaBean:User对象内部嵌套了一个复杂数据类型的属性:role.接下来 在 UserMapper.java 接口里 增加根据角色id 获取用户列表的方法.

/**
* 根据角色id 获取对应的用户以及角色信息
* @param roleId
* @return
*/
List<User> getUserListByRoleId(@Param("userRole") int roleId);

修改对应的UserMapper.xml 增加 getUserListByRoleId , 该select查询语句返回类型为resultMap,并且外部引用的resultMap的类型为User,由于User对象内嵌JavaBean对象(Role),因此需要使用association来实现结果映射,代码如下:

<!-- 根据角色id 获取对应的用户以及角色信息 -->
<select id="getUserListByRoleId" resultMap="userRoleResult">
	select u.*,r.id as roleId, r.roleCode,r.roleName
		from smbms_user as u inner join smbms_role as r
		on u.userRole = r.id
	where u.userRole = #{userRole}
</select>
<!-- 根据roleId获取用户列表 association start-->
<resultMap type="u"  id="userRoleResult">
	<id column="id" property="id"/>
	<result column="userName" property="userName"/>
	<result column="userCode" property="userCode"/>
	<result column="userRole" property="userRole"/>
	<association property="role" javaType="role">
		<id column="roleId" property="id"/>
		<result column="roleCode" property="roleCode" />
		<result column="roleName" property="roleName" />
	</association>
</resultMap>
<!-- association end -->

从上述代码 分析association的属性

  • javaType : 完整的Java类名 或者 别名. 若映射到一个JavaBean,则MyBatis通常会自行检测到其类型,若映射到一个HashMap,则应该明确指定JavaType,来确保所需行为.此处为role
  • property : 映射数据库列的实体对象的属性.此处在User里定义的属性:role

association的子元素如下:

  • id
  • result
    • property : 映射数据库列的实体对象的属性,此处为 Role属性
    • column : 数据库列名或者别名

在做结果映射的过程中,需要注意的是:要确保所有的列名都是唯一且无歧义的.

注意:id子元素在嵌套结果映射中扮演了非常重要的角色,应该指定一个或者多个属性来唯一表示这个结果集.实际上,即便没有指定id,MyBatis也会工作,但是会导致严重的性能开销,所以最好选择尽量少的属性来唯一表示结果,主键或者联合建均可

修改测试类UserMapperTest.java,增加测试方法,代码如下:

@Test //测试根据id获取对应的用户 以及 角色信息
public void testGetUserListByRoleId(){
	SqlSession sqlSession = MyBatisUtil.createSqlSession();
	try {
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		List<User> list = userMapper.getUserListByRoleId(1);
		list.forEach(logger :: info);
	} catch (Exception e) {
		e.printStackTrace();
	}finally {
		MyBatisUtil.close(sqlSession);
	}
}

在测试方法中 调用 getUserListByRoleId() 方法 获取 userList,并进行结果输出,指定映射的属性都输出了对应的值,没有指定的都是默认值,这是为什么呢?是因为之前所说的关于 resultMap 默认的映射级别 只会自动映射没有定义嵌套结果集映射的结果集 起作用 , 而上个实例 我们用的 association 元素 是嵌套结果集映射.,因此,为指定映射的属性,并没有对应的列的值.若想让其映射,则可以修改映射级别,修改方法参考 1.4.3 中的resultMap的自动映射级别

通过上面的示例,我们了解了关于association 的基本用法以及使用场景,继续上面的示例,思考一个问题:上一个列子中使用 “userRoleResult” 联合一个 association 的结果 映射来加载User实例,问题来了,assciation的role结果映射是否可以复用?

答案是肯定的,association提供了另一个属性: resultMap. 通过这个属性,可以扩展一个resultMap来进行联合映射,这样就可以是role结果映射重复使用.当然,若不需要重用,也可以按照之前的写法,直接嵌套这个联合结果映射,根据具体业务而定.

下面就来改造刚才的示例,使用association中 的 resultMap属性 完成 association 的 role 映射结果的复用.

修改UserMapper.xml,增加resultMap来完成role的结果映射,association增加属性resultMap 来引用外部的"roleResult",代码如下:

<!-- 根据roleId获取用户列表 association start-->
<resultMap type="u"  id="userRoleResult">
	<id column="id" property="id"/>
	<result column="userName" property="userName"/>
	<result column="userCode" property="userCode"/>
	<result column="userRole" property="userRole"/>
	<!-- 使用association 的属性 引用外部的resultMap元素 -->
	<association resultMap="roleResult" property="role" />
</resultMap>
<resultMap type="role" id="roleResult">
	<id column="roleId" property="id"/>
	<result column="roleCode" property="roleCode" />
	<result column="roleName" property="roleName" />
</resultMap>
<!-- association end -->

在上述代码中,把之前的角色结果映射代码抽取出来 放在一个 resultMap中,然后设置了 association 的resultMap属性来引用外部的"roleResult" . 这样做的好处是:可以达到复用效果,并且整体的结构较为清晰明了,特别适合association的结果映射比较多的情况.

association处理一对一的关联关系,那么对于一对多的关联关系的处理,则需要collection来实现了

3.3 collection

collection元素的作用 和 association 元素的作用 差不多一样,事实上,他们非常的类似,也是映射到JavaBean的某个"复杂类型" 属性,只不过是这个属性是一个集合列表,即 JavaBean内部嵌套一个复杂数据类型(集合)属性.和使用association元素一样,我们使用嵌套查询,或者从连接中嵌套结果集.

下面通过一个示例, 来演示 collection 的具体应用, 示例需求:获取指定用户的相关信息和地址列表.

首先: 需要先创建POJO:Address.java,根据数据库表(smbms_address) 设计 相应的属性,并增加getter和setter方法, 以及测试用的toString方法,示例代码如下:

public class Address {
	private Integer id;			//主键id
	private String contact;		//联系人
	private String addressDesc; //地址
	private String postCode;	//邮编
	private String tel;			//联系方式
	private Integer createdBy;	//创建者
	private Date creationDate;	//创建日期
	private Integer modifyBy;	//更新者
	private Date modifyDate;	//更新时间
	private Integer userId;		//用户id
	//省略 getter  setter toString 
}

然后 : 修改User.java,增加地址列表属性( List<Address> addressList),并增加响应的getter 和 setter方法 ,重新生成对应的toString方法, 代码如下:

private List<Address> addressList;//用户地址列表

通过以上改造,我们的JavaBean:User对象内部嵌套了一个复杂的数据类型属性:addressList.接下来在UserMapper.java 中 增加 根据 用户id 获取用户信息 以及 地址列表信息的方法,代码如下:

/**
 * 根据用户id 获取用户信息 以及 用户的地址列表信息
 * @param id
 * @return
 */
User getAddressListByUserId(@Param("id") int id);

修改对应的UserMapper.xml,增加getAddressListByUserId,该select 查询语句返回类型 为 resultMap,并且引用外部的resultMap 的类型 为 User.由于User对内 嵌套 集合对象(addressList),因此需要collection来实现结果映射,示例代码如下:

<!-- 根据用户id 查询用户信息 以及 对应的用户地址列表信息 -->
<select id="getAddressListByUserId" resultMap="userAddressResult">
	select u.*,a.id as aId,a.contact,a.addressDesc,a.postCode
		from smbms_user as u left join smbms_address as a
		on u.id = a.userId
		where u.id=#{id}
</select>
<resultMap type="u" id="userAddressResult">
	<id column="id" property="id"/>
	<result column="userName" property="userName" />
	<result column="userCode" property="userCode" />
	<collection property="addressList" ofType="address">
		<id column="aId" property="id"/>
		<result column="contact" property="contact"/>
		<result column="addressDesc" property="addressDesc"/>
		<result column="postCode" property="postCode"/>
	</collection>
</resultMap>
<!-- collection end -->

通过上述的代码,简单分析collection的属性.

ofType : 完整的Java 类名 或者 别名,即集合所包含的类型.此处为address.

property : 映射数据库的实体对象的属性.此处为在User里定义的属性:addressList.

对于collection的这段代码:

<collection property="addressList" ofType="address">
  <id column="aId" property="id"/>
  <result column="contact" property="contact"/>
  <result column="addressDesc" property="addressDesc"/>
  <result column="postCode" property="postCode"/>
</collection>

可以理解为:一个名为 addressList,元素类型为Address的ArrayList 集合.

collection 的子元素 与 association 基本一致,此处不再赘述

最后修改测试类 UserMapperTest.java,增加测试方法,示例代码如下:

@Test //测试根据id获取对应的用户 以及 用户的地址列表
public void testGetAddressListByUserId(){
	SqlSession sqlSession = MyBatisUtil.createSqlSession();
	try {
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		User user = userMapper.getAddressListByUserId(1);
		logger.info(user);
	} catch (Exception e) {
		e.printStackTrace();
	}finally {
		MyBatisUtil.close(sqlSession);
	}
}

同理,通过之前学习的association,我们就可以想到上述例子中的collection结果映射可以复用.提取相应代码到一个resultMap中,给collection 增加 resultMap属性,进行外部引用即可,改造UserMapper.xml文件,具体示例代码如下:

<!-- collection start -->
<!-- 根据用户id 查询用户信息 以及 对应的用户地址列表信息 -->
<select id="getAddressListByUserId" resultMap="userAddressResult">
	select u.*,a.id as aId,a.contact,a.addressDesc,a.postCode
		from smbms_user as u left join smbms_address as a
		on u.id = a.userId
		where u.id=#{id}
</select>
<resultMap type="u" id="userAddressResult">
	<id column="id" property="id"/>
	<result column="userName" property="userName" />
	<result column="userCode" property="userCode" />
	<!-- 使用collection元素的resultMap属性 引用自 外部的resultMap  -->
	<collection property="addressList" resultMap="addressResult"/>
</resultMap>
<resultMap type="address" id="addressResult">
	<id column="aId" property="id"/>
	<result column="contact" property="contact"/>
	<result column="addressDesc" property="addressDesc"/>
	<result column="postCode" property="postCode"/>
</resultMap>
<!-- collection end -->

通过上述代码的书写,不能发现,其实collection元素跟刚学过的association元素的resultMap属性用法基本是一致的

4 . resultMap 自动映射级别 和 MyBatis 缓存

4.1 resultMap 自动映射级别 (在 1.4.3 已经讲过了)

回顾上个示例,根据用户id查询用户信息 以及 地址列表,在此基础上重新进行测试,UserMapper.xml中的代码片段是:

<!-- collection start -->
<!-- 根据用户id 查询用户信息 以及 对应的用户地址列表信息 -->
<select id="getAddressListByUserId" resultMap="userAddressResult">
	select u.*,a.id as aId,a.contact,a.addressDesc,a.postCode
		from smbms_user as u left join smbms_address as a
		on u.id = a.userId
	where u.id=#{id}
</select>
<resultMap type="u" id="userAddressResult">
	<id column="id" property="id"/>
	<result column="userName" property="userName" />
	<result column="userCode" property="userCode" />
	<!-- 使用collection元素的resultMap属性 引用自 外部的resultMap  -->
	<collection property="addressList" resultMap="addressResult"/>
</resultMap>
<resultMap type="address" id="addressResult">
	<id column="aId" property="id"/>
	<result column="contact" property="contact"/>
	<result column="addressDesc" property="addressDesc"/>
	<result column="postCode" property="postCode"/>
</resultMap>
<!-- collection end -->

从上述代码中,可以看到User类 中 只映射了id, userName 和 userCode属性,在对于addressList属性中映射时,对于Address也只映射了id,contact,addressDesc,postCode属性,其他的并未做映射,重新运行测试方法,观察未做匹配映射的属性值:

@Test //测试根据id获取对应的用户 以及 用户的地址列表
public void testGetAddressListByUserId(){
	SqlSession sqlSession = MyBatisUtil.createSqlSession();
	try {
		UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
		User user = userMapper.getAddressListByUserId(1);
		logger.info(user);
	} catch (Exception e) {
		e.printStackTrace();
	}finally {
		MyBatisUtil.close(sqlSession);
	}
}

观察输出结果发现,当没有设置setting元素中属性autoMappingBehavior时,也就是默认情况下(autoMappingBehavior值为PARTIAL),若是普通数据类型的属性 或者没有内部嵌套结果集映射的结果集,会自动匹配所有,这是在之前示例中发现的规律.

但是若是有内部嵌套结果映射的结果集,那么没有映射到的属性的值都会是成员表的默认值.也就说它不会自动匹配,除非手工设置 setting元素中属性 autoMappingBehavior 的value 为FULL(自动匹配所有),或者修改当前 resultMap中的autoMapping值为true,但是请注意:如果设置这个属性,MyBatis将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset),true开启自动匹配,false为关闭自动匹配

4.1.1 第一种: 修改mybatis-config.xml 代码如下:
<settings>
    <setting name="logImpl" value="LOG4J" />
    <!--设置resultMap的自动映射级别  为FULL(自动匹配所有) -->
    <setting name="autoMappingBehavior" value="FULL"/> 
</settings>

运行刚才的测试代码,观察输出结果,发现没有手动映射的字段,MyBatis自动映射了

4.1.2 第二种 : 修改UserMapper.xml,为对应的resultMap设置autoMapping属性值为true,代码如下:
<!-- collection start -->
<!-- 根据用户id 查询用户信息 以及 对应的用户地址列表信息 -->
<select id="getAddressListByUserId" resultMap="userAddressResult">
	select u.*,a.id as aId,a.contact,a.addressDesc,a.postCode,
	  a.tel,a.createdBy,a.creationDate,a.modifyBy,a.modifyDate,a.userId
		from smbms_user as u left join smbms_address as a
		on u.id = a.userId
	where u.id=#{id}
</select>
<!-- 设置resultMap中的属性 autoMapping的值为true (备注:true是自动匹配,false为不自动匹配)-->
<resultMap type="u" id="userAddressResult" autoMapping="true">
	<id column="id" property="id"/>
	<result column="userName" property="userName" />
	<result column="userCode" property="userCode" />
	<!-- 使用collection元素的resultMap属性 引用自 外部的resultMap  -->
	<collection property="addressList" resultMap="addressResult"/>
</resultMap>
<!-- 设置resultMap中的属性 autoMapping的值为true (备注:true是自动匹配,false为不自动匹配)-->
<resultMap type="address" id="addressResult" autoMapping="true">
	<id column="aId" property="id"/>
	<result column="contact" property="contact"/>
	<result column="addressDesc" property="addressDesc"/>
	<result column="postCode" property="postCode"/>
</resultMap>
<!-- collection end -->

同时注视掉 setting 元素中的autoMappingBehavior属性,观察输出结果,符合需求.

若将setting元素中的autoMappingBehavior属性的value修改为NONE(不自动匹配),观察本示例输出结果,无变化;

可见:当我们设置了resultMap中autoMapping属性时,结果确实会覆盖全局的属性 autoMappingBehavior。

> 综上演示,总结MyBatis对于resultMap自动的映射的三个匹配级别:

全局属性autoMappingBehavior自动映射的三个级别:

NONE : 禁止自动匹配

PARTIAL(默认) : 自动匹配所有属性,有内部嵌套映射结果集(association,collection)的除外

FULL : 自动匹配所有

resultMap元素中的autoMapping属性的三个级别:

unset(默认) : 未设置

true : 自动匹配所有

false: 不自动匹配所有

注意:若设置了resultMap中的autoMapping属性时,则会覆盖全局属性autoMappingBehavior

4.2 MyBatis 缓存(了解即可)

正如大多数持久化框架一样,MyBatis 提供了 一级缓存 和 二级缓存 的支持

4.2.1 一级缓存

一级缓存 是 基于PerpetualCache (MyBatis 自带) 的 HashMap 本地缓存,作用范围为session域内, 当session flush 或者 close 之后,该session中所有的cache 就会被清空

  • 重点的那句话就是:MyBatis执行查询的SQL语句之后,这条语句就是被缓存,以后再执行这条语句的时候,会直接从缓存中拿结果,而不是再次执行SQL

这也就是大家常说的MyBatis一级缓存,一级缓存的作用域scope是SqlSession。

增加测试案例,代码演示如下:

UserMapper.java增加如下方法 , 以便测试MyBatis的一级缓存:

/**
* 根据id查询用户信息
* @param id
* @return
*/
User selectUser(int id);

UserMapper.xml加入如下代码:

<select id="selectUser" resultType="user">
  select * from smbms_user where id = #{id}
</select>

测试方法如下:

@Test
public void testSelectUserById() throws IOException {
    try (SqlSession session = MyBatisUtil.openSession()) {
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user1 = userMapper.selectUser(1);
        User user2 = userMapper.selectUser(1);
        System.out.println(user1 == user2);
    }
}

控制台打印如图所示:
在这里插入图片描述
从打印出来的日志 , 可以看出MyBatis只执行了一条查询语句

修改测试方法如下:

@Test
public void testSelectUserById() throws IOException {
    try (SqlSession session = MyBatisUtil.openSession()) {
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user1 = userMapper.selectUser(1);
    }
	//重新新建一个SqlSession
    try (SqlSession session = MyBatisUtil.openSession()) {
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user1 = userMapper.selectUser(1);
    }
}

控制台打印如图所示:
在这里插入图片描述

从打印出来的日志 , 可以看出MyBatis执行了两条查询语句 , 因为有两个SqlSession对象 , 由此可以得出结论:MyBatis一级缓存的作用域scope是SqlSession

清空缓存 , 修改测试方法如下:

@Test
public void testSelectUserById() throws IOException {
    try (SqlSession session = MyBatisUtil.openSession()) {
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user1 = userMapper.selectUser(1);
        //当执行所有 insert、update 和 delete 语句会刷新缓存
        // 或者调用方法手动清空缓存
        session.clearCache();
        User user2 = userMapper.selectUser(1);
    }
}

程序结果如图所示:
在这里插入图片描述

从控制台打印出的日志 , 可以得出如下结论:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 当session flush 或者 close 之后,该session中所有的cache 就会被清空
4.2.2 二级缓存
  • 二级缓存就是global caching,它超出 session 范围之外,可以被所有的SqlSession共享,开启它只需要在MyBatis的核心配置文件(mybatis-config.xml) settings 中设置即可

  • 一级缓存 缓存的是SQL语句,二级缓存缓存的是结果对象.通俗的讲:一级缓存是SqlSession级别的,二级缓存是mapper级别的。这也说明了,当使用同一个sqlSession时,查询到的数据可能是一级缓存;而当使用同一个mapper是,查询到的数据可能是二级缓存。二级缓存在SqlSession关闭或提交之后才会生效

4.2.3 二级缓存的配置
  • (1) MyBatis 的全局 cache 配置,需要在 mybatis-config.xml 的settings中设置,代码如下:

    <settings>
    	<setting name="cacheEnabled" value="true" />
    </settings>
    
  • (2) 在mapper 文件(如 : UserMapper.xml) 中设置缓存,默认情况下是没有开启缓存的.需要注意的是: global caching的作用与是针对 mapper 的 namespace 而言的,即只有在此namespace内的查询才能共享这个cache,代码如下:

    <mapper namespace="cn.smbms.dao.UserMapper" >
    	<!-- cache 配置 -->
    	<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true" />
    </mapper>
    
  • (3) 在 mapper 文件配置 支持 cache后,如果需要对个别查询进行调整,可以单独设置cache , 代码如下:

    <select id="getUserList" resultType="user" useCache="true">
    ......
    </selec>
    
4.2.4 测试MyBatis中的二级缓存

mybatis-config.xml文件增加如下代码:

<settings>
	<!--开启MyBatis的二级缓存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

UserMapper.xml文件增加如下代码:

<mapper namespace="cn.smbms.dao.UserMapper" >
    <!-- cache 配置 -->
    <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true" />
</mapper>

修改测试方法如下:

@Test
public void testSelectUserById() throws IOException {
    try (SqlSession session = MyBatisUtil.openSession()) {
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user1 = userMapper.selectUser(1);
    }
	//重新新建一个SqlSession 测试二级缓存
    try (SqlSession session = MyBatisUtil.openSession()) {
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user1 = userMapper.selectUser(1);
    }
}

控制台打印日志如下:
在这里插入图片描述

控制台只打印出一条SQL语句 , 证明二级缓存生效 .

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

<!-- cache 使用默认的二级缓存生成策略 -->
<cache />

运行上述测试方法 , 若控制台抛出异常 , 异常信息如下:

org.apache.ibatis.cache.CacheException: Error serializing object.  Cause: java.io.NotSerializableException: cn.smbms.pojo.User

则说明实体类User没有实现序列化接口 , 修改User类 , 使其实现可序列化接口 , 代码如下:

public class User implements Serializable {
    private static final long serialVersionUID = 5934020932767183054L;
    //省略其他代码
    
}

重新运行上述测试方法 , 结果正常输出

4.2.5 补充
  • MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。 为了使它更加强大而且易于配置,我们对 MyBatis 3 中的缓存实现进行了许多改进。

  • 默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

    <cache/>
    

基本上就是这样。这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

这些属性可以通过 cache 元素的属性来修改。比如:

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

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

对于MyBatis 缓存的内容仅做了解即可,因为面对一定规模的数据量,内置的Cache方式 就派不上用场了,并且对查询结果做缓存并不是MyBatis 框架擅长的,它专心做的是应该是SQL映射.所以采用OSCache、Memcached、Redis等专门的缓存服务器来做更为合理…

返回顶部

5. 补充 Lombok

5.1 Lombok 背景介绍

​ 官网介绍

  • Lombok项目是一个Java库,它会自动插入您的编辑器和构建工具中,从而为您的Java增光添彩。
    永远不要再写另一个getter或equals方法,带有一个注释的您的类有一个功能全面的生成器,自动执行日志记录变量等等。

5.2 Lombok 的使用

  1. 使用Lombok需要的开发环境Java+Maven+IntelliJ IDEA或者Eclipse(安装Lombok Plugin)

  2. 在项目中添加Lombok依赖jar,在pom文件中添加如下部分。

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.8</version>
        <scope>provided</scope>
    </dependency>
    
  3. 在对应的类或者方法上使用对应注解即可

5.3 常用注解:

  • @Setter :注解在类或字段,注解在类时为所有字段生成setter方法,注解在字段上时只为该字段生成setter方法。
  • @Getter :使用方法同上,区别在于生成的是getter方法。
  • @ToString :注解在类,添加toString方法。
  • @EqualsAndHashCode: 注解在类,生成hashCode和equals方法。
  • @NoArgsConstructor: 注解在类,生成无参的构造方法。
  • @RequiredArgsConstructor: 注解在类,为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull注解的字段。
  • @AllArgsConstructor: 注解在类,生成包含类中所有字段的构造方法。
  • @Data: 注解在类,生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
  • @Slf4j: 注解在类,生成log变量,严格意义来说是常量。

5.4 Lombok优缺点

> 优点:
  1. 能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,提高了一定的开发效率
  2. 让代码变得简洁,不用过多的去关注相应的方法
  3. 属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等
> 缺点:
  1. **JDK版本问题:**当需要将现有项目的JDK从Java 8升级到Java 11时,发现Lombok不能正常工作了。于是不得不将所有的Lombok注解从项目源代码中清除,并使用IDE自带的功能生成getter/setter,equals,hashCode,toString以及构造器等方法,你也可以使用Delombok工具完成这一过程。但这终究会消耗你很多的时间。
  2. **胁迫使用:**当你的源代码中使用了Lombok,恰好你的代码又被其他的人所使用,那么依赖你代码的人,也必须安装Lombok插件(不管他们喜不喜欢),同时还要花费时间去了解Lombok注解的使用情况,如果不那么做,代码将无法正常运行。使用过Lombok之后,我发现这是一种很流氓的行为
  3. **可读性差:**Lombok隐藏了JavaBean封装的细节,如果你使用@AllArgsConstructor注解,它将提供一个巨型构造器,让外界有机会在初始化对象时修改类中所有的属性。首先,这是极其不安全的,因为类中某系属性我们是不希望被修改的;另外,如果某个类中有几十个属性存在,就会有一个包含几十个参数的构造器被Lombok注入到类中,这是不理智的行为;其次,构造器参数的顺序完全由Lombok所控制,我们并不能操控,只有当你需要调试时才发现有一个奇怪的“小强”在等着你;最后,在运行代码之前,所有JavaBean中的方法你只能想象他们长什么样子,你并不能看见。
  4. **代码耦合度增加:**当你使用Lombok来编写某一个模块的代码后,其余依赖此模块的其他代码都需要引入Lombok依赖,同时还需要在IDE中安装Lombok的插件。虽然Lombok的依赖包并不大,但就因为其中一个地方使用了Lombok,其余所有的依赖方都要强制加入Lombok的Jar包,这是一种入侵式的耦合,如果再遇上JDK版本问题,这将是一场灾难。
  5. **得不偿失:**使用Lombok,一时觉得很爽,但它却污染了你的代码,破坏了Java代码的完整性,可读性和安全性,同时还增加的团队的技术债务,这是一种弊大于利,得不偿失的操作。如果你确实想让自己的代码更加精炼,同时又兼顾可读性和编码效率,不妨使用主流的Scala或Kotlin这一基于JVM的语言

5.5 总结

Lombok本身是一个优秀的Java代码库,它采用了一种取巧的语法糖,简化了Java的编码,为Java代码的精简提供了一种方式,但在使用此代码库时,需要了解到Lombok并非一个标准的Java库。使用Lombok,会增加团队的技术债务,降低代码的可读性,增大代码的耦合度和调式难度。虽然在一定程度上Lombok减少了样板代码的书写,但也带来了一些未知的风险。如果你正在参与一个团队项目(或大型项目),考虑到后续的升级与扩展,是否使用Lombok,请与你的团队多沟通和三思。

6. MyBatis执行过程

  1. 读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。

  2. 加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。//动态代理模式

  3. 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
    在这里插入图片描述

  4. 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。

  5. Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。

  6. MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。

  7. 输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。

  8. 输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值