This is the 17th day of my participation in the First Challenge 2022
Introduction to the
In the previous part, we completed the MyBatis part of the function of the building, has been able to write Mapper interface class, automatic execution of the relevant statements, then improve the result processing part
Final effect display
Let’s modify our Mapper
public interface PersonMapper {
@Select("select * from person")
List<Person> list(a);
@Insert("insert into person (id, name) values ('1', '1')")
void save(a);
}
Copy the code
The test code is as follows:
public class SelfMybatisTest {
@Test
public void test(a) {
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(a) {
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 newSelfSqlSession(configuration); }}Copy the code
The output is as follows:
add sql source: mapper.mapper.PersonMapper.list
add sql source: mapper.mapper.PersonMapper.save
executor
executor
Person(id=1, name=1)
Copy the code
We successfully returned our custom Person object. The Demo already looks a little bit like this
Below are the implementation details
The Demo code
The full project is available on GitHub: github.com/lw124392545…
The corresponding Tag for this article is: V2
Thinking to comb
To convert SQL query results to Java objects, we need the following:
- 1. Returned Java object information
- 2. Field information of the SQL table
- 3. Conversion of SQL field values to Java object fields
- 4. Read SQL results and convert them to Java objects
1. Returned Java object information
We need to know the Java object information returned by the current interface method, so that we can read the SQL query results later and convert them into Java objects
Taking a page from MyBatis, we define the following class to hold TypeHandler handlers for objects returned by interface methods and SQL query result fields and Java object fields
@Builder
@Data
public class ResultMap {
private Object returnType;
private Map<String,TypeHandler> typeHandlerMaps;
}
Copy the code
2. Field information of the SQL table
In the previous MyBatis source code analysis, we know about the TypeHandler is based on JavaType and JdbcType, we need to know the types of each field in the database table, convenient to match the corresponding TypeHandler
When we initialize the program, we read all the tables in the database and save the corresponding jdbcType of each field
Different tables may have related fields, but of different types, so the first layer is the table name, the second layer is the field name, and finally corresponds to its jdbcType
The code is as follows:
public class SelfConfiguration {
/** * Read all tables in the database * get the corresponding types of their fields *@throws SQLException e
*/
private void initJdbcTypeCache(a) 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) {
}
}
}
}
}
Copy the code
3. Conversion of SQL field values to Java object fields
Next we define TypeHandler for JavaType and jdbcType conversions
To simplify things, we have built-in String and Long handlers that are registered at initialization (no argument conversion handlers are involved, so we’ll define jdbcType to JavaType handlers for now)
The TypeHandler code looks like this:
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(a) {
return instance;
}
@Override
public Object getResult(ResultSet res, String cluName) throws SQLException {
returnres.getString(cluName); }}public class LongTypeHandler implements TypeHandler {
private static LongTypeHandler instance = new LongTypeHandler();
public static LongTypeHandler getInstance(a) {
return instance;
}
@Override
public Object getResult(ResultSet res, String cluName) throws SQLException {
returnres.getLong(cluName); }}Copy the code
Default initial registration:
public class SelfConfiguration {
private void initTypeHandlers(a) {
final Map<Integer, TypeHandler> varchar = new HashMap<>();
varchar.put(JDBCType.VARCHAR.getVendorTypeNumber(), StringTypeHandler.getInstance());
typeHandlerMap.put(String.class, varchar);
final Map<Integer, TypeHandler> intType = newHashMap<>(); intType.put(JDBCType.INTEGER.getVendorTypeNumber(), LongTypeHandler.getInstance()); typeHandlerMap.put(Long.class, intType); }}Copy the code
The next important step is to construct the return result of the interface function method. The details are as follows, and the key points are annotated:
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 finalMap<Class<? >, Map<Integer, TypeHandler>> typeHandlerMap =new HashMap<>();
/** * Mapper adds * method path as unique ID * holds the SQL type and method of the interface method * holds the interface method return type *@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 instanceofInsert) { addSqlSource(id, ((Insert) annotation).value(), SqlType.INSERT); }}// Build interface function method return value processingaddResultMap(id, method); }}/** * Build interface function method return value processing *@paramId Id of the interface function *@paramMethod Interface function method */
private void addResultMap(String id, Method method) {
// Return null
if (method.getReturnType().getName().equals("void")) {
return;
}
// Get the return object type
// Special processing is needed to get the objects in the List
Type type = method.getGenericReturnType();
Type returnType;
if (type instanceof ParameterizedType) {
returnType = ((ParameterizedType) type).getActualTypeArguments()[0];
} else {
returnType = method.getReturnType();
}
// The interface method id is the key, and the value is the TypeHandler mapping of the object type returned by the interface method and the corresponding processing for each field within itresultMapCache.put(id, ResultMap.builder() .returnType(returnType) .typeHandlerMaps(buildTypeHandlerMaps((Class<? >) returnType)) .build()); }/** * Each field of the entity class is built to correspond to the TypeHandler map processed *@paramReturnType The interface function returns the object type *@returnTypeHandler map * /
private Map<String, TypeHandler> buildTypeHandlerMaps(Class
returnType) {
// by default, the lower case of the class name is the corresponding database TableName. You can also use an annotation like @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);
// Get the corresponding TypeHandler based on JavaType and jdbcType
typeHandler.put(javaType, typeHandlerMap.get(field.getType()).get(jdbcType));
}
returntypeHandler; }}Copy the code
4. Read SQL results and convert them to Java objects
Next comes the processing of SQL query results, based on a ResultMap built during initialization for each return type
- Generate an object instance based on the returned object type in the ResultMap
- According to the TypeHandler mapping in ResultMap, the Corresponding TypeHandler of each field is obtained, and the processing result is obtained
- Reflection calls the Set method of an object
The code is as follows:
public class ResultHandler {
public List<Object> parse(String id, ResultSet res, SelfConfiguration config) throws SQLException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
if (res == null) {
return null;
}
// Get the ResultMap of the national team when initialization according to the interface function Id
ResultMap resultMap = config.getResultType(id);
final List<Object> list = new ArrayList<>(res.getFetchSize());
while (res.next()) {
// Generate an instance based on the interface function return typeClass<? > returnType = (Class<? >) resultMap.getReturnType(); Object val = returnType.getDeclaredConstructor().newInstance();for (Field field: returnType.getDeclaredFields()) {
final String name = field.getName();
// According to the field type of the returned object, get the corresponding TypeHandler, call TypeHandler to get the result
TypeHandler typeHandler = resultMap.getTypeHandlerMaps().get(field.getType().getName());
Object value = typeHandler.getResult(res, name);
// Call the Set method of the object
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);
}
returnlist; }}Copy the code
conclusion
This article has improved the automatic processing and conversion part of the query result set in Demo. After completion, the core functions have been completed and basically achieved the goal