Hibernate OneToOne: Forced to chose between optional = false or cascade? - java

In order to keep transfered data small I created two entities for my files in the database. The fileheader to keep some general information about the files and the fileblob, including fileId and the blob. Often, I only need to ask for general fileinformations.
So I need to load the fileblobs lazily.
As I learned in this discussion and that discussion. this could be achieved with optional = false. It works perfect to load the fileblobs lazily. Unfortunately it affects save by cascade.
So here is my attribute in the Fileh.class for the blob:
#OneToOne(mappedBy = "fileh", targetEntity = Fileblob.class, fetch = FetchType.LAZY, optional = false)
#org.hibernate.annotations.Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK })
private Fileblob fileblob;
If I now save a fileh with attached fileblob, this error is thrown:
org.hibernate.id.IdentifierGenerationException: null id generated
for:class Fileblob
if i switch from id-generation strategy "identity" to "increment" this error is thrown:
ERROR SqlExceptionHelper:147 - Cannot add or update a child row: a foreign key constraint fails (`CORE`.`FILEBLOB`, CONSTRAINT
FKFILEBLOB412557 FOREIGN KEY (ID) REFERENCES FILEH (ID))
Query is: insert into CORE.FILEBLOB (FILEBLOB, ID) values (?, ?)
So there is a problem with generating the id...
If i now turn off save by cascade my attribute looks like this.
#OneToOne(mappedBy = "fileh", targetEntity = Fileblob.class, fetch = FetchType.LAZY, optional = false)
private Fileblob fileblob;
In order to save now, I have to call
persistentSession.saveOrUpdate(fileh);
persistentSession.saveOrUpdate(fileblob);
Is this not just what CascadeType.SAVE_UPDATE is supposed to do?
Why is this working for "manual cascading" but not automatically?
P.s.: To complete my example here the counterpart in fileblob.class
#PrimaryKeyJoinColumn
#OneToOne(targetEntity=Fileh.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="`ID`", referencedColumnName="`ID`", unique=true, nullable=false) })
private Fileh fileh;
#Column(name="`ID`", nullable=false, insertable=false, updatable=false, unique=true)
#Id
#GeneratedValue(generator="FILEBLOB_FILEHID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="FILEBLOB_FILEHID_GENERATOR", strategy="foreign", parameters=#org.hibernate.annotations.Parameter(name="property", value="fileh"))
private int filehId;

I encountered exactly the same issue and tried to solve it for several days! It turns out it's an issue which has lied in Hibernate's bug system for more than 2 years!
Though the answer here does help resolve the issue, we are forced to give up using Persist Cascade from the parent Entity (the inverse side, also the side with mappedBy defined)
Please see this link: https://hibernate.atlassian.net/browse/HHH-9670
If you want to apply lazy loading to a OneToOne relationship, and be able to do Persist cascade as usual, please leave a comment to show your concern for the issue!

Well first i would use JPA cascading instead of hibernate one:
#OneToOne(mappedBy = "fileh", fetch = FetchType.LAZY, optional = false)
private Fileblob fileblob;
And the Fileblob, well i think your configuration can be a bit simpler without those generators and stuff(assuming that the id should be actually a foreign key pointing to the Fileh.id).
#Column(name="`ID`", nullable=false, unique=true)
#Id
private int filehId;
#JoinColumn(name = "id", referencedColumnName = "id")
#OneToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#MapsId
private Fileh fileh;

Related

Hibernate #OneToOne loaded even though is lazy

I'm working with Spring Boot 2.3, Spring Data and Hibernate.
I've the following entities
#Entity
#Getter
#Setter
#EqualsAndHashCode(of = "id")
public class User {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
private Long id;
private String name;
#OneToOne(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
private Address address;
#Version
private Long version;
}
#Entity
#Getter
#Setter
#EqualsAndHashCode(of = "id")
public class Address {
#Id
private Long id;
private String fullAddress;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id")
#MapsId
private User user;
#Version
private Long version;
}
When the following code is executed, any query related to the user repository is performed (and for me it is the expected behavior).
Address addressFromDb = addressRepository.findAll().get(0);
log.info("" + addressFromDb.getUser().getId());
// select address0_.id as id1_0_, address0_.full_address as full_add2_0_, address0_.version as version3_0_ from address address0_
but when I execute the following code, then there are multiple queries and I don't understanding why. Apparently the FetchType.LAZY from user to address is not honored.
User userFromDb = userRepository.findAll().get(0);
// select user0_.id as id1_4_, user0_.name as name2_4_, user0_.version as version3_4_ from user user0_
// select address0_.id as id1_0_0_, address0_.full_address as full_add2_0_0_, address0_.version as version3_0_0_ from address address0_ where address0_.id=?
What am I missing?
In order to be more helpful and more clear I've created the following github repo
Hibernate (or more specifically PersistenceContext) needs to know, whether the entity exists or not, so that it can decide, whether to provide a proxy for the entity or null. This does not apply for XToMany relationships, because the whole collection can be wrapped in a proxy and in special case it will be empty.
It is also important to point out, that FetchType is just a suggestion for the JPa implementation and there is no guarantee, that in every case it will be fulfilled. You can read more about #OneToOne here, especially in terms of fetching strategy:
While the unidirectional #OneToOne association can be fetched lazily, the parent-side of a bidirectional #OneToOne association is not. Even when specifying that the association is not optional and we have the FetchType.LAZY, the parent-side association behaves like a FetchType.EAGER relationship. And EAGER fetching is bad.
Even if the FK is NOT NULL and the parent-side is aware about its non-nullability through the optional attribute (e.g. #OneToOne(mappedBy = "post", fetch = FetchType.LAZY, optional = false)), Hibernate still generates a secondary select statement.
For every managed entity, the Persistence Context requires both the entity type and the identifier,
so the child identifier must be known when loading the parent entity, and the only way to find the associated post_details primary key is to execute a secondary query.
Bytecode enhancement is the only viable workaround. However, it only works if the parent side is annotated with #LazyToOne(LazyToOneOption.NO_PROXY) and the child side is not using #MapsId.

Hibernate-OneToMany mapping for existing DB Tables

I am trying to join to Hibernate Entities in a OneToOne Mapping. I am able to fetch the data for a given primary key from the Main Entity, the joining entity, however, returns null. I am new to hibernate and any help will be appreciated.
I have two Tables,
PT_CORE
Primary Key: ptId - Integer;
Foreign Key: stId(ST_AUX) - Integer;
Columns: ptId, ptName
ST_AUX
Primary Key: stId;
Columns: stId, stName
The two tables get populated by other applications and mine is a read-only operation.
Below is my first Entity class(PtCore.java)
#Entity
#Table(name="PT_CORE")
public class PtCore implements Serializable{
#Id
#Column(name="ptId", nullable = false)
private int id;
#Column(nullable=false)
private int stId; //The Foreign key column
#OneToOne
#JoinTable( name = "core_aux", joinColumns = {#JoinColumn(Name="ptId")},
inverseJoinColumns = {#JoinColumn(Name="stId")}
)
private StAux staux;
//Getters, setters and toString() for above
}
StAux is another Entity, defined as below,
#Entity
#Table(name="ST_AUX")
public class StAux implements Serializable {
#Id
#Column(nullable=false)
private Integer stId;
#OneToOne
private PtCore ptcore;
#Column
private String stName;
//Getters, Setters and toString follow.
}
I do below in the Service method:
PtCore obj = (PtCore) session.get(PtCore.class,1);
System.out.println(obj);
In the Results, I get the value of ptName, but the stAux class variables are null, Indicating that the join does not work as expected.
First of all you have the mapping information existing in your PT_CORE. And I assume it is something like FOREIGN KEY (stid) REFERENCES (stid). If you want to use existing schema and existing data I guess there is no mapping table core_aux really existing. At least you did not mention it. However it is visible as #JoinTable annotation but still there is this above mentioned foreign key which seems to be the real mapping (so again not the join table).
I suggest the following
remove this
#Column(nullable=false)
private int stId; //The Foreign key column
from your PtCore. I think it is not needed. Also in PtCore, remove the #JoinTable (because what I told above) and add mapping informaiion to #OneToOne annotation, like:
#OneToOne
#JoinColumn(name = "stid")
private StAux staux;
from your PT_CORE.
Then in StAux alter also a bit:
#Id
#Column(name = "stid") // this might not be needed but if there is like "st_id"...
private Integer stId; // so just for sure
#OneToOne(mappedBy = "staux")
private PtCore ptcore;
Because you have existing tables and constraints there might raise errors if hibernate tries to auto-generate those again by JPA instructions.
Check this for example for more information.
UPDATE: just realized also that in your title is #OneToMany but in your code is #OneToOne.
So you might want to elaborate your question and/or title a bit.
In your relation, the owning side is PtCore, the inverse side is StAux.
In bidirectional OneToOne relations, the inverse side has to have the mappedBy attribute. Actually, the mappedBy attribute contains the name of the association-field on the owning side.
So, you must change your inverse side code (StAux Entity). You have to add mappedBy attribute to #OneToOne in StAux class:
#OneToOne(mappedBy="staux")
private PtCore ptcore;

JPA cascading merge when it shouldn't

I'm having a hard time understanding this JPA behavior which to me doesn't seem to follow the specification.
I have 2 basic entities:
public class User {
#Id
#Column(name = "id", unique = true, nullable = false, length = 36)
#Access(AccessType.PROPERTY)
private ID id;
#OrderBy("sequence ASC")
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.REMOVE })
private final Set<UserProfile> userprofiles = new HashSet<UserProfile>(0);
//Ommiting rest of fields since they aren't relevant
}
public class UserProfile {
#Id
#Column(name = "id", unique = true, nullable = false, length = 36)
#Access(AccessType.PROPERTY)
private ID id;
#NotNull
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "userID", nullable = false, foreignKey = #ForeignKey(name = "FK_UserProfile_User"))
private User user;
//Ommiting rest of fields since they aren't relevant
}
As you can see I only have cascading set to REMOVE, the behavior will be the same if I don't have cascade set at all.
Now if I call:
User user = new User();
user.setId(UUIDGenerator.generateId());
UserProfile userProfile = new UserProfile();
userProfile.setId(UUIDGenerator.generateId());
userProfile.setUser(user);
user.getUserProfiles().add(userProfile);
em.merge(user);
merge will throw an exception.
I see Hibernate is executing a SQL query against the UserProfile table:
select userprofil0_.userProfileID as userProf1_4_0_, userprofil0_.profileID as profileI3_4_0_, userprofil0_.sequence as sequence2_4_0_, userprofil0_.userID as userID4_4_0_ from UserProfile userprofil0_ where userprofil0_.userProfileID=?
And then it will throw an exception
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find com.mytest.domain.UserProfile with id 6aaab891-872d-41e6-8362-314601324847;
Why is this query even called?
Since I don't have cascade type set to MERGE in userprofiles my expectation would be that JPA/Hibernate would simply ignore the entities inside userprofiles set and only insert/update the user record, doesn't this go against the JPA specs?
If I change cascadetype to MERGE things will work as expected and both User and UserProfile will be added to the database, so no problem there. What puzzles me is why is Hibernate querying the database and erroring out about an entity that's not supposed to be merged at all since I don't have it set to cascade.
This is more of an academic scenario that I ran into, of course I could simply clear the userprofiles set and things would work, but I'm trying to understand why the above behavior happens since I'm probably missing some crucial piece of information about how merge works. It seems it will always try to attach all entities to the session regardless cascade type being set or not.
Why is this query even called?
It's because you are trying to merge the entity, in JPA merge() is used to make the entity managed/attached. To "merge" User, JPA needs to still maintian the references it holds(UserProfile). In your case its not trying to persist UserProfile its trying to get a reference to it to merge User. Read here
If you use persist rather than merge this should not happen.

Hibernate: either null foreign key, or duplicate foreign keys for OneToOne related entity

Started with getting a hibernate exception on a "select * from PARENT" type of a query, which said I have more than one entry in the CHILD table with the same [foreign] key, which should be unique. That was a red herring - the CHILD table had no such entries. I wiped the table clean, and moved on to the same error, this time citing ANOTHER_CHILD as an offender. In fact ANOTHER_CHILD only has 1 row...
Here, CHILD table(s) store persistent #Entity(s), which are in a bi-directional #OneToOne relationship with Parent #Entity.
NOTE: PARENT tables has a primary key, PARENT_PK, and there is a PARENT_PK column on all "Children", which points to the PARENT entry, which 'owns' the CHILD.
It was suggested to me that the problem is likely due to the bi-directionality of the relationship(s); and that I should make them uni-directional, if possible. OK, as I don't really need bi-directionality, I changed the annotations and made made relationship(s) uni-directional. Now the "get all" kind of a request works fine - but now updating the Parent, adding new Child, is failing with a ConstraintViolationException: ORA-01400: cannot insert NULL into CHILD.PARTNER_PK
I also found this article:
Target Foreign Keys, Primary Key Join Columns, Cascade Primary Keys
it claims uni-directional relationships are not supported when the the foreign key is in the "child" table, which the object relationship is to be unidirectional from Parent to Child. It's not clear on how to solve it, though.
I am looking for either of the solutions:
1) how to fix the bi-directional mapping to avoid the bogus "duplicate id" exception
2) how to make the relationship uni-directional, without changing the schema
.....
Code snippets:
This is how the relationship was defined in the Parent:
#OneToOne(mappedBy = "parent",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true)
private Child child;
And this is what was in the Child:
#OneToOne
#JoinColumn(name="PARTNER_PK")
private Parent parent;
, (+ all the getters/setters).
After attempting the change for uni-directional relationship, Child class doesn't have the Parent at all. Parent class now looks like this:
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "PARENT_PK")
private AddressDO address;
Gave up on making one-to-one unidirectional, and fixed the bidirectional like so:
In Parent class:
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "parent", orphanRemoval = true)
private Child child;
In Child class:
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "PARENT_PK", insertable = true, updatable = false, nullable = false)
private Parent parent;
Still not sure what the real cause was for the problem, though =(

JPA #JoinTable - Three ID Columns

I have a situation that is quite similar to the one outlined in this question's diagram: JPA. JoinTable and two JoinColumns, although with different issues.
I have three tables: Function, Group, and Location. Currently, I have a join table set up between Location and Group using #JoinTable. It is #ManyToMany on both sides, and works perfectly fine.
I am attempting to add the constraint that no Location should be associated with more than one Group that has the same Function. So I added a column for Function to my join table in my SQL schema and a uniqueness constraint across the Location and Function columns, like so:
create table function_table (
id varchar(50),
primary key(id)
);
create table group_table (
id varchar(50),
function_id varchar(50) not null,
primary key(id)
);
alter table group_table add constraint FK_TO_FUNCTION foreign key (function_id) references function_table;
create table location_table (
id varchar(50),
primary key(id)
);
create table group_location_join (
location_id varchar(50) not null,
group_id varchar(50) not null,
function_id varchar(50) not null,
primary key(location_id, group_id, function_id),
unique(location_id, function_id)
);
alter table group_location_join add constraint FK_TO_LOCATION foreign key (location_id) references location_table;
alter table group_location_join add constraint FK_TO_GROUP foreign key (group_id) references group_table;
alter table group_location_join add constraint FK_TO_FUNCTION foreign key (function_id) references function_table;
I then attempted to set up the following in my model entities:
#Entity
#Table(name = "function_table")
public class Function {
#Id
#Column(name = "id", length = 50)
private String id;
}
#Entity
#Table(name = "group_table")
public class Group {
#Id
#Column(name = "id", length = 50)
private String id;
#ManyToOne
#JoinColumn(name = "function_id", referencedColumnName = "id", nullable = false)
private Function function;
#ManyToMany
#JoinTable(name = "group_location_join",
joinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id"),
#JoinColumn(name = "function_id", referencedColumnName = "function_id")},
inverseJoinColumns = #JoinColumn(name="location_id", referencedColumnName = "id"))
private Set<Location> locations;
}
#Entity
#Table(name = "location_table")
public class Location {
#Id
#Column(name = "id", length = 50)
private String id;
#ManyToMany
#JoinTable(name = "group_location_join",
joinColumns = #JoinColumn(name="location_id", referencedColumnName = "id")
inverseJoinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id"),
#JoinColumn(name = "function_id", referencedColumnName = "function_id")})
private Set<Group> groups;
}
(Obviously, there is more to these entities, but I stripped them down to only the parts relevant to this question.)
This does not work. When I write a simple test to create a Location associated with a Group that is associated with a Function, the minute I try to flush the session to commit the transaction, Hibernate gives me this:
java.lang.ClassCastException: my.package.Group cannot be cast to java.io.Serializable
I think what's happening is that Hibernate is getting confused, throwing up its hands, and saying "I'll just serialize it, send it to the database, and hope it knows what's going on."
When I add implements Serializable and add a serialVersionUID to Group, I then get this:
org.hibernate.exception.SQLGrammarException: user lacks privilege or object not found: FUNCTION_ID
I'm not really sure how to proceed at this point, or if perhaps I have already proceeded too far down the wrong path. Maybe I'm not thinking about the SQL correctly, and there is a much easier way to ensure this constraint that doesn't involve all this ridiculousness.
Edit: In my system, the DAOs for the tables involved have no save capabilities. Which means that as long as my constraint is set up in the database, my application doesn't care; it can't insert things that violate the constraint because it can't insert things at all.
Edit 2: I never originally solved the stated problem, and instead simply added a third column in my database schema without touching the Java code, as stated in my first Edit section above. But I have since experimented with creating an explicit join table object with an #Embedded compound key, and it seems to work.
You are trying to create a composite primary key. In Hibernate you can do it using the #Embeddable annotation. In the example below you can find the way to use a composite key for two entities.
I believe you can move forward with this example and create your own version of primary key.
Mapping ManyToMany with composite Primary key and Annotation:

Categories