Java 转换&;在Spring MVC中验证CSV文件上载

Java 转换&;在Spring MVC中验证CSV文件上载,java,validation,spring-mvc,csv,Java,Validation,Spring Mvc,Csv,我有一个包含站点列表的客户实体,如下所示: public class Customer { @Id @GeneratedValue private int id; @NotNull private String name; @NotNull @AccountNumber private String accountNumber; @Valid @OneToMany(mappedBy="customer")

我有一个包含站点列表的客户实体,如下所示:

public class Customer {

    @Id
    @GeneratedValue
    private int id;

    @NotNull
    private String name;

    @NotNull
    @AccountNumber
    private String accountNumber;

    @Valid
    @OneToMany(mappedBy="customer")
    private List<Site> sites
}

public class Site {

    @Id
    @GeneratedValue
    private int id;

    @NotNull
    private String addressLine1;

    private String addressLine2;

    @NotNull
    private String town;

    @PostCode
    private String postCode;

    @ManyToOne
    @JoinColumn(name="customer_id")
    private Customer customer;
}
我的MVC配置将文件上载限制为1MB:

@Bean
public MultipartResolver multipartResolver() {
    CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
    multipartResolver.setMaxUploadSize(1000000);
    return multipartResolver;
}

是否有一种spring方式可以同时转换和验证,而不必打开CSV文件并循环两次,一次用于验证,另一次用于实际读取/填充数据?

IMHO,将整个CSV加载到内存中是个坏主意,除非:

  • 你肯定它总是非常小(如果用户点击了错误的文件怎么办?)
  • 验证是全局的(只有真实的用例,但这里似乎没有)
  • 您的应用程序永远不会在负载严重的生产环境中使用
如果您不想将业务类绑定到Spring,那么您应该坚持使用
MultipartFile
对象,或者使用一个包装器公开
InputStream
(以及最终可能需要的其他信息)

然后仔细设计、编码和测试一个方法,将InputStream作为输入,逐行读取并调用逐行方法来验证和插入数据。差不多

class CsvLoader {
@Autowired Verifier verifier;
@Autowired Loader loader;

    void verifAndLoad(InputStream csv) {
        // loop through csv
        if (verifier.verify(myObj)) {
            loader.load(myObj);
        }
        else {
            // log the problem eventually store the line for further analysis
        }
        csv.close();
    }
}
这样,应用程序只使用它真正需要的内存,只在文件中循环一次

编辑:我所说的包装Spring MultipartFile的精确性

首先,我将验证分为两部分。正式验证在控制器层,仅控制以下各项:

  • 有一个客户字段
  • 文件大小和mimetype似乎正常(例如:size>12&&mimetype=text/csv)
内容验证是一种业务层验证,可以在以后进行。在此模式中,
SiteCSVFileValidator
只测试csv的mimetype和大小

通常,您避免直接从业务类使用Spring类。如果没有问题,控制器直接将MultipartFile发送到服务对象,同时传递BindingResult以直接填充最终的错误消息。控制器变为:

@RequestMapping(value="/new", method = RequestMethod.POST)
public String newCustomer(@Valid @ModelAttribute("customerForm") CustomerForm customerForm, BindingResult bindingResult) {

    if (bindingResult.hasErrors()) {
        return "NewCustomer"; // only external validation
    } else {

        /* 
           validation has passed, so now we must:
           1) open customerForm.csvFile 
           2) loop through it to validate each line and populate customerForm.customer.sites 
        */

        customerService.insert(customerForm.customer, customerForm.csvFile, bindingResult);
        if (bindingResult.hasErrors()) {
            return "NewCustomer"; // only external validation
        } else {
            return "CustomerList";
        }
    }
}
在服务课上我们有

insert(Customer customer, MultipartFile csvFile, Errors errors) {
    // loop through csvFile.getInputStream populating customer.sites and eventually adding Errors to errors
    if (! errors.hasErrors) {
        // actually insert through DAO
    }
}
但是我们在服务层的方法中得到了两个Spring类。如果有问题,只需替换行
customerService.insert(customerForm.customer、customerForm.csvFile、bindingResult)带有:

List<Integer> linesInError = new ArrayList<Integer>();
customerService.insert(customerForm.customer, customerForm.csvFile.getInputStream(), linesInError);
if (! linesInError.isEmpty()) {
    // populates bindingResult with convenient error messages
}
打电话

customerService.insert(customerForm.customer, new CsvFile(customerForm.csvFile), linesInError);

没有直接的Spring依赖性

感谢您的反馈;我已经更新了我的原始问题,以显示我的MVC配置,该配置将文件上载限制为1MB(可能应该首先包括这个!)。CSV文件相对较小(平均5KB),因此双循环不应该引起问题,必须打开并读取文件两次似乎不是一个好的解决方案。你的CsvLoader回答让我感兴趣,但我不确定你所说的“使用包装器公开输入流”是什么意思-你能详细说明一下吗?(更多的示例代码将非常有用)。
insert(Customer customer, MultipartFile csvFile, Errors errors) {
    // loop through csvFile.getInputStream populating customer.sites and eventually adding Errors to errors
    if (! errors.hasErrors) {
        // actually insert through DAO
    }
}
List<Integer> linesInError = new ArrayList<Integer>();
customerService.insert(customerForm.customer, customerForm.csvFile.getInputStream(), linesInError);
if (! linesInError.isEmpty()) {
    // populates bindingResult with convenient error messages
}
class CsvFile {

    private String name;
    private InputStream inputStream;

    CsvFile(MultipartFile file) {
        name = file.getOriginalFilename();
        inputStream = file.getInputStream();
    }
    // public getters ...
}
customerService.insert(customerForm.customer, new CsvFile(customerForm.csvFile), linesInError);