Using JPA mapped by not primary key field? - java

I am having troubles getting this working and I wonder if what I am doing simply does not make sense?
public class Application {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
private long id;
....
}
#MappedSuperclass
public abstract class Sample {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToOne (cascade=CascadeType.ALL)
protected Application application;
....
}
// TestSample contains a list that is mapped not by the primary key of the Sample
public class TestSample extends Sample {
#OneToMany(mappedBy="application", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<Part> parts = new ArrayList<Part>();
....
}
public class Part {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id = 0;
#ManyToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
Application application;
}
The problem I am having is that I am able to add parts, the database looks correct, then when I attempt to fetch the the parts list I get an empty list.
I can get it to work if I compromise on the database structure by changing these classes:
// TestSample contains a list that is mapped not by the primary key of the Sample
public class TestSample extends Sample {
#OneToMany(mappedBy="testSample", cascade=CascadeType.ALL, fetch=FetchType.LAZY)
private List<Part> parts = new ArrayList<Part>();
....
}
public class Part {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private long id = 0;
#ManyToOne (cascade=CascadeType.ALL, fetch=FetchType.LAZY)
TestSample testSample;
}
The tables are being auto generated by hibernate, so they are coming out like this:
application
id : number
....
test_sample
id : number
application_id : number
...
part
id : number
application_id : number
If I change it to the less desirable way that works, the last table is different:
part
id : number
test_sample_id : number
Because the id's in all cases are being auto generated, there are no shared primary keys. Essentially what I am trying to do is use mappedby where mappedby is referring to a field that is not the primary key of the table/class called "TestSample". This is what I am not sure if makes sense in JPA.

The OneToMany is bi-directional with the "Part" class. I think this is getting very difficult to explain (:
Your one-to-many association between TestSample and Part is not bidirectional, the mappedBy is not correct (the application table is not owning the relation, it is not even aware of test_sample), your mapping doesn't make sense. There is something to change.
I think that you should show what the expected tables are, not the generated one (since the mappings are incoherent, the generated result can't be satisfying). You are talking about compromise so I believe that you have an idea of what the expected result should be. Please show it.

Related

JPA Hibertane unidirectional one-to-one with shared primary key save parent first NO REVERSE

I'v been searching internet for answer, but nothing was working for me. There is a lot of topics with similar cases but specific details are different in a way that make them unusable for me.
So I have two tables: t_item and t_item_info:
item_id field from t_item_info table references id field from t_item table. I'm using mysql db and id column form t_item is auto incremented
I need to make unidirectional one-to-one mapping in a specific way. Here are my classes:
#Entity
#Table(name = "t_item")
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#PrimaryKeyJoinColumn(name = "id", referencedColumnName = "item_id")
private ItemInfo info;
}
And other one
#Entity
#Table(name = "t_item_info")
public class ItemInfo {
#Id
private Long itemId;
private String descr;
}
So the point is that i need Item object to have a reference to ItemInfo object. NOT The other way!
Item -> ItemInfo --YES
Item <- ItemInfo --NO
The other thing is that i need parent (Item) id to become id for a child (ItemInfo)
For example I create Item object with null id and set it's info field with ItemInfo object which also have null id field. Like this:
{
"id": null,
"name": "Some name",
"info": {
"itemId": null,
"descr": "some descr"
}
}
Then when Item object persists hibernate should generate id for parent(Item) object and set it as itemId field for child(ItemInfo).
I have been trying to achieve this with different hibernate annotations and I noticed that no matter how hard I tried Hibernate always seems to try to persist child object first. I noticed it in the logs when I turned sql logging on. insert into t_item_info always goes first (and dies because of null id :D)
So the question is: Is it even possible to achieve this and if so what should I change in my code to do so
I hope that what I'm trying to ask makes sens to you given my poor explanations =)
Why people always insist the child object table in one-to-one associations should be the one with the foreign key is beyond me.
Anyway, as a workaround, since both objects share the id and the association is non-optional, you might as well declare the autogenerated key for the child object:
#Entity
#Table(name = "t_item_info")
public class ItemInfo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long itemId;
private String descr;
}
and then use #MapsId for the parent:
#Entity
#Table(name = "t_item")
public class Item {
#Id
private Long id;
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "id")
#MapsId
private ItemInfo info;
}
Note that this approach, will, in a sense, fool Hibernate into thinking it is the Item that should be treated as the child object. You have been warned.
While there is an accepted answer here it looks to me like #Secondary table would be a better and more convenient solution: you may have 2 tables at the database level but I do not see any reason that that fact needs be exposed to any client code. There does not seem to be a lot of benefit to that? The following gives you a simpler API.
Entity:
#Entity
#Table(name = "t_item")
#SecondaryTable("t_item_info", pkJoinColumns={
#PrimaryKeyJoinColumn(name="id", referencedColumnName="item_id")})
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#Colulumn(name = "description", table= "t_item_info"")
private String description;
}
API:
{
"id": null,
"name": "Some name",
"descr": "some descr"
}

Spring and JPA 2.0 - Single value Primary Key through OneToOne

I have a simple table (ActivityLog) and I want it to have a PK that is also a FK to another table (User).
It seems to be a common thing to have, and I tried to follow this wikibook
Primary Keys through OneToOne and ManyToOne Relationships. The example there involved a composite key. I need just a primitive key, so I ended up with:
#Entity
public class User {
#Id
private Long id;
// other stuff
}
#Entity
public class ActivityLog {
#Id
#OneToOne(optional = false)
#JoinColumn(name="user_id", referencedColumnName="id")
private User user;
// other stuff
}
Unfortunately i am getting:
Caused by: java.lang.IllegalArgumentException: This class [class com.example.ActivityLog] does not define an IdClass
at org.hibernate.metamodel.internal.AbstractIdentifiableType.getIdClassAttributes(AbstractIdentifiableType.java:183)
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation$IdMetadata.<init>(JpaMetamodelEntityInformation.java:253)
I tried to annotate ActivityLog with:
#IdClass(Long.class)
(even though from what I understand it is applicable only for composite keys), yet I am getting the exact same error.
Is my case different than what's on the mentioned wikibook?
Is Spring at fault here? (As suggested in this question? (no accepted answers)).
This should help:
#Entity
public class ActivityLog {
#Id
#Column(name = "user_id")
private Long id;
#OneToOne(optional = false)
#JoinColumn(name="user_id", referencedColumnName="id")
private User user;
// other stuff
}
Btw. I would expect, that you need more logs per user, so you would probably need some additional (generated) id anyway ...

Saving to child table with springboot+jpa

I have trouble in saving... fetch from child table is working
Here is my parent table pojo
#Entity
#Table(name = "Dei_Resources")
public class DeiResources {
private int id;
private String employeeId;
private Set<DeiResourceType> deiResourceType;
//other setters getters not included
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public int getId() {
return id;
}
#OneToMany(fetch = FetchType.EAGER, mappedBy = "deiResource", cascade = CascadeType.ALL)
public Set<DeiResourceType> getDeiResourceType() {
return deiResourceType;
}
Child Table pojo
#Entity
public class DeiResourceType implements Serializable{
private int id;
private int resourceId;
private String typeValue;
#JsonBackReference
private DeiResources deiResource;
//other setters getters not included
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public int getId() {
return id;
}
#ManyToOne(fetch=FetchType.LAZY,optional=false)
#JoinColumn(name = "resourceId", referencedColumnName="id",insertable = false, updatable = false)
public DeiResources getDeiResource() {
return deiResource;
}
I have DeiResourcesRepository in place, in my service Im trying this
DeiResources dei = new DeiResources();
DeiResourceType deii = new DeiResourceType();
Set<DeiResourceType> deiResourceType = new HashSet<DeiResourceType>();
deii.setTypeValue("Driver");
deiResourceType.add(deii);
dei.setEmployeeId("unique1");
dei.setDeiResourceType(deiResourceType);
deiResourcesRepository.save(dei);
Getting this error
[ERROR] org.hibernate.engine.jdbc.spi.SqlExceptionHelper - ORA-02291:
integrity constraint (DEI_ADMIN.DEI_RESOURCE_TYPE_R01) violated -
parent key not found
In DeiResourceType table I have added foreign key constrain with Parent table ID. How can I get rid of this error, any suggestion/help ?
First, in SQL there is no Child/Parent pattern. Thats why the imagination of a structure like a real family is wrong. In this example a parent can not be a child, in the real world everyone who is parent is a child too!
We only have two Tables who have a 1-n relation respecting the constraint that every DeiResource must have a DeiResourceType! As long as this constraint keeps it integrity, there is no exception.
This in mind you call the constructor of two entitys but you save only one (you save the DeiResource without DeiResourceType). So you save a Resource without a type. But the constraint that every DeiResource must have a DeiResourceType is broken and the database has no integrity anymore and the Exception is thrown!
You have two options:
You save the DeiResourceType first and then the DeiResource
You load a existing DeiResourceType first, wire it to the DeiResource and save the DeiResource then.
I would prefer the second method to avoid duplicates in the Table "DeiResourceType".
(By the way, theese constraints can be deferrable, a old, forgotten and mighty magic)

JPA OneToMany insert not setting the id's correctly

I have two entities, let's say
Person.java:
#Entity
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = AUTO)
private long id;
#OneToMany(mappedBy = "personData", cascade = CascadeType.PERSIST)
private List<SkillsData> skillsData;
// ...
}
SkillsData.java
#Entity
#Table(name = "SkillsData")
public class SkillsData implements Serializable {
#Id
#GeneratedValue(strategy = AUTO)
private long id;
#JoinColumn(name = "PERSONID")
#ManyToOne(cascade = REMOVE)
private Person personData;
// ...
}
When I create a person, add a list of type SkillsData to it's skillsData field and persist it everything works with no exceptions thrown, but when I browse the database directly in the SkillsData table the field PERSONID is not populated and because of that the skills added can't be referenced to the right person.
I'm trying to fix this problem for quite some time and I'll be thankful for any help.
The problem might be in the fact that you're not setting SkillsData.personData before persisting leaving it null.
You must set it cause adding SkillsData to the Person.skillsData list is not enough since you declared this side of relationship as inverse(mappedBy attribute).
Therefore it is the SkillsData.personData non-inverse side who is responsible for establishing this relationship.

Hibernate and #JoinFormula: org.hibernate.mapping.Formula cannot be cast to org.hibernate.mapping.Column

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!

Categories