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)
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 {}
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 {
...
}
my problem is that I cannot save my entity because it contains another entity, mapped by a key that is also a part of this table's primary key. The table looks like this:
table C:
+-----+------+
| id_A | id_B |
+-----+------+
..where idA is the primary key of table A with EntityA and idB the primary key of table B with EntityB.
so its basically a n-to-m relation. This is the entity I'm using for table C:
#Entity
public class EntityC {
private long idA;
private EntityB b;
#Id
#Column(name = "id_A")
public long getIdA() {
return idA;
}
#Id
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_B")
public EntityB getB() {
return b;
}
...setters are here...
}
Please note that id_A is mapped as is (the id), while id_B is mapped as its object representation, EntityB. This is what I want to do with it:
EntityC c = new EntityC();
c.setIdA(123);
c.setB(new EntityB());
em.persist(c);
tx.commit();
em.close();
I want to persist EntityB ONLY IF I can persist EntityC.
on tx.commit() I get this exception: org.hibernate.TransientObjectException: object references an unsaved transient instance
I suppose this happens because part of the primary key, id_B, is not saved. But i set cascading to all so there should be no problem!
Why is this not working?
EDIT:
When I do this:
em.persist(c.getB());
em.persist(c);
it works. But can't Hibernate/JPA do that automatically? I thought that's what cascading is good for.
EDIT2:
added an embeddedId instead of id_A and id_B:
#Embeddable
public class EntityCID implements Serializable {
public long idA;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_B", referencedColumnName = "id")
public EntryB b;
}
EntityC now looks like:
#Entity
public class EntityC implements Serializable {
private EntityCID id;
...
#EmbeddedId
public void getId() {
return id;
}
}
but I still get the transient object exception if I don't em.persist(c.getId().b); before em.persist(c). Sticking to that, although it is ugly.
#Trein: it is not bidirectional. EntityB code:
#Entity
public class EntityB implements Serializable {
public long id;
public String text;
}
If you think about it what you are seeing makes perfect sense.
EntityC is is the 'owning side' of the relationship C<>B: it defines the JoinColumn and EntityB has the 'mappedBy' attribute.
So on saving C, order of events would normally be:
insert into C/update C
insert into B/update B
Now in your case this causes issues as obviously C can only be saved if B has been persisted first.
In terms of your statement above: I want to persist "EntityB ONLY IF I can persist EntityC." How can this ever be the case?
JPA has a concept of 'Derived Identifiers', which I am not overly familiar with however is defined in the book Pro JPA as occurring when:
When an identifier in one entity includes a foreign key to another
entity, we call it a derived identifier. Because the entity containing
the derived identifier depends upon another entity for its identity,
we call the first the dependent entity. The entity that it depends
upon is the target of a many-to-one or one-toone relationship from the
dependent entity, and is called the parent entity
Now, despite the original advice that you had two #Id attributes defined and this was wrong it would however appear that having an additional #Id on a 1-2-m is in fact valid in JPA 2 for precisely this case.
The book gives a number of ways of dealing with Derived Identifiers however one example given below looks fairly similar to your case. So you may want to investigate further the #MapsId attribute.
#Entity
public class Project {
#EmbeddedId private ProjectId id;
#MapsId("dept")
#ManyToOne
#JoinColumns({
#JoinColumn(name="DEPT_NUM", referencedColumnName="NUM"),
#JoinColumn(name="DEPT_CTRY", referencedColumnName="CTRY")})
private Department department;
// ...
}
#Embeddable
public class ProjectId implements Serializable {
#Column(name="P_NAME")
private String name;
#Embedded
private DeptId dept;
// ...
}
See further:
How do I properly cascade save a one-to-one, bidirectional relationship on primary key in Hibernate 3.6
Is it a bidirectional relationship? I would suggest you to remove #Id getB() and perform the modifications:
#OneToOne(cascade = CascadeType.ALL, mappedBy = "id_B")
#PrimaryKeyJoinColumn(name = "id_B")
public EntityB getB() {
return b;
}
Your entity class must have only one attribute annotated with #Id. Usually when you need this, you create a class that will store both properties and this will act as a Id Class.
You can not pass new Entity() for reference. Because it won't have any values in it(even primary key). So how can hibernate will insert it as foreign key to the table. And cascade will save your parent object if its not saved,no need to call save method for all. But when you passing new object it won't do.
I am trying to map a bi-directional one-to-many relationship. I am having some trouble as the "many" side references an abstract superclass. While searching the internet for possible causes I discovered that this is a known problem but I wasn't able to find a solution for my case.
I have checked the workarounds on this blog and the "Single table, without mappedBy" looks like a solution but I really need the bi-directional association.
These are the classes I am trying to map:
Owning Side
#Entity(name = "CC_Incl_Site")
public class IncludedSite {
#OneToMany(fetch=FetchType.LAZY, mappedBy = "includedSite")
private Set<CtaContractBase> ctas = new HashSet<CtaContractBase>();
#OneToMany(fetch=FetchType.LAZY, mappedBy = "includedSite")
private Set<WoContractBase> wos = new HashSet<WoContractBase>();
}
Other Side:
#Entity
public abstract class SCContract extends Contract {
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JoinColumn(name = "incl_site_id")
private IncludedSite includedSite;
}
Contract (the superclass of SCContract):
#Entity(name = "CC_CONTRACT")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "contractType", discriminatorType = DiscriminatorType.STRING)
#ForceDiscriminator
public abstract class Contract {
...
}
When trying to run the application I get this exception:
mappedBy reference an unknown target entity property:
CtaContractBase.includedSite in IncludedSite.ctas
Another solution appears to be replacing the #Entity annotation in SCContract with #MappedSuperClass but this results in another exception (Use of #OneToMany or #ManyToMany targeting an unmapped class: StudyContract.contracts[SCContract]) because in another class (StudyContract) I have
#OneToMany(fetch = FetchType.LAZY, mappedBy = "studyContract", targetEntity = SCContract.class)
#BatchSize(size = 10)
private Set<SCContract> contracts;
and as the blog explains having a collection of the superclass is not possible anymore using this approach.
Are there any other workarounds or am I missing something?
The association in IncludedSite is defined as
#OneToMany(fetch=FetchType.LAZY, mappedBy = "includedSite")
private Set<CtaContractBase> ctas = new HashSet<CtaContractBase>();
So Hibernate looks for an attribute of type IncludedSite named includedSite in the class CtaContractBase. There is no such field. The field only exists in the subclass SCContract. This means that only SCContract instances can be the target of this association, and the association should thus be defined as
#OneToMany(fetch=FetchType.LAZY, mappedBy = "includedSite")
private Set<SCContract> ctas = new HashSet<SCContract>();