Java persistence associated superclass with additional properties

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

I have the following entities:

 public class UserEntity extends IdEntity { } 

which extends the displayed superclass called IdEntity :

 @MappedSuperclass public class IdEntity extends VersionEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; // Getters and setters below... } 

The IdEntity superclass extends another mapped superclass named VersionEntity so that all objects inherit version properties:

 @MappedSuperclass public abstract class VersionEntity { @Version private Integer version; // Getters and setters below... } 

Why?

Since now I can create general queries in the IdEntity class for all objects, 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, such as created_at and deleted_at . But not all entities.

I could provide these properties in my entity classes as follows:

 public class UserEntity extends IdEntity { @Basic(optional = false) @Column(name = "updated_at") @Temporal(TemporalType.TIMESTAMP) private Date updatedAt; } 

But since I have a lot of entities, this will force me to put a lot of redundant code into all objects that should have timestamps. I would like, somehow, I could make the corresponding classes inherit these fields in some way.

One possible solution is to create an IdEntity parallell superclass, possibly called IdAndTimeStampEntity , and make those objects that should have timestamps inherited from this new superclass, but, unfortunately, this is not true for my fellow developers, because now they have to know which superclass to choose when writing generic queries:

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

And general object queries become less universal ..

My question is: how can I make all my objects inherit id and version , but only part of the subset of all objects inherits timestamp, but keep my queries for one type of object?

Update # 1

Question from Bolzano: "Can you add code that sets the path (contains information about the table) for objects?"

Here is a working example of a UserEntity request, which is 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(); 
+5
source share
3 answers

I would choose a solution that did not use the class-based object model, as you described. What happens when you don't need an optimistic concurrency check and no timestamps or timestamps, but without OCC or the next semi-general part of the functionality you want to add? Permutations will become unmanageable.

I would add these general interactions as interfaces, and I would increase your reusability by identifier using generics to return the actual class that you are interested in for the caller, instead of the base superclass.

Note. I wrote this code on Stack Overflow. Compilation may require some configuration.

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

Main use:

 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! 
+5
source
 @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{... 

Advantages of this solution:

  • In a simple and understandable way, it uses OOP without the need to implement duplicate code that implements intefaces in each subclass. (Each class is also an interface)

  • The optimistic locking column is basically the approach used. And should be part of the base class. Except for clean objects such as code.

Using:

 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; } } 
+3
source

I would like, somehow, I could make the corresponding classes inherit these fields in some way.

You can create a custom @Timed annotation and use the annotation handler to add a timestamp and annotation field, either by using the bytecode manipulation structure or by creating a delegate subclass. Or, for example, if you use Lombok, create a Lombok annotation.

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

+2
source

Source: https://habr.com/ru/post/1260337/


All Articles