这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战
简介
在上篇中,我们完成了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上:github.com/lw124392545…
本篇文章的代码对应的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中对查询结果集的自动处理转换部分,完成后,核心的功能就算已经完成了,基本达到了目标