JPA - Eclipselink - InheritanceType.JOINED not doing the correct JOIN - jpa

I dont know if this is a bug or there is something I am missing, but there is a problem when you try to use Inheritance.JOINED when the superclass has a composite primary key.
I have the following classes:
(Superclass)
#Entity
#Table(name = "tipos_opciones_misiones")
#IdClass(TipoOpcionMisionId.class)
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "clase", discriminatorType = DiscriminatorType.STRING)
#ReadOnly
public abstract class TipoOpcionMision{
#Id
#ManyToOne
#JoinColumn(name="tipo_mision")
private TipoMision tipoMision;
#Id
#Column(name = "numero")
private int numero;
}
And a child class:
#Entity
#Table(name="tipos_misiones_opciones_comercio_compra")
#DiscriminatorValue("COMERCIO_COMPRA")
public class TipoOpcionMisionComercioCompra extends TipoOpcionMision{
#Column(name="valor")
double valor;
}
When I try to get a list on objects "TipoOpcionMision" the generated SQL ignores that there is a composite key [tipo_mision, numero] and it just uses "t1.numero = t0.numero". There should also be a "t1.tipo_mision= t0.tipo_mision".
SELECT **list of fields***, FROM tipos_misiones_opciones t0, tipos_misiones_opciones_comercio_compra t1 WHERE ((t0.tipo_mision = 'MISION_1') AND ((t1.numero = t0.numero) AND (t0.clase = 'COMERCIO_COMPRA')))
There is no errors, but I get false results because I am getting the values of the first row in the cartesian product.
I have tried to add:
#PrimaryKeyJoinColumns({
#PrimaryKeyJoinColumn(name = "tipo_mision", referencedColumnName = "tipo_mision"),
#PrimaryKeyJoinColumn(name = "numero", referencedColumnName = "numero")
})
But program fails when starting with the following error:
Exception Description: A #PrimaryKeyJoinColumns was found on the annotated element [class TipoOpcionMisionComercioCompra]. When the entity uses a single primary key, only a single (or zero) #PrimaryKeyJoinColumn should be specified.
It seems that for some reason Eclipselink is ignoring tthat the superclass has a composite primary key.

Related

Creating new JPA Entity with EmbeddedID

I have a very simple set of tables modelling a many to many relationship.
Foo -< FooBar >- Bar
Which is working fine when I select data but when I try to create a new instance of the relationship I can getting errors because JPA is trying to insert nulls.
I am having to set both the #ManyToOne values as well as the key (which represent the same things) and this makes me think this is not setup correctly.
Question is therefore how do I correctly setup the annotations to create a new FooBar relationship?
Entities
#Entity
#Table(name = "FOO")
public class Foo implements Serializable {
#Id
#Column(name = "FOO_ID")
private int id;
#column(name = "FOO_DESC")
private String desc;
#OneToMany(mappedBy = "foo")
private List<FooBar> fooBars;
//getters / setters / hashCode / equals
}
#Entity
#Table(name = "BAR")
public class Bar implements Serializable {
#Id
#Column(name = "BAR_ID")
private int id;
#column(name = "BAR_DESC")
private String desc;
#OneToMany(mappedBy = "bar")
private List<FooBar> fooBars;
//getters / setters / hashCode / equals
}
Intersection Table (and Key)
#Embeddable
public class FooBarKey implements Serializable {
private int fooId;
private int BarId;
//getters / setters / hashCode / equals
}
#Entity
#Table(name ="FOO_BAR_XREF")
public class FooBar implements Serializable {
#EmbeddedId
private FooBarKey key;
#MapsId("fooId")
#ManyToOne
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID")
private Foo foo;
#MapsId("barId")
#ManyToOne
#JoinColumn(name = "BAR_ID", referencedColumnName = "BAR_ID")
private Bar bar;
#Column(name = "DESC")
private String desc;
//getters / setters / hashCode / equals
}
Creating Relationship
Finally I am creating a new intersection instance:
//foo and bar exist and are populated
FooBar fb = new FooBar();
FooBarKey fbk = new FooBarKey();
fbk.setFooId(foo.getId());
fbk.setBarId(bar.getId());
fb.setKey(fbk);
fb.setDesc("Some Random Text");
entityManager.persist(fb);
at which point JPA errors with the insert saying it cannot insert null into FOO_ID.
I have checked and at the point I persist the object, the key is populated with both Foo and Bar IDs.
If I add
fb.setFoo(foo);
fb.setBar(bar);
prior to the persist it works but should the #MapsId not effectively tell JPA to map using the key?
I presume that I should be setting both the #ManyToOne and key values which are logically the same thing so I must have something not configured correctly?
I am using Eclipselink if that makes a difference.
When you use mapsId, you are telling JPA that this relationship, and the value of the target entity's primary key, is used to set the mapping named within the mapsId value. JPA then uses this relationship mapping to set the foreign key AND the basic mapping value when it flushes or commits. In your case, you left the relationship NULL, which forces the FK to be null when it gets inserted.
This allows sequencing to be delayed, as you may not have the primary key generated in the referenced entity when creating and traversing the graph - JPA will calculate and populate the values and propagate them through the graph when it needs them.
If you aren't using the ID class in your model, the simplest solution is just to remove it and avoid the overhead:
#Entity
#IdClass(package.FooBarKey.class)
#Table(name ="FOO_BAR_XREF")
public class FooBar implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID")
private Foo foo;
#Id
#ManyToOne
#JoinColumn(name = "BAR_ID", referencedColumnName = "BAR_ID")
private Bar bar;
#Column(name = "DESC")
private String desc;
//getters / setters / hashCode / equals
}
public class FooBarKey implements Serializable {
private int foo;
private int bar;
}
IdClass has similar restrictions to what is needed for #EmbeddedId but with one more - the names within it must match the property names designated with #Id, but the types must be the same as the ID class within the referenced entity. Pretty easy if you are using basic mappings within Foo and Bar, but can be more complex.
Adding more to your composite key is easy:
#Entity
#IdClass(package.FooBarKey.class)
#Table(name ="FOO_BAR_XREF")
public class FooBar implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID")
private Foo foo;
#Id
#ManyToOne
#JoinColumn(name = "BAR_ID", referencedColumnName = "BAR_ID")
private Bar bar;
#Id
private int someValue
#Column(name = "DESC")
private String desc;
//getters / setters / hashCode / equals
}
public class FooBarKey implements Serializable {
private int foo;
private int bar;
private int someValue
}
JPA will populate your foreign keys for you when the relationships are not-null, but any other fields require either sequencing or your own mechanisms to ensure they are populated prior to insert, and all Ids should be treated as immutable within JPA.

How to query base class in JPA with JOINED strategy

I have a class hierarchy mapped to a database using the JOINED strategy. I set discriminator values on the subclasses. When I persist an entity, the propery joined tables are populated and my discriminator column contains the correct discriminator for the class persisted to that row.
When I try to read back the data by either querying the base entity or through a relation on the base entity, I get the error:
<openjpa-2.4.2-r422266:1777108 nonfatal user error> org.apache.openjpa.persistence.ArgumentException: Could not map discriminator value "LabEngagement" to any known subclasses of the requested class "com.cerner.chronicle.fable.models.stories.Story" (known discriminator values: [Story]).
The list of known discriminator values does not contain the discriminator values I set on my subclasses.
My Entities are defined as:
#Entity
#Table(name = "STORY")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "DISCRIMINATOR_TYPE", discriminatorType = DiscriminatorType.STRING)
public abstract class Story extends AuditedEntity {}
#Entity
#Table(name = "USER_STORY")
#PrimaryKeyJoinColumn(name = "ID", referencedColumnName = "ID")
#DiscriminatorValue(value = "UserStory")
public class UserStory extends Story {}
#Entity
#Table(name = "SERVICE_ACCOUNT_STORY")
#PrimaryKeyJoinColumn(name = "ID", referencedColumnName = "ID")
#DiscriminatorValue(value = "ServiceAccount")
public class ServiceAccountStory extends Story {}
#Entity
#Table(name = "LAB_ENGAGEMENT_STORY")
#PrimaryKeyJoinColumn(name = "ID", referencedColumnName = "ID")
#DiscriminatorValue(value = "LabEngagement")
public class LabEngagementStory extends Story {}
I expect to be able to be able to run a query against the base entity with code such as:
final CriteriaQuery<Story> criteriaQuery = entityManager.getCriteriaBuilder().createQuery(Story.class);
criteriaQuery.select(criteriaQuery.from(Story.class));
final TypedQuery<Story> typedQuery = entityManager.createQuery(criteriaQuery);
final List<Story> results = typedQuery.getResultList();
and have the discriminator value be used to instantiate the proper Story type so that my results would contain both UserStory and ServiceAccountStory stories. Instead, I get the error noted above.
I can query the subclasses successfully by querying against the subclass entity:
final CriteriaQuery<UserStory> criteriaQuery = entityManager.getCriteriaBuilder().createQuery(UserStory.class);
criteriaQuery.select(criteriaQuery.from(UserStory.class));
final TypedQuery<UserStory> typedQuery = entityManager.createQuery(criteriaQuery);
final List<UserStory> results = typedQuery.getResultList();
but then I only get the list of UserStories.
It's unclear what value setting a discriminator provides if it doesn't allow for work against the base class. If I need to specify the subclass I clearly already know the type I'm dealing with and do not need a discriminator.
Am I doing something wrong? Are my expectations wrong?

How to reference a Composite Secondary Key

The below examples show what I tried to reference an entity by a unique combination of columns that is not its primary key.
I want to do it that way because the referencing table contains all the necessary constituents of the referenced "Composite Secondary Key" but it does not contain the Primary Key.
How can I achieve this? (If possible without being Hibernate specific)
Base example
Suppose an entity has a primary key as well as a composite secondary key:
#Entity
#Table(name = "EXAMPLE_DATA",
uniqueConstraints = {
#UniqueConstraint(columnNames = {
"COMPOSITE_KEY_PART_1",
"COMPOSITE_KEY_PART_2"})
})
public class ExampleData {
#Id
#Column
private Long exampleDataId;
#Column(name = "COMPOSITE_KEY_PART_1")
private String compositeKeyPart1;
#Column(name = "COMPOSITE_KEY_PART_2")
private String compositeKeyPart2;
}
I would like to reference it from another table by its composite secondary key:
#Entity
#Table(name = "EXAMPLE")
public class Example {
#Id
#Column
private Long exampleId;
#Column(name = "COMPOSITE_KEY_PART_1")
private String compositeKeyPart1; // of ExampleData
#Column(name = "COMPOSITE_KEY_PART_2")
private String compositeKeyPart2; // of ExampleData
#MayToOne
#JoinColumn(name = "COMPOSITE_KEY_PART_1", insertable = false, updatable = false)
#JoinColumn(name = "COMPOSITE_KEY_PART_2", insertable = false, updatable = false)
private ExampleData exampleData;
}
However, this leads to
org.hibernate.AnnotationException:
A Foreign key refering com.example.ExampleData from com.example.Example has the wrong number of column. should be 1
Making a separate #Embeddable composite key
I tried making the secondary key embeddable
#Embeddable
public class CompositeKey implements Serializable {
#Column(name = "COMPOSITE_KEY_PART_1")
private String compositeKeyPart1;
#Column(name = "COMPOSITE_KEY_PART_2")
private String compositeKeyPart2;
}
and using it as an embedded object:
#Entity
#Table(name = "EXAMPLE_DATA",
uniqueConstraints = {
#UniqueConstraint(columnNames = {
"COMPOSITE_KEY_PART_1",
"COMPOSITE_KEY_PART_2"})
})
public class ExampleData {
#Id
#Column
private Long exampleDataId;
#Embedded
private CompositeKey compositeKey;
}
but that leads to the same exception:
org.hibernate.AnnotationException:
A Foreign key refering com.example.ExampleData from com.example.Example has the wrong number of column. should be 1
Using #EmbeddedId
Using #EmbeddedId instead of just #Embedded leads to issues with multiple keys
org.hibernate.AnnotationException:
com.example.CompositeKey must not have #Id properties when used as an #EmbeddedId: com.example.ExampleData.compositeKey
Having only a single key works but is undesirable
The only way I can actually make it work is by removing the primary key and making the composite key the primary key (but I don't want that)
#Entity
#Table(name = "EXAMPLE_DATA")
public class ExampleData {
// #Id // removing this primary key is undesirable
#Column
private Long exampleDataId;
#EmbeddedId // This now becomes the primary key
private CompositeKey compositeKey;
}

spring data error when trying to sort by a field of joined entity inside a crudrepository

I am using springboot and springdata with Mysql.
I have 2 entities, Customer & Order:
#Entity
#Table(name = "customers")
public class Customer {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="id", nullable = false)
protected long id;
#Column(name = "name")
private String name;
}
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="id", nullable = false)
protected long id;
#Column(name="customer_id")
private long customerId;
}
I also have a repository:
#Repository
public interface OrdersRepository extends JpaRepository<Order, Long> {
#Query("select o from Order o, Customer c where o.customerId = c.id")
Page<Order> searchOrders(final Pageable pageable);
}
The method has some more arguments for searching, but the problem is when I send a PageRequest object with sort that is a property of Customer.
e.g.
Sort sort = new Sort(Sort.Direction.ASC, "c.name");
ordersRepository.search(new PageRequest(x, y, sort));
However, sorting by a field of Order works well:
Sort sort = new Sort(Sort.Direction.ASC, "id");
ordersRepository.search(new PageRequest(x, y, sort));
The error I get is that c is not a property of Order (but since the query is a join of the entities I would expect it to work).
Caused by: org.hibernate.QueryException: could not resolve property c of Order
Do you have any idea how I can sort by a field of the joined entity?
Thank you
In JPA , the thing that you sort with must be something that is returned in the select statement, you can't sort with a property that is not returned
You got the error because the relationship is not modeled properly. In your case it is a ManyToOne relation. I can recomend the wikibooks to read further.
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name="id", nullable = false)
protected long id;
#ManyToOne
#JoinColumn(name="customer_id", referencedColumnName = "id")
private Customer customer;
}
The query is not needed anymore because the customer will be fetched.
#Repository
public interface OrdersRepository extends PagingAndSortingRepository<Order, Long> {
}
Now you can use nested properties.
Sort sort = new Sort(Sort.Direction.ASC, "customer.name");
ordersRepository.findAll(new PageRequest(x, y, sort));

#ElementCollection of #Embeddable containing #ManyToOne

I have the following model
#Entity
#Table(name = "GRAPH")
public class Graph {
[...]
#ElementCollection
#CollectionTable(name = "ROOT", joinColumns = #JoinColumn(name = "GRAPH", nullable = false))
private Set<Root> roots;
}
#Entity
#Table(name = "NODE")
public class Node {
[...]
}
#Embeddable
public class Root {
[...]
#ManyToOne(optional = false)
#JoinColumn(name = "NODE", nullable = false)
private Node node;
}
I use EclipseLink as the JPA Provider. When letting EclipseLink generate the DDL for this structure, the following stuff happens:
The is no primary key on the ROOT table (OK, it is an #Embeddable, and it does not have an identity)
A foreign key is generated from ROOT.GRAPH to GRAPH.ID (As expected)
There is no foreign key from ROOT.NODE to NODE.ID (That's something I can not understand)
Can you help explain me the cause for this behavior? Is there something that can be done about the primary key and the missing foreign key?
Thanks,
M.