javacsvopencsv

How to write java objet with superclass to csv file?


I have class Order, witch extends drom superclass BaseEntity with field id. Order object also contains User filed, witch is also extends from BaseEntity. But, when i write Order object to file, it's id field from BaseEntity writes correctly, but id of User field not recorded. There are my classes and method to write them into file: Order:

@Data
@EqualsAndHashCode(callSuper = true)
@SuperBuilder
@NoArgsConstructor
@Entity
@Table(name = "orders")
public class Order extends BaseEntity {
    @Column(name = "price")
    @CsvBindByPosition(position = 1)
    private Float price;

    @Column(name = "date")
    @CsvBindByName
    @CsvBindByPosition(position = 2)
    private LocalDate date;

    @ManyToOne(optional = false)
    @JoinColumn(name = "user_id", nullable = false)
    @CsvBindByName
    @CsvBindByPosition(position = 3)
    private User user;

    @Column(name = "cc_number")
    @CsvBindByPosition(position = 5)
    private String creditCardNumber;

    @Column(name = "shipping_type")
    @CsvBindByPosition(position = 6)
    private String shippingType;

    @Column(name = "shipping_cost")
    @CsvBindByPosition(position = 7)
    private Float shippingCost;

    @Column(name = "code")
    @CsvBindByPosition(position = 8)
    private String code;

    @Column(name = "address")
    @CsvBindByPosition(position = 9)
    private String address;

    @Column(name = "customer_notes")
    @CsvBindByPosition(position = 10)
    private String customerNotes;
}

User:

@Data
@EqualsAndHashCode(callSuper = true)
@SuperBuilder
@NoArgsConstructor
@Entity
@Table(name = "users")
public class User extends BaseEntity{

    @NotNull(message = "Field is null validation error")
    @Email(message = "Field does not satisfy regexp")
    @Column(name = "mail", unique = true)
    @CsvBindByPosition(position = 1)
    private String mail;

    @NotNull(message = "Field is null validation error")
    @Pattern(regexp = "^(?=.*[0-9])(?=.*[!@#$%^&*])[a-zA-Z0-9!@#$%^&*]{6,16}$", message = "Field does not satisfy regexp")
    @Column(name = "password")
    @CsvBindByPosition(position = 2)
    private String password;

    @NotNull(message = "Field is null validation error")
    @Pattern(regexp = "[a-zA-Z ,.'-]+", message = "Field does not satisfy regexp")
    @Column(name = "name")
    @CsvBindByPosition(position = 3)
    private String name;

    @NotNull(message = "Field is null validation error")
    @Pattern(regexp = "[a-zA-Z ,.'-]+", message = "Field does not satisfy regexp")
    @Column(name = "surname")
    @CsvBindByPosition(position = 4)
    private String surname;

    @NotNull(message = "Field is null validation error")
    @Column(name = "date")
    @CsvBindByPosition(position = 5)
    private LocalDate date;

    @Column(name = "balance")
    @CsvBindByPosition(position = 6)
    private Float currentBalance;

    @Column(name = "mobile")
    @CsvBindByPosition(position = 7)
    private String mobile;

    @Column(name = "street")
    @CsvBindByPosition(position = 8)
    private String street;

    @Column(name = "accommodation_number")
    @CsvBindByPosition(position = 9)
    private String accommodationNumber;

    @Column(name = "flat_number")
    @CsvBindByPosition(position = 10)
    private String flatNumber;

    @OneToMany(mappedBy = "user", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    private List<Order> orders;
}

BaseEntity:

@Data
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@MappedSuperclass
public class BaseEntity {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @CsvBindByPosition(position = 0)
    protected Integer id;
}

Method to write file:

    public ModelAndView exportUserOrders(User user) throws CSVExportException {
        try (Writer writer  = new FileWriter(FileSystemView.getFileSystemView().getHomeDirectory().getAbsolutePath() + "/orders.csv")) {
            StatefulBeanToCsv<Order> sbc = new StatefulBeanToCsvBuilder<Order>(writer)
                    .withSeparator(CSVWriter.DEFAULT_SEPARATOR)
                    .build();
            sbc.write(user.getOrders());
        } catch (IOException | CsvRequiredFieldEmptyException | CsvDataTypeMismatchException e) {
            throw new CSVExportException(EshopConstants.errorOrdersExportMessage, PagesPathEnum.ACCOUNT_PAGE.getPath());
        }
        ModelMap model = new ModelMap();
        model.addAttribute("successfulMessage", EshopConstants.successfulExportMessage);
        return new ModelAndView(PagesPathEnum.ACCOUNT_PAGE.getPath(), model);
    }
}

As a result i get something like this: "1","800.0","2023-08-07","User(mail=user1@gmail.com, password=hello1@, name=Ivan, surname=Hello, date=2023-08-01, currentBalance=0.0, mobile=+777777777, street=Rokossovskogo, accommodationNumber=44, flatNumber=777)","11111 **** **** 1111","Delivery by courier","10.0","code","Rokossovskogo","Notes"

And i expect "1","800.0","2023-08-07","User(id = 1, mail=user1@gmail.com, password=hello1@, name=Ivan, surname=Hello, date=2023-08-01, currentBalance=0.0, mobile=+777777777, street=Rokossovskogo, accommodationNumber=44, flatNumber=777)","11111 **** **** 1111","Delivery by courier","10.0","code","Rokossovskogo","Notes"


Solution

  • I don't know how to do it properly with opencsv.

    But I would suggest you to use DTO's (Data Transer Object) here instead of using Entity directly to write it into CSV. So you can choose which fields you need from the entity and map them to your DTOs and then write them to CSV.

    Here is an example of how this can be done:

    Create OrderDto:

    @Data
    public class OrderDto {
        @CsvBindByPosition(position = 0)
        private Integer id;
    
        @CsvBindByPosition(position = 1)
        private Float price;
    
        @CsvBindByPosition(position = 2)
        private LocalDate date;
    
        @CsvBindByPosition(position = 3)
        private UserDto user;
    
        @CsvBindByPosition(position = 4)
        private String creditCardNumber;
    
        @CsvBindByPosition(position = 5)
        private String shippingType;
    
        @CsvBindByPosition(position = 6)
        private Float shippingCost;
    
        @CsvBindByPosition(position = 7)
        private String code;
    }
    

    Create UserDto:

    @Data
    public class UserDto {
        @CsvBindByPosition(position = 0)
        private Integer id;
    
        @CsvBindByPosition(position = 1)
        private String mail;
    
        @CsvBindByPosition(position = 2)
        private String password;
    
        @CsvBindByPosition(position = 3)
        private String name;
    
        @CsvBindByPosition(position = 4)
        private String surname;
    
        @CsvBindByPosition(position = 5)
        private LocalDate date;
    
        @CsvBindByPosition(position = 6)
        private Float currentBalance;
    
        @CsvBindByPosition(position = 7)
        private String mobile;
    
        @CsvBindByPosition(position = 8)
        private String street;
    
        @CsvBindByPosition(position = 9)
        private String accommodationNumber;
    
        @CsvBindByPosition(position = 10)
        private String flatNumber;
    }
    

    Create OrderDtoMapper:

    import org.modelmapper.ModelMapper;
    
    public class OrderDtoMapper {
       private final ModelMapper modelMapper;
    
        public OrderDtoMapper() {
            this.modelMapper = new ModelMapper();
            // Specifying how to map an id field from a BaseEntity to DTO's
            this.modelMapper.typeMap(Order.class, OrderDto.class).addMapping(BaseEntity::getId, OrderDto::setId);
            this.modelMapper.typeMap(User.class, UserDto.class).addMapping(BaseEntity::getId, UserDto::setId);
        }
       public OrderDto mapOrder(Order order) {
           return modelMapper.map(order, OrderDto.class);
       }
    }
    

    In order to use ModdelMapper you will need to add dependency to yout project. Here is link to mvnrepository https://mvnrepository.com/artifact/org.modelmapper/modelmapper/3.1.1

    And also an example of using modelMapper to map User.orders to OrderDtos:

            List<OrderDto> orderDtos = user.getOrders().stream().map(order -> mapper.mapOrder(order)).collect(Collectors.toList());
    
    

    So your file writing method would look like this:

     public ModelAndView exportUserOrders(List<OrderDto> orderDtos) throws CSVExportException {
            try (Writer writer  = new FileWriter(FileSystemView.getFileSystemView().getHomeDirectory().getAbsolutePath() + "/orders.csv")) {
                StatefulBeanToCsv<OrderDto> sbc = new StatefulBeanToCsvBuilder<OrderDto>(writer)
                        .withSeparator(CSVWriter.DEFAULT_SEPARATOR)
                        .build();
                sbc.write(orderDtos);
            } catch (IOException | CsvRequiredFieldEmptyException | CsvDataTypeMismatchException e) {
                throw new CSVExportException(EshopConstants.errorOrdersExportMessage, PagesPathEnum.ACCOUNT_PAGE.getPath());
            }
            ModelMap model = new ModelMap();
            model.addAttribute("successfulMessage", EshopConstants.successfulExportMessage);
            return new ModelAndView(PagesPathEnum.ACCOUNT_PAGE.getPath(), model);
        }
    

    This is what I got in result:

    1,800.0,2023-08-07,"UserDto(id=1, mail=user1@gmail.com, password=hello1@, name=Ivan, surname=Hello, date=2023-08-01, currentBalance=0.0, mobile=+777777777, street=Rokossovskogo, accommodationNumber=44, flatNumber=777)",11111 **** **** 1111,Delivery by courier,10.0,code
    

    I hope this helped you and I didn't miss anything.