4

我正在使用以下 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) 将失败,因为实体管理器甚至不知道有子实例要先删除。

有没有人遇到过这种情况?有没有关于这个主题的最佳实践?

4

0 回答 0