Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java persistence mapped superclass with optional properties

I'm using the javax.persistence package to map my Java classes.

I have entities like these:

public class UserEntity extends IdEntity {
}

which extends a mapped superclass named IdEntity:

@MappedSuperclass
public class IdEntity extends VersionEntity {

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

    // Getters and setters below...    

}

The IdEntity super class extends another mapped super class named VersionEntity to make all entities inherit version properties:

@MappedSuperclass
public abstract class VersionEntity {

    @Version
    private Integer version;

    // Getters and setters below...

}

Why?

Because now I can make generic queries on the IdEntity class for all entities, and it will look like this: (example)

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

Now to the problem.

Some of my entities will have timestamps like created_at and deleted_at. But not all entities.

I could provide these properties in my entity classes like this:

public class UserEntity extends IdEntity {

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

But as I have a lot of entities, this will make me put a lot of redundant code in all entities that should have timestamps. I wish there was some way I could make the relevant classes inherit these fields in some way.

One possible solution is to create a parallell IdEntity superclass, maybe named IdAndTimeStampEntity and make those entities that should have timestamps inherit from this new superclass instead, but hey that's not fair to my colleague-developers because now they have to know which super class to choose from when writing generic queries:

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

And the generic entity queries become not so generic..

My question: How can I make all of my entities inherit id and version fields, but only a sub part of all entities inherit timestamp fields, but keep my queries to a single type of entities?

Update #1

Question from Bolzano: "can you add the code which you specify the path(holds table info) for entities ?"

Here is a working example of querying a UserEntity which is a 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();
like image 916
henrik Avatar asked Nov 25 '16 10:11

henrik


People also ask

What is a MappedSuperclass?

A mapped superclass is a special type of class that is not persistent itself, but has subclasses that are persistent. A mapped superclass is useful for defined a common persistence superclass that defines common behavior across a set of classes, such as an id or version attribute.

What is @MappedSuperclass in hibernate?

Annotation Type MappedSuperclass @Documented @Target(value=TYPE) @Retention(value=RUNTIME) public @interface MappedSuperclass. Designates a class whose mapping information is applied to the entities that inherit from it. A mapped superclass has no separate table defined for it.

How does JPA inheritance work?

JPA Inheritence Overview Inheritence is a key feature of object-oriented programming language in which a child class can acquire the properties of its parent class. This feature enhances reusability of the code. The relational database doesn't support the mechanism of inheritance.

Can entity class inherit from non-entity class?

Entities support class inheritance, polymorphic associations, and polymorphic queries. Entity classes can extend non-entity classes, and non-entity classes can extend entity classes.


3 Answers

I would pick a solution that didn't enforce a class-based object model like you've outlined. What happens when you don't need optimistic concurrency checking and no timestamps, or timestamps but no OCC, or the next semi-common piece of functionality you want to add? The permutations will become unmanageable.

I would add these common interactions as interfaces, and I would enhance your reusable find by id with generics to return the actual class you care about to the caller instead of the base superclass.

Note: I wrote this code in Stack Overflow. It may need some tweaking to compile.

@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();
    }
}

Basic Usage:

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!
like image 136
Jeff Avatar answered Nov 15 '22 04:11

Jeff


@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{...

Pros of this solution:

  1. In simple and clear way uses OOP without need to embed duplicate code implementing intefaces in every subclass. (Every class is also interface)

  2. Optimistic locking version column is mostly used approach. And should be part of base class. Except read only entities like codetables.

Usage:

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;
    }
}
like image 28
Peter Šály Avatar answered Nov 15 '22 04:11

Peter Šály


I wish there was some way I could make the relevant classes inherit these fields in some way.

You could make a custom annotation @Timed and use an annotation processor to add the timestamp field and annotations, either by using a bytecode manipulation framework or creating a delegating subclass. Or, for example if you use Lombok, create a Lombok annotation.

That way, your team members only have to remember to use the @Timed annotation when you have entities with timestamps. Whether you like such approach or not is up to you.

like image 27
Nick Vanderhoven Avatar answered Nov 15 '22 04:11

Nick Vanderhoven