Mybatis返回实体或者返回List Mybatis返回单个实体或者返回List的实现
柳风123 人气:10Mybatis 的强大之处之一体现在映射语句上,让我们可以使用简单的配置,就可以实现对参数和返回结果的映射。
实体
package com.test.User public class User{ private String userId; private String userName; private String userPassword; private Date createTime; ... setter getter.... }
DAO
public interface UserMapper{ User getUserById(String userId); //返回单个实体 List<User> getUserByName(String userName); //返回List Map<String,Object> getUserInfoById(String userId); List<Map<String,Object>> getUserInfoByName(String userName); }
数据库
create table user{ USER_ID varchar(40), USER_NAME varchar(200), USER_PASSWORD varchar(100), CREATE_TIME datetime, .... }
1.返回某个实体
mybatis映射文件
<select id="getUserById" parameterType="string" resultType="com.test.User"> select * from user where id = #{userId} </select>
- id :identification:语句的标识,在同一个mapper映射文件下id需要唯一
- parameterType: 参数类型,可以不写。因为 MyBatis 可以推断出传入语句的具体参数
- resultType: 全限定类名或者是类型别名.
当使用resultType来映射结果时,需要 数据库表的列名或列别名 和 类的属性名相同,这样才能进行字段的匹配(USER_ID 和userId 就不能匹配)。但是如果在Mybatis配置文件中设置了
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> <!--开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。--> </settings>
此时,表列名的下划线标记方式可以映射到驼峰标记的形式。(USER_ID -> userId)。mybatis进行映射时会将实体类属性和数据库列名(别名)都转化为大写来比较,所以USER_ID 和 UserId,userID等都可以匹配。
TooManyResultsException
返回单个实体时,调用方法 getUserById
,但是如果是因为数据错误导致实际查询结果存在多个时,则会抛出异常。
User getUserById(String userId); //返回单个实体
当实际返回值有多个时则抛出异常。
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 2
除非可以确定最多只能查询到一条结果,否则的话不建议这么写.可以尝试返回集合的方式。
2.返回List<entityName>
<select id="getUserByName" resultType="com.test.User"> select * from user where user_name = #{userName} </select>
返回List<T> 集合时,resultType设置为集合元素的类型即T。然后使用返回几何数据的Mapper方法即可。
List<User> getUserByName(String userName); //返回List
从上面可以看到,返回单个实体与返回集合的resultType指定类型是一样的,不一样的地方在Mapper接口或者sqlSession中定义的返回结果类型。实际上mybatis执行查询的时候也都是使用sqlSession.selectList()来进行查询的。
- 使用Mapper 接口的方式的查询结果时,Mybatis会生成该接口的代理类(MapperProxy),然后根据Method的getReturnType()方法,拿到返回类型,来确定返回的是列表还是单个实体。最后也是调用sqlSession的一些方法。
- 使用SqlSession时,提供了selectOne() 或者selectList()来返回单个实体或者集合。selectOne 实际会调用selectList获取结果。
推荐使用返回List的方式来查询结果
//查询单条结果 List<User> userList= mapper.getUserByName(userName); if(userList.isEmpty() || userList.size() >1)//期望获得一条结果 //业务处理,一般是抛出异常或者直接返回错误结果 //return xx; //throw xxx User user = userList.get(0);
扩展
为什么查询单条和查询多条使用的是相同的resultType
,而返回的结果不同呢。
这是因为Mybatis 在内部进行数据查询的时,无论查询单条还是多条都是通过selectList
实现的,不同的是查询单条Mybatis会获取第一条,并且如果结果中存在多条时抛出异常 TooManyResultsException
。
查询单数据
@Override public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.<T>selectList(statement, parameter); if (list.size() == 1) { return list.get(0); } else if (list.size() > 1) { throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size()); } else { return null; } }
查询列表
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) { List<E> result; Object param = method.convertArgsToSqlCommandParam(args); if (method.hasRowBounds()) { RowBounds rowBounds = method.extractRowBounds(args); result = sqlSession.<E>selectList(command.getName(), param, rowBounds); } else { result = sqlSession.<E>selectList(command.getName(), param); } // issue #510 Collections & arrays support if (!method.getReturnType().isAssignableFrom(result.getClass())) { if (method.getReturnType().isArray()) { return convertToArray(result); } else { return convertToDeclaredCollection(sqlSession.getConfiguration(), result); } } return result; }
那么Mybatis怎么知道是查询的单条数据还是列表呢?
- 如果直接使用 SqlSession,这个需要自己控制 是调用
selectOne
还是selectList
- 如果使用 Mapper 接口,Mybatis会解析Mapper接口中的方法,会根据方法的返回值,判断该方法属于那种类型
解析方法中的参数、返回值
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) { Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface); if (resolvedReturnType instanceof Class<?>) { this.returnType = (Class<?>) resolvedReturnType; } else if (resolvedReturnType instanceof ParameterizedType) { this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType(); } else { this.returnType = method.getReturnType(); } this.returnsVoid = void.class.equals(this.returnType); this.returnsMany = (configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray()); this.returnsCursor = Cursor.class.equals(this.returnType); this.mapKey = getMapKey(method); this.returnsMap = (this.mapKey != null); this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class); this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class); this.paramNameResolver = new ParamNameResolver(configuration, method); }
method
对象就是 Mapper中的方法
// select 查询操作 case SELECT: // 方法中没有定义返回结果,并且方法存在结果处理器 if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { // 返回列表 result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { // 返回Map result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { // 返回游标 result = executeForCursor(sqlSession, args); } else { // 返回单条数据 Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } break;
3.返回Map
返回map 本质上也是返回一个实体。
<select id="getUserInfoById" resultType="map"> select * from user where id=#{userId} </select>
如果想要返回单个Map<key,value>集合,只需要设置resultType="map"
就可以了,此时返回的实例类型是HashMap。
Map的key就是数据表的列名
或者列别名
, value 就是查询的数据库中的结果。如果需要返回LinkedHashMap,需要使用全限定类名resultType="java.util.LinkedHashMap"
。
注意: 返回列对应的结果为 null
,则不显示该 key - value 键值对(只针对该行数据对应的Map)
<select id="getUserInfoById" resultType="map"> select * from user where id=#{userId} </select>
在Oracle
环境下:
最终返回Map:
{"name ":"全部"} // code 不是显示
如果使用map接受则不会该map不存在数据. 因为Mybatis默认情况下,不会进行赋值,此时该key-value
缺失
如果需要改变该行为可以在mybatis配置文件中设置
<setting name="callSettersOnNulls" value="true"/>
callSettersOnNulls指定当返回结果为null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,注意基本类型(int、boolean等)是不能设置成 null 的。
配置之后的返回结果:
{"code":null,"name":"全部"}
4.返回List<Map>
<select id="" parameterType="" resultType="map"> sql_caluse </select>
resultType设置为map,跟上面一样resultType设置为List集合中元素的类型。
关于mybatis传递多个参数,可以参考mybatis3-传递多参数
注意:
偶然发现Mybatis 会自动对重名的列做去重。
比如我有一组数据,使用Map接受
SELECT l1.*,l2.*,l3.* FROM ITEM_CAT l1 LEFT JOIN ITEM_CAT l2 ON l1.Id=L2.PARENT_ID LEFT JOIN ITEM_CAT l3 ON l2.Id=L3.PARENT_ID WHERE L1.PARENT_ID='0';
实际返回结果,会发现 name1,name2 都没有映射到Map中
[ {"parent_id":0,"name":"图书","id":1}, {"parent_id":0,"name":"图书","id":1}, {"parent_id":0,"name":"图书","id":1}, {"parent_id":0,"name":"图书","id":1} ...... ]
稍微修改一下字段名称。
SELECT l1.*,l2.*,l3.*,'test' as name1 FROM ITEM_CAT l1 LEFT JOIN ITEM_CAT l2 ON l1.Id=L2.PARENT_ID LEFT JOIN ITEM_CAT l3 ON l2.Id=L3.PARENT_ID WHERE L1.PARENT_ID='0';
[ {"parent_id":0,"name":"图书","id":1,"name1":"test"}, {"parent_id":0,"name":"图书","id":1,"name1":"test"}, {"parent_id":0,"name":"图书","id":1,"name1":"test"}, {"parent_id":0,"name":"图书","id":1,"name1":"test"} ...... ]
可以看到 新增的自定义列名 “name1”,可以正常显示。这是因为使用sql 查询出的同名的列名自动追加数字做区分,而实际保存在 元数据信息中的列名还是原来的。就如同Excel 的单元格一样,不管单元格内容以什么样式显示都不会修改实际值。
小结:
- 返回集合与返回单个实体对象在映射文件的写法是一致的,不同的地方在于Mapper的返回类型不同。
- 如果不确定返回值是否是唯一的,尽量使用 集合的返回方式。然乎使用get(0)的方式获取实体。
- 如果返回实体,一般情况会使用 resultMap来映射返回结果。这样更清晰,直观,而且还可以使用typeHandler对数据类型做进一步处理
返回结果 | Mapper | xml |
---|---|---|
实体 | T getT() | returnType=“T” |
集合 | List<T> getTList() | returnType=“T” |
加载全部内容