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.
Related
Question: Am I somehow able to map two fields of my Entity class to only one Column in the Database?
Scenario: The database is not fully normalized. There exists one Column which contains a composite information. It is not my actual use case, but an comprehensible example might be X- and Y-coordinate of a point in the plane. So the Database may contain a String 12:45 and the Entity class should contain only two integer field x width value 12 and ywith value 45.
Currently the Entity class has just two additional getter and setter for x and y and performs the proper translation. But I am wondering if there is a way to let JPA do this for me magically in the background.
I am already working with custom converter classes, e.g. for a proper mapping between between enums and database columns, but this works only for a "one-to-one" mapping between the field in the Entity class and the column in the database.
Of course it would be the most preferable way to redesign the table in the database, but that's not an option at the moment.
Vendor specific solutions are also fine.
2 Entity fields into one database column can be done fairly simply by specifying JPA use your accessor in the entity to handle the conversion:
#Entity
#Access(AccessType.FIELD)
class myEntity {
#Id
int id;
#Transient
String x;
#Transient
String y;
#Mutable //EclipseLink specific to prevent change tracking issues
#Access(AccessType.PROPERTY)
#Column(name="yourDatabaseFieldName")
private String getCoords() {
return x+":"+y;
}
private void setCoords(String coords) {
//parse the string and set x+y.
}
EclipseLink and Hibernate have transformation mappings that are able to handle the reverse; 2 or more database fields into one java property but this is outside of JPA.
Is it possible to create a basic FK relationship in JPA without involving the full entity target object?
As an example, imagine I have an entity:
#Entity(name = "Mechanic")
public class Mechanic {
#Id
private Long id;
//...
and a Car that I want to reference a Mechanic.id:
#Entity(name = "Car")
public class Car {
//...
#NotNull
private Long mechanic_id;
From an Object perspective, this would be a unidirectional, one to one relationship with the Car requiring a Mechanic.id and the Mechanic not needing any back reference to Car.
All I want out of this is to store the Mechanic.id ONLY. For the purposes of this question it is not useful to have a #OneToOne (or #OneToMany etc) relationship with the entity reference, I'm explicitly trying to avoid that but still retain the underlying integrity that a FK will provide.
JPA 2 and I'm using EclipseLink.
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.
I suspect there's no perfect solution to this problem so least worst solution are more than welcome.
I'm implementing a dashboard using PrimeFaces and I would like to persist the model backing it (using JPA2). I've written my own implementation of DashboardModel and DashboardColumn with the necessary annotations and other fields I need. The model is shown below:
#Entity
public class DashboardSettings implements DashboardModel, Serializable{
#Id
private long id;
#OrderColumn( name="COLUMN_ORDER" )
private List<DashboardColumn> columns;
...a few other fields...
public DashboardSettings() {}
#Override
public void addColumn(DashboardColumn column) {
this.columns.add(column);
}
#Override
public List<DashboardColumn> getColumns() {
return columns;
}
...snip...
}
The problem is the columns field. I would like this field to be persisted into it's own table but because DashboardColumn is an interface (and from a third party so can't be changed) the field currently gets stored in a blob. If I change the type of the columns field to my own implementation (DashboardColumnSettings) which is marked with #Entity the addColumn method would cease to work correctly - it would have to do a type check and cast.
The type check and cast is not the end of the world as this code will only be consumed by our development team but it is a trip hazard. Is there any way to have the columns field persisted while at the same time leaving it as a DashboardColumn?
You can try to use targetEntity attribute, though I'm note sure it would be better than explicit cast:
#OrderColumn( name="COLUMN_ORDER" )
#OneToMany(targetEntity = DashboardColumnSettings.class)
private List<DashboardColumn> columns;
Depends on the JPA implementation (you don't mention which one); the JPA spec doesn't define support for interface fields, nor for Collections of interfaces. DataNucleus JPA certainly allows it, primarily because we support it for JDO also, being something that is part of the JDO spec.
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.