写点什么

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.listadd sql source: mapper.mapper.PersonMapper.saveexecutorexecutorPerson(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@Datapublic 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 中对查询结果集的自动处理转换部分,完成后,核心的功能就算已经完成了,基本达到了目标

发布于: 刚刚阅读数: 2
用户头像

关注

还未添加个人签名 2018.09.09 加入

代码是门手艺活,也是门艺术活

评论

发布
暂无评论
MyBatis Demo 编写(2)结果映射转换处理