I have a group of tables, that are all identical apart from their owner table, and the corresponding foreign keys to that table. I made it all generic thanks to Hibernate/JPA, but cannot pass the #JoinColumn information via #AssociationOverride since the name value for it is ignored, or not overridden at all.
For example;
#Data
#Entity
#Table(name = "etc")
#AssociationOverride(name = "parent", joinColumns = #JoinColumn(name = "id"))
public class RealEntity extends BaseEntity<ParentEntity, String> {
}
with;
#Data
#MappedSuperClass
public class BaseEntity<K, P> implements Serializable {
#EmbeddedId
protected Key<K> key = new Key<>();
#MapsId("fk")
#ManyToOne
#JsonBackReference
protected P parent;
#Data
#Embeddable
public static class Key<K> implements Serializable {
protected K fk;
#Column(name = "sub_id")
protected String subId;
}
}
parent:
#Data
#Entity
#Table(name = "parentc")
public class ParentEntity implements Serializable {
#Column(name = "id")
protected String parentId;
}
So as you see, it works well except for the parent's fk reference definition, I get mapping error since Hibernate tries to find a parent_id, rather than just id for the foreignKey, since #JoinColumn override is ignored. It works if I put the parent information in the RealEntity directly (obviously), or if I put #JoinColumn(name = "id") on parent in BaseEntity but I want to keep it as generic as possible. Is there any solution to this issue? Or should I just give up?
edit: it seems when I put a proper #JoinColumn with acceptable mapping for joining on parent in BaseEntity, that does get overridden, so it needs something valid to override. I cannot just add an association from nothingness is that the case? I've seen many examples on the web where they were putting associations from scratch, my usage of #MapsId, might be breaking the usage I guess. But I cannot change my current structure, since it is necessary to be able to represent composite foreign key definition for dependent child tables... I feel like there is a very simple solution, or some hacky way to achieve what I want, and I cannot seem to find it!
Related
I'm trying to make an #OneToOne mapping following the https://vladmihalcea.com/the-best-way-to-map-a-onetoone-relationship-with-jpa-and-hibernate/ the mapping itself works but its triggering an N+1 query problem.
The query is being made on the parent entity service and its triggering N+1 queries.
How can I improve this code to only make 1 query? We don't need to access the ParentDetails in this case.
EDIT: I've tried using JPQL and LEFT JOIN FETCH ParentDetails and didn't work either.
EDIT2: Just to try to add more information. I've put a breakpoint on the getParentDetails just to make sure I was not calling the getter anywhere and I'm not calling and double-checked and it seems a join problem on the repo call.
Let's go to the code:
Parent
#Entity
#DynamicUpdate
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
mappedBy = "parent")
private ParentDetails parentDetails;
// Getters, setters, etc omitted for brevity
}
ParentDetails
#Entity
public class ParentDetails {
#Id
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private Parent parent;
// Getters, setters, etc omitted for brevity
ParentDetailsRepository
#Repository
public interface ParentRepository extends JpaRepository<Parent, Long> {
Page<Parent>findByNameOrderByName(#Param("name") final String name,final Pageable pageable);
}
Hibernate executes the additional queries because the Parent entity doesn't map the foreign key column. Hibernate doesn't support lazy fetching for that end of the association. When Hibernate instantiates a Parent object, it needs to check if it needs to initialize the association with a proxy or a null object. And at some point, the team decided that they would fetch the associated entity if they are forced to perform a query anyways. I explained that in more detail here: https://thorben-janssen.com/hibernate-tip-lazy-loading-one-to-one
If you want to avoid the additional queries, you need to model a unidirectional association between ParentDetails and Parent. In your example, that would mean that you need to remove the parentDetails attribute from your Parent entity.
#Entity
#DynamicUpdate
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// Getters, setters, etc omitted for brevity
}
Because your ParentDetails entity uses the same id value as the Parent entity, you don't need a bidirectional association. If you want to get the ParentDetails entity for a Parent entity, you can get it with a call of the em.find(...) method
Parent p = // fetch the parent object ...
ParentDetails pd = em.find(p.getId(), ParentDetails.class);
I'm dealing with a couple of Entities with Tree like structures that were getting more complicated so I decided to create an abstract class for it so code was a bit more mainainable:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class TreeStructure<T extends TreeStructure>
{
#ManyToOne
protected T parent;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
protected Set<T> children = new HashSet<>();
//...
Then I have two Entities which extend it:
#Entity(name = "TreeStructureOne")
public class TreeStructureOne extends TreeStructure<TreeStructureOne>
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonProperty("TreeStructureOne_id")
private long id;
And I basically want the database to be completely unaware of this TreeStructure abstraction and save all of the fields in each Entities tableand expected InheritanceType.TABLE_PER_CLASS to deal with that. But it seems I need to define the Id in the TreeStructure Entity at least or I get:
Invocation of init method failed; nested exception is org.hibernate.AnnotationException: No identifier specified for entity: TreeStructure
And I don't want to add an ID into the abstract class since this makes three tables in the database called: HT_TREE_STRUCTURE, HT_TREE_STRUCTURE_ONE and HT_TREE_STRUCTURE_TWO with one field ID each one.
Is there any solution to that?
Since TreeStructure is not an #Entity use only #MappedSuperclass
#MappedSuperclass
public abstract class TreeStructure<T extends TreeStructure> {
instead of #Entity and #Inheritance for the parent class.
You can find #MappedSuperclass in the Oracle JEE API documentation.
I'm using Play Framework 2.4. I want to model a set of classes with this inheritance chain:
Research <- Publication <- Article
The problem is, I would like the Publication class to refer to the other objects of this class, like this:
#MappedSuperclass
public abstract class Research extends Model {
#Id
public Long id;
}
#Entity
public class Publication extends Model {
#ManyToOne
public Publication translationOf;
}
This works when I don't inherit from Publication.
When I add inheriting from Article:
#MappedSuperclass
public abstract class Publication extends Model {
#ManyToOne
public Publication translationOf;
}
#Entity
public class Article extends Publication { }
I get:
java.lang.RuntimeException: Error with association
to [class models.research.Publication]
from [models.research.publication.Article.translationOf].
Is class models.research.Publication registered?
I thought that maybe configuring the InheritanceType explicity will help, but adding #Inheritance tag causes NullPointerException without any other informative nested exceptions in the stacktrace. This happens for example in this case:
#MappedSuperclass
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "dtype", discriminatorType = DiscriminatorType.STRING)
public abstract class Research extends Model {
#Id
public Long id;
public String dtype;
}
#Entity
#DiscriminatorValue("P")
public class Publication extends Model {
#ManyToOne
public Publication translationOf;
}
In your first example, Publication is an #Entity. After that it's suddenly a #Mappedsuperclass
I don't understand what you want here. Do you want one publication to be linked to other publications? Because that's a ManyToMany relationship.
And for ManyToMany relationships, you need to specify the "mappedBy" argument.
Same for articles and publications. One Article could appear in many Publications and many publications could have the same article.
Unless it has to be specifically different for your setup?
EDIT: Played around with it a bit, this setup works:
#MappedSuperclass
public abstract class Research extends Model {
#Id
public Long id;
}
#Entity
public class Publication extends Research {
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "myReferences")
// What other publications refer to this one?
public List<Publication> referencedBy;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "CROSS_REFERENCE",
joinColumns = {
#JoinColumn(name = "MY_ID")
},
inverseJoinColumns = {
#JoinColumn(name = "REFERENCER_ID")
})
// What publications am I referring to?
public List<Publication> myReferences;
// The list of articles in this publication.
#ManyToMany(cascade = CascadeType.ALL)
public List<Article> articles;
}
#Entity
public class Article extends Research {
// The publications this article appears in.
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "articles")
public List<Publication> publications;
}
ManyToMany relations always need have an "owner".
In the publication-to-publication case, it is the myReferences field. This is why the "referencedBy" field has the "mappedBy" argument in the #ManyToMany annotation.
A publication can't know ahead of time what future publications will reference them and past publications won't change to reference this new one.
So you can only ever say of a new publication which old ones it is referencing.
And which articles it contains.
You can add in both directions, but it is generally a best practice to use 1 direction and stick to it.
Hope it helps.
I'm trying to write a hibernate adapter for an old database schema. This schema does not have a dedicated id column, but uses about three other columns to join data.
On some tables, I need to use coalesce. This is what I came up with so far:
About the definition:
A car can have elements, assigned by the car's user or by the car's group of users.
If FORIGN_ELEMENT holds a user's name, definition will be 'u'
If FORIGN_ELEMENT holds a group's name, definition will be 'g'
This also means, one table (CAR_TO_ELEMENT) is misused to map cars to elements and cargroups to elements. I defined a superclass CarElement and subclasses CarUserElement and CarGroupElement.
state is either "active" or an uninteresting string
I set definitition and state elsewhere, we do not need to worry about this.
Use DEP_NR on the join table. If it's zero, use USR_DEP_NR. I did this with COALESCE(NULLIF()) successfully in native SQL and want to achieve the same in Hibernate with Pojos.
Okay, here we go with the code:
#Entity
#Table(name="CAR")
public class Car extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="USER_NAME")
#Type(type="TrimmedString")
private String username;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=CarGroup.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private CarGroup group;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarUserElement.class, mappedBy="car")
private Set<CarUserElement> elements;
}
#Entity
#Table(name="CAR_GROUP")
public class CarGroup extends TableEntry implements Serializable {
#Id
#Column(name="DEP_NR")
private int depnr;
#Id
#Column(name="GROUP_NAME")
#Type(type="TrimmedString")
private String group;
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Car.class)
#JoinColumns(value={
#JoinColumn(name="GROUP_NAME"),
#JoinColumn(name="DEP_NR"),
#JoinColumn(name="state"),
})
private Set<Car> cars;
#OneToMany(fetch=FetchType.EAGER, targetEntity=CarGroupElement.class, mappedBy="car")
private Set<CarGroupElement> elements;
}
#MappedSuperclass
public class CarElement extends TableEntry {
#Id
#ManyToOne(fetch = FetchType.EAGER, targetEntity=Element.class)
#JoinColumns(value={
#JoinColumn(name="ELEMENT_NAME"),
#JoinColumn(name="state"),
})
private Element element;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarGroupElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="GROUP_NAME")),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE"))
})
private Car car;
}
I tried all available versions of hibernate (from 3.5.1 [first version with #JoinColumnsOrFormulas] up to 4.x.x), but I always get this error:
Exception in thread "main" java.lang.ClassCastException: org.hibernate.mapping.Formula cannot be cast to org.hibernate.mapping.Column
at org.hibernate.cfg.annotations.TableBinder.bindFk(TableBinder.java:351)
at org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1338)
at org.hibernate.cfg.annotations.CollectionBinder.bindOneToManySecondPass(CollectionBinder.java:791)
at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:719)
at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:668)
at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:66)
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1597)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1355)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1737)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1788)
Other hibernate users seem to have the same problem: They can't get it working with any version, see this thread and other stackoverflow questions:
https://forum.hibernate.org/viewtopic.php?f=1&t=1010559
To be more complete, here's my TrimmedString Class:
https://forum.hibernate.org/viewtopic.php?p=2191674&sid=049b85950db50a8bd145f9dac49a5f6e#p2191674
Thanks in advance!
PS: It works with joining just these three colulmns with just one DEP-NR-Column (i.e. either DEP_NR OR USR_DEP_NR using just #JoinColumns). But I need this coalesce(nullif()).
I ran into a similar problem, and it seems that the issue is that you are using a #Formula inside an #Id. Hibernate wants Ids to be insertable, and Formulas are read-only.
In my case I was able to work around the problem by making the individual columns Id properties on their own, and making the joined object a separate property. I don't know if this would work in your case since you're using two different columns in your formula, but if so your code might look something like:
#Entity
#Table(name="CAR_TO_ELEMENT")
public class CarUserElement extends CarElement {
#Id
#Column(name="DEFINITION")
private char definition;
#Id
#Column(name="DEP_NR")
private Integer depNr;
#Id
#Column(name="USR_DEP_NR")
private Integer usrDepNr;
#Id
#Column(name="FORIGN_ELEMENT")
private String userName;
#Id
#Column(name="STATE")
private String state;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(formula=#JoinFormula(value="COALESCE(NULLIF(DEP_NR, 0), USR_DEP_NR)", referencedColumnName="DEP_NR")),
#JoinColumnOrFormula(column=#JoinColumn(name="FORIGN_ELEMENT", referencedColumnName="USER_NAME", insertable = false, updatable = false)),
#JoinColumnOrFormula(column=#JoinColumn(name="STATE", referencedColumnName="STATE", insertable = false, updatable = false))
})
private Car car;
}
Join formulas are very fragile in Hibernate for the time being; I always had a difficult time to get them work properly.
The workaround that helped me often was to create database views which exposed the proper columns (including foreign keys that don't exist in the original tables). Then I mapped the entities to the views using classing Hibernate/JPA mappings.
Sometimes there are redundant joins in the generated SQL when using such entities, but the database optimizes such queries in most cases so that the execution plan is optimal anyway.
Another approach could be using #Subselects, which are some kind of Hibernate views, but I expect them to be less performant than the classic database views.
I ran into the cast exception as well and I'm on Hibernate 5.x.
Until Hibernate dedicates time to fix the issue, I found that while this guy's approach may not be cleanest (he even eludes to that fact!), it works.
You just need to add the #Column mappings (and get/set methods) to your association table objects that are returning null and manually set the values when you populate the relation data. Simple but effective!
I have searched and found similar issues, but they don't quite seem to be the same problem as
Why am I getting this NullPointer exception?
OneToOne Mapping with hibernate/JBoss/Seam
ANN-613 - NPE when mappedBy property is wrong on a #OneToOne
ANN-558 - #OneToMany(mappedBy="") can not recognize properties in parent classes
Hibernate Users - NPE with #Id on #OneToOne
I have a few entities mapped like this:
Person
|
+--User
I want to add a new entity PersonPartDeux with a OneToOne mapping to Person. The resulting mapping should look something like this:
Person + PersonPartDeux
|
+--User
When I do so, a NullPointerException is thrown while trying to load the mapping:
java.lang.NullPointerException
at org.hibernate.cfg.OneToOneSecondPass.doSecondPass(OneToOneSecondPass.java:135)
How do I specify the mapping so I can avoid this exception?
Here's my code:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Person implements Serializable
{
#Id
#GeneratedValue
public Long id;
#Version
public int version = 0;
public String name;
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
public PersonPartDeux personPartDeux;
}
#Entity
public class PersonPartDeux implements Serializable
{
#Id
#GeneratedValue(generator = "person-primarykey")
#GenericGenerator(
name = "person-primarykey",
strategy = "foreign",
parameters = #Parameter(name = "property", value = "person")
)
public Long id = null;
#Version
public int version = 0;
#OneToOne(optional=false, mappedBy="person")
public Person person;
public String someText;
}
#Entity
#PrimaryKeyJoinColumn(name = "person_Id")
public class User extends Person
{
public String username;
public String password;
}
As for why I'm bothering, I need both the inheritance and the OneToOne mapping to solve different known issues in my application.
Attach the Hibernate source to your project, so you can click thru or 'Open Type' (Ctrl-Shift-T in Eclipse) to view the OneToOneSecondPass source.
Seeing the source, will give you a clear indication as to what needs to be specified.
In my source (Hibernate 4.1.7), line 135 is
propertyHolder.addProperty( prop, inferredData.getDeclaringClass() );
However you're probably using an earlier version.
Looking at the mappings, I'm suspicious of the #OneToOne definition -- mappedBy="person".
#OneToOne(optional=false, mappedBy="person")
public Person person;
What does it usefully mean, to map an association property by itself? Hibernate already knows the property is a OneToOne -- you just told it that.
Pointing the underpinning mapping/ FK of the property, at itself.. probably isn't actually telling Hibernate any correct or useful information.
Here's an example from the HB dosc, perhaps showing better how to do what you want:
#Entity
class MedicalHistory implements Serializable {
#Id Integer id;
#MapsId #OneToOne
#JoinColumn(name = "patient_id")
Person patient;
}
#Entity
class Person {
#Id #GeneratedValue Integer id;
}
Source: http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/
(3.5 docs off JBoss site.)
Cheers, hope this helps.