第五篇:查询结果为什么能自动变成 Java 对象?
查询结果为什么能自动变成 Java 对象?
上一篇我们讲了:
一条 SQL 在 MyBatis 里到底经历了什么?
最终执行链路:
MapperProxy
↓
SqlSession
↓
Executor
↓
StatementHandler
↓
Database
SQL 执行完成以后。
数据库返回:
ResultSet
新的问题来了,数据库返回的是表数据。
而业务代码拿到的却是对象。
例如数据库:
id=1
name=Tom
age=18
业务代码:
User user = userMapper.selectById(1L);
直接得到:
user.getName();
这是怎么做到的?
难道 MyBatis 会魔法?当然不是
今天就看看 MyBatis 最重要的能力之一:
结果映射
先想一个问题
如果不用 MyBatis,纯 JDBC 怎么写?
PreparedStatement ps =
connection.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
User user = new User();
if (rs.next()) {
user.setId(rs.getLong("id"));
user.setName(rs.getString("name"));
user.setAge(rs.getInt("age"));
}
有没有觉得很烦?
每查一次数据,都要自己写映射逻辑,而且字段越多越痛苦。
MyBatis 帮你干了什么?
我们平时只写:
User user = userMapper.selectById(1L);
剩下的映射过程,全部由 MyBatis 自动完成。
核心组件:
ResultSetHandler
它专门负责:
ResultSet
↓
Java对象
转换。
ResultSet 到底长什么样?
假设数据库返回:
| id | name | age |
|---|---|---|
| 1 | Tom | 18 |
JDBC 拿到的是:
ResultSet
本质上可以理解成:
列名 → 值
例如:
id → 1
name → Tom
age → 18
但业务需要的是:
User
对象。
所以必须完成一次映射。
MyBatis 是怎么映射的?
假设实体:
public class User {
private Long id;
private String name;
private Integer age;
}
数据库字段:
id
name
age
MyBatis 会发现:
字段名
==
属性名
于是直接反射赋值:
user.setId(1L);
user.setName("Tom");
user.setAge(18);
最终得到:
User
对象。
如果字段名不一样呢?
现实项目里经常出现:
数据库:
user_name
Java:
userName
这时候就匹配不上了。
怎么办,很多人知道:
map-underscore-to-camel-case: true
开启驼峰转换。
但很少有人知道原理,实际上 MyBatis 在映射时。
会把:
user_name
转换成:
userName
然后继续完成属性匹配。
所以:
数据库字段
↓
驼峰转换
↓
Java属性
这就是自动映射的本质。
自动映射真的可靠吗?
很多人觉得:
字段一样
自动映射
结束
其实没这么简单。
例如:
select
id,
name
from user
返回:
User
其中:
age
没有查询出来,怎么办?
MyBatis 会自动跳过,不会报错。
反过来。
如果 SQL 返回:
nickname
而对象里没有:
nickname
对应属性,MyBatis 同样会忽略。
所以自动映射容错性非常高。
复杂对象怎么办?
简单对象很好处理,那这种呢?
public class Order {
private Long id;
private User user;
}
数据库返回:
order_id
user_id
user_name
此时已经不是简单映射了。
MyBatis 引入:
ResultMap
解决。
很多人只知道 ResultMap 很强,但不知道为什么存在。
原因很简单:自动映射只能处理简单对象,复杂对象必须告诉 MyBatis:
哪个字段
对应哪个属性
所以:
ResultMap
本质上是一份映射规则。
为什么 MyBatis 要设计 ResultMap?
因为自动映射终究有极限。
例如:
订单
↓
用户
↓
角色
这种嵌套对象,靠字段名猜测已经不现实了,必须有明确映射关系。
于是:
ResultMap
诞生了。
官方甚至说过一句话:
ResultMap 是 MyBatis 最强大的特性之一。
看一眼源码
数据库查询完成后。
最终进入:
DefaultResultSetHandler
核心方法:
handleResultSets()
负责处理:
ResultSet
然后:
createResultObject()
创建对象。
接着:
applyAutomaticMappings()
自动映射字段。
如果配置了:
ResultMap
则进入:
applyPropertyMappings()
按照映射规则赋值。
最终得到:
User
Order
Product
等业务对象。
总结
很多人以为:
User user =
userMapper.selectById(1L);
返回对象是一件理所当然的事情。
实际上背后经历了完整转换过程:
ResultSet
↓
ResultSetHandler
↓
字段匹配
↓
反射赋值
↓
Java对象
简单场景:
自动映射
复杂场景:
ResultMap
所以:
MyBatis 之所以能把查询结果自动变成 Java 对象,本质上是因为 ResultSetHandler 完成了数据库结果与 Java 对象之间的映射。
这也是 MyBatis 比 JDBC 好用得多的重要原因之一。
上一篇:
《一条 SQL 在 MyBatis 里到底经历了什么?》
下一篇:
《MyBatis 动态 SQL 为什么能如此灵活?》
更多推荐
所有评论(0)