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

时间:2016-11-25 10:39:53

标签: java jpa inheritance criteria-api java-persistence-api

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

我有这样的实体:

public class UserEntity extends IdEntity {
}

扩展名为IdEntity映射超类

@MappedSuperclass
public class IdEntity extends VersionEntity {

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

    // Getters and setters below...    

}

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

@MappedSuperclass
public abstract class VersionEntity {

    @Version
    private Integer version;

    // Getters and setters below...

}

为什么呢?

因为现在我可以在所有实体的IdEntity类上进行泛型查询,它将如下所示:(示例)

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

现在问题。

我的某些实体将包含created_atdeleted_at等时间戳。但并非所有实体。

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

public class UserEntity extends IdEntity {

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

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

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

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

通用实体查询变得不那么通用了。

  

我的问题:如何让all我的实体继承id和   version个字段,但只有所有实体的子部分继承   时间戳字段,但我的查询是否只保留一种类型的实体?

更新#1

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

以下是查询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();

3 个答案:

答案 0 :(得分: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();
    }
}

基本用法:

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!

答案 1 :(得分: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{...

此解决方案的优点:

  1. 以简单明了的方式使用OOP而不需要在每个子类中嵌入实现接口的重复代码。 (每个班级也是界面)

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

  3. 用法:

    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;
        }
    }
    

答案 2 :(得分:2)

  

我希望有一些方法可以让相关的类以某种方式继承这些字段。

可以制作自定义注释@Timed并使用注释处理器添加时间戳字段和注释,方法是使用字节码操作框架或创建委托子类。或者,例如,如果您使用Lombok,请创建一个Lombok注释。

这样,当您拥有带时间戳的实体时,您的团队成员只需记住使用@Timed注释。您是否喜欢这种方法取决于您。