JPA modeling of FK pointing to various tables - java

There's a table with a "FK" column whose values might point back to either TableA or TableB, something like this:
CREATE TABLE TableC (
"id" bigint GENERATED BY DEFAULT AS IDENTITY,
"linked_entity_id" integer,
"linked_entity_type" varchar(15),
...other columns
PRIMARY KEY ("id")
);
I'm struggling with representing this in JPA. I'd like to have both TableA and TableB entities in JPA to have a List. Is there a way to do this?
This is my attempt, having TableC entity modeled like this:
#Entity
public class TableC {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long linkedEntityId;
#Enumerated(EnumType.STRING)
private LinkedEntityType linkedEntityType;
#ManyToOne
#JoinColumn(name = "linked_entity_id", referencedColumnName = "id", insertable=false, updateable=false)
private TableA tableA;
#ManyToOne
#JoinColumn(name = "linked_entity_id", referencedColumnName = "id", insertable=false, updateable=false)
private TableB tableB;
}
But, what happens when the TableC has a row whose id in the linked_entity_id column belongs to TableA id not to TableB? I think an exception would be thrown.
PS: TableA and TableB do not have anything in common.

You must use the #Where annotation:
#ManyToOne
#Where(clause = "linked_entity_type = 'TableA'")
#JoinColumn(name = "linked_entity_id", referencedColumnName = "id", insertable=false, updateable=false)
private TableA tableA;
#ManyToOne
#Where(clause = "linked_entity_type = 'TableB'")
#JoinColumn(name = "linked_entity_id", referencedColumnName = "id", insertable=false, updateable=false)
private TableB tableB;
Source: https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#pc-where

Related

Joining 2 tables with part of a Composite Primary key in JPA, Hibernate

I have an old database where can't change the schema so I have to adapt my code to work.
Within that db I have two tables which I'd like to join.
The main Table called SFTSEB2 has a 5 Column Composite primary key: empfkey, ebmsg, ebbord, ebsort, ebrefn only these will going to fulfill the uniqueness on that Table.
I'd like to join SFTSEB3 to SFTSEB2 but based on only 4 Columns: empfkey, ebmsg, ebbord, ebsort
Entites:
#Entity
#Table(name = "SFTSEB2")
#IdClass(Sftseb2ID.class)
#Immutable
public class Sftseb2 {
#Id
private String empfkey;
#Id
private String ebmsg;
#Id
private String ebbord;
#Id
private int ebsort;
private int ebbpos;
private String ebsdgn;
#Id
private String ebrefn;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "empfkey", referencedColumnName = "empfkey", insertable = false, updatable = false),
#JoinColumn(name = "ebmsg", referencedColumnName = "ebmsg", insertable = false, updatable = false),
#JoinColumn(name = "ebbord", referencedColumnName = "ebbord", insertable = false, updatable = false),
#JoinColumn(name = "ebsort", referencedColumnName = "ebsort", insertable = false, updatable = false)
})
List<Sftseb3> sftseb3;
//GettersSetters
...
}
#Entity
#Table(name = "SFTSEB3")
#IdClass(Sftseb3ID.class)
#Immutable
public class Sftseb3 {
#Id
private String empfkey;
#Id
private String ebmsg;
#Id
private String ebbord;
#Id
private int ebsort;
#Id
private int ebsrt2;
#Id
private String ebsdgn;
private int ebbpos;
private String ebstst;
private String ebstsc;
//GettersSetters
//...
}
Unfortunately I can get past this because hibernate is giving back:
A Foreign key refering Sftseb2 from Sftseb3 has the wrong number of column. should be 5
I tried to have a bi-directional mapping so the complete JoinColumns part went to Sftseb3 with a #ManyToOne and Sftseb2 would only contain the mappedBy at the #OneToMany but I got the same issue.
As I see there is no way to have different PK on an entity and Join another table to it based on part of the PK or even other columns? Why?
If I reduce the number of #Id columns to match in the two tables so Sftseb2's #Ids: empfkey, ebmsg, ebbord, ebsort then it starts to work, however because this is non-unique all instances of Sftseb2 will be the same...

Repeated column in mapping for entity: entity.LaAssociateGridEntity column: AssociateId (should be mapped with insert="false" update="false")

I have got two different rest endpoints to insert data into DB where:
/api1 will insert data into Table1 and Table3
where as /api2 will insert data into Table Table1 and Table3 but facing this error while inserting the data into db.
I have got three tables: Table1, Table2, Table3
Table1 and Table3's association is #OneToMany
Table2 and Table3's association is ALSO #OneToMany
The solution that I am getting is: (should be mapped with insert="false" update="false") but this will prevent the insertion and updating of the data into database for these columns.
How do I use these
Sharing snippet of the code block where I'm facing the issue. Please let me know if you want me to share the whole code
#Data #EqualsAndHashCode(callSuper = true) #Entity #Table(name = "[dbo].[LAInstructions]")
public class LaInstructionsEntity extends AuditableFields<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private Long id;
#JsonManagedReference
#**OneToMany**(mappedBy = "laInstructionsEntity", cascade = CascadeType.ALL)
private List<LaAssociateGridEntity> laAssociateGridEntities;
#Column(name = "Active")
private Boolean active;
}
Table 2:
#Data
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "[dbo].[LALegalCreditCheck]")
public class LALegalCreditCheckEntity extends AuditableFields<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private Long id;
#JsonManagedReference
#**OneToMany**(mappedBy = "laLegalCreditCheckEntity", cascade = CascadeType.ALL, targetEntity = LaAssociateGridEntity.class)
List<LaAssociateGridEntity> laAssociateGridEntities;
#Column(name = "Active")
Boolean active;
}
Table 3:
#Data #EqualsAndHashCode(callSuper = true) #Entity #Table(name = "[dbo].[LAAssociateGrid]")
public class LaAssociateGridEntity extends AuditableFields<Long> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private Long id;
#Column(name = "Active")
private Boolean active;
#JsonBackReference
#**ManyToOne**(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "AssociateId", referencedColumnName = "Id")
private LaInstructionsEntity laInstructionsEntity;
#**ManyToOne**(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "AssociateId", referencedColumnName = "Id")
private LALegalCreditCheckEntity laLegalCreditCheckEntity;
}
This error says that in LaAssociateGridEntity , both laInstructionsEntity and laLegalCreditCheckEntity fields are also mapped to the same column (i.e. AssociateId) , so Hibernate does not know which fields should it consider when mapping this column.
I believe there is a typo in one of them. LaInstructionsEntity and LALegalCreditCheckEntity are different entities. And without another discriminator column , how can you differentiate for the same AssociateId in LaAssociateGridEntity , its parent is LaInstructionsEntity or LALegalCreditCheckEntity ?

Unable to create Mapping Entity with JPA

A. I have a entity EntityA like below:
Table - EntityA(id long PK, name varchar)
#Entity #Table
EntityA{
#Id
long id;
String name;
}
B. Based on this I want to fetch data in below class through JPA(using unidirectional mapping):
#Entity #Table
EntityMap{
long id;
#OneToOne
EntityA entity;
#OneToMany
List<EntityA> mappedEntity;
}
C. To make it work for now I have created an entity like below:
Table - entity_map(id long pk, source_entity_id long FK-EntityA_id, target_entity_id long FK-EntityA_id)
#Entity #Table
EntityMap{
#Id
long id;
#OneToOne
#JoinColumn(name = "source_entity_id")
EntityA sourceEntity;
#ManyToOne
#JoinColumn(name = "target_entity_id")
EntityA targetEntity;
}
This is working as expected but I need entity explained in #B. Any suggestion?
A #OneToMany relationship can be archieved by
#JoinColumn(name = "entity_map_id")
#OneToMany
List<EntityA> mappedEntity;
In your EntityA table you need a column entity_map_id and a foreign key (entity_map_id) references EntityMap(id) constraint.
If you cannot change the EntityA table you need a #JoinTable to perform the mapping:
#JoinTable(name = "JOIN_TABLE", joinColumns = {#JoinColumn(name = "MY_ENTITY_MAP_FK")}, inverseJoinColumns = {#JoinColumn(name = "ENTITY_A_FK")})
#OneToMany
List<EntityA> mappedEntity;
The join table contains two columns ENTITY_A_FK and MY_ENTITY_MAP_FK:
create table JOIN_TABLE(
ENTITY_A_FK integer not null,
MY_ENTITY_MAP_FK integer not null
);

Foreign key mapping to Embeddable class

I am using Hibernate to join Entity1 to Entity2 where Entity2 have a composite primary key.
Entity2 contains 2 columns id and type
public class Entity2 {
#EmbeddedId
private Entity2PK id;
}
#Embeddable
public class Entity2PK implements Serializable {
#Column(name = "id")
private String id;
#Column(name = "type")
private String type;
}
Entity1 contains 2 columns id and entity_2_id (foreign key ref to Entity2)
public class Entity1 {
#Id
#Column(name = "id", updatable = false, nullable = false)
private String id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="entity_2_id")
private Entity2 entity2;
}
All the calls are failing because of this. Does anyone have a solution for this ?
Error Message:
org.hibernate.AnnotationException: A Foreign key refering Entity2 from Entity1 has the wrong number of column. should be 4
at org.hibernate.cfg.annotations.TableBinder.bindFk(TableBinder.java:646)
at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:102)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processEndOfQueue(InFlightMetadataCollectorImpl.java:1814)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processFkSecondPassesInOrder(InFlightMetadataCollectorImpl.java:1758)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1646)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:286)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.build(MetadataBuildingProcess.java:83)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:473)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:84)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:689)
You seem to have the same problem as this user: AnnotationException: A Foreign key refering has the wrong number of column. should be 2
Entity1 can not store the relationship in one single column, because Entity2 has an id with 2 columns. You can solve this by using JoinColumns instead of JoinColumn.
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "entity2_id", insertable = false, updatable = false),
#JoinColumn(name = "entity2_type", insertable = false, updatable = false)
})
private Entity2 entity2;

Update association on child delete

I have three entity classes.
Product, Category, and SubCategory.
A Category has a OneToMany relation with SubCategory
#Entity
#Table(name = "CATEGORY")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "category_id")
private Long categoryId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "category_id")
private List<SubCategory> subCategories;
}
The product is assocciated with a Category and one of its SubCategories
#Entity
#Table(name = "PRODUCTS")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = ("id"))
private Long id;
#ManyToOne
#JoinColumn(name = "category", unique = false, nullable = true, insertable = true, updatable = true)
private Category category;
#ManyToOne
#JoinColumn(name = "sub_category", unique = false, nullable = true, insertable = true, updatable = true)
private SubCategory subCategory;
}
now if I delete a Category, all its SubCatogries are deleted, but I also want the associations in Product to be updated to null. I thought of manually fetching all the products with the associated deleted Category and updating them manually, but is there a way to handle this with JPA annotations?
This update from JPA will be very inefficient in performance perspective.
Your table PRODUCTS has columns category abd sub_category which linked with correspond tables by foreign keys. Add to end of definition of each of these columns string 'ON DELETE SET NULL' and what you want will be done by database automatically.
The Product entity has a ManyToOne relationship with SubCategory that means that the SubCategory entity has a OneToMany relationship with Product. So, in the SubCategory class, where you have defined the OneToMany relationship, you need to mention the cascadetype = remove
#manytoone(cascade = cascadetype.remove)
Hope it solves your problem

Categories