简体   繁体   English

在spring-mvc控制器中找到由帖子修改的内容的正确方法?

[英]Correct way of finding what was modified by a post in an spring-mvc controller?

It is a rather general question, but I will give a stripped down example. 这是一个相当普遍的问题,但我会给出一个简单的例子。 Say I have a Web CRUD application that manages simple entities stored in a database, nothing but classic : JSP view, RequestMapping annotated controller, transactional service layer and DAO. 假设我有一个Web CRUD应用程序来管理存储在数据库中的简单实体,只有经典:JSP视图,RequestMapping带注释的控制器,事务服务层和DAO。
On an update, I need to know the previous values of my fields, because a business rule asks a for a test involving the old and new values. 在更新时,我需要知道我的字段的先前值,因为业务规则要求进行涉及旧值和新值的测试。

So I am searching for a best practice on that use case. 所以我正在寻找关于该用例的最佳实践。

I thing that spring code is way more extensively tested and more robust than my own, and I would like to do it the spring way as much as possible. 我认为弹簧代码的测试方式比我自己的更广泛,并且我希望尽可能地采用弹簧方式

Here is what I have tried : 这是我尝试过的:

1/ load an empty object in controller and manage the update in service : 1 /在控制器中加载一个空对象并管理服务中的更新:
Data.java: Data.java:

class Data {
    int id; // primary key
    String name;
    // ... other fields, getters, and setters omitted for brevity
}

DataController DataController类

...
@RequestMapping("/data/edit/{id}", method=RequestMethod.GET)
public String edit(@PathVariable("id") int id, Model model) {
    model.setAttribute("data", service.getData(id);
    return "/data/edit";
}
@RequestMapping("/data/edit/{id}", method=RequestMethod.POST)
public String update(@PathVariable("id") int id, @ModelAttribute Data data, BindingResult result) {
    // binding result tests omitted ..
    service.update(id, data)
    return "redirect:/data/show";
}

DataService DataService的

@Transactional
public void update(int id, Data form) {
    Data data = dataDao.find(id);
    // ok I have old values in data and new values in form -> do tests stuff ...
    // and MANUALLY copy fields from form to data
    data.setName(form.getName);
    ...
}

It works fine, but in real case, if I have many domain objects and many fields in each, it is quite easy to forget one ... when spring WebDataBinder has done it including validation in the controller without I have to write any single thing other than @ModelAttribute ! 它工作正常,但在实际情况下,如果我有很多域对象和每个域中的许多字段,很容易忘记一个...当Spring WebDataBinder完成它包括在控制器中进行验证而不必编写任何单个事物除了@ModelAttribute

2/ I tried to preload the Data from the database by declaring a Converter 2 /我试图通过声明一个转换器从数据库中预加载数据
DataConverter DataConverter

public class DataConverter<String, Data> {
    Data convert(String strid) {
        return dataService.getId(Integer.valueOf(strid));
    }
}

Absolutely magic ! 绝对神奇! The data if fully initialized from database and fields present in form are properly updated. 如果从数据库和表单中存在的字段完全初始化的data已正确更新。 But ... no way to get the previous values ... 但是......没办法得到以前的价值......

So my question is : what could be the way to use spring DataBinder magic and to have access to previous values of my domain objects ? 所以我的问题是:使用spring DataBinder魔术访问我的域对象的先前值的方法是什么?

You have already found the possible choices so i will just add some ideas here ;) 你已经找到了可能的选择,所以我只想在这里添加一些想法;)

I will start with your option of using a empty bean and copying the values over to a loaded instance: 我将从您选择使用空bean并将值复制到已加载的实例开始:

As you have shown in your example it's an easy approach. 正如您在示例中所示,这是一种简单的方法。 It's quite easily adaptable to create a generalized solution. 它很容易适应创建通用解决方案。

You do not need to copy the properties manually! 您无需手动复制属性! Take a look at the 'BeanWrapperImpl' class. 看看'BeanWrapperImpl'类。 This spring object allows you to copy properties and is in fact the one used by Spring itself to achieve it's magic. 这个spring对象允许你复制属性,实际上是Spring本身使用它来实现它的魔力。 It's used by the 'ParameterResolvers' for example. 例如,它由'ParameterResolvers'使用。

So copying properties is the easy part. 所以复制属性很容易。 Clone the loaded object, fill the loaded object and compare them somehow. 克隆加载的对象,填充加载的对象并以某种方式比较它们。

If you have one service or just several this is the way to go. 如果您有一项服务或只有几项服务,这是可行的方法。

In my case we needed this feature on each entity. 就我而言,我们需要在每个实体上使用此功能。 Using Hibernate we have the issue that an entity might not only change inside a specific service call, but theoretically all over the place.. 使用Hibernate我们遇到的问题是,一个实体不仅可以在特定的服务调用内部进行更改,而且理论上可以在整个地方进行更改。

So I decided to create a 'MappedSuperClass' which all entities need to extend. 所以我决定创建一个'MappedSuperClass',所有实体都需要扩展它。 This entity has a 'PostLoad' event listener which clones the entity in a transient field directly after loading. 该实体具有“PostLoad”事件侦听器,可在加载后直接克隆瞬态字段中的实体。 (This works if you don't have to load thousands of entities in a request.) Then you need also the 'PostPersist' and 'PostUpdate' listeners to clone the new state again as you probably don't reload the entity before another modification. (如果您不必在请求中加载数千个实体,则此方法有效。)然后您还需要“PostPersist”和“PostUpdate”侦听器再次克隆新状态,因为您可能在另一次修改之前不重新加载实体。

To facilitate the controller mapping I have implemented a 'StringToEntityConverter' doing exactly what you did, just generalized to support any entity type. 为了方便控制​​器映射,我实现了一个'StringToEntityConverter'来完成你所做的事情,只是为了支持任何实体类型而推广。

Finding the changes in a generalized approach will involve quite a bit of reflection. 在一般化方法中找到变化将涉及相当多的反思。 It's not that hard and I don't have the code available right now, but you can also use the 'BeanWrapper' for that: 它并不难,我现在没有可用的代码,但您也可以使用'BeanWrapper':

Create a wrapper for both objects. 为两个对象创建包装器。 Get all 'PropertyDescriptors' and compare the results. 获取所有'PropertyDescriptors'并比较结果。 The hardest part is to find out when to stop. 最难的部分是找出何时停止。 Compare only the first level or do you need deep comparison? 仅比较第一级或您是否需要深入比较?

One other solution could also be to rely on Hibernate Envers. 另一个解决方案也可能是依赖Hibernate Envers。 This would work if you do not need the changes during the same transaction. 如果您在同一事务中不需要更改,这将起作用。 As Envers tracks the changes during a flush and creates a 'Revision' you can "simply" fetch twp revisions and compare them. 当Envers在刷新期间跟踪更改并创建“修订版”时,您可以“简单地”获取twp修订并进行比较。

In all scenarios you will have to write a comparison code. 在所有情况下,您都必须编写比较代码。 I'm not aware of a library but probably there is something around in the java world :) 我不知道一个库,但可能在java世界中有一些东西:)

Hope that helps a bit. 希望有点帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM