Limitation of JPA 1.0 using #IdClass with *nested* composite primary keys? - java

Given the following example (departments - projects):
A department has the following properties (composite primary key):
#Entity
#IdClass(DeptId.class)
public class Department
{
#Id
#Column(name="number")
private Integer number;
#Id
#Column(name="country")
private String country;
#Column(name="name")
private String name;
#OneToMany(mappedBy="dept")
private Collection<Project> projects;
...
}
Here the PK class:
public class DeptId implements Serializable
{
private Integer number;
private String country;
...
}
The relationship between projects and departments is many-to-one, that is a deptartment can have many projects. The Project class is itself using a composite key referencing Department's composite key. Important note: it's only about the implementation with #IdClass not #EmbeddedId.
Then the (problematic) JPA 1.0 #IdClass implementation would have to look something like that (redundant deptNum and deptCtry properties): -> it's just a unique name within a department
#Entity
#IdClass(ProjectId.class)
public class Project
{
#Id
#Column(name="dept_number")
private Integer deptNumber;
#Id
#Column(name="dept_country")
private String deptCountry;
#Id
#Column(name="name")
private String name;
#ManyToOne
#JoinColumns({
#JoinColumn(name="dept_number", referencedColumnName="number"),
#JoinColumn(name="dept_country", referencedColumnName="country")
})
private Department dept;
...
}
The ProjectId is:
public class ProjectId implements Serializable
{
private String name;
private DeptId dept;
...
}
The problem with this is that neither Hibernate nor EclipseLink know how to map the two redundant properties deptNum and deptCtry in Project to the dept property in DeptId (or the properies within it). -> MappingException etc.
My question is:
Is this a limitation of JPA 1.0, that tables with composite keys referencing other composite keys with #IdClass implementations generally WON'T work, because the JPA implementation simply can't know how to map these fields?
As a workaround, you'd have to use #EmbeddedId for these classes or use JPA 2.0 syntax to annotate the #XToX associations with #Id. I just want to make sure my view on this is right.
Thanks

Yes, this is a limitation of JPA 1.0, corrected in JPA 2.0. In the new JPA 2.0, you can put the ID annotation on your dept relationship and completely avoid having the redundent deptCountry and deptNumber attributes, with the key class using nesting. In JPA 1.0, only basic mappings can be marked as apart of the ID, requiring redundent mappings and some code to ensure that the values/relationships get put into the cache correctly when persisting. Because of the redundancy, as mentioned in other answers, one of the mappings for a field needs to be marked read-only via the insertable/updatable=false. Doing so though means that value is not merged into the cache - so changes (such as on insert, since you can't change an objects ID once it exists) will not be reflected unless the object is refreshed from the database. If you mark the JoinColumns as read-only, you will need to get the values from the referenced dept and put them into the correspoinding basic id attributes manually when you want to persist a Project. But, you can also mark the basic attributes as read-only. Eclipselink anyway will not have any problems and will correctly set the field values using the associated dept entity (as long as it is set before persist is called on the Project). Notice though that the basic attributes may or may not be populated when you read back the project in a different context- this will depend on if the entity is refreshed from the database or not. If they are read-only, they do not get merged into the shared cache since they, being read only, should not have changed. So they can be just ignored, or if they must be populated, the entity refreshed or the values set from the dept in an event.
This same model can be reused by using the JPA2.0 #MapsId, which will also maintain the basic mappings using the values from the relationship for you. Only benifit I see is that you don't need to access the relationship (potentially causing unneccessary joins or database access on lazy relationships) to get the foreign key/id field values.
As for the ZipArea EclipseLink exceptions, they are due to ZipAreaId having a ZipId zip attribute instead it being flattened out. JPA 1.0 requires the key class to have an attribute of the same type and name for each #ID attribute in the Entity.

The problem with this is that neither Hibernate nor EclipseLink know how to map the two redundant properties deptNum and deptCtry in Project to the dept property in DeptId
This is why you need to define the ManyToOne foreign key(s) as read-only with this kind of mapping. This is done by setting the JoinColumn attributes insertable and updatable to false.
So try the following:
#Entity
#IdClass(ProjectId.class)
public class Project
{
#Id
#Column(name="dept_number")
private Integer deptNumber;
#Id
#Column(name="dept_country")
private String deptCountry;
#Id
#Column(name="name")
private String name;
#ManyToOne
#JoinColumns({
#JoinColumn(name="dept_number", referencedColumnName="number", insertable=false, updatable=false),
#JoinColumn(name="dept_country", referencedColumnName="country", insertable=false, updatable=false)
})
private Department dept;
...
}

The problem with the posted code is, that JPA 1.0 really doesn't allow nesting of composite primary key classes. This ProjectId is invalid:
public class ProjectId implements Serializable
{
private String name;
private DeptId dept;
...
}
DeptId has to be flattened, like:
public class ProjectId implements Serializable
{
private Integer deptNumber;
private String deptCountry;
private String name;
...
}
I just got an EclipseLink version to go, but Hibernate has problems with that. I wonder how to tell Hibernate that JPA 1.0 is assumed.

Related

Define an inherited column to be part of composite primary key in JPA

We have an abstract base entity class that defines columns found in all of our entities (e.g. creation and modifications timestamps and related user IDs). For most of our entities, there are two related database tables: one that holds the "live" data, i.e. the current state of the entity; and one that holds the audit trail, i.e. all historical versions of the entity, as snapshots.
One of the columns shared by all entities (and defined in the base entity class) is a version number column, which is a simple running sequence that tells how many edits have been made to that particular entity. The version column is present in both the audit table and the "regular" table, but in the audit table, the version column is also part of a composite primary key, which is formed by combining the primary key of the "regular" entity and the version number. Simplified example (some entity-level annotations omitted for brevity):
#MappedSuperclass
public abstract class BaseEntity {
#Column(name = "version")
private Long version;
// Timestamps, user IDs etc.
}
#MappedSuperclass
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class PersonBase extends BaseEntity {
// All basic columns of a person defined here
}
public class Person extends PersonBase {
#Id
#Column(name = "id")
private long id;
// All references to other entities defined here
}
public class PersonAudit extends PersonBase {
#Id
#Column(name = "id")
private long id;
// FIXME: Version column should be part of composite primary key
// All foreign key columns defined here (can't have
// a direct FK relationship in an audit table, as you
// don't know which version to link to)
}
Is it possible to make the inherited version column be part of a composite primary key in the audit entities? I tried declaring the version column also in the PersonAudit table and adding the #Id annotation to it, but after that the version field in BaseEntity wasn't populated anymore. It'd be very convenient to have the field in BaseEntity as we use it in some generic Criteria Queries using the JPA static metamodel, and if we move the field declaration to the individual audit entities, we can't reference the column anymore in those queries.
P.S. We're using Hibernate, so Hibernate-specific solutions are also acceptable if JPA doesn't enable this.

Is it possible to force Hibernate to embed an Entity?

In my use-case, I would like to #Embedded a class C in an entity.
Another entity refers to C with #OneToMany association and therefore C is annotated with #Entity.
I am aware that this seems like bad design, yet I believe that it makes perfect sense in my case.
Is it possible to force Hibernate to embed an Entity? If I try it, Hibernate complains about a missing setter for the id property of C.
I think the problem comes from this:
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private Long id;
Why not just create the entity that you want, and in that entity, embed C as well. That way you have C in both classes, one as embedded and another as embedded of the new entity.
#Embeddable
public class Contact {
private String firstname;
private String lastname;
// getters and setters removed.
}
and here is your embedding class:
#Entity
public class Student {
#Embedded
private Contact contact;
}
and here is the new entity that embeds contact also
#Entity
public class FirmContact {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int contactId;
#Embedded
private Contact contact;
}
And finally the class that insists the contact must be an entity:
#Entity
public class Business {
#OneToOne(cascade=CascadeType.ALL)
private FirmContact contacts;
}
It'll just be a couple of extra steps in java to populate the object, but it should do the mapping you want. I hope this helps.
Hibernate doesn't allow you to treat an Embeddable as an Entity or to embed an Entity. According to Hibernate types:
an Embeddable, doesn't have an identifier, since it's state is part of an owning Entity.
an Entity cannot be embedded, because each Entity has a distinct life-cycle.
Since another class already has a #OneToMany association to class C, it's obvious you cannot turn it into an Embeddable.
More, a bidirectional #OneToMany association will perform better than an embeddable collection.
What you can do, is to use it as a #OneToOne association in the entity where you wanted to embed the C entity. You can make that target entity be the owning side of the association so that the C association is bound to the target entity life-cycle.

Storing data using hibernate for inter related tables

I have two tables (say table A and table B). Table B has foreign key from table A primary key. I generated my java entities using netbeans IDE and i now have something like:
For table A:
#Entity
#Table(name = "WORKFLOW_TRANSACTION")
public class WorkflowTransaction implements {
#OneToMany(mappedBy = "wtId")
private Collection<WorkflowTask> workflowTaskCollection;
#Id
#Basic(optional = false)
#NotNull
#Column(name = "APP_ID")
private BigDecimal appId;
.
.
.
For table B:
#Entity
#Table(name = "WORKFLOW_TASK")
public class WorkflowTask implements Serializable {
#JoinColumn(name = "WT_ID", referencedColumnName = "APP_ID")
#ManyToOne
private WorkflowTransaction wtId;
#Id
#Basic(optional = false)
#NotNull
#Column(name = "TASK_ID")
private BigDecimal taskId;
#Column(name = "STEP_NUM")
private BigInteger stepNum;
.
.
.
Now my questions are:
What is the correct way to save data, should I create object for table B entity and set it in table A entity and then save table A?
I am generating entity beans using netbeans IDE feature. Are there any known disadvantages of it?, if yes, what?
You have bi-directional association, so you need to setTable B property in Table A entity class and also vice-versa. Since you have not declared cascading, you need to save the Table A entity first and then Table B entity.
Alternatively, if you save Table B and then Table A entity classes, the hibernate generates an extra SQL update command to maintain the relationship.
But if you want hibernate to save Table B entity when you save Table A entity then you need to add Cascade property:
#OneToMany(mappedBy = "wtId", cascade=CascadeType.ALL)
I don't think you will have any disadvantages if you use Netbeans for generating the entity classes, it save you time in writing the entities. But if you want to learn then writing entities without Netbeans is good.
To achieve this you can use two type of techniques XML mapping or Annotations
In both these techniques the common point is use
cascade="save-update"
What happens is that you set an attribute in one table getter and it will automatically insert into the many relation when inserting into one relation table

Prevent duplicate entry for unique constraint

I am trying to save tags which are related to an article in my MySQL database. The relation between those two columns is 1:N. Each item has an auto generated key. The name of a tag is unique.
If I insert a new article with an existing tag, I get a duplicate entry exception for the unique constraint (MySQLIntegrityConstraintViolationException). This are my two entities:
Article.java
#Entity
public class Article implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(cascade = CascadeType.ALL)
#JoinTable
private Set<Tag> tags = new HashSet<Tag>();
/* getter and setter */
}
Tag.java
#Entity
public class Tag implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(unique = true)
private String name;
/* getter and setter */
}
Hibernate generate following tables: article, tag, article_tag. For the first article the records are correct.
I use following code to insert a new article (only for testing):
Article article = new Article();
Tag tag = new Tag();
/* set the values */
EntityManager em = EMF.getInstance().get();
em.getTransaction().begin();
em.merge(article);
em.getTransaction().commit();
How could I get JPA to use the existing tag for the article instead of create a new one. How do I set the relation between those so components correctly?
In general the relationship between articles and tags is a many-to-many relationship as an article may have many tags and each of these tags may be reused in many articles.
To indicate a many-to-many relationship the #ManyToMany annotation is required.
Also to make something clear, in the OP it is indicated a unidirectionaly one-to-many relationship as the #JoinTable annotation has been used on the many side. This is the reason a join table has been created. In addition as a consequence if a #ManyToOne annotation is used in Tag class the many-to-one will be another unidirectional relationship. Just be careful there as they will be handled as two independent unidirectional relationship with probable strange behaviour and any configuration will not affect both entities, since it is not a biderictional relationship.
Finally, if it is required to have a one-to-many unidirectional relationship but also reuse the tags, it is required to retrieve them based on their name, so that they have the correct record id and then set the to the Article instance. If you try to set a new instance of Tag which will have no record id but a name that already exists, then the jpa provider will try to insert the new tag and a unique constraint exception will be thrown, because of the duplicate tag name. Also will need to remove any unique constraint referred to tag_id in article_tag table.

Using unique constraint on Hibernate JPA2

How can I implement my unique constraints on the hibernate POJO's? assuming the database doesn't contain any.
I have seen the unique attribute in #Column() annotation but I couldn't get it to work?
What if I want to apply this constraint to more than one column?
You can declare unique constraints using the #Table(uniqueConstraints = ...) annotation in your class
#Entity
#Table(uniqueConstraints=
#UniqueConstraint(columnNames = {"surname", "name"}))
public class SomeEntity {
...
}
Bascially, you cannot implement unique constraint without database support.
#UniqueConstraint and unique attribute of #Column are instructions for schema generation tool to generate the corresponsing constraints, they don't implement constraints itself.
You can do some kind of manual checking before inserting new entities, but in this case you should be aware of possible problems with concurrent transactions.
Therefore applying constraints in the database is the preferred choice.
In JPA2, you can add the Unique constraint directly to the field:
#Entity
#Table(name="PERSON_TABLE")
public class Person{
#Id
#Column(name = "UUID")
private String id;
#Column(name = "SOCIALSECURITY", unique=true)
private String socialSecurityNumber;
#Column(name = "LOGINID", unique=true)
private String loginId;
}
IMHO its much better to assign the unique constraint directly to the attributes than at the beggining of the table.
If you need to declare a composite unique key however, then declaring it in the #table annotation is your only option.

Categories

Resources