如何将数据库行反序列化为DTO?PostgreSQL数组转换异常解决
问题:PostgreSQL数组类型无法通过BeanPropertyRowMapper转换为ArrayList
背景代码
DAO方法
// 原方法:传入DTO类使用BeanPropertyRowMapper public <T> List selectObjectList(String sql, Object[] args, Class<T> dto) { return jdbcTemplate.query( sql, args, new BeanPropertyRowMapper(dto) ); } // 已支持的重载方法:允许传入自定义RowMapper @Override public <T> List<T> selectObjectList(String sql, Object[] args, RowMapper<T> mapper) { return jdbcTemplate.query( sql, args, mapper ); }
DTO定义
@Data @NoArgsConstructor public class SimpleDto{ private Integer id; private Date createDate; // ...其他字段 private ArrayList<String> recipents; private ArrayList<Integer> objects; // ...其他字段 }
数据库表结构
create table notification ( id SERIAL PRIMARY KEY, create_date timestamp not null, recipents varchar ARRAY, objects integer ARRAY );
执行代码与异常
执行代码:
ArrayList<Notification> notifications = (ArrayList<Notification>) comDao.selectObjectList(sql, args, SimpleDto.class);
抛出异常:
Caused by: org.springframework.beans.ConversionNotSupportedException:
Failed to convert property value of type 'org.postgresql.jdbc.PgArray'
to required type 'java.util.ArrayList' for property 'recipents';
nested exception is java.lang.IllegalStateException: Cannot convert
value of type 'org.postgresql.jdbc.PgArray' to required type
'java.util.ArrayList' for property 'recipents': no matching editors or
conversion strategy found
解决方案(符合DRY原则)
方案1:自定义通用增强版RowMapper
继承BeanPropertyRowMapper,扩展数组转集合的逻辑,无需为每个DTO单独编写RowMapper:
public class EnhancedBeanPropertyRowMapper<T> extends BeanPropertyRowMapper<T> { public EnhancedBeanPropertyRowMapper(Class<T> mappedClass) { super(mappedClass); } public static <T> EnhancedBeanPropertyRowMapper<T> newInstance(Class<T> mappedClass) { return new EnhancedBeanPropertyRowMapper<>(mappedClass); } @Override public T mapRow(ResultSet rs, int rowNumber) throws SQLException { // 先调用父类完成基础字段映射 T dto = super.mapRow(rs, rowNumber); // 遍历DTO所有字段,处理ArrayList类型的属性 Field[] fields = dto.getClass().getDeclaredFields(); for (Field field : fields) { if (ArrayList.class.isAssignableFrom(field.getType())) { field.setAccessible(true); // 驼峰转下划线,匹配数据库列名 String columnName = CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName()); PgArray pgArray = (PgArray) rs.getObject(columnName); if (pgArray != null) { try { Object[] array = (Object[]) pgArray.getArray(); ArrayList<?> list = new ArrayList<>(Arrays.asList(array)); field.set(dto, list); } catch (Exception e) { throw new SQLException("转换数组字段失败: " + field.getName(), e); } } } } return dto; } }
使用方式:
- 修改原DAO方法,替换为增强版Mapper:
public <T> List<T> selectObjectList(String sql, Object[] args, Class<T> dto) { return jdbcTemplate.query( sql, args, EnhancedBeanPropertyRowMapper.newInstance(dto) ); }
- 或直接调用重载方法传入:
ArrayList<SimpleDto> notifications = (ArrayList<SimpleDto>) comDao.selectObjectList(sql, args, EnhancedBeanPropertyRowMapper.newInstance(SimpleDto.class));
方案2:注册全局类型转换器
通过Spring的类型转换机制,全局支持PgArray到ArrayList的转换,无需修改DAO逻辑:
1. 实现类型转换器
public class PgArrayToArrayListConverter implements Converter<PgArray, ArrayList<?>> { @Override public ArrayList<?> convert(PgArray source) { try { Object[] array = (Object[]) source.getArray(); return new ArrayList<>(Arrays.asList(array)); } catch (SQLException e) { throw new IllegalArgumentException("无法转换PgArray为ArrayList", e); } } }
2. 注册转换器(Spring Boot环境)
@Configuration public class ConverterConfig { @Bean public ConversionService conversionService() { DefaultConversionService conversionService = new DefaultConversionService(); conversionService.addConverter(new PgArrayToArrayListConverter()); return conversionService; } }
3. 关联到BeanPropertyRowMapper
如果原DAO方法没有自动注入ConversionService,需手动设置:
public <T> List<T> selectObjectList(String sql, Object[] args, Class<T> dto) { BeanPropertyRowMapper<T> mapper = new BeanPropertyRowMapper<>(dto); mapper.setConversionService(conversionService()); // 注入自定义的转换服务 return jdbcTemplate.query(sql, args, mapper); }
方案对比
- 方案1:轻量直接,无需全局配置,适合只需要处理部分DTO数组字段的场景;
- 方案2:全局生效,符合DRY原则,适合项目中有大量DTO需要处理数组类型的场景。
内容的提问来源于stack exchange,提问作者Семен Немытов
相关产品推荐
相关产品推荐

