You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将数据库行反序列化为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,提问作者Семен Немытов

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 13:24:29