How to create a composite primary key which contains a #ManyToOne attribute as an #EmbeddedId in JPA? - jpa

I'm asking and answering my own question, but i'm not assuming i have the best answer. If you have a better one, please post it!
Related questions:
How to set a backreference from an #EmbeddedId in JPA
hibernate mapping where embeddedid (?)
JPA Compound key with #EmbeddedId
I have a pair of classes which are in a simple aggregation relationship: any instance of one owns some number of instances of the other. The owning class has some sort of primary key of its own, and the owned class has a many-to-one to this class via a corresponding foreign key. I would like the owned class to have a primary key comprising that foreign key plus some additional information.
For the sake of argument, let's use those perennial favourites, Order and OrderLine.
The SQL looks something like this:
-- 'order' may have been a poor choice of name, given that it's an SQL keyword!
create table Order_ (
orderId integer primary key
);
create table OrderLine (
orderId integer not null references Order_,
lineNo integer not null,
primary key (orderId, lineNo)
);
I would like to map this into Java using JPA. Order is trivial, and OrderLine can be handled with an #IdClass. Here's the code for that - the code is fairly conventional, and i hope you'll forgive my idiosyncrasies.
However, using #IdClass involves writing an ID class which duplicates the fields in the OrderLine. I would like to avoid that duplication, so i would like to use #EmbeddedId instead.
However, a naive attempt to do this fails:
#Embeddable
public class OrderLineKey {
#ManyToOne
private Order order;
private int lineNo;
}
OpenJPA rejects the use of that as an #EmbeddedId. I haven't tried other providers, but i wouldn't expect them to succeed, because the JPA specification requires that the fields making up an ID class be basic, not relationships.
So, what can i do? How can i write a class whose key contains #ManyToOne relationship, but is handled as an #EmbeddedId?

I don't know of a way to do this which doesn't involve duplicating any fields (sorry!). But it can be done in a straightforward and standard way that involves duplicating only the relationship fields. The key is the #MapsId annotation introduced in JPA 2.
The embeddable key class looks like this:
#Embeddable
public class OrderLineKey {
private int orderId;
private int lineNo;
}
And the embedding entity class looks like this:
#Entity
public class OrderLine{
#EmbeddedId
private OrderLineKey id;
#ManyToOne
#MapsId("orderId")
private Order order;
}
The #MapsId annotation declares that the relationship field to which it is applied effectively re-maps a basic field from the embedded ID.
Here's the code for OrderId.

Related

JPA how ensure uniqueness over 2 fields, string and boolean

I want to create an entity containing 2 fields that need to be unique in together. One of the fields is a Boolean:
#Entity
public class SoldToCountry {
private String countryId;
private Boolean isInt;
}
For a given String there should never exist more than 2 entries one with isInt:true and the other isInt:false.
I read the doc about #Id but it seems that Boolean is not supported. For me it would also be ok to have a unique constraint spanned over both fields and using a generated id.
What is the best way to get this constraint via JPA?
If your table has really two fields only, and you want they are unique, then they should be the composite PK of the table. Take a look at How to create and handle composite primary key in JPA
If, instead, you have another PK, consider Sebastian's comment.

Composite key with JPA entity, implementing tree of objects in one table?

I have one table named PLACES with one composite primary key (parent_id, version_id). It is a tree of objects, which are linked through the keys. One child has just one parent, and one parent may have many children.
How can I describe it with JPA entity?
Use a ManyToOne relation from the child to the parent.
This is for OpenJpa. Might even work.
public class Place{
#EmbeddedId
PlaceId id;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumns({
#JoinColumn(name="PARENT_ID" referencedColumnName="ID"), // ID = matching primary key
#JoinColumn(name="PARENT_VER" referencedColumnName="VER") //etc
})
public Place parent;
#OneToMany(fetch=FetchType.LAZY, mappedBy="parent")
public List<Place> childPlaces;
}
The OneToMany relation might be omitted if it's not needed. If I remember correctly, it needs to be managed, ie childs need to be inserted there too when creating child-places, by you, using java.
Btw.
I would advise against using a version column in a composite key in order to manually keep old versions of your data (for auditing or similar purposes) as that slows down and complicates all joins, and generally will make you miserable at some point in your life - As opposed to using a version column that is not part of a composite key, used for optimistic locking.
You might want to look into some kind of build in support for auditing/logging. OpenJpa has auditing support (OpenJPA Audit) and most database provide some support, either out-of-the-box or by using triggers. All alternatives are faster and better than using composite keys.

JPA #EmbeddedId: How to update part of a composite primary key?

I have a many-to-many relationship where the link table has an additional property. Hence the link table is represented by an entity class too and called Composition. The primary key of Composition is an #Embeddable linking to the according entities, eg. 2 #ManyToOne references.
It can happen that a user makes an error when selecting either of the 2 references and hence the composite primary key must be updated. However due to how JPA (hibernate) works this will of course always create a new row (insert) instead of an update and the old Composition will still exist. The end result being that a new row was added instead of one being updated.
Option 1:
The old Composition could just be deleted before the new one is inserted but that would require that the according method handling this requires both the old and new version. plus since the updated version is actually a new entity optimistic locking will not work and hence last update will always win.
Option 2:
Native query. The query also increments version column and includes version in WHERE clause. Throw OptimisticLockException if update count is 0 (concurrent modification or deletion)
What is the better choice? What is the "common approach" to this issue?
Why not just change the primary key of Composition to be a UID which is auto-generated? Then the users could change the two references to the entities being joined without having to delete/re-create the Composition entity. Optimistic locking would then be maintained.
EDIT: For example:
#Entity
#Table(name = "COMPOSITION")
public class Composition {
#Id
#Column(name = "ID")
private Long id; // Auto-generate using preferred method
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn( .... as appropriate .... )
private FirstEntity firstEntity;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn( .... as appropriate .... )
private SecondEntity secondEntity;
....

JPA 2.0 retrieve entity by business key

I know there have been a number of similar posts about this, but I couldn't find a clear answer to my problem.
To make it as simple as possible, say I have such an entity:
#Entity
public class Person implements Serializable {
#Id
private Long id; // PK
private String name; // business key
/* getters and setters */
/*
override equals() and hashCode()
to use the **name** field
*/
}
So, id is the PK and name is the business key.
Say that I get a list of names, with possible duplicates, which I want to store.
If I simply create one object per name, and let JPA make it persistent, my final table will contain duplicate names - Not acceptable.
My question is what you think is the best approach, considering the alternatives I describe here below and (especially welcome) your own.
Possible solution 1: check the entity manager
Before creating a new person object, check if one with the same person name is already managed.
Problem: The entity manager can only be queried by PK. IS there any workaround Idon't know about?
Possible solution 2: find objects by query
Query query = em.createQuery("SELECT p FROM Person p WHERE p.name = ...");
List<Person> list = query.getResultList();
Questions: Should the objects requested be already loaded in the em, will this still fetch from database? If so, I suppose it would still be not very efficient if done very frequently, due to parsing the query?
Possible solution 3: keep a separate dictionary
This is possible because equals() and hashCode() are overridden to use the field name.
Map<String,Person> personDict = new HashMap<String,Person>();
for(String n : incomingNames) {
Person p = personDict.get(n);
if (p == null) {
p = new Person();
p.setName(n);
em.persist(p);
personDict.put(n,p);
}
// do something with it
}
Problem 1: Wasting memory for large collections, as this is essentially what the entity manager does (not quite though!)
Problem 2: Suppose that I have a more complex schema, and that after the initial writing my application gets closed, started again, and needs to re-load the database. If all tables are loaded explicitly into the em, then I can easily re-populate the dictionaries (one per entity), but if I use lazy fetch and/or cascade read, then it's not so easy.
I started recently with JPA (I use EclipseLink), so perhaps I am missing something fundamental here, because this issue seems to boil down to a very common usage pattern.
Please enlighten me!
The best solution which I can think of is pretty simple, use a Unique Constraint
#Entity
#UniqueConstraint(columnNames="name")
public class Person implements Serializable {
#Id
private Long id; // PK
private String name; // business key
}
The only way to ensure that the field can be used (correctly) as a key is to create a unique constraint on it. You can do this using #UniqueConstraint(columnNames="name") or using #Column(unique = true).
Upon trying to insert a duplicate key the EntityManager (actually, the DB) will throw an exception. This scenario is also true for a manually set primary key.
The only way to prevent the exception is to do a select on the key and check if it exists.

DiscriminatorColumn as part of primary key / id

Situation
I have an Entity with a DiscriminatorColumn, configured for single table inheritance:
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="TYPE")
public class ContainerAssignment{
...
}
'ContainerAssignment' has a reference to another Entity:
#JoinColumn(name="CONTAINER_ID")
private Container container;
A container may have one ContainerAssignment of each TYPE. This means that the primary key of the ContainerAssignment table is defined by the CONTAINER_ID and the TYPE.
ContainerAssignment has some subclasses e.g.
#Entity
#DiscriminatorValue("SOME_TYPE")
public class SomeTypeOfContainerAssignment extends ContainerAssignment{
...
}
There will only be a single SomeTypeOfContainerAssignment instance for a given CONTAINER_ID.
Problem
If I define the JPA #Id as just the Container on the ContainerAssignment table, I can do entityManager.find(SomeTypeOfContainerAssignment.class, containerId), which is great. This runs something along the lines of SELECT * FROM CONTAINER_ASSIGNMENT WHERE CONTAINER_ID = 1 AND TYPE = 'SOME_TYPE';. It knows it needs the TYPE check in here, because of the #DiscriminatorValue("SOME_TYPE") annotation on the Entity.
However, this means that the back references from Container to ContainerAssignment breaks as Container is not really the primary key. For example, if Container has a #OneToOne(mappedBy=container) private SomeTypeOfContainerAssignment assignment;, when you read in a container, it will read in the assignment by something like SELECT * FROM CONTAINER_ASSIGNMENT WHERE CONTAINER_ID = 1;, without the type checking. This gives it all assignments for a container, and then it picks one seemingly at random, potentially of the wrong type, in which case, it throws an exception.
If instead, I define the JPA #Id of ContainerAssignment as a composite id using container and type, references to the sub-classes of ContainerAssignment work fine.
However, I cannot do entityManager.find(SomeTypeOfContainerAssignment.class, containerId), because containerId is not the id. I have to do entityManager.find(SomeTypeOfContainerAssignment.class, new MyPk(containerId, "SOME_TYPE")), which seems to defeate the point of #DiscriminatorValue("SOME_TYPE"). I might as well just use a single ContainerAssignment Entity if I have to specify type on find anyway.
Question
Is there a way to have working references to sub-classes of a single table inheritance Entity where the primary key on the table is composite on the discriminator column, whilst also being able to EntityManager.find by just the part(s) of the primary key which are not the discriminator?
I´m going to assume that the composite primary key of ContainerAssignment is working fine (I really think it may be JPA implementation dependent!), and all that still bothers you is the annoying call to the entityManager.find and PK instantiation.
My solution is to define finder methods independent of the JPA API. Don´t lock yourself to JPA.
The simplest way is to just define a static finder at your domain class (or, define another class with just finders, if you want to keep domain uncoupled do JPA. Dig at IoC to know how to do that).
At ContainerAssignment (or your finder class):
public static <T extends ContainerAssignment> T findByPK(EntityManager manager,Class<T> type,long id) {
DiscriminatorValue val = type.getAnnotation(DiscriminatorValue.class); // this is not optimal...can be cached...
return (T) manager.find(type, new MyPk(containerId, val.getValue()));
}
At your code:
SomeTypeOfContainerAssignment ca = ContainerAssignment.findByPK(entityManager,SomeTypeOfContainerAssignment.class,containerId);
Notice that making the type part of the PK means that you can have two ContainerAssignment instances of distinct types with the same id. You going to need a Query to retrieve ContainerAssignment if you don´t know its type. If, however, your id is generated from a sequence, you can just write another finder method that hides the inner calls to entity framework, returning the first result of the resultset.
If Container has a bidirectional OneToOne with SomeTypeOfContainerAssignment, which extends ContainerAssignment, then the container field should not be defined and mapped in ContainerAssignment, but in SomeTypeOfContainerAssignment:
public class Container {
#Id
private Long id;
#OneToOne(mappedBy = "container")
private SomeTypeOfContainerAssignment someTypeOfContainerAssignment;
}
public class ContainerAssignment {
#Id
private Long id;
}
public class SomeTypeOfContainerAssignment extends ContainerAssignment {
#OneToOne
private Container container;
}
If all the types of container assignments have such a OneToOne association with COntainer, you can define the Container as
public abstract class ContainerAssignment {
#Id
private Long id;
public abstract Container getContainer();
public abstract void setContainer(Container container);
}
To be honest, I don't know if you're allowed to use the same join column in the table to map the #OneToOne container fields of each subclass.
I think this is the best you can have. If you put the container field in the base class, then you must define the association as a OneToMany/ManyToOne association, since it's what it really is.
I don't think what you want to do is possible, and I wouldn't mess with composite PKs, as they're discouraged for good reasons, and a nightmare to use.
If you are okay with provider specific extension, Hibernate provides annotation #DiscriminatorOptions.
It helped me to solve a problem where the discriminator column is part of composite primary key.