如何使用OpenCSV将含嵌套Bean的对象映射为指定列的CSV?
解决OpenCSV导出嵌套List属性到指定CSV列的问题
我来帮你解决这个问题!你遇到的核心问题是OpenCSV的默认ColumnPositionMappingStrategy没法自动处理嵌套集合属性并提取其中的指定字段,同时你需要把一个Student对应的多本Book展开成CSV的多行记录。下面给你两种可行的解决方案,其中第一种更简单直观,推荐优先使用:
方案一:先将嵌套结构扁平化为DTO
这种方法的思路是先把Student和对应的Book组合成一个扁平化的DTO(数据传输对象),让每个DTO对应CSV中的一行记录,再用OpenCSV导出这个DTO列表。
1. 创建扁平化的DTO类
class FlatStudentDTO { @CsvBindByName(column = "s_id") private String sId; @CsvBindByName(column = "name") private String name; @CsvBindByName(column = "bookName") private String bookName; // 构造方法,用于从Student和Book生成DTO public FlatStudentDTO(Student student, Book book) { this.sId = student.getS_id(); this.name = student.getName(); this.bookName = book != null ? book.getBookName() : ""; } // 必须提供无参构造方法(OpenCSV需要) public FlatStudentDTO() {} // 生成getter和setter方法 public String getSId() { return sId; } public void setSId(String sId) { this.sId = sId; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } }
2. 转换原始数据为DTO列表
把你的Student列表转换成FlatStudentDTO列表,每个Book对应一个DTO记录:
List<FlatStudentDTO> flatStudentList = new ArrayList<>(); for (Student student : students) { List<Book> books = student.getBooks(); if (books == null || books.isEmpty()) { // 如果学生没有书,生成一条bookName为空的记录 flatStudentList.add(new FlatStudentDTO(student, null)); } else { // 每本书对应一条记录 for (Book book : books) { flatStudentList.add(new FlatStudentDTO(student, book)); } } }
3. 使用OpenCSV导出DTO
这时候用默认的映射策略就可以轻松导出了,不需要复杂的配置:
StatefulBeanToCsv<FlatStudentDTO> btcsv = new StatefulBeanToCsvBuilder<FlatStudentDTO>(writer) .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER) .withSeparator(';') .build(); btcsv.write(flatStudentList);
方案二:自定义MappingStrategy(进阶)
如果不想创建额外的DTO类,可以自定义MappingStrategy来处理嵌套集合的展开和字段提取。不过这种方法需要你手动处理每行数据的生成:
public class CustomStudentMappingStrategy extends ColumnPositionMappingStrategy<Student> { @Override public String[] generate(Student student) throws CsvRequiredFieldEmptyException { // 注意:OpenCSV的StatefulBeanToCsv默认一个Bean对应一行,所以如果要生成多行,需要先拆分Bean // 这里我们还是建议用DTO方案,因为自定义策略无法直接让一个Bean生成多行,需要配合其他扩展逻辑 // 如果你坚持用这种方式,可能需要自己遍历集合并手动写入CSV行,而不是依赖StatefulBeanToCsv的write方法 String[] row = new String[3]; row[0] = student.getS_id(); row[1] = student.getName(); // 这里只能取第一本书的名字,无法自动展开所有书,所以DTO方案更适合你的需求 row[2] = student.getBooks() != null && !student.getBooks().isEmpty() ? student.getBooks().get(0).getBookName() : ""; return row; } }
不过这种自定义策略只能取集合中的第一个元素,无法自动展开所有Book成多行,所以还是方案一的DTO方法更符合你的需求。
为什么你的原始代码不生效?
你的原始代码中,ColumnPositionMappingStrategy只能识别Student类的直接属性(s_id、name),对于嵌套的List<Book>属性,它无法自动遍历集合并提取bookName字段;而@CsvRecurse注解是用来递归导出整个Book对象的所有属性,不是提取单个字段,所以无法得到你想要的结果。
内容的提问来源于stack exchange,提问作者SatyaKinkar Gorai
相关产品推荐
相关产品推荐

