Java持久性映射了具有可选属性的超类

hen*_*rik 5 java inheritance jpa criteria-api java-persistence-api

我正在使用该javax.persistence包来映射我的Java类.

我有这样的实体:

public class UserEntity extends IdEntity {
}
Run Code Online (Sandbox Code Playgroud)

它扩展了一个名为的映射超类IdEntity:

@MappedSuperclass
public class IdEntity extends VersionEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    // Getters and setters below...    

}
Run Code Online (Sandbox Code Playgroud)

IdEntity超类扩展了另一个名为映射超类VersionEntity,使所有实体继承版本特性:

@MappedSuperclass
public abstract class VersionEntity {

    @Version
    private Integer version;

    // Getters and setters below...

}
Run Code Online (Sandbox Code Playgroud)

为什么?

因为现在我可以在所有实体的IdEntity类上进行泛型查询,它看起来像这样:(示例)

CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
CriteriaQuery<IdEntity> criteria = builder.createQuery(IdEntity.class);
Run Code Online (Sandbox Code Playgroud)

现在来问题了.

我的一些实体将有created_at和时间戳一样的deleted_at.但并非所有实体.

我可以在我的实体类中提供这些属性,如下所示:

public class UserEntity extends IdEntity {

    @Basic(optional = false)
    @Column(name = "updated_at")
    @Temporal(TemporalType.TIMESTAMP)
    private Date updatedAt;
}
Run Code Online (Sandbox Code Playgroud)

但由于我有很多实体,这将使我在所有应该有时间戳的实体中放入大量冗余代码.我希望有一些方法可以让相关的类以某种方式继承这些字段.

一种可能的解决方案是创建一个parallell IdEntity超类,可能命名IdAndTimeStampEntity并使那些应该有时间戳的实体继承这个新的超类,但是这对我的同事开发人员来说是不公平的,因为现在他们必须知道哪个超类可以选择编写通用查询:

CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
CriteriaQuery<???> criteria = builder.createQuery(???); // Hmm which entity should I choose IdEntity or IdAndTimeStampEntity ?? *Annoyed*
Run Code Online (Sandbox Code Playgroud)

并且通用实体查询变得不那么通用了..

我的问题:如何使all我的实体继承idversion字段,但只有所有实体的子部分继承时间戳字段,但我的查询保持单一类型的实体?

更新#1

来自Bolzano的问题:"您可以添加为实体指定路径(保存表信息)的代码吗?"

下面是查询的工作示例UserEntity这是一个IdEntity

CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
CriteriaQuery<IdEntity> criteria = builder.createQuery(IdEntity.class);
Root<IdEntity> from = criteria.from(IdEntity.class);
criteria.select(from);

Path<Integer> idPath = from.get(UserEntity_.id); //generated meta model
criteria.where(builder.in(idPath).value(id));

TypedQuery<IdEntity> query = JPA.em().createQuery(criteria);
return query.getSingleResult();
Run Code Online (Sandbox Code Playgroud)

Jef*_*eff 5

我会选择一个没有像你概述的那样强制执行基于类的对象模型的解决方案.当您不需要乐观并发检查,没有时间戳,时间戳但没有OCC,或者您想要添加的下一个半常见功能时,会发生什么?排列将变得无法管理.

我会将这些常见的交互作为接口添加,并且我将使用泛型增强您的可重用查找,以将您关心的实际类返回给调用者而不是基类超类.

注意:我在Stack Overflow中编写了这段代码.可能需要一些调整来编译.

@MappedSuperclass
public abstract class Persistable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    // getter/setter
}

public interface Versioned {
    Integer getVersion();
}

public interface Timestamped {
    Date getCreated();
    Date getLastUpdated();
}

@Embeddable
public class TimestampedEntity {
    @Column(name = "create_date")
    @Temporal
    private Date created;

    @Column
    @Temporal
    private Date lastUpdated;

    // getters/setters
}

@Entity
public class UserEntity extends Persistable implements Versioned, Timestamped {
    @Version
    private Integer version;

    @Embedded
    private TimestampedEntity timestamps;

    /*
     * interface-defined getters.  getTimestamps() doesn't need to 
     * be exposed separately.
     */
}

public class <CriteriaHelperUtil> {
    public <T extends Persistable> T getEntity(Class<T> clazz, Integer id, SingularAttribute idField) {
        CriteriaBuilder builder = JPA.em().getCriteriaBuilder();
        CriteriaQuery<T> criteria = builder.createQuery(clazz);
        Root<T> from = criteria.from(clazz);
        criteria.select(from);

        Path<Integer> idPath = from.get(idField);
        criteria.where(builder.in(idPath).value(id));

        TypedQuery<T> query = JPA.em().createQuery(criteria);
        return query.getSingleResult();
    }
}
Run Code Online (Sandbox Code Playgroud)

基本用法:

private UserEntity ue = CriteriaHelperUtil.getEntity(UserEntity.class, 1, UserEntity_.id);
ue.getId();
ue.getVersion();
ue.getCreated();

// FooEntity implements Persistable, Timestamped
private FooEntity fe =  CriteriaHelperUtil.getEntity(FooEntity.class, 10, FooEntity_.id);
fe.getId();
fe.getCreated();
fe.getVersion();  // Compile Error!
Run Code Online (Sandbox Code Playgroud)


Pet*_*ály 3

@MappedSuperclass
public class IdEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Version
    private Integer version;
}   
@MappedSuperclass
public class IdAndTimeStampEntity extends IdEntity{
    Date created;
}
@Entity
public class UserEntity extends IdAndTimeStampEntity{
    String name;
}
@Entity
public class FooEntity extends IdEntity{...
Run Code Online (Sandbox Code Playgroud)

该解决方案的优点:

  1. 以简单明了的方式使用 OOP,无需在每个子类中嵌入实现接口的重复代码。(每个类也是接口)

  2. 乐观锁定版本列是最常用的方法。并且应该是基类的一部分。除了只读实体(例如代码表)。

用法:

public <T extends IdEntity> T persist(T entity) {

    if (entity instanceof IdAndTimeStampEntity) {
        ((IdAndTimeStampEntity) entity).setCreated(new Date());
    }

    if (!em.contains(entity) && entity.getId() != null) {
        return em.merge(entity);
    } else {
        em.persist(entity);
        return entity;
    }
}
Run Code Online (Sandbox Code Playgroud)