MyBatis Demo 编写(2)结果映射转换处理
- 2022 年 2 月 16 日
本文字数:5593 字
阅读完需:约 18 分钟
简介
在上篇中,我们完成了 MyBatis 一部分功能的搭建,已经能通过 Mapper 接口类的编写,自动执行相关的语句了,接下来完善结果处理部分
最终效果展示
修改下我们的 Mapper
public interface PersonMapper {
@Select("select * from person")
List<Person> list();
@Insert("insert into person (id, name) values ('1', '1')")
void save();
}
测试代码如下:
public class SelfMybatisTest {
@Test
public void test() {
try(SelfSqlSession session = buildSqlSessionFactory()) {
PersonMapper personMapper = session.getMapper(PersonMapper.class);
personMapper.save();
List<Person> personList = personMapper.list();
for (Object person: personList) {
System.out.println(person.toString());
}
}
}
public static SelfSqlSession buildSqlSessionFactory() {
String JDBC_DRIVER = "org.h2.Driver";
String DB_URL = "jdbc:h2:file:./testDb";
String USER = "sa";
String PASS = "";
HikariConfig config = new HikariConfig();
config.setJdbcUrl(DB_URL);
config.setUsername(USER);
config.setPassword(PASS);
config.setDriverClassName(JDBC_DRIVER);
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
DataSource dataSource = new HikariDataSource(config);
SelfConfiguration configuration = new SelfConfiguration(dataSource);
configuration.addMapper(PersonMapper.class);
return new SelfSqlSession(configuration);
}
}
输出如下:
add sql source: mapper.mapper.PersonMapper.list
add sql source: mapper.mapper.PersonMapper.save
executor
executor
Person(id=1, name=1)
成功的返回我们自定义的 Person 对象,这个 Demo 已经有一点样子,算是达成了目标
下面是实现的相关细节
Demo 编写
完整的工程已放到 GitHub 上:https://github.com/lw1243925457/MybatisDemo/tree/master/
本篇文章的代码对应的 Tag 是: V2
思路梳理
要实现 SQL 查询结果到 Java 对象的转换,我们需要下面的东西:
1.返回的 Java 对象信息
2.对应的 SQL 表字段信息
3.SQL 字段值到 Java 对象字段的转换处理
4.读取 SQL 结果,转换成 Java 对象
1.返回的 Java 对象信息
我们需要知道当前接口方法返回的 Java 对象信息,方便后面的读取 SQL 查询结果,转换成 Java 对象
借鉴 MyBatis,我们定义下面一个类,来保存接口方法返回的对象和 SQL 查询结果字段与 Java 对象字段的 TypeHandler 处理器
@Builder
@Data
public class ResultMap {
private Object returnType;
private Map<String,TypeHandler> typeHandlerMaps;
}
2.对应的 SQL 表字段信息
在以前的 MyBatis 源码解析中,我们大致知道获取 TypeHandler 是根据 JavaType 和 JdbcType,我们就需要知道数据库表中各个字段的类型,方便后面去匹配对应的 TypeHandler
我们在程序初始化的时候,读取数据库中所有的表,保存下其各个字段对应的 jdbcType
可能不同表中有相关的字段,但是不同的类型,所以第一层是表名,第二层是字段名称,最后对应其 jdbcType
代码如下:
public class SelfConfiguration {
/**
* 读取数据库中的所有表
* 获取其字段对应的类型
* @throws SQLException e
*/
private void initJdbcTypeCache() throws SQLException {
try (Connection conn = dataSource.getConnection()){
final DatabaseMetaData dbMetaData = conn.getMetaData();
ResultSet tableNameRes = dbMetaData.getTables(conn.getCatalog(),null, null,new String[] { "TABLE" });
final List<String> tableNames = new ArrayList<>(tableNameRes.getFetchSize());
while (tableNameRes.next()) {
tableNames.add(tableNameRes.getString("TABLE_NAME"));
}
for (String tableName : tableNames) {
try {
String sql = "select * from " + tableName;
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
ResultSetMetaData meta = rs.getMetaData();
int columnCount = meta.getColumnCount();
Map<String, Integer> jdbcTypeMap = new HashMap<>(columnCount);
for (int i = 1; i < columnCount + 1; i++) {
jdbcTypeMap.put(meta.getColumnName(i).toLowerCase(), meta.getColumnType(i));
}
jdbcTypeCache.put(tableName.toLowerCase(), jdbcTypeMap);
} catch (Exception ignored) {
}
}
}
}
}
3.SQL 字段值到 Java 对象字段的转换处理
接下来我们要定义 JavaType 与 jdbcType 相互转换的 TypeHandler
简化点,我们内置定义 String 和 Long 类型的处理,并在初始化的时候进行注册(还没涉及到参数转换处理,所以暂时定义 jdbcType 到 JavaType 的处理)
TypeHandler 代码如下:
public interface TypeHandler {
Object getResult(ResultSet res, String cluName) throws SQLException;
}
public class StringTypeHandler implements TypeHandler {
private static final StringTypeHandler instance = new StringTypeHandler();
public static StringTypeHandler getInstance() {
return instance;
}
@Override
public Object getResult(ResultSet res, String cluName) throws SQLException {
return res.getString(cluName);
}
}
public class LongTypeHandler implements TypeHandler {
private static LongTypeHandler instance = new LongTypeHandler();
public static LongTypeHandler getInstance() {
return instance;
}
@Override
public Object getResult(ResultSet res, String cluName) throws SQLException {
return res.getLong(cluName);
}
}
默认初始化注册:
public class SelfConfiguration {
private void initTypeHandlers() {
final Map<Integer, TypeHandler> varchar = new HashMap<>();
varchar.put(JDBCType.VARCHAR.getVendorTypeNumber(), StringTypeHandler.getInstance());
typeHandlerMap.put(String.class, varchar);
final Map<Integer, TypeHandler> intType = new HashMap<>();
intType.put(JDBCType.INTEGER.getVendorTypeNumber(), LongTypeHandler.getInstance());
typeHandlerMap.put(Long.class, intType);
}
}
接着重要的一步是构建接口函数方法返回的结果处理了,具体的细节如下,关键的都进行了相关的注释:
public class SelfConfiguration {
private final DataSource dataSource;
private final Map<String, SqlSource> sqlCache = new HashMap<>();
private final Map<String, ResultMap> resultMapCache = new HashMap<>();
private final Map<String, Map<String, Integer>> jdbcTypeCache = new HashMap<>();
private final Map<Class<?>, Map<Integer, TypeHandler>> typeHandlerMap = new HashMap<>();
/**
* Mapper添加
* 方法路径作为唯一的id
* 保存接口方法的SQL类型和方法
* 保存接口方法返回类型
* @param mapperClass mapper
*/
public void addMapper(Class<?> mapperClass) {
final String classPath = mapperClass.getPackageName();
final String className = mapperClass.getName();
for (Method method: mapperClass.getMethods()) {
final String id = StringUtils.joinWith("." ,classPath, className, method.getName());
for (Annotation annotation: method.getAnnotations()) {
if (annotation instanceof Select) {
addSqlSource(id, ((Select) annotation).value(), SqlType.SELECT);
continue;
}
if (annotation instanceof Insert) {
addSqlSource(id, ((Insert) annotation).value(), SqlType.INSERT);
}
}
// 构建接口函数方法返回值处理
addResultMap(id, method);
}
}
/**
* 构建接口函数方法返回值处理
* @param id 接口函数 id
* @param method 接口函数方法
*/
private void addResultMap(String id, Method method) {
// 空直接发返回
if (method.getReturnType().getName().equals("void")) {
return;
}
// 获取返回对象类型
// 这里需要特殊处理下,如果是List的话,需要特殊处理得到List里面的对象
Type type = method.getGenericReturnType();
Type returnType;
if (type instanceof ParameterizedType) {
returnType = ((ParameterizedType) type).getActualTypeArguments()[0];
} else {
returnType = method.getReturnType();
}
// 接口方法id作为key,值为 接口方法返回对象类型和其中每个字段对应处理的TypeHandler映射
resultMapCache.put(id, ResultMap.builder()
.returnType(returnType)
.typeHandlerMaps(buildTypeHandlerMaps((Class<?>) returnType))
.build());
}
/**
* 构建实体类的每个字段对应处理的TypeHandler映射
* @param returnType 接口函数返回对象类型
* @return TypeHandler映射
*/
private Map<String, TypeHandler> buildTypeHandlerMaps(Class<?> returnType) {
// 这里默认取类名的小写为对应的数据库表名,当然也可以使用@TableName之类的注解
final String tableName = StringUtils.substringAfterLast(returnType.getTypeName(), ".").toLowerCase();
final Map<String, TypeHandler> typeHandler = new HashMap<>(returnType.getDeclaredFields().length);
for (Field field : returnType.getDeclaredFields()) {
final String javaType = field.getType().getName();
final String name = field.getName();
final Integer jdbcType = jdbcTypeCache.get(tableName).get(name);
// 根据JavaType和jdbcType得到对应的TypeHandler
typeHandler.put(javaType, typeHandlerMap.get(field.getType()).get(jdbcType));
}
return typeHandler;
}
}
4.读取 SQL 结果,转换成 Java 对象
接下来就是 SQL 查询结果的处理了,主要是根据在初始化阶段构建好的针对每个返回类型 ResultMap
根据 ResultMap 中的返回对象类型,生成对象实例
根据 ResultMap 中的 TypeHandler 映射,得到各个字段对应的 TypeHandler,得到处理结果
反射调用对象的 Set 方法
代码如下:
public class ResultHandler {
public List<Object> parse(String id, ResultSet res, SelfConfiguration config) throws SQLException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
if (res == null) {
return null;
}
// 根据接口函数Id得到初始化时国家队ResultMap
ResultMap resultMap = config.getResultType(id);
final List<Object> list = new ArrayList<>(res.getFetchSize());
while (res.next()) {
// 根据接口函数返回类型,生成一个实例
Class<?> returnType = (Class<?>) resultMap.getReturnType();
Object val = returnType.getDeclaredConstructor().newInstance();
for (Field field: returnType.getDeclaredFields()) {
final String name = field.getName();
// 根据返回对象的字段类型,得到对应的TypeHandler,调用TypeHandler处理得到结果
TypeHandler typeHandler = resultMap.getTypeHandlerMaps().get(field.getType().getName());
Object value = typeHandler.getResult(res, name);
// 调用对象的Set方法
String methodEnd = name.substring(0, 1).toUpperCase() + name.substring(1);
Method setMethod = val.getClass().getDeclaredMethod("set" + methodEnd, field.getType());
setMethod.invoke(val, value);
}
list.add(val);
}
return list;
}
}
总结
本篇中完善了 Demo 中对查询结果集的自动处理转换部分,完成后,核心的功能就算已经完成了,基本达到了目标
版权声明: 本文为 InfoQ 作者【萧】的原创文章。
原文链接:【http://xie.infoq.cn/article/2fb3474791a5033118f37129b】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
萧
还未添加个人签名 2018.09.09 加入
代码是门手艺活,也是门艺术活
评论