JPA: Resolving "Muliple respresentations of the same entity" while still permitting merging - java

So, I know this question has been asked a lot, but I haven't seen it asked about a case like this.
I have the following entities:
#Entity
public class A {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(updatable=false)
private Integer id;
#OneToMany(mappedBy="a", cascade=CascadeType.ALL)
private List<B> bs;
}
#Entity
public class B {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(updatable=false)
private Integer id;
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(
name="BToC"
, joinColumns={
#JoinColumn(name="BId", referencedColumnName="id")
}
, inverseJoinColumns={
#JoinColumn(name="CId", referencedColumnName="id")
}
)
private List<C> cs;
}
#Entity
public class C {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(updatable=false)
private Integer id;
#ManyToMany(mappedBy="cs",cascade=CascadeType.ALL)
private List<B> bs;
}
If you had an A, a1, containing two B's with id's 1 and 2 respectively (let's call them b1 and b2), each containing a single C with id 1 (let's call them c1 and c2), that would make c1 and c2 each have a bs list containing b1 and b2. When trying to merge A, the merge cascades to b1 and b2, and each of the B's merges then cascade to c1 and c2. Since c1 and c2 have identical contents, I would expect the following result to be pushed to table BtoC:
BId | CId
----------
1 | 1
2 | 1
However, the merge fails because c1 and c2 both represent the same entity but are technically different objects.
So, my question is this: Is there a change I can make to allow merges applied to either A, B, or C to succeed and cascade, while permitting cases like with c1 and c2 where the contents are identical but the Object is different? Or would I need to omit the Merge Cascade Type from at least one of the entities, and have merges I would like to "cascade" from one object to another be handled manually (i.e. iterate through each entity, merging only those whose contents have not been previously merged OR assigning objects with identical contents to a single reference)?

Ok, seems like the fix was pretty simple, I just needed to implement a proper equals method for C class. Using that, I'm guessing JPA was able to determine that both representations were identical, and had no problems merging them.
Hoping this works for anyone else running into a similar issue.

Related

Spring/JPA: Entity referenced by a view as a #ManyToOne association

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.

Hibernate/JPA, save a new entity while only setting id on #OneToOne association

I have two entities,
class A { #OneToOne B b; }
class B { ... lots of properties and associations ... }
When I create new A() and then save, i'd like to only set the id of b.
So new A().setB(new B().setId(123)).
Then save that and have the database persist it.
I do not really need to or want to fetch the entire B first from the database, to populate an instance of A.
I remember this used to work, but when I am testing it is not.
I have tried Cascade All as well.
B b = (B) hibernateSession.byId(B.class).getReference(b.getId());
a.setB(b);
hibernateSession.load(...) // can also be used as it does the same.
The JPA equivalent is :
entitymanager.getReference(B.class, id)
Below code should help.It will fetch B only when its accessed.
class A {
#OneToOne(fetch = FetchType.LAZY) B b;
}

JPA: The issue when deleting orphan grandchild elements

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.

JPA 3 level inheritance

First of all I've been all the morning seeking for a solution for my problem. I've found similar problems but no one fixed my problem :(
I have a 3 level inheritance on my Java classes model:
A <-- B <-- C
Mapping this into a Relational Data Base I've made
A (1) -- (0..1) B (1) -- (0..1) C, so my annotated Java classes are something like:
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#DiscriminatorColumn(name="type", discriminatorType=DiscriminatorType.INTEGER)
#XmlRootElement
public class A implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long idA;
private Integer type;
...
}
#Entity
#DiscriminatorValue("2")
#PrimaryKeyJoinColumn(name="idB", referencedColumnName="idA")
#XmlRootElement
public class B extends class A implements Serializable{
private Long idB;
private Integer type;
#OneToOne(cascade={CascadeType.REMOVE}, fetch=FetchType.LAZY)
#JoinColumn(name = "idB")
private A a;
...
}
#Entity
#DiscriminatorValue("8")
#PrimaryKeyJoinColumn(name="idC", referencedColumnName="idB")
#XmlRootElement
public class C extends class B implements Serializable{
private Long idC;
#OneToOne(cascade={CascadeType.REMOVE}, fetch=FetchType.LAZY)
#JoinColumn(name = "idC")
private B b;
...
}
I don't know why, invoking a Query SELECT c FROM C c retrieves me 0 result when I have info on that table. If I do the query SELECT b FROM B b it works fine. It seems to be something wrong with the hierarchy annotations I've used.
Any idea? Thank you in advance :)
Joined table inheritance means that when you have a C entity, there must be a row in the A, B AND C tables. If you have data in C but no corresponding data in A or B that match up, then you will get no C entities back - the data is invisible to JPA because it first queries the A table to see if there are rows with a Type=3, and then joins it to B and C.
So check that your java inheritance model actually matches the data model. You also show that B and C have a reference to their parent - this cause the same id field to be mapped twice, as it is in both the Entity's PrimaryKeyJoinColumn and the mapping's JoinColumn. Since the referenced A (or B) is apart of the B (or C), there is no need to also have an object referenence to it - any of the inherited get methods will return the data from the A table anyway.

Jpa - join different tables depend by column type

i have a question that i'm not sure its possible with jpa
if i have let say table A
public class A {
Long id;
Long type;
Details details; // can this connect to B or c depends on type?
}
and table B and C are two different details tables. (not sure anything in common)
can i connect B or C to A depends on A.type ?
thanks
Alon
EDIT: let me try to be more accurate
i have entity
#Entity
#Table(name = "tbl_A")
public class A implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
private Long id; // seems like spring's jpa has issue hanlde "_"
/*
*can i achive some thinglike
* if detailsTableType== 1 use Details as B table
*/
#OneToOne(mappedBy = "details", cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private B details;
/*
*can i achive some thinglike
* if detailsTableType== 2 use Details as C table
*/
#OneToOne(mappedBy = "details", cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private C details; //Details
/*
* this field set the from where the join should be
*/
private Long detailsTableType;
}
note the B,C not necessarily share anything in common
EDIT 2:
one possible solution can be some kind of hack using getter. which means:
map in A all the possible join(mark them as lazy) and create a getter that will know depend by type which join it should use..
I guess that you want to add some details to A instance with inheriting B and C class from A class.
I recommand you to read documentation about inheritance and discriminator value/column.
On the documentation of hibernate you will see how to
- use the concept of inheritance and TABLE_PER_CLASS strategy
- and describe the descriminator column (your type column)
Edit : but i recommand you to use another strategy like SINGLE_TABLE_PER_CLASS. Note that a JPA provider don't have to support the TABLE_PER_CLASS strategy, and also, this particular startegy have consequences on performance.
*Second Edit : * Ok : i would suggest you to use polymorphism for B and C class because they use something in common => the link whith the base class A !
You could have :
Class A
* Member list of DetailsItf
and
DetailItf (interface)
|
AbstractDetail (SINGLE_TABLE_PER_CLASS strategy with discriminator colum ou TABLE_PER_CLASS) implements DetailItf
|
|-B inherits from Details
|-C inherits from Details
Then you have to use an AbstractDetail class in your base A class like :
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany( targetEntity=foo.pck.AbstractDetail.class )
private List<DetailsItf> details = new ArrayList<DetailItf>();
And on usage, you should make a
B myB = new B();
// saving myB entity
A myA = new A();
myA.addDetails(myB);
// saving myA
You should also do specific queriez basing you on TYPE() JPQL specific keywords or, on using FROM CLASS in jpql but you have to create a proof of concept and validate performances.

Categories

Resources