/* * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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; import com.google.common.base.CaseFormat; import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod; /** * DefaultBeanRowMapper * *

* 默认实现的将 {@link ResultSet} 转换为 Java Bean 的 {@link RowMapper}。 *

* *

* NOTE: 使用反射获取类型信息,也是使用反射调用无参构造器和 {@code setter} 方法。 * 实际使用中还是建议针对目标类型自定义 {@link RowMapper}。 *

* @author ZhouXY * @since 1.0.0 */ public class DefaultBeanRowMapper implements RowMapper { /** Bean 的无参构造器 */ private final Constructor constructor; /** 列名到属性的映射 */ private final Map colPropertyMap; private DefaultBeanRowMapper(Constructor constructor, Map colPropertyMap) { this.constructor = constructor; this.colPropertyMap = colPropertyMap; } /** * 创建一个 {@code DefaultBeanRowMapper} * * @param Bean 类型 * @param beanType Bean 类型 * @return DefaultBeanRowMapper 对象 * @throws SQLException 创建 {@code DefaultBeanRowMapper} 出现错误的异常时抛出 */ @StaticFactoryMethod(DefaultBeanRowMapper.class) public static DefaultBeanRowMapper of(Class beanType) throws SQLException { return of(beanType, null); } /** * 创建一个 {@code DefaultBeanRowMapper} * * @param Bean 类型 * @param beanType Bean 类型 * @param propertyColMap Bean 字段与列名的映射关系。key 是字段,value 是列名。 * @return {@code DefaultBeanRowMapper} 对象 * @throws SQLException 创建 {@code DefaultBeanRowMapper} 出现错误的异常时抛出 */ @StaticFactoryMethod(DefaultBeanRowMapper.class) public static DefaultBeanRowMapper of(Class beanType, @Nullable Map propertyColMap) throws SQLException { try { // 获取无参构造器 Constructor constructor = beanType.getDeclaredConstructor(); constructor.setAccessible(true); // NOSONAR final Map colPropertyMap = buildColPropertyMap(beanType, propertyColMap); return new DefaultBeanRowMapper<>(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); } } /** {@inheritDoc} */ @Override public T mapRow(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); // 获取查询结果列名对应的属性,调用 setter 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); } } /** * 构建 column name 和 PropertyDescriptor 的 映射 * * @param Java bean 类型 * @param beanType Java bean 类型 * @param propertyColMap 属性与列名的映射 * @return column name 和 PropertyDescriptor 的映射 * @throws IntrospectionException if an exception occurs during introspection. */ private static Map buildColPropertyMap( Class beanType, Map propertyColMap) throws IntrospectionException { BeanInfo beanInfo = Introspector.getBeanInfo(beanType); PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); // Bean 的属性名为小驼峰,对应的列名为下划线 Function keyMapper; if (propertyColMap == null || propertyColMap.isEmpty()) { keyMapper = p -> CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, p.getName()); } else { keyMapper = p -> { String propertyName = p.getName(); String colName = propertyColMap.get(propertyName); return colName != null ? colName : CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, propertyName); }; } return Arrays.stream(propertyDescriptors) .collect(Collectors.toMap(keyMapper, Function.identity(), (a, b) -> b)); } }