函數映射圖像,MyBatis Demo 編寫(2)結果映射轉換處理

 2023-10-12 阅读 32 评论 0

摘要:簡介 在上篇中,我們完成了MyBatis一部分功能的搭建,已經能通過Mapper接口類的編寫,自動執行相關的語句了,接下來完善結果處理部分 最終效果展示 函數映射圖像、修改下我們的Mapper public interface PersonMapper {@Select("select * fro

簡介

在上篇中,我們完成了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中對查詢結果集的自動處理轉換部分,完成后,核心的功能就算已經完成了,基本達到了目標

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/2/135528.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息