I have three classes, with each having a table:
Class A {
long id;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
B b;
}
Class B {
long id;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
C c;
}
Class C {
long id;
}
There is an entry in each of tables. The link between table B and C is based on the foreign keys. If I set A.b as null, and then update the table for the class A, it first deletes the entry of table for class B, and then the entry of table for class C, which causes a violation exception:
delete from B_table where id=? [23503-176]];
nested exception is:
org.hibernate.exception.ConstraintViolationException: could not
execute statement
Any ideas?
When having such deep hierarchies you have to "chase the pointers". When you set A.b to null, JPA correctly tries to delete the B entity in the table but you get the constraint violation because it still has a reference on C. You have to also set to null B.c first and according to your setting C will be removed from the table, along with the B.c reference (FK)
The deletion of B is implicitly applied by JPA during flush; you did not call a delete yourself so the cascade.ALL in B is not in effect. If you have a FK with cascade delete in database level then theoretically it might work but the order that the orphan removal is applied is implementation dependent and JPA does not recommend to rely on it.
Related
Using Entity entity = hibernateTemplate.get(Entity.class, id); when I hit a entity.getChild() which is a OneToOne relation, every other OneToOne relations are loaded as well. I use hibernate 5.4.1-Final.
I use bytecode enhancement as below :
<configuration>
<failOnError>true</failOnError>
<enableLazyInitialization>true</enableLazyInitialization>
<enableDirtyTracking>false</enableDirtyTracking>
<enableAssociationManagement>true</enableAssociationManagement>
</configuration>
A.java
#Entity
#Table(name = "A")
public class A {
#Id
#Column(name = "ID_A")
private String id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID_A")
#LazyToOne(LazyToOneOption.NO_PROXY)
private B b;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ID_A")
#LazyToOne(LazyToOneOption.NO_PROXY)
private C c;
...
getters/setters
...
B.java
#Entity
#Table(name = "B")
public class B {
#Id
#Column(name = "ID_A")
private String id;
}
C.java
#Entity
#Table(name = "C")
public class C {
#Id
#Column(name = "ID_A")
private String id;
}
So when I do
A a = hibernateTemplate.get(A.class, "100");
// triggers an Hibernate query only on A entity. The B and C aren't fetched => OK
// Hibernate: select a0_.ID_A as ID_A_27_0_ from A a0_ where a0_.ID_A=?
a.getB(); // ERROR : triggers two queries : one on C and one on B
// Hibernate: select c0_.ID_A as ID_A _26_0_ from C c0_ where c0_.ID_A =?
// Hibernate: select b0_.ID_A as ID_A _13_0_ from B b0_ where b0_.ID_A =?
Even if I fetch the B in an HQLQuery, I still have a query to C :
Query<A> queryA = hibernateTemplate.createHQLQuery("from A a join fetch a.b where a.id=:id", A.class);
queryA.setParameter("id", "100");
A a = queryA.uniqueResult(); // triggers an inner join
// Hibernate: select a0_.as ID_A1_27_0_, b1_.ID_A as ID_A1_13_1_ from A a0_ inner join B b1_ on a0_.ID_A=b1_.ID_A where a0_.ID_A=?
a.getB(); // KO -> triggers a query to select C !
// Hibernate: select c0_.ID_A as ID_A1_26_0_ from C c0_ where c0_.ID_A=?
I've tried to do a double mapping (OneToOne with mappedBy specified) without success.
The PK of B and C are the same as A.
I expect the a.getB(); not to trigger the fetch of C. Is this a hibernate bug ? I can't find anything regarding this behaviour in their documentation.
Is my mapping correct ?
It seems that it works as designed :) b and c belong to the same default "LazyGroup". If any of b or c needs to be loaded, the whole group will.
Quote from the bytecode enhancer documentation :
Lazy attributes can be designated to be loaded together and this is
called a "lazy group". By default, all singular attributes are part of
a single group, meaning that when one lazy singular attribute is
accessed all lazy singular attributes are loaded. Lazy plural
attributes, by default, are each a lazy group by themselves. This
behavior is explicitly controllable through the
#org.hibernate.annotations.LazyGroup annotation.
Just add the #LazyGroup("b") on the b field, and #LazyGroup("c") on c and it should work as expected : b will be loaded only on getB(), anc c on getC().
More on this here and here.
I've got two tables, b and a:
they have a one-to-one bidirectional relationship
a has a foreign key to b that defines this relationship
this foreign key is also considered as a primary key for a, and a JPA #ID
I want a cascade removal that deletes the related b when a is deleted
in MySQL, a's b_id is NOT NULL
The problem is that when I delete my A object with JPA repository, I get a ConstraintViolationException on its foreign key.
I would expect that both a and b rows are deleted (cleverly starting with a's one).
How could I work around this knowing that I want to keep:
my DB schema the same
the cascade removal from a to b
the b id being the JPA #Id for a
CREATE TABLE `b` (
`dbid` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`dbid`),
);
CREATE TABLE `a` (
`b_id` int(11) NOT NULL,
KEY `b_fk` (`b_id`),
CONSTRAINT `b_fk` FOREIGN KEY (`b_id`) REFERENCES `b` (`dbid`),
);
#Entity
#Table(name = "a")
public class A {
#Id
#Column(name = "b_id")
#GeneratedValue(generator = "gen")
#GenericGenerator(name = "gen", strategy = "foreign", parameters = #Parameter(name="property", value="b"))
private Integer bId;
#OneToOne(cascade = CascadeType.REMOVE)
#PrimaryKeyJoinColumn
private B b;
}
#Entity
#Table(name = "b")
public class B {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
#Column(name = "dbid")
private Integer id;
#OneToOne(mappedBy = "b")
private A a;
}
[EDIT] After all discussions in answer comments and re-reading my question, the proposals with orphanRemoval indeed are in scope and work.
If you want to delete object of B, whenever the associated A is deleted (it's the fourt point of your wishlist:
I want a cascade removal that deletes the related b when a is deleted
then you need to change your mapping in A to:
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
#PrimaryKeyJoinColumn
private B b;
In terms of just the MySQL side of your implementation, the records in table B have no 'knowledge' of any record in table A. In the database the relationship is unidirectional
The native cascade functionality exists to prevent foreign key errors, by telling the DB what to do when deleting a record would leave a foreign key pointing nowhere. Deleting a table A record would not cause a foreign key error in any table B records, so any native cascade functionality would not be triggered
To reiterate; You cannot keep the schema the same, and the cascade removal from a to b, because you don't actually have the cascade removal from a to b
You also mentioned in the comments that some table B records can exist without a table A records which isn't in the original question
To obtain the automatic deletion of table B records you describe, you have a few options with regards to the DB:
Swap the relation over - Remove the current foreign key and add a nullable foreign key column in table B that references the primary key of table A. You can then put a cascade delete on this foreign key. Keep the new column null for the table B records that do not 'belong' to a table A record. You could also add a unique index to this column to secure a one to one relationship
Add a DB trigger - On deletion of a table A record, add a DB trigger that removes the referenced table B record
Add a DB procedure - Add a procedure that deletes a table A record and then the referenced table B record in turn, probably within a transaction. Going forwards, only delete table A records using the procedure
Don't solve the problem at the DB level - Basically the same as option 3, but move the procedure logic out of the DB layer into the application logic
There may be something in JPA that solves your dilemma out of the box, but under the hood it will be doing one of the above (not option 1 and probably option 4)
In order to achieve what you have asked, I have tweaked your tables as follows:
CREATE TABLE b (
dbid INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY
);
CREATE TABLE a (
b_id int(11) NOT NULL PRIMARY KEY REFERENCES b(dbid) ON DELETE CASCADE
);
CASCADE DELETE wasn't added in your DDL.
This will enable cascade delete. To delete the b record on deletion of a I made following changes in class A:
#Entity
#Table(name = "a")
public class A {
#Id
#Column(name = "b_id")
#GeneratedValue(generator = "gen")
#GenericGenerator(name = "gen", strategy = "foreign", parameters = #Parameter(name="property", value="b"))
private Integer bId;
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
#PrimaryKeyJoinColumn
private B b;
}
Find link here to the working solution.
Can you try in class B to add the following
#OneToOne(mappedBy = "b", cascade = CascadeType.REMOVE)
private A a;
In addition, if in the database you have only a foreign key "a has a foreign key to b" can you also make a foreign key from b to a as well.
#OneToOne(mappedBy = "b",cascade = CascadeType.ALL,fetch = FetchType.LAZY,orphanRemoval=true )
private A a;
Currently, my database is organized in a way that I have the following relationships(in a simplified manner):
#Entity
class A {
/*... class A columns */
#Id #NotNull
private Long id;
}
#Entity
#Immutable
#Table(name = "b_view")
class B {
/* ... same columns as class A, but no setters */
#Id #NotNull
private Long id;
}
The B entity is actually defined by a VIEW, which is written in this manner(assuming Postgres):
CREATE VIEW b_view AS
SELECT a.* FROM a WHERE EXISTS
(SELECT 1 FROM filter_table ft WHERE a.id = ft.b_id);
The idea here is that B references all elements of A that are present on filter_table. filter_table is another view that isn't really important, but it's the result of joining the A table with another, unrelated table, through a non-trivial comparison of substrings. These views are done so that I don't need to duplicate and control which elements of A also show up in B.
All of these are completely fine. JpaRepository is working great for B(obviously without saving the data, as B is Immutable) and it's all good.
However, at one point we have an entity that has a relationship with B objects:
#Entity
class SortOfRelatedEntity {
/** ... other columns of SortOfRelatedEntity */
#ManyToOne(fetch = FetchType.EAGER, targetEntity = Fornecedor.class)
#JoinColumn(name = "b_id", foreignKey = #ForeignKey(foreignKeyDefinition = "references a(id)"))
private B b;
}
For obvious reasons, I can't make this foreign key reference "b", since B is a view. However, I do want the query for searching this attribute to be defined by the b_view table, and having the foreign key defined by the underlying table(as written above) would be also nice in order to guarantee DB integrity.
However, when applying the above snippet, my sort-of-related-entity table doesn't create a foreign key as I would have expected. For the record, I'm using Hibernate 5.2.16 atm.
What am I doing wrong? Is this even possible? Is there something else I should do that I'm not aware of?
Oh FFS
I realized my mistake now. This:
#JoinColumn(name = "b_id", foreignKey = #ForeignKey(foreignKeyDefinition = "references a(id)"))
Should have been this:
#JoinColumn(name = "b_id", foreignKey = #ForeignKey(foreignKeyDefinition = "foreign key(b_id) references a(id)"))
Notice that the foreignKeyDefinition must include foreign key(), not just the references part.
Hopefully this helps someone in the future.
I have the following problem (pseudo-java-code):
Let me a class A,B,C with the following relationships:
#Entity
#Table(name = "A")
public class A {
#OneToMany(mappedBy = "a")
private B b;
}
#Entity
#Table(name = "B")
public class B {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "a_id")
private A a;
#OneToOne(mappedBy = "b", fetch = FetchType.LAZY)
private C c;
}
#Entity
#Table(name = "C")
public class C {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "b_id")
private B b;
}
I'm using JpaRepository with #Query annotation and I implemented the following query:
#Query("SELECT DISTINCT(a) FROM A a "
+ "LEFT JOIN FETCH a.b as b"
+ "WHERE a.id = :id ")
A findById(#Param("id") Integer id);
I want retrieve the informations about class A and B, but not C.
Somehow (I don't know why) the query try to retrive also the relation between B and C.
And then, with hibernate, start the lazy invocation for retrieving C.
Naturally, if I fetch also the relation between B and C (adding LEFT JOIN FETCH b.c as c) that's not happen.
My question is, why? Why I'm forced to fetch all nested relations and not only the ones which I need?
thank you.
Carmelo
Nullable #OneToOne relation are always eager fetched as explained in this post
Making a OneToOne-relation lazy
Unconstrained (nullable) one-to-one association is the only one that
can not be proxied without bytecode instrumentation. The reason for
this is that owner entity MUST know whether association property
should contain a proxy object or NULL and it can't determine that by
looking at its base table's columns due to one-to-one normally being
mapped via shared PK, so it has to be eagerly fetched anyway making
proxy pointless.
Shot in the dark, but there are some issues with lazy-loading #OneToOne-relationships. At least in older versions of Hibernate. I think (but can't seem to find any documentation) that this was fixed in one of the newer versions, so if you are not using a new version of Hibernate, try upgrading.
I have a class A{Set b .....} which holds references of class B as Set. It is one to many relationship.
Both class have sequencer in oracle. I put cascade to all in hibernate annotations. When i save class A, it gives me error that cannot insert null B.a_id . A-id is not nullable in my database. How can i persist this relationship.
This is a unidirectional relationship from A->B. a_id column in table B is not nullable. When hibernate tries to save class B, it not able to find value for a_id.
Well, did you try to make the JoinColumn non nullable?
#OneToMany
#Cascade({CascadeType.ALL})
#JoinColumn(name="A_ID", nullable=false)
private Set<B> b;
See also
Hibernate Core Reference Guide
6.2.1. Collection foreign keys
I ran into the same problem and solved it by using the mappedBy attribute of the #OneToMany annotation in Class A:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "m_a")
private Set<B> b;
Here, m_a is the field in Class B that refers to Class A:
#JoinColumn(name = "aId", nullable = false)
#ManyToOne
private A m_a;
This way, the #JoinColumn is only specified in one place, no duplicated code.