我正在使用以下 JPA 实体:
@Entity @Table(name = "FOO")
public class Foo {
@Id @Column(name = "ID")
private Integer id;
@Column(name = "NAME")
private String name;
// getters/setters ommited for brevity
}
我有一个服务类,它执行一些验证并保留此类的实例(假设 _entityManager 是每个请求一个):
public static Foo save( Foo f ) {
if ( f.getName().length() > 4 )
throw new RuntimeException("Name is too big");
_entityManager.persist(f);
return f;
}
调用服务按预期工作:
// works
_entityManager.getTransaction().begin();
save( new Foo(1, "One") );
_entityManager.getTransaction().commit();
// fails
_entityManager.getTransaction().begin();
save( new Foo(2, "Large") );
_entityManager.getTransaction().commit();
但是,它是一个 JPA 特性,可以更改为传递到数据库的对象(在事务结束时,可能更早)。因此,我可以写:
_entityManager.getTransaction().begin();
Foo f = FooService.save( new Foo( 1, "One" ) );
Foo g = _entityManager.find(Foo.class, 1);
g.setName("Large");
_entityManager.getTransaction().commit();
问题在于,通过在服务实现之外修改对象,开发人员可能会无意中绕过所有验证。
我知道如果您的数据库有约束,或者如果您在 Foo 上有验证输入的注释,这不是一个问题。但是,如果存在不可数据库执行的约束怎么办?或者需要外部信息并且可能不写为注释?
在我的项目中,我们一直在使用经验法则:每个返回 Entity 的公共服务都必须首先 detach() 它。这样,更改将不会被保留。然而,这使得服务组合更加困难。例如:
Foo f = FooService.findById(1); // returns detached instance
Bar b = new Bar();
f.getBars().add(b);
b.setFoo(f);
由于返回的 Foo 是分离的,添加到它的 Bars 列表对真正的实体管理的 Foo 没有任何作用。因此,尝试(例如) delete(f) 将失败,因为实体管理器甚至不知道有子实例要先删除。
有没有人遇到过这种情况?有没有关于这个主题的最佳实践?