I'm writing an application using Hibernate and I was wondering how should I map an entity defined as:
#Entity
public Class MyEntity implements Serializable {
#Id
private Long id;
#Column
private Class<?> clazz;
// getters/setters...
}
Is it possible to map this to a DB? Should I add an extra annotation to clazz?
Note that this is a curiosity, I'm not in a hurry this time, so please do not try to supply alternate solutions. Thanks in advance for your answers.
There is no JPA mapping for a Class object out of the box, but to satisfy most (if not all) use cases, you could do something like this
#Entity
public Class MyEntity implements Serializable {
#Id
private Long id;
#Column
private String clazz;
public Class getClazz () {
return this.clazz != null ? Class.forName(this.clazz) : null;
}
public Class setClazz (Class clazz) {
this.clazz = clazz.getName();
}
}
In this case, you are simply storing the name of the class as a String, and then when you retrieve it from the database, the getter will convert it into a proper class object.
Related
Animal.java
#Data
#Entity
public class Animal implements MyEntityInterface {
public enum Sex {MALE, FEMALE}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String name;
private Sex sex;
private boolean castrated;
#OneToMany
private List<Symptom> symptoms;
}
AnimalDTO.java
#Getter
#Setter
public class AnimalDTO implements Serializable {
private long id;
private String name;
private Animal.Sex sex;
private boolean castrated;
private List<Long> symptoms;
}
I wish for a list of Symptoms to be automatically mapped to a list of ID's. This could be achieved in many ways, such as creating a TypeMap, creating a Converter or even just by creating a method in AnimalDTO.java:
public void setSymptoms(List<Symptom> symptoms) {
if (symptoms != null)
this.symptoms = symptoms.stream().map(s -> s.getId()).collect(Collectors.toList());
}
But now imagine it's not only Symptoms, but 50 other fields too. That's a lot of code for the same functionality. And then, it's not only Animal to AnimalDTO, but another 30 different classes with their respective DTOs too.
Also, that still leaves the way back open. From ID to entity. This can (in theory) be achieved easily with the following pseudocode:
List<EntityMemberField.class> list;
for (var entityid : listOfEntityIDsOfDto) {
Object persistedObject = entityManager.find(EntityMemberField.class, entityid);
list.add(persistedObject);
}
...
ModelMapperDestination.setField(list);
This is the same for absolutely every Entity/DTO and should automatically happen for every Entity relationship where the Entity implements MyEntityInterface.
An idea how I could achieve that would be overriding MappingEngineImpl.java from ModelMapper which I register as a Spring Service and inject the EntityManager into, but how could I get ModelMapper to use mine? Or is there maybe an easier way?
The goal is to have a fairly automated conversion from Spring Entities to their corresponding DTO by... just calling modelMapper.map(entity, EntityDTO.class);
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();
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!
#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:
In simple and clear way uses OOP without need to embed duplicate code implementing intefaces in every subclass. (Every class is also interface)
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;
}
}
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.
I have defined an attribute converter like this:
#Converter(autoApply = true)
public class MyConverter implements AttributeConverter<MyType, String> {
#Override
#Nullable
public String convertToDatabaseColumn(#Nullable final MyType attribute) {
LOG.log(Level.INFO, "Converting '{'{0}'}' to DB column", attribute);
return attribute != null ? map(attribute) : null;
}
#Override
#Nullable
public MyType convertToEntityAttribute(#Nullable final String dbData) {
LOG.log(Level.INFO, "Converting '{'{0}'}' to type", dbData);
return dbData != null ? map(dbData) : null;
}
}
Then in my entity class:
#Entity
public class MyEntity implements Serializable {
#Id
private Long id;
private MyType myData;
}
According to jpa2.1 specs, i do not have to annotate the attribute field with #Convert if i have specified autoApply on the converter.
Nonetheless, even if i do not specify the autoApply on the converter and specify the following:
#Entity
public class MyEntity implements Serializable {
#Id
private Long id;
#Convert(converter = MyConverter.class)
private MyType myData;
}
Hibernate still does not consider this converter.
What could i be doing wrong?
I have deleted the table and regenerated it, but nothing does help.
I have tried hibernate versions from 4.3.4 - 4.3.8 with no success, n wildfly 8.1
As aside note, My converter is declared in an entity-jar, which is then included in ejb-jar as a dependency.
Well.
After several hours, the solution seems to be simple.
My mistake was that i declared classes in the ejb-jars persistence.xml, instead of specifying the jar-file element. Therefore, the jpa hibernate annotation engine had no idea of my entity-jar, and could not scan it for the Converter annotation
I have some tables that have some common columns and I thought that when creating entities I would have the entities extend a class where all of the mapping is defined:
#MappedSuperclass
public class AbstractLogMaster implements Serializable
{
#Id
#Basic(optional = false)
#Column("id")
protected long id;
#Column("field1")
protected String field1;
#Column("field2")
protected String field2;
public long getId()
{
return id;
}
public void setId(long id)
{
this.id = id;
}
etc...
}
#Entity
#Table(name = "logwork")
public class LogWork extends AbstractLogMaster implements Serializable
{
#Column("field3")
protected int field3;
public int getField3()
{
return field3;
}
}
Using this in a query Predicate p1 = builder.equal(logWork.get(LogWork_.field1), f1); results in a NPE (specifically because of the logWork.get(LogWork_.field1) part - the #StaticMetaModel doesn't seem to work with mapped superclass)
Looking at the JPA documentation I discovered that:
Mapped superclasses cannot be queried and can’t be used in EntityManager or Query operations. You must use entity subclasses of the mapped superclass in EntityManager or Query operations. Mapped superclasses can’t be targets of entity relationships. Mapped superclasses can be abstract or concrete.
Does this mean that none of the fields I map in the mapped superclass are available to be used by the extending entities in criteria queries? If so, what is the point of putting mapping into the superclass?? If not, what am I doing wrong??
My bad. Turns out the answer to this question is the "proper" use of the #StaticMetamodel annotated companion classes. The mapped superclasses must all have individual static metamodels that inherit in the same way (I had bundled everything for the entity into one metamodel, LogWork_)
#StaticMetamodel(AbstractLogMaster.class)
public final class AbstractLogMaster_
{
public static volatile SingularAttribute<AbstractLogMaster, Long> id;
public static volatile SingularAttribute<AbstractLogMaster, String> field1;
public static volatile SingularAttribute<AbstractLogMaster, String> field2;
}
#StaticMetamodel(LogWork.class)
public final class LogWork_ extends AbstractLogMaster_
{
public static volatile SingularAttribute<LogWork, Integer> field3;
}
I have an abstract class:
#MappedSuperclass
public abstract class BaseEntity<K>
{
#Temporal(value = TemporalType.TIMESTAMP)
private Date cadastrado;
#Temporal(value = TemporalType.TIMESTAMP)
private Date modificado;
#Column(length = 30)
private String ip;
private String autorModificacao;
public abstract K getId();
public abstract void setId(K id);
...
and a derived class:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Pessoa extends BaseEntity<Integer> implements Serializable {
#Id
#ColumnGridPF
#GeneratedValue(strategy = GenerationType.AUTO, generator = "pessoa")
private Integer id;
....
#Override
Integer getId() {
return id;
}
#Override
public void setId(Integer id) {
this.id = id;
}
....
when my application try to unmarshall the object, I get an error
**
SEVERE: The RuntimeException could not be mapped to a response, re-throwing to the HTTP container
java.lang.ClassCastException: com.sun.org.apache.xerces.internal.dom.ElementNSImpl cannot be cast to java.lang.Integer
at br.com.sigmaonline.entity.cadastro.pessoa.Pessoa.setId(Pessoa.java:46)
at br.com.sigmaonline.entity.common.generic.BaseEntity$JaxbAccessorM_getId_setId_java_lang_Object.set(MethodAccessor_Ref.java:60)
**
Can Any one help me?
By default when your JAXB (JSR-222) implementation is creating metadata for Pessoa it is also going to create metadata for the super class BaseEntity. Since JAXB by default considers properties as mapped it is going to consider that it has a property called id of type Object. When JAXB doesn't know the type of the property it will convert it to a DOM Element. This is resulting in the ClassCastException.
Solution
The solution really depends upon whether or not you want BaseEntity considered part of the inheritance hierachy (see: http://blog.bdoughan.com/2011/06/ignoring-inheritance-with-xmltransient.html). But what I would recommend is either leveraging #XmlTransient or #XmlAccessorType(XmlAccessType.NONE) on BaseType to remove problematic properties:
http://blog.bdoughan.com/2012/04/jaxb-and-unmapped-properties.html