JPA - “版本化”实体,需要设计建议

时间:2011-03-24 22:21:00

标签: java hibernate jpa

想象一下以下两个实体。 Element是包含一些数据的简单类:

@Entity
public class Element {
    private String data;

    public String getData() { return data; }    
    public void setData(String data) { this.data = data; }
}

名为VersionedElement的下一个类扩展Element并包含不同版本以及当前版本。这是我的“解决方案”:

@Entity
public class VersionedElement extends Element {
    private Set<Element> versions;
    private Element currentVersion;

    @Override
    public String getData() {
        return getCurrentVersion().getData();
    }

    @Override
    public void setData(String data) {
        getCurrentVersion().setData(data);
    }

    @OneToMany
    public Set<Element> getVersions() {
        return versions;
    }

    public void setVersions(Set<Element> versions) {
        this.versions = versions;
    }

    @ManyToOne
    public Element getCurrentVersion() {
        return currentVersion;
    }

    public void setCurrentVersion(Element currentVersion) {
        this.currentVersion = currentVersion;
    }
}

我不喜欢我写的东西,错误的东西,太直接的方法。首先,在后一课currentVersion不受versions的限制,与{{1}}无关。看起来代码缺少一些辅助类,或抽象级别,或JPA注释技术,或以上所有。对于这个简单的案例,我需要一个优雅的,值得JPA手动解决方案。 任何提示,链接或代码片段都将不胜感激。

6 个答案:

答案 0 :(得分:13)

如果你想要一个随时可用的hibernate实体版本解决方案试试hibernate-envers。它将使对象版本控制/审计变得轻而易举。 查看http://docs.jboss.org/envers/docs/index.html

上的文档 欢呼,祝你好运!

答案 1 :(得分:4)

Element在对象version中可以有一个整数字段Element,作为行的运行计数,并由序列更新。如果您需要最新版本,只需按此顺序按降序排序并获取第一个结果。

@Entity
@NamedQueries({
    @NamedQuery(name="GetHistory", query = "FROM Element e WHERE e.id = :id"),
    @NamedQuery(name="GetLatest", query = "FROM Element e \
                                      WHERE e.id = :id order by e.version"),
})
public class Element {
    private String data;

    @GeneratedValue(strategy = GenerationType.SEQUENCE, 
                    generator = "SEQ_ELEMENT_VERSION")
    private int version;
    private int id;


    public String getData() { return data; }    
    public void setData(String data) { this.data = data; }
}

答案 2 :(得分:2)

您的解决方案可行,但为VersionedElement提供另一个表将是性能开销:VersionedElement除了一些外键列之外没有任何有用的数据。

我要做的只是将元素最新作为字段添加到类元素。然后,在DAO中,我将添加一些基于此字段执行查询的方法:

List<Element> getHistory(Element element)...
Element getLatest(Element element)...

JPA也支持@Version注释,但它用于乐观并发控制。它仍然可能用于跟踪版本号。

答案 3 :(得分:2)

好吧,我看到你对@Ioan Alexandru Cucu的回答发表评论

  

我希望它会以某种方式从sql方面得到补偿 - 涉及更少的行,更优化的查询

根据您问题中显示的映射,如果您需要检索完全初始化的VersionedElement实体,则需要执行类似这样的查询

from
    VersionedElement v
inner join fetch
    v.versions
inner join fetch
    v.currentVersion
where
    v.id = :id

如您所见,您需要两个联接来检索您的VersionedElement实体。但是Element和VersionedElement 共享数据属性。为了避免重复代码,我们可以定义一个抽象类,其中包含两个实体所需的数据,如下所示

@MappedSuperclass
public abstract class AbstractElement {

    private String data;

    public String getData() { return data; }
    public void setData(String data) { this.data = data; }

}

JPA 1.0规范很简单

  

抽象类和具体类都可以是实体。实体可以扩展非实体类以及实体类,非实体类可以扩展实体类。

我们需要@MappedSuperclass注释,因为应该应用其映射信息  从继承它的实体。在我们的例子中,Element和VersionedElement。

因此我们可以将Element实体重写为

@Entity
public class Element extends AbstractElement {}

为避免内部联接获取v.currentVersion ,为什么不将AbstractElement提供的数据存储为@Embedded属性而不是@ManyToOne属性?

@Embeddable
public class ElementAsEmbeddable extends AbstractElement {}

@Entity
public class VersionedElement {

    private ElementAsEmbeddable currentElement;

    private Set<Element> versions;

    @Embedded
    public ElementAsEmbeddable getCurrentVersion() { return currentVersion; }
    public void setCurrentVersion(ElementAsEmbeddable currentVersion) { this.currentVersion = currentVersion; }

    @OneToMany
    public Set<Element> getVersions() { return versions; }
    public void setVersions(Set<Element> versions) { this.versions = versions; }

}

现在您的查询应该是

from
    VersionedElement v
inner join fetch
    v.versions
where
    v.id = :id

只有一个加入

要从Element设置currentVersion属性,只需将Element转换为AbstractElement

versionedElement.setCurrentVersion((AbstractElement) element);

答案 4 :(得分:0)

您的解决方案没有任何问题,但您可能希望使用entity listeners以更优雅的方式确保实体的状态,我想是@prePersist和@preUpdate监听器。或者,也可以选择列表而不是集合。

答案 5 :(得分:-1)

您可以在单独的表(一行)中保留对最新实体的引用。类似的东西:

@Entity
public class CurrentElement {

    @OneToOne        
    private Element currentVersion;

    public static Element getCurrentVersion(EntityManager em) {
         return em.createQuery("select x from Element x ").getSingleResult().currentVersion;
    }

    public static void setCurrentVersion(EntityManager em, Element newVersion) {
         em.remove(getCurrentVersion(em));
         CurrentElement ce = new CurrentElement();
         ce.currentVersion = newVersion;
         em.persist(ce);
    }
}