这是一个相当普遍的问题,但我将给出一个简单的例子。假设我有一个Web CRUD应用程序,它管理存储在数据库中的简单实体,只不过是经典的:JSP视图、RequestMapping注释控制器、事务服务层和DAO。在更新时,我需要知道字段的先前值,因为业务规则要求a进行涉及旧值和新值的测试。
所以我正在寻找这个用例的最佳实践。
我认为spring代码比我自己的代码测试得更广泛,更健壮,我希望尽可能用spring的方式。
这是我尝试过的:
1/在controller中加载一个空对象,并在service中管理更新:
Data.java:
class Data {
int id; // primary key
String name;
// ... other fields, getters, and setters omitted for brevity
}
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
@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);
...
}
它工作得很好,但在实际情况下,如果我有许多领域对象和许多字段在每个,很容易忘记一个…当spring WebDataBinder
完成它时,包括控制器中的验证,而我不必编写除@ModelAttribute
以外的任何单一内容!
2/我试图通过声明一个Converter
从数据库预加载数据DataConverter
public class DataConverter<String, Data> {
Data convert(String strid) {
return dataService.getId(Integer.valueOf(strid));
}
}
太神奇了!data
如果从数据库中完全初始化,并且表单中存在的字段被正确更新。但是…没有办法得到之前的值…
所以我的问题是:使用spring DataBinder魔术和来访问我的域对象的先前值的方法是什么?
你已经找到了可能的选择,所以我只是在这里添加一些想法;)
我将从使用空bean并将值复制到加载实例的选项开始:
如您在示例中所示,这是一种简单的方法。创建通用解决方案非常容易。您不需要手动复制属性!看一下'BeanWrapperImpl'类。这个spring对象允许您复制属性,实际上它是spring本身用来实现其魔力的对象。例如,它被ParameterResolvers使用。
所以复制属性是简单的部分。克隆加载的对象,填充加载的对象,并以某种方式比较它们。
如果你有一个或几个服务,这是你要走的路。
在我的例子中,我们需要在每个实体上都有这个功能。使用Hibernate时,我们会遇到这样的问题:一个实体可能不仅在一个特定的服务调用中发生了变化,而且理论上在所有地方都可能发生变化。
所以我决定创建一个'MappedSuperClass',所有实体都需要扩展。这个实体有一个'PostLoad'事件监听器,它在加载后直接在一个瞬态字段中克隆实体。(如果您不需要在请求中加载数千个实体,则可以使用此方法。)然后你还需要'PostPersist'和'PostUpdate'监听器来再次克隆新状态,因为你可能不会在另一次修改之前重新加载实体。
为了方便控制器映射,我已经实现了一个'StringToEntityConverter'做你所做的,只是广义地支持任何实体类型。
在一般化的方法中发现变化将涉及相当多的反思。这并不难,我现在没有可用的代码,但你也可以使用'BeanWrapper':
为两个对象创建包装器。获取所有的"PropertyDescriptors"并比较结果。最难的部分是知道什么时候该停下来。只比较第一级还是需要更深入的比较?
另一个解决方案可能是依赖Hibernate enver。如果在同一事务期间不需要更改,则可以使用此方法。当Envers在刷新期间跟踪更改并创建"修订"时,您可以"简单地"获取twp修订并比较它们。
在所有情况下,您都必须编写比较代码。我不知道有一个库,但可能在java世界里有一些东西:)
希望对你有帮助。