From 7f31d477d60cac16e68bacead8185fd027d24f71 Mon Sep 17 00:00:00 2001 From: ZhouXY108 Date: Thu, 3 Oct 2024 16:27:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20DefaultBeanResultMap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +- .../xyz/zhouxy/jdbc/DefaultBeanResultMap.java | 88 +++++++++++++++++++ src/main/java/xyz/zhouxy/jdbc/ResultMap.java | 10 ++- .../{ => test}/SimpleJdbcTemplateTests.java | 69 +++++++++++++-- 4 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 src/main/java/xyz/zhouxy/jdbc/DefaultBeanResultMap.java rename src/test/java/xyz/zhouxy/jdbc/{ => test}/SimpleJdbcTemplateTests.java (82%) diff --git a/README.md b/README.md index ee1d3ce..84499f8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # SimpleJDBC 对 JDBC 的简单封装。 -之前遇到的一个老项目,没有引入任何 ORM 框架,使用的 JDK7 明明支持泛型,所依赖的 spring-jdbc 居然是没有泛型的远古版本,该项目又不允许随意添加依赖,对数据库的操作几乎都在写原生 JDBC。故自己写了几个工具类,对 JDBC 进行简单封装,有了这东西的雏形。 \ No newline at end of file + +之前遇到的一个老项目,没有引入任何 ORM 框架,使用的 JDK7 明明支持泛型,所依赖的 spring-jdbc 居然是没有泛型的远古版本,该项目又不允许随意添加依赖,对数据库的操作几乎都在写原生 JDBC。故自己写了几个工具类,对 JDBC 进行简单封装,后来逐渐改进完善。 + +本项目不比成熟的工具,如若使用请自行承担风险。建议仅作为 JDBC 的学习参考。 diff --git a/src/main/java/xyz/zhouxy/jdbc/DefaultBeanResultMap.java b/src/main/java/xyz/zhouxy/jdbc/DefaultBeanResultMap.java new file mode 100644 index 0000000..ea67b5f --- /dev/null +++ b/src/main/java/xyz/zhouxy/jdbc/DefaultBeanResultMap.java @@ -0,0 +1,88 @@ +package xyz.zhouxy.jdbc; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; + +public class DefaultBeanResultMap implements ResultMap { + + private final Constructor constructor; + private final Map colPropertyMap; + + private DefaultBeanResultMap(Constructor constructor, Map colPropertyMap) { + this.constructor = constructor; + this.colPropertyMap = colPropertyMap; + } + + public static DefaultBeanResultMap of(Class beanType) throws SQLException { + return of(beanType, null); + } + + public static DefaultBeanResultMap of(Class beanType, @Nullable Map propertyColMap) + throws SQLException { + try { + BeanInfo beanInfo = Introspector.getBeanInfo(beanType); + PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); + Map colPropertyMap; + if (propertyColMap != null && !propertyColMap.isEmpty()) { + colPropertyMap = Arrays.stream(propertyDescriptors) + .collect(Collectors.toMap(p -> { + String propertyName = p.getName(); + String colName = propertyColMap.get(propertyName); + return colName != null ? colName : propertyName; + }, Function.identity(), (a, b) -> b)); + } + else { + colPropertyMap = Arrays.stream(propertyDescriptors) + .collect(Collectors.toMap(PropertyDescriptor::getName, Function.identity(), (a, b) -> b)); + } + + Constructor constructor = beanType.getDeclaredConstructor(); + constructor.setAccessible(true); // NOSONAR + return new DefaultBeanResultMap<>(constructor, colPropertyMap); + } + catch (IntrospectionException e) { + throw new SQLException("There is an exception occurs during introspection.", e); + } + catch (NoSuchMethodException e) { + throw new SQLException("Could not find a no-args constructor in " + beanType.getName(), e); + } + } + + @Override + public T map(ResultSet rs, int rowNumber) throws SQLException { + try { + T newInstance = this.constructor.newInstance(); + ResultSetMetaData metaData = rs.getMetaData(); + for (int i = 1; i <= metaData.getColumnCount(); i++) { + String colName = metaData.getColumnName(i); + PropertyDescriptor propertyDescriptor = this.colPropertyMap.get(colName); + if (propertyDescriptor != null) { + Method setter = propertyDescriptor.getWriteMethod(); + if (setter != null) { + Class propertyType = propertyDescriptor.getPropertyType(); + setter.setAccessible(true); // NOSONAR + setter.invoke(newInstance, rs.getObject(colName, propertyType)); + } + } + } + return newInstance; + } + catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { + throw new SQLException(e); + } + } +} diff --git a/src/main/java/xyz/zhouxy/jdbc/ResultMap.java b/src/main/java/xyz/zhouxy/jdbc/ResultMap.java index f7e6f7e..800ff82 100644 --- a/src/main/java/xyz/zhouxy/jdbc/ResultMap.java +++ b/src/main/java/xyz/zhouxy/jdbc/ResultMap.java @@ -37,7 +37,6 @@ public interface ResultMap { return result; }; - public static final ResultMap recordResultMap = (rs, rowNumber) -> { DbRecord result = new DbRecord(); ResultSetMetaData metaData = rs.getMetaData(); @@ -48,4 +47,13 @@ public interface ResultMap { } return result; }; + + public static ResultMap beanResultMap(Class beanType) throws SQLException { + return DefaultBeanResultMap.of(beanType); + } + + public static ResultMap beanResultMap(Class beanType, Map propertyColMap) + throws SQLException { + return DefaultBeanResultMap.of(beanType, propertyColMap); + } } diff --git a/src/test/java/xyz/zhouxy/jdbc/SimpleJdbcTemplateTests.java b/src/test/java/xyz/zhouxy/jdbc/test/SimpleJdbcTemplateTests.java similarity index 82% rename from src/test/java/xyz/zhouxy/jdbc/SimpleJdbcTemplateTests.java rename to src/test/java/xyz/zhouxy/jdbc/test/SimpleJdbcTemplateTests.java index 73bc490..a50436d 100644 --- a/src/test/java/xyz/zhouxy/jdbc/SimpleJdbcTemplateTests.java +++ b/src/test/java/xyz/zhouxy/jdbc/test/SimpleJdbcTemplateTests.java @@ -1,4 +1,4 @@ -package xyz.zhouxy.jdbc; +package xyz.zhouxy.jdbc.test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static xyz.zhouxy.jdbc.ParamBuilder.*; import static xyz.zhouxy.plusone.commons.sql.JdbcSql.IN; +import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.time.LocalDate; import java.time.LocalDateTime; @@ -21,10 +22,15 @@ import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; +import xyz.zhouxy.jdbc.DbRecord; +import xyz.zhouxy.jdbc.DefaultBeanResultMap; +import xyz.zhouxy.jdbc.ResultMap; +import xyz.zhouxy.jdbc.SimpleJdbcTemplate; import xyz.zhouxy.jdbc.SimpleJdbcTemplate.JdbcExecutor; import xyz.zhouxy.plusone.commons.sql.SQL; import xyz.zhouxy.plusone.commons.util.ArrayTools; @@ -53,20 +59,19 @@ class SimpleJdbcTemplateTests { @Test void testQuery() throws SQLException { - Object[] ids = buildParams("501533", "501554", "544599"); + Object[] ids = buildParams(22915, 22916, 22917, 22918, 22919, 22920, 22921); String sql = SQL.newJdbcSql() .SELECT("*") .FROM("test_table") .WHERE(IN("id", ids)) .toString(); log.info(sql); - List rs = jdbcTemplate - .queryToRecordList(sql, ids); + List rs = jdbcTemplate.queryToRecordList(sql, ids); assertNotNull(rs); for (DbRecord baseEntity : rs) { // log.info("id: {}", baseEntity.getValueAsString("id")); // NOSONAR log.info(baseEntity.toString()); - assertEquals(Optional.empty(), baseEntity.getValueAsString("updated_by")); + assertTrue(baseEntity.getValueAsString("username").isPresent()); } } @@ -218,4 +223,58 @@ class SimpleJdbcTemplateTests { throw e; } } + + @Test + void testBean() throws Exception { + Optional t = jdbcTemplate.queryFirst( + "SELECT * FROM test_table WHERE id = ?", + buildParams(22915), + ResultMap.beanResultMap(TestBean.class, ImmutableMap.of("usageDate", "usage_date", "usageDuration", "usage_duration"))); + log.info("t: {}", t); + } +} + +class TestBean { + Long id; + String username; + LocalDate usageDate; + Long usageDuration; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public LocalDate getUsageDate() { + return usageDate; + } + + public void setUsageDate(LocalDate usageDate) { + this.usageDate = usageDate; + } + + public Long getUsageDuration() { + return usageDuration; + } + + public void setUsageDuration(Long usageDuration) { + this.usageDuration = usageDuration; + } + + @Override + public String toString() { + return "TestBean [id=" + id + ", username=" + username + ", usageDate=" + usageDate + ", usageDuration=" + + usageDuration + "]"; + } }