简介

在上篇中,我们完成了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 {@Testpublic 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;}@Overridepublic 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;}@Overridepublic 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得到对应的TypeHandlertypeHandler.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得到初始化时国家队ResultMapResultMap 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中对查询结果集的自动处理转换部分,完成后,核心的功能就算已经完成了,基本达到了目标

MyBatis Demo 编写(2)结果映射转换处理相关推荐

  1. MyBatis Demo 编写(1)基础功能搭建

    简介 在Mybatis3的源码解析系列中,我们对其核心功能有了一定的了解,下面我们尝试简单写一下Demo,让其有简单的Mybatis的一些核心功能,本篇是基础功能的搭建 Dome 编写 完整的工程已放 ...

  2. ssis 转换中文字符乱码_SSIS软件包中的字符映射转换

    ssis 转换中文字符乱码 This article explores the Character Map Transformation in SSIS package with available ...

  3. MyBatis官方文档-XML 映射文件

    最近更新: 15 七月 2019|版本: 3.5.2 文章目录 XML 映射文件 insert, update 和 delete sql 结果映射 高级结果映射 结果映射(resultMap) id ...

  4. 框架:Mybatis开发规范及输入输出映射配置时注意事件

    程序员需要编写mapper.xml映射文件 程序员编写mapper接口需要遵循一些开发规范,mybatis可以自动生成mapper接口实现类代理对象. 1.开发规范: 1.在mapper.xml中na ...

  5. 紫薇星上的Java——映射转换

    在之前我们有讲过一节引用传递,当我们了解引用传递后就可以在实际开发中运用到它,那今天我们就来实践一下叭! 1.数据表与简单Java类映射转换 简单Java类是现在面向对象设计的主要分析基础,但对于死机 ...

  6. mybatis学习笔记(7)-输出映射

    2019独角兽企业重金招聘Python工程师标准>>> mybatis学习笔记(7)-输出映射 标签: mybatis [TOC] 本文主要讲解mybatis的输出映射. 输出映射有 ...

  7. 关于python中的字符串映射转换

    关于python中的字符串映射转换 利用Python字符串映射的方式来快速准确对Python字符串中对应的字符串进行替换,方法主要有两种: 第一种:maketrans方法 maketrans方法的参数 ...

  8. python 代码转程序_如何用pyinstaller把自己编写的python源代码转换成可执行程序?...

    昨天慢步熬夜写了一篇干货满满的文章,不知道什么原因,文章并未被推荐. 今天再来换个方式写一次. 把自己编写的python源代码转换成可执行程序 笔者继续用自编的<货币兑换程序3.0>为例. ...

  9. Mybatis - 一对多/多对一映射

    文章目录 前言 项目结构 一.数据库表 1. 员工表 t_emp 2. 部门表 t_dept 二.实体类 1. Emp 员工实体类(添加多对一属性) 2. dept 部门实体类(添加一对多属性) 三. ...

最新文章

  1. Python编程基础:第三十七节 石头剪刀布游戏Rock, Paper, Scissors Game
  2. 如何用Snapgene 4.3.6进行序列比对及查找碱基位点
  3. Java 8:功能性VS传统
  4. MapXtreme开发(二)
  5. MySQL实现时间按月,日,小时分组查询
  6. 直接拿来用!前端如何快速实现跨平台开发?
  7. AutoJs学习-几个QQ群脚本(群引流\提取成员\加群友\加群)
  8. msf之制作木马进行远程控制
  9. [嵌入式linux]RTL8111/RTL8168网卡内核驱动安装
  10. 当国际贸易撞上AI,会产生怎样的化学反应?
  11. 20135231 —— Linux 基础入门学习
  12. 云科技网络验证源码_【原创】酸酸云科技-网络验证界面版注册机注入工具 V3.2...
  13. codevs 切糕 网络流
  14. 基于HTML5和JS实现的在线电子钢琴网页版
  15. mysql数据库基础:存储过程和函数
  16. ngx-datatable的使用
  17. 云起实验室:ECS数据管理实践-备份与恢复
  18. Navicat Premium的下载及安装
  19. 运算放大器的性能指标
  20. 【LaTeX】LaTeX新手入门教程-基础排版

热门文章

  1. 数据库快照的工作方式
  2. 今天你写控件了吗?----ASP.net控件开发系列之(一)开篇
  3. 跨域问题,解决方案-Nginx反向代理
  4. 网易严选搜索推荐实践之:“全能选手”召回表征算法实践.pdf(附下载链接)...
  5. 教师节,老师最大的愿望是...
  6. windows下anaconda环境激活报错CommandNotFoundError: Your shell has not been properly configured to use ‘con
  7. 浙大PAT乙级1004. 成绩排名 (20)
  8. Facebook AI研究员田渊栋:2021年年终总结
  9. pythoncookbook和流畅的python对比_为什么你学Python效率比别人慢?因为你没有这套完整的学习资料...
  10. pdfwin10闪退_win10系统打开文件夹闪退的解决方法