MyBatis<select>节点中的resultType和resultMap属性
1.为什么要进行resultType或resultMap注解配置
当需要实现的数据访问是查询类型的,在<select>节点中必须配置resultType或resultMap中的某1个属性(二选一),如果都没有指定,则会出现如下错误:
Caused by: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'cn.tedu.mybatis.UserMapper.count'. It's likely that neither a Result Type nor a Result Map was specified.
解决因为名称不一致导致MyBatis无法自动封装查询结果的问题
- 当自定义字段列表时,自定义别名,使得名称保持一致,并且,在<select>节点中使用resultType即可;
- 当使用星号表示字段列表时,配置<resultMap>以指导MyBatis封装,并且,在<select>节点中使用resultMap应用配置的<resultMap>。
2.resultType
其中,resultType指的就是“封装查询结果的数据的类型”,也可以理解为“抽象方法的返回值的类型”,例如可以配置为:
<select id="count" resultType="java.lang.Integer">
SELECT COUNT(*) FROM t_user
</select>
如果某个查询的抽象方法的返回值是List集合类型的,例如:
List<User> findAll();
在配置<select>的resultType属性时,该属性值必须是集合中的元素的类型,例如:
<select id="findAll" resultType="cn.tedu.mybatis.User">
SELECT * FROM t_user ORDER BY id
</select>
注意:使用了这种做法后,就需要对抽象方法名称的定义增加一个要求“不允许重载”!
3.resultMap
3.1 一对一查询中的resultMap的使用
在没有自定义别名的情况下,如果查询结果的列名与类的属性名不一致,还可以自定义<resultMap>节点进行配置,该节点的作用就是指导MyBatis封装查询到的数据!
<!-- id:自定义的名称,将应用于select节点的resultMap属性 -->
<!-- type:封装查询结果的数据类型 -->
<resultMap id="UserMap" type="cn.tedu.mybatis.User">
<!-- result节点:将查询结果中column列对应的值封装到类的property属性中去 -->
<result column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="age" property="age"/>
<result column="phone" property="phone"/>
<result column="email" property="email"/>
<result column="group_id" property="groupId"/>
</resultMap>
<select id="findById" resultMap="UserMap">
SELECT
*
FROM
t_user
WHERE
id=#{id}
</select>
在以上配置中,需要注意:<resultMap>节点中的id属性值就是<select>节点的resultMap属性值!
如果需要执行的查询操作是单表数据查询,在配置<resultMap>时,对于那些列名与属性名一致的,可以不作配置!
在配置<resultMap>时,推荐在子级使用<id>节点配置主键,然后使用<result>节点配置其它的,便于实现缓存!MyBatis有2级缓存,其中,MyBatis的1级缓存是SqlSession缓存,开发人员无法干预,2级缓存是namespace缓存,一旦开启,默认情况下,将作用于整个XML文件,需要事先在当前XML文件中添加<cache></cache>节点,表示“启用缓存”,理启用缓存,需要封装查询结果的数据类型是实现了Serializable接口的,如果某个查询不需要使用缓存,还可以在<select>节点中配置useCache="false",然后,就不需要开发人员进行其它配置了,MyBatis会自动处理缓存的数据,并且,一旦当前namespace执行了增删改类型的操作,就会重建缓存!
小结:在SQL中使用自定义别名,并在<select>中使用resultType指定封装结果的数据类型,或在SQL中使用星号表示字段列表,并配置<resultMap>节点后在<select>中使用resultMap,均可解决由于名称不一致导致无法封装查询结果的问题。
3.2 一对多查询中的resultMap的使用
在一对多的查询中,MyBatis根本就不知道怎么封装查询结果中的数据!当没有匹配的查询结果,或只有1条查询结果时,并不会报错,只是查询结果的数据不正确而已,如果查询结果有多条,则会出现如下错误信息:
Caused by: org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 2
为了保障能够正确的封装查询结果,必须使用<resultMap>来指导MyBatis封装!并且,在SQL语句中,还需要为某个id定义别名,以保证查询结果每一列的名称都不同,否则,MyBatis只会从相同的列名中排列靠前的那一列中取数据,甚至,如果在<resultMap>中使用<id>配置主键,当出现多条数据的最靠前的那一列id值相同时,重复id的数据将不会被封装!
具体配置如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.tedu.mybatis.GroupMapper">
<resultMap id="GroupVOMap" type="cn.tedu.mybatis.GroupVO">
<id column="gid" property="id" />
<result column="name" property="name"/>
<!-- collection节点:用于配置1对多的属性 -->
<!-- ofType属性:集合元素类型 -->
<collection property="users"
ofType="cn.tedu.mybatis.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="age" property="age"/>
<result column="phone" property="phone"/>
<result column="email" property="email"/>
<result column="group_id" property="groupId"/>
</collection>
</resultMap>
<select id="findVOById" resultMap="GroupVOMap">
SELECT
t_group.id AS gid, name,
t_user.*
FROM
t_group
LEFT JOIN
t_user
ON
t_group.id=t_user.group_id
WHERE
t_group.id=#{id}
</select>
</mapper>
注意:在配置一对多的查询时,在<resultMap>中,即使存在列名与属性名完全相同的数据,也必须通过<id>或<result>节点进行配置!