JPA Criteria API recursive fetch lazy association - java

I have a recursive entity which has an association with itself.
#Entity
public class Function {
(...)
#ManyToMany(fetch = FetchType.LAZY)
private Set<Function> children = new HashSet<>();
}
If this association would be eager #ManyToMany(fetch = FetchType.EAGER), then all of its children would always be initialized with one trip to the database. Of course, I don't want this association to be eager.
With the JPA Criteria API, one could fetch a collection with someRoot.fetch(Function_.children). This only fetches it one level deep though and is not recursive like the eager fetch type.
Is there a way to recursively fetch its children without making it an eager association?

I've opted to go for multiple mappings for the same table. There's some dangers in this, but since the entities are short lived and only one of them needs to be writable, it's fine.
In my case, one of them doesn't even need to access its children, further simplifying my this entity.
#MappedSuperclass
public class BaseFunction {
(...)
}
#Entity
#Table(name = "function")
public class RecursiveFunction extends BaseFunction {
#ManyToMany(fetch = FetchType.EAGER)
private Set<RecursiveFunction> children = new HashSet<>();
}
#Entity
#Table(name = "function")
public class SimpleFunction extends BaseFunction {}

Related

JPA Storing nested object with association, but not retrieving [duplicate]

What is the difference between:
#Entity
public class Company {
#OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
#JoinColumn(name = "companyIdRef", referencedColumnName = "companyId")
private List<Branch> branches;
...
}
and
#Entity
public class Company {
#OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY,
mappedBy = "companyIdRef")
private List<Branch> branches;
...
}
The annotation #JoinColumn indicates that this entity is the owner of the relationship (that is: the corresponding table has a column with a foreign key to the referenced table), whereas the attribute mappedBy indicates that the entity in this side is the inverse of the relationship, and the owner resides in the "other" entity. This also means that you can access the other table from the class which you've annotated with "mappedBy" (fully bidirectional relationship).
In particular, for the code in the question the correct annotations would look like this:
#Entity
public class Company {
#OneToMany(mappedBy = "company",
orphanRemoval = true,
fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
private List<Branch> branches;
}
#Entity
public class Branch {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "companyId")
private Company company;
}
#JoinColumn could be used on both sides of the relationship. The question was about using #JoinColumn on the #OneToMany side (rare case). And the point here is in physical information duplication (column name) along with not optimized SQL query that will produce some additional UPDATE statements.
According to documentation:
Since many to one are (almost) always the owner side of a bidirectional relationship in the JPA spec, the one to many association is annotated by #OneToMany(mappedBy=...)
#Entity
public class Troop {
#OneToMany(mappedBy="troop")
public Set<Soldier> getSoldiers() {
...
}
#Entity
public class Soldier {
#ManyToOne
#JoinColumn(name="troop_fk")
public Troop getTroop() {
...
}
Troop has a bidirectional one to many relationship with Soldier through the troop property. You don't have to (must not) define any physical mapping in the mappedBy side.
To map a bidirectional one to many, with the one-to-many side as the owning side, you have to remove the mappedBy element and set the many to one #JoinColumn as insertable and updatable to false. This solution is not optimized and will produce some additional UPDATE statements.
#Entity
public class Troop {
#OneToMany
#JoinColumn(name="troop_fk") //we need to duplicate the physical information
public Set<Soldier> getSoldiers() {
...
}
#Entity
public class Soldier {
#ManyToOne
#JoinColumn(name="troop_fk", insertable=false, updatable=false)
public Troop getTroop() {
...
}
Unidirectional one-to-many association
If you use the #OneToMany annotation with #JoinColumn, then you have a unidirectional association, like the one between the parent Post entity and the child PostComment in the following diagram:
When using a unidirectional one-to-many association, only the parent side maps the association.
In this example, only the Post entity will define a #OneToMany association to the child PostComment entity:
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "post_id")
private List<PostComment> comments = new ArrayList<>();
Bidirectional one-to-many association
If you use the #OneToMany with the mappedBy attribute set, you have a bidirectional association. In our case, both the Post entity has a collection of PostComment child entities, and the child PostComment entity has a reference back to the parent Post entity, as illustrated by the following diagram:
In the PostComment entity, the post entity property is mapped as follows:
#ManyToOne(fetch = FetchType.LAZY)
private Post post;
The reason we explicitly set the fetch attribute to FetchType.LAZY is because, by default, all #ManyToOne and #OneToOne associations are fetched eagerly, which can cause N+1 query issues.
In the Post entity, the comments association is mapped as follows:
#OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
The mappedBy attribute of the #OneToMany annotation references the post property in the child PostComment entity, and, this way, Hibernate knows that the bidirectional association is controlled by the #ManyToOne side, which is in charge of managing the Foreign Key column value this table relationship is based on.
For a bidirectional association, you also need to have two utility methods, like addChild and removeChild:
public void addComment(PostComment comment) {
comments.add(comment);
comment.setPost(this);
}
public void removeComment(PostComment comment) {
comments.remove(comment);
comment.setPost(null);
}
These two methods ensure that both sides of the bidirectional association are in sync. Without synchronizing both ends, Hibernate does not guarantee that association state changes will propagate to the database.
Which one to choose?
The unidirectional #OneToMany association does not perform very well, so you should avoid it.
You are better off using the bidirectional #OneToMany which is more efficient.
I disagree with the accepted answer here by Óscar López. That answer is inaccurate!
It is NOT #JoinColumn which indicates that this entity is the owner of the relationship. Instead, it is the #ManyToOne annotation which does this (in his example).
The relationship annotations such as #ManyToOne, #OneToMany and #ManyToMany tell JPA/Hibernate to create a mapping. By default, this is done through a seperate Join Table.
#JoinColumn
The purpose of #JoinColumn is to create a join column if one does
not already exist. If it does, then this annotation can be used to
name the join column.
MappedBy
The purpose of the MappedBy parameter is to instruct JPA: Do NOT
create another join table as the relationship is already being mapped
by the opposite entity of this relationship.
Remember: MappedBy is a property of the relationship annotations whose purpose is to generate a mechanism to relate two entities which by default they do by creating a join table. MappedBy halts that process in one direction.
The entity not using MappedBy is said to be the owner of the relationship because the mechanics of the mapping are dictated within its class through the use of one of the three mapping annotations against the foreign key field. This not only specifies the nature of the mapping but also instructs the creation of a join table. Furthermore, the option to suppress the join table also exists by applying #JoinColumn annotation over the foreign key which keeps it inside the table of the owner entity instead.
So in summary: #JoinColumn either creates a new join column or renames an existing one; whilst the MappedBy parameter works collaboratively with the relationship annotations of the other (child) class in order to create a mapping either through a join table or by creating a foreign key column in the associated table of the owner entity.
To illustrate how MapppedBy works, consider the code below. If MappedBy parameter were to be deleted, then Hibernate would actually create TWO join tables! Why? Because there is a symmetry in many-to-many relationships and Hibernate has no rationale for selecting one direction over the other.
We therefore use MappedBy to tell Hibernate, we have chosen the other entity to dictate the mapping of the relationship between the two entities.
#Entity
public class Driver {
#ManyToMany(mappedBy = "drivers")
private List<Cars> cars;
}
#Entity
public class Cars {
#ManyToMany
private List<Drivers> drivers;
}
Adding #JoinColumn(name = "driverID") in the owner class (see below), will prevent the creation of a join table and instead, create a driverID foreign key column in the Cars table to construct a mapping:
#Entity
public class Driver {
#ManyToMany(mappedBy = "drivers")
private List<Cars> cars;
}
#Entity
public class Cars {
#ManyToMany
#JoinColumn(name = "driverID")
private List<Drivers> drivers;
}
The annotation mappedBy ideally should always be used in the Parent side (Company class) of the bi directional relationship, in this case it should be in Company class pointing to the member variable 'company' of the Child class (Branch class)
The annotation #JoinColumn is used to specify a mapped column for joining an entity association, this annotation can be used in any class (Parent or Child) but it should ideally be used only in one side (either in parent class or in Child class not in both) here in this case i used it in the Child side (Branch class) of the bi directional relationship indicating the foreign key in the Branch class.
below is the working example :
parent class , Company
#Entity
public class Company {
private int companyId;
private String companyName;
private List<Branch> branches;
#Id
#GeneratedValue
#Column(name="COMPANY_ID")
public int getCompanyId() {
return companyId;
}
public void setCompanyId(int companyId) {
this.companyId = companyId;
}
#Column(name="COMPANY_NAME")
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
#OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL,mappedBy="company")
public List<Branch> getBranches() {
return branches;
}
public void setBranches(List<Branch> branches) {
this.branches = branches;
}
}
child class, Branch
#Entity
public class Branch {
private int branchId;
private String branchName;
private Company company;
#Id
#GeneratedValue
#Column(name="BRANCH_ID")
public int getBranchId() {
return branchId;
}
public void setBranchId(int branchId) {
this.branchId = branchId;
}
#Column(name="BRANCH_NAME")
public String getBranchName() {
return branchName;
}
public void setBranchName(String branchName) {
this.branchName = branchName;
}
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="COMPANY_ID")
public Company getCompany() {
return company;
}
public void setCompany(Company company) {
this.company = company;
}
}
I'd just like to add that #JoinColumn does not always have to be related to the physical information location as this answer suggests. You can combine #JoinColumn with #OneToMany even if the parent table has no table data pointing to the child table.
How to define unidirectional OneToMany relationship in JPA
Unidirectional OneToMany, No Inverse ManyToOne, No Join Table
It seems to only be available in JPA 2.x+ though. It's useful for situations where you want the child class to just contain the ID of the parent, not a full on reference.
Let me make it simple.
You can use #JoinColumn on either sides irrespective of mapping.
Let's divide this into three cases.
1) Uni-directional mapping from Branch to Company.
2) Bi-direction mapping from Company to Branch.
3) Only Uni-directional mapping from Company to Branch.
So any use-case will fall under this three categories. So let me explain how to use #JoinColumn and mappedBy.
1) Uni-directional mapping from Branch to Company.
Use JoinColumn in Branch table.
2) Bi-direction mapping from Company to Branch.
Use mappedBy in Company table as describe by #Mykhaylo Adamovych's answer.
3)Uni-directional mapping from Company to Branch.
Just use #JoinColumn in Company table.
#Entity
public class Company {
#OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
#JoinColumn(name="courseId")
private List<Branch> branches;
...
}
This says that in based on the foreign key "courseId" mapping in branches table, get me list of all branches. NOTE: you can't fetch company from branch in this case, only uni-directional mapping exist from company to branch.
JPA is a layered API, the different levels have their own annotations. The highest level is the (1) Entity level which describes persistent classes then you have the (2) relational database level which assume the entities are mapped to a relational database and (3) the java model.
Level 1 annotations: #Entity, #Id, #OneToOne, #OneToMany, #ManyToOne, #ManyToMany.
You can introduce persistency in your application using these high level annotations alone. But then you have to create your database according to the assumptions JPA makes. These annotations specify the entity/relationship model.
Level 2 annotations: #Table, #Column, #JoinColumn, ...
Influence the mapping from entities/properties to the relational database tables/columns if you are not satisfied with JPA's defaults or if you need to map to an existing database. These annotations can be seen as implementation annotations, they specify how the mapping should be done.
In my opinion it is best to stick as much as possible to the high level annotations and then introduce the lower level annotations as needed.
To answer the questions: the #OneToMany/mappedBy is nicest because it only uses the annotations from the entity domain. The #oneToMany/#JoinColumn is also fine but it uses an implementation annotation where this is not strictly necessary.
#Entity
public class Company {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "company_id_ref", referencedColumnName = "company_id")
private List<Branch> branches;
...
}
That Will give below Hibernate logs
Hibernate: select nextval ('hibernate_sequence')
Hibernate: select nextval ('hibernate_sequence')
Hibernate: insert into company (name, company_id) values (?, ?)
Hibernate: insert into branch (company_id_ref, name, id) values (?, ?, ?)
Hibernate: update branch set company_id_ref=? where id=?
And
#Entity
public class Company {
#OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY,
mappedBy = "company")
private List<Branch> branches;
...
}
That will give below Hibernate logs
Hibernate: select nextval ('hibernate_sequence')
Hibernate: select nextval ('hibernate_sequence')
Hibernate: insert into company (name, company_id) values (?, ?)
Hibernate: insert into branch (company_id_ref, name, id) values (?, ?, ?)
We can clearly see that #joinColumn will cause additional update queries.
so you do not need to set parent entity explicitly to child entity,
That we have to do while using mappedBy
to save children with a parent

How do you map two abstract InheritanceType.TABLE_PER_CLASS entities with a OneToMany relationship?

I'm using Hibernate with Spring Boot and JPA, and have a business requirement to retrieve and combine in to a single paged response data that is stored in four different tables in the DB.
Let's call the first two tables "tblCredits", containing Credits, and "tblDebits", containing Debits. For our purposes, those two tables are IDENTICAL - same column names, same column types, same ID fields, everything. And my endpoint is supposed to be able to return a combined list of both Credits and Debits, with the ability to search/sort by any/all of the fields being returned, and with paging.
If I controlled that DB, I would simply merge the two tables in to a single table, or create a view or stored proc which did that for me, but this is a legacy DB used by other applications which I can't modify in any way, so that's not an option.
If I didn't have to sort and page, I could just create two completely independent entities, create a separate Spring Data JPA repository for each entity, query the two repositories separately, and then just combine the results in my own code. But paging the combined results especially would get very hairy, I don't want to have to implement the merged paging logic myself unless I absolutely have to. Ideally I should be able to get JPA to handle all of that for me out-of-the-box.
I have been able achieve this first step for these first two tables using an abstract class declared as an Entity with InheritanceType.TABLE_PER_CLASS, like this:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractCreditDebitEntity {
/* literally all my properties and ID and column mappings here
...
*/
}
And then two concrete classes which extend that abstract entity and simply specify the two different table mappings, have no class-specific properties or column mappings at all:
#Entity
#Table(name = "tblCredits")
public final class Credit extends AbstractCreditDebitEntity {
//Literally nothing inside this class
}
#Entity
#Table(name = "tblDebits")
public final class Debit extends AbstractCreditDebitEntity {
//Literally nothing inside this class
}
So far so good, this works great, I am able to create a Spring JPA Repository on the AbstractCreditDebitEntity entity, under the hood that generates a union query on the two tables, and I am able to get back records from both tables in a single query, with appropriate paging and sorting. (The performance issues around union queries don't concern me at the moment.)
However, where I'm getting tripped up is on the next step, when I incorporate the additional two tables. tblCredits has a one-to-many relationship to tblCreditLineItems, and tblDebits has a one-to-many relationship to tblDebitLineItems. Again, tblCreditLineItems and tblDebitLineItems are IDENTICAL tables, from our perspective - same column names, same column types, same ID fields, everything.
So I can follow the same pattern as before for those sub-entities:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractCreditDebitLineItemEntity {
/* literally all my properties and ID and column mappings here
...
*/
}
#Entity
#Table(name = "tblCreditLineItems")
public final class CreditLineItem extends AbstractCreditDebitLineItemEntity {
//Literally nothing inside this class
}
#Entity
#Table(name = "tblDebitLineItems")
public final class DebitLineItem extends AbstractCreditDebitLineItemEntity {
//Literally nothing inside this class
}
But now I need to create the mappings between the Credit/Debit entities and CreditLineItem/DebitLineItem entities. And this is where I'm struggling. Because I need to be able to filter which specific Credit/Debit entities I return based on the values of properties inside their associated CreditLineItem/DebitLineItem entities, I need a bidirectional mapping between the two entities, and I've been unable to get that working successfully.
Here's how far I've gotten so far. First the three Credit/Debit entities with the OneToMany mapping to their associated CreditLineItem/DebitLineItem entities:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractCreditDebitEntity {
/* literally all my properties and ID and column mappings here
...
*/
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(
name = "MyIdColumnName",
referencedColumnName = "MyIdColumnName"
)
public abstract List<AbstractCreditDebitLineItemEntity> getCreditDebitLineItems();
public abstract void setCreditDebitLineItems(List<AbstractCreditDebitLineItemEntity> items);
}
#Entity
#Table(name = "tblCredits")
public final class Credit extends AbstractCreditDebitEntity {
private List<CreditLineItem> creditDebitLineItems;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, targetEntity = CreditLineItem.class)
#JoinColumn(
name = "MyIdColumnName",
referencedColumnName = "MyIdColumnName"
)
#Override
public List<AbstractCreditDebitLineItemEntity> getCreditDebitLineItems() {
return Optional.ofNullable(creditDebitLineItems).stream()
.flatMap(List::stream)
.filter(value -> AbstractCreditDebitLineItemEntity.class.isAssignableFrom(value.getClass()))
.map(AbstractCreditDebitLineItemEntity.class::cast)
.collect(Collectors.toList());
}
#Override
public void setCreditDebitLineItems(List<AbstractCreditDebitLineItemEntity> items) {
creditDebitLineItems = Optional.ofNullable(items).stream()
.flatMap(List::stream)
.filter(value -> CreditLineItem.class.isAssignableFrom(value.getClass()))
.map(CreditLineItem.class::cast)
.collect(Collectors.toList());
}
}
#Entity
#Table(name = "tblDebits")
public final class Debit extends AbstractCreditDebitEntity {
private List<DebitLineItem> creditDebitLineItems;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, targetEntity = DebitLineItem.class)
#JoinColumn(
name = "MyIdColumnName",
referencedColumnName = "MyIdColumnName"
)
#Override
public List<AbstractCreditDebitLineItemEntity> getCreditDebitLineItems() {
return Optional.ofNullable(creditDebitLineItems).stream()
.flatMap(List::stream)
.filter(value -> AbstractCreditDebitLineItemEntity.class.isAssignableFrom(value.getClass()))
.map(AbstractCreditDebitLineItemEntity.class::cast)
.collect(Collectors.toList());
}
#Override
public void setCreditDebitLineItems(List<AbstractCreditDebitLineItemEntity> items) {
creditDebitLineItems = Optional.ofNullable(items).stream()
.flatMap(List::stream)
.filter(value -> DebitLineItem.class.isAssignableFrom(value.getClass()))
.map(DebitLineItem.class::cast)
.collect(Collectors.toList());
}
}
And then the three CreditLineItem/DebitLineItem entities with their ManyToOne mappings back to the Credit/Debit entities:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractCreditDebitLineItemEntity {
/* literally all my properties and ID and column mappings here
...
*/
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "MyIdColumnName",
referencedColumnName = "MyIdColumnName",
updatable = false,
insertable = false)
public abstract AbstractCreditDebitEntity getCreditDebit();
public abstract void setCreditDebit(AbstractCreditDebitEntity creditDebitEntity);
}
#Entity
#Table(name = "tblCreditLineItems")
public final class CreditLineItem extends AbstractCreditDebitLineItemEntity {
private Credit creditDebit;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "MyIdColumnName",
referencedColumnName = "MyIdColumnName",
updatable = false,
insertable = false)
#Override
public Credit getCreditDebit() {
return creditDebit;
}
#Override
public void setCreditDebit(AbstractCreditDebitEntity creditDebitEntity) {
creditDebit =
Optional.ofNullable(creditDebitEntity)
.filter(value -> Credit.class.isAssignableFrom(value.getClass()))
.map(Credit.class::cast)
.orElse(throw new RuntimeException());
}
}
#Entity
#Table(name = "tblDebitLineItems")
public final class DebitLineItem extends AbstractCreditDebitLineItemEntity {
private Debit creditDebit;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "MyIdColumnName",
referencedColumnName = "MyIdColumnName",
updatable = false,
insertable = false)
#Override
public Debit getCreditDebit() {
return creditDebit;
}
#Override
public void setCreditDebit(AbstractCreditDebitEntity creditDebitEntity) {
creditDebit =
Optional.ofNullable(creditDebitEntity)
.filter(value -> Debit.class.isAssignableFrom(value.getClass()))
.map(Debit.class::cast)
.orElse(throw new RuntimeException());
}
}
This code compiles, however... when in my automated tests I try to persist one of my Credit entities (I use a simple H2 database for my automated tests), I get the following error:
2021-04-02 13:53:52 [main] DEBUG org.hibernate.SQL T: S: - update AbstractCreditDebitLineItemEntity set MyIdColumnName=? where ID=?
2021-04-02 13:53:52 [main] DEBUG o.h.e.jdbc.spi.SqlExceptionHelper T: S: - could not prepare statement [update AbstractCreditDebitLineItemEntity set MyIdColumnName=? where ID=?]
org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "ABSTRACTCREDITDEBITLINEITEMENTITY" does not exist
It appears to be trying to persist based on the #OneToMany mapping from my AbstractCreditDebitEntity class to my AbstractCreditDebitLineItemEntity. Which, since it's an abstract class with InheritanceType.TABLE_PER_CLASS, has no table specified for it, so it assumes the table it needs to persist to has the same name as the class.
What I wanted to happen here is for the #OneToMany mapping on the concrete getter in the Credit subclass, which specifies its targetEntity as the concrete CreditLineItem.class, to essentially override/replace the #OneToMany mapping on its parent abstract class. But it seems the mapping on the concrete class gets completely ignored?
I could remove the #OneToMany mapping from the AbstractCreditDebitEntity class entirely, and only define that mapping in the two concrete Credit/Debit entities that extend it. That makes the persistence error go away, and 90% of my test cases pass... but in that case when I try to filter or sort the results coming back from the combined AbstractCreditDebitEntity Spring Data JPA repository based on one of the fields that only exists in the CreditLineItem/DebitLineItem sub-entity, the query fails due to the AbstractCreditDebitEntity no longer having any mapping to the AbstractCreditDebitLineItemEntity.
Is there any good way of resolving this problem, so that the OneToMany mapping from AbstractCreditDebitEntity to AbstractCreditDebitLineItemEntity still exists, but the knowledge that the Credit entity maps specifically to the CreditLineItem entity and the Debit entity maps specifically to the DebitLineItem entity is also maintained?
After a lot of experimentation, I found something that works for me.
Basically, rather than try to override the OneToMany mapping in the abstract entity class with the OneToMany mappings in the concrete entities, I had to make them completely separate mappings to completely different properties. Which means my concrete entities have two different collections of AbstractCreditDebitLineItemEntity, and some AbstractCreditDebitLineItemEntity objects will appear twice, in both collections. A bit wasteful in terms of memory/computation, but I'm okay with that, it works!
So here's what I ended up with:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractCreditDebitEntity {
/* literally all my properties and ID and column mappings here
...
*/
private List<AbstractCreditDebitLineItemEntity> creditDebitLineItems;
#OneToMany(fetch = FetchType.LAZY, targetEntity = AbstractCreditDebitLineItemEntity.class)
#JoinColumn(
name = "MyIdColumnName",
referencedColumnName = "MyIdColumnName",
updatable = false,
insertable = false
)
public List<AbstractCreditDebitLineItemEntity> getCreditDebitLineItems() {
return creditDebitLineItems;
}
public void setCreditDebitLineItems(List<AbstractCreditDebitLineItemEntity> items) {
creditDebitLineItems = items;
}
}
#Entity
#Table(name = "tblCredits")
public final class Credit extends AbstractCreditDebitEntity {
private List<CreditLineItem> creditLineItems;
#OneToMany(cascade = CascadeType.ALL, targetEntity = CreditLineItem.class)
#LazyCollection(LazyCollectionOption.FALSE)
#JoinColumn(
name = "MyIdColumnName",
referencedColumnName = "MyIdColumnName"
)
public List<CreditLineItem> getCreditLineItems() {
return creditLineItems;
}
#Override
public void setCreditDebitLineItems(List<CreditLineItem> items) {
creditLineItems = items;
}
}
With the exact same pattern repeated for the Debit entity.
This allows me to both:
persist, using the OneToMany mappings from the concrete Credit and Debit entities to the concrete CreditLineItem and DebitLineItem entities; and
do finds on the Spring Data JPA repository of AbstractCreditDebitEntity, using the the completely separate OneToMany mapping from that abstract entity to the AbstractCreditDebitLineItemEntity.
Not as clean as if I'd been able to override the OneToMany mapping in the abstract parent class with a more specific OneToMany mapping in the concrete child classes... but as I said, it works!
(The answer on this issue helped me know I needed to replace fetchType=FetchType.EAGER on my concrete OneToMany mappings with #LazyCollection(LazyCollectionOption.FALSE):
Hibernate throws MultipleBagFetchException - cannot simultaneously fetch multiple bags)

Hibernate multi-module project: extend entity to add many-to-many relationship

I have a project that uses Hibernate and is divided into multiple modules.
I have the following modules:
device (defines entity Device)
appstore (defines entity Application)
device-appstore-integration (adds many-to-many relationship between Device and Application).
Entities look like this:
#Entity
#Table(name = "devices")
public class Device extends AbstractAuditingEntity implements Serializable
{
#NotNull
#EmbeddedId
private DeviceIdentity identity;
// ...
}
#Entity
#Table(name = "apps")
public class App extends AbstractAuditingEntity implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// ...
}
Now I want device-appstore-integration module to add many-to-many relationship between the two entities above.
I thought about adding entity DeviceWithInstalledApps to define this many2many relationship and used the following code:
#Entity
#Table(name = "devices")
public class DeviceWithInstalledApps extends Device
{
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "devices_installed_apps",
joinColumns = {/*...*/},
inverseJoinColumns = {/*...*/}
)
private Set<App> installedApps = new HashSet<>();
// ...
}
The problem is that Hibernate requires devices table to include dtype column and treats DeviceWithInstalledApps as a separate entity that inherits from Device but I don't actually want it to be a separate entity. It's still the same entity, just with many-to-many relationship added so that I can actually access this relationship, no columns are added so there is no actual need to provide dtype column, it simply doesn't make sense in this context.
Is there any other way to define many-to-many relationship in JPA/Hibernate so that I can actually implement business logic without getting into issues mentioned above?

JPA : Automatically fetch child object

I'm using #OneToMany feature, with FetchType.EAGER set. But JPA (through EclipseLink) keep making one sub request for each sub object in my list.
I really dont get why.
I follow many thread I found, but cant make this select work in one single request.
Also, I retrive my object with CriteriaBuilder, CriteriaQuery and some Predicate. This may be an issue ?
Anyway, all this filter are only use for the top lvl classes. I still want the lower one to be fetched as well.
My entities look like :
#Entity
#Table(name = "mainTable")
public class MainTable extends SqlEntity {
#OneToMany(fetch = FetchType.EAGER)
private List<MainLocation> locations = new ArrayList<MainLocation>();
...
}
#Entity
#Table(name = "mainLocation")
public class MainLocation extends SqlEntity {
#OneToMany(fetch = FetchType.EAGER)
private List<MainDetail> details = new ArrayList<MainDetail>();
...
}
#Entity
#Table(name = "mainDetail")
public class MainDetail extends SqlEntity {
...
}

JPA how to delete an entity that is a target of onetoone with cascade remove

I have two entities with a onetoone relationship, A and B. B entity is optional, can be updated and removed on it's own, but must always be linked to an instance of A.
So i have two JPA entities, A and B with a bi-directional relationship. THis is the one from A to B.
#OneToOne(mappedBy = "a", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
I can create a A and B, remove the A and both get deleted. good.
But because of the cascade from A to B, if i em.remove(b) the delete doesn't get persisted. Even if i do a.setB(null) first.
The only way to delete the optional entity, while keeping the cascade, seems to be to use a new JPA2 feature, orphanRemoval=true. Call a.setB(null), then persist A.
This means i can't do operations directly on B, it implies too strong of a composition relationship, all actions on B must be done via A.
But B is not an embedded object, it's a full blown Entity in it's own right, how can i delete it independently of A?
The best way seems to be to remove the cascade, and force users to make sure they delete any related objects separately before they delete the A? Enforced by a FK constraint in the B table.
This is such a straight forward case. two related entities, the relationship is optional on one end, and mandatory on the other.
Oh, this is with hibernate 4.2.3-Final
Your current object design implicitly defined that one is more important than another. That is, one will have the foreign key to another.
To make them equal, you just define the JoinTable between them. Set cascade on both sides, and then everything will work as expected.
Example:
Document class
#Entity
public class Document extends ABaseEntity {
private Medicine medicine;
#OneToOne(cascade = CascadeType.REMOVE)
#JoinTable(
name = "Document_Medicine",
joinColumns =
#JoinColumn(name = "DOC_ID", referencedColumnName = "ID"),
inverseJoinColumns =
#JoinColumn(name = "MED_ID", referencedColumnName = "ID"))
public Medicine getMedicine() {
return medicine;
}
public void setMedicine(Medicine medicine) {
this.medicine = medicine;
}
}
Medicine class
#Entity
public class Medicine extends ABaseEntity{
private Document document;
#OneToOne(mappedBy = "medicine", cascade = CascadeType.REMOVE)
public Document getDocument() {
return document;
}
public void setDocument(Document document) {
this.document = document;
}
}

Categories

Resources