Why JPA-2.0 Primary Key Classes have to implement Serializable but my example works without? - persistence

In many sources I have read PrimaryKey Classes and even JPA2 entities should be serializable.
IN my example (legacy database) there is a relationship between employee and languages:
Employee Class:
#Entity
#IdClass(EmpleadoId.class)
#Table(name = "NO_INFGRAEMPL")
public class Empleado {
#Id
#Column(name = "IGECOMPANIA", unique = true)
private String compania;
#Id
#Column(name = "IGENUMEROIDENTIFIC", unique = true)
private String numeroIdentificacion;
//...
}
Employee Compound PrimaryKey Class:
public class EmpleadoId {
private String compania;
private String numeroIdentificacion;
//...
}
Employee Language SKill Class:
#Entity
#IdClass(IdiomaEmpleadoId.class)
#Table(name = "NO_IDIOMEMPLE")
public class IdiomaEmpleado {
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns(value = {
#JoinColumn(name= "IEMCOMPANIA", referencedColumnName = "IGECOMPANIA"),
#JoinColumn(name = "IEMEMPLEADO", referencedColumnName = "IGENUMEROIDENTIFIC")
})
private Empleado empleado;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "IEMIDIOMA")
private Idioma idioma;
#Column(name = "IEMNIVELLECTURA")
private String nivelLectura;
//...
}
Employee Language Skill Compound PrimaryKey Class:
public class IdiomaEmpleadoId {
private EmpleadoId empleado;
private String idioma;
//...
}
Language Class:
#Entity
#Table(name = "NO_IDIOMAS")
public class Idioma {
#Id
#Column(name = "IDICODIGO")
private String codigo;
#Column(name = "IDIDESCRIPCION")
private String descripcion;
//...
}
I am using EclipseLink JPA2 Provider under a J2SE application and it is not giving me any exceptions.
My questions are:
Why is it not giving me exceptions? Is it not enforced to have Serializable?
Is it safe to continue this way or should I definitely implemente serializable?.
In which ones?, JPA2 Entities or PrimaryKey Classes?
Thanks a lot for the help.

JPA specification contains such a requirement (JSR-317 secion 2.4 Primary Keys and Entity Identity):
The primary key class must be serializable.
If EclipseLink really doesn't enforce this requirement, it's an implementation detail of EclipseLink and I wouldn't recommend you to rely on it.
However, there are no requirements on serializability of entities, except for the following one which looks more like a recommendation than a requirement:
If an entity instance is to be passed by value as a detached object (e.g., through a remote interface), the
entity class must implement the Serializable interface.

Nothing is required to be serializable, but it seems it is requried by the spec (10x to axtavt) for primary keys, although there is no direct need for it.
Serialization is needed if the objects are transferred over-the-wire or persisted to disk, so I can't see the reason behind that decision. However, you should conform to it.

Primary key classes have to implement serializable and composite-ID class must implement serializable are two different questions.
I am going to answer you both, and hope it will help you to distinguish and understand holistically.
Primary key classes have to implement serializable:
Note: It could work without its iplementation also.
JPA specification contains such a requirement (JSR-317 secion 2.4 Primary Keys and Entity Identity):
The primary key class must be serializable.
However, there are no requirements on serializability of entities, so it's a recommendation than a requirement
exception:
If an entity instance is to be passed by value as a detached object (e.g., through a remote interface), the entity class must implement the Serializable interface.
Composite-ID class must implement serializable.
The id is used as a key to index loaded objects in the session.
The session object needs to be serializable, hence all objects referenced by it must be serializable as well.
In case of CompositeIds the class itself is used as the id.

Related

How to use multiple foreign keys in JPA?

I'm starting a project to know more in detail JPA.
Context:
At the end of his internship, the student has a report to make and a presentation in front of his professor to do about the internship.
I've a database, which is called "grade_management". It must contains a "student", "presentation", "report", "professor" and a "mark" (there are several rating criteria such as expression, quality of powerpoint ...) table. But now it's empty, since I want to make it throught JPA.
I've a "Presentation" class. Which countain this:
#Entity
public class Presentation implements Serializable {
#Id
#GeneratedValue (strategy=GenerationType.AUTO)
private int presentation_id;
private Date date;
private mark_id;
private int professor_id;
public Soutenance() {}
public Soutenance(Date date) {
this.date = date;
}
}
But the Presentation table contain 2 foreign key: professor_id and mark_id.
My question is: How can I indicate that both of them are foreign key ?
I'm sorry if I'm not clear, don't hesitation to ask question.
Cordially
You shouldn't reference other entities by their ID, but by a direct reference to the entity.
Something like that :
#ManyToOne
#JoinColumn(name = "mark_id", referencedColumnName = "id")
private Mark mark; // supposed here that mark_id if link to entity `Mark`
#ManyToOne
#JoinColumn(name = "professor_id", referencedColumnName = "id") // suppose "id" is the column name of the PK inside the table Professor.
private Professor professor; // supposed here that professor_id if link to entity `Professor`
This code is supposing that you use an unidirectional relation.
For bidirectional you have to define this in the other side (Mark/Professor type)
#OneToMany(mappedBy = "professor")
private Presentation presentation;
From your explanation, it looks like you have a Database named grade_management and in that database you have "student", "presentation", "report", "professor" and a "mark" tables (i.e: which are #Entity by themselves defined in their separate respective classes )
I'm not sure whether you have defined them or not. If not then you have to define them first and then use the refactored code mentioned below.
So, you will have many-to-one relation mapping. You can annotate your foreign keys belonging to different tables using #ManyToOne annotation to indicate relation type and #JoinColumn annotation to indicate that this entity has a foreign key to the referenced table.
You can redefine your Presentation class show below:
#Entity
#Table(name = "Presentation")
public class Presentation implements Serializable {
#Id
#Column(name="presentation_id")
#GeneratedValue (strategy=GenerationType.AUTO)
private int presentation_id;
private Date date;
#ManyToOne
#JoinColumn(name = "mark_id")
private Mark mark_id;
#ManyToOne
#JoinColumn(name = "professor_id")
private Professor professor_id;
public Soutenance() {}
public Soutenance(Date date) {
this.date = date;
}
//getter and setter
}
Also, if you need more information to read upon for yourself you can always checkout this Hibernate Documentation that explains everything you'll need to know.

Load newest version of an entity with JPA

I have the following entities:
#Entity
public class Policy {
#ID
private String uuid;
private String policyId;
private Long version;
private Long auditVersion;
}
#Entity
public class PolicySearch {
#ID
private String uuid;
#ManyToOne(optional = false)
#JoinColumn(name = "policy_id", referencedColumnName = "policy_id")
private Policy policy;
}
Basically, I've got an insurance policy where all changes are tracked in the DB (auditVersion). After some smaller changes a version can be released, that's when version increments and auditVersion starts at 0 again. Each DB entry has a different UUID, but the insuranceId stays the same for all versions of one policy.
The problem: I've got an entity for searches, a search always searches all versions of a policy - that's why I reference the policyId and not the uuid. When JPA loads the entity I end up with any policy. I would like a way to always get the highest version of a policy given the referenced policyId (and the highest auditVersion of that version).
I've thought of the following ways, but I'm not happy with either of those:
Change the type of the referenced Policy from Policy to String and only save the policyId, this would work but I would still want the foreign key constraint and I can't seem to find a way to create it with a JPA annotation (JPA creates my DB schema).
Keep the entities as is but discard the loaded Policy in favor of the newest one after loading the PolicySearch. This could be done in the DAO but if any entities in the future have PolicySearch as a member this seems like a really bad idea.
Any help would be greatly appreciated. I use EclipseLink.
I tried to add constraints to the DB but Postgres won't let you add foreign key constraints for columns which are not unique. The solution (which a coworker of mine came up with) for us was to change the database design and create a new entity which holds the PolicyId. So our Entities now look like this:
#Entity
public class Policy {
#ID
private String policyId;
}
#Entity
public class PolicyVersion {
#ID
private String uuid;
private Policy policy;
private Long version;
private Long auditVersion;
}
#Entity
public class PolicySearch {
#ID
private String uuid;
#ManyToOne(optional = false)
#JoinColumn(name = "policy_id", referencedColumnName = "policy_id")
private Policy policy;
}
This basically solves all the problems and has some other benefits too (like easy queries).

Exception using Compsite Keys on EclipseLink

Im having trouble using composite primary keys with JPA EclipseLink. The problem is when I theres a foreign key that is the primary key of another table. I have this simple scenario.
User
public class Users implements Serializable {
...
private Collection<UserCompany> userCompanyCollection;
#JoinColumn(name = "user_roles", referencedColumnName = "user_role_id")
#ManyToOne(optional = false)
private UserRoles userRoles;
...
}
User Roles
public class UserRoles implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected UserRolesPK userRolesPK;
…
}
User Roles PK
#Embeddable
public class UserRolesPK implements Serializable {
#Basic(optional = false)
#Column(name = "user_role_id")
private int userRoleId;
#Basic(optional = false)
#NotNull
#Column(name = "user_role_company_id")
private int userRoleCompanyId;
...
}
With that objects, I get this exception:
Caused by: Exception [EclipseLink-7220] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The #JoinColumns on the annotated element [field userRoles] from the entity class [class jpa.Users] is incomplete. When the source entity class uses a composite primary key, a #JoinColumn must be specified for each join column using the #JoinColumns. Both the name and the referencedColumnName elements must be specified in each such #JoinColumn.
at org.eclipse.persistence.exceptions.ValidationException.incompleteJoinColumnsSpecified(ValidationException.java:1805)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.MappingAccessor.getJoinColumnsAndValidate(MappingAccessor.java:575)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.MappingAccessor.getJoinColumns(MappingAccessor.java:525)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ObjectAccessor.processOneToOneForeignKeyRelationship(ObjectAccessor.java:629)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ObjectAccessor.processOwningMappingKeys(ObjectAccessor.java:686)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ManyToOneAccessor.process(ManyToOneAccessor.java:119)
at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processOwningRelationshipAccessors(MetadataProject.java:1432)
at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processStage3(MetadataProject.java:1667)
at org.eclipse.persistence.internal.jpa.metadata.MetadataProcessor.processORMMetadata(MetadataProcessor.java:521)
at org.eclipse.persistence.internal.jpa.deployment.PersistenceUnitProcessor.processORMetadata(PersistenceUnitProcessor.java:526)
at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.predeploy(EntityManagerSetupImpl.java:1320)
... 36 more
|#]
Thanks in advance for all the help.
Regards,
Daniel
JPA requires using the full primary key in relationship mappings, which is why it doesn't like your mapping - you are not using the user_role_company_id pk field. If user_role_id is enough to uniquely identify userRoles, then it should not be using a composite key and instead only use the single field.
EclipseLink is capable of mapping foreign keys to non or incomplete ID fields, but I recommend against it: Entities are cached on their primary keys, so resolving relationships may require unnecessary database queries even when the entity is in the cache already. Mapping it requires using a customizer to either create or modify the mapping. An example using a customizer is here
http://wiki.eclipse.org/EclipseLink/Examples/JPA/MappingSelectionCriteria

Single Table Inheritance Query

i have an existing table for TransactionLogs which is either links to a External or to a InternalType. the id's corresponding to the cash adjustment & game transaction are stored in a single column called transaction id and a separate column called type indicates which table is it linked to
Because of the nature of the existing table, i mapped it in a single table inheritance:
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.INTEGER)
public class TransLog implements Serializable {
#Id
#GeneratedValue
private Long id;
private Integer type;
// getters and setters
}
#Entity
public class InternalAdjustmentTransLog extends TransLog {
#ManyToOne
#JoinColumn(name = "TransID", nullable = false)
private InternalAdjustmentRecord internalAdjustmentRecord;
// getters and setters
}
#Entity
public class ExternalTransLog extends TransLog {
#ManyToOne
#JoinColumn(name = "TransID", nullable = false)
private ExternalAdjustmentRecord externalAdjustmentRecord;
}
each of these two subclasses has their subclasses with defined descriminator values..
With the setup given above, there are instances that i need to get a unified data of both
internal and external records. What is the best way to accomplish this? at first i thought it would be enough to use the TransLog as the root class for the query (i'm using jpa criteria). however, i need to get TransId (which are defined in the subclasses and points to 2 different objects of no relationship).
Thanks.
You can make abstract method in TransLog that returns what you need and implement it in both subclasses.

Is it legal to have a top-level JPA class that is abstract and does not have a discriminator?

For example, is the following top-level JPA class valid:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(
name = "type",
discriminatorType = DiscriminatorType.STRING
)
public abstract class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
public Long getId() {
return id;
}
}
Yes, it is perfectly legal. According specification (JPA 2.0) DiscriminatorValue belongs only to concrete entity class.
I can no no reason why it shouldn't be. Try it out.
The discriminator value of the specified discriminator column will be the entity name of the concrete implementation(s) if the discriminatorvalue annotation has not been specified on the entities (as described in the spec).
Does this answer your question?