JPA #ManyToOne unidirectional mapping with key = id + tenant_id - java

I'm programming a Multi-Tenant application. The tenant_id is present in every table and is part of each PK.
My tables are:
table S {
tenant_id PK
id PK
pid
...
}
table P {
tenant_id PK
id PK
...
}
Table S has a PK made up with (tenant_id, id). Table P has a PK made up with (tenant_id, id). Table S links to table P via (S.tenant_id = P.tenant_id and S.pid = P.id)
When I've implemented this in Java JPA, I can read table S including P, but adding a S has a strange behavior: JPA adds the missing P but doesn't fill S.pid so the P is correct but there is no link from S to P.
I don't want to have bidirectional links because P is also referred to by other fields of other tables.
My problem is somewhat similar to another question which has no answer yet.
The Java classes look like (simplified):
#Embeddable
public class MultiTenantId implements Serializable {
#Column(name = "tenant_id")
private String tenantId;
#Column(name = "id")
private String id;
...
}
#Entity
#Table(name = "P")
public class P {
#EmbeddedId
private MultiTenantId id;
...
}
#Entity
#Table(name = "S")
public class S {
#EmbeddedId
private MultiTenantId id;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "tenant_id", referencedColumnName = "tenant_id", insertable = false, updatable = false),
#JoinColumn(name = "pid", referencedColumnName = "id", insertable = false, updatable = false)}
)
private P p;
...
}
If I don't set the insertable = false, updatable = false then error
because the tenant_id is already used (in the MultiTenantId and in
this #ManyToOne)
if I set them then reading is OK but adding a S via JPA has a strange effect in the DB: it correctly adds the P, correctly adds the S except that it never sets the pid in the table S ==> the link is not done.
Thanks a lot in advance for your help, I'm completely lost with this problem !!

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...

JPA/Hibernate Join only one selected column

To the existing table through #ManyToOne
#ManyToOne
#JoinColumns({
#JoinColumn(name = "ID", referencedColumnName = "APPLICATION_ID", insertable = false, updatable = false),
#JoinColumn(name = "VERSION", referencedColumnName = "APPLICATION_VERSION", insertable = false, updatable = false)
})
private ApplicationsBodies applicationsBodies;
I join another table.
But from the join table, I want to join only one column.
#Entity
#Table
public class ApplicationsBodies implements Serializable {
...
#Column(name = "APPLICATION_ID")
private Long applicationId;
#Column(name = "APPLICATION_VERSION")
private Long applicationVersion;
//I want to attach only this column
#Lob
#Column(name = "BODY")
private String body;
#Column(name = "ACTIVE_STATE")
private Integer activeState;
How can this be implemented using JPA / Hibernate?
UPDATE: Solution
My problem was solved by #Formula annotation. Because When I refer to an entity only for the purpose of loading this one field, for me it has become the most optimal solution.
I deleted the field: private ApplicationsBodies applicationsBodies. And created a field: private String body with annotation #Formula with value - SQL query joining only one column.
if you want to use join with JPA data, then I recommend to use JPL Query Language. So in your repository classes use as annotation :
#Query("select a.name from ApplicationsBodies a join a.applicationId d where d.id = :applicationVersion")

Bidirectional OneToMany and ManyToOne returns "NULL not allowed for column" on save

This is a shortened version of the entities where I only show the relevant parts.
#Entity
#Data
public class Wrapper {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id
#OneToOne(mappedBy = "wrapper", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Application application;
public Wrapper(Application application) {
this.application = application;
application.setWrapper(this);
}
}
#Data
#Entity
#EqualsAndHashCode(exclude = "wrapper")
public class Application {
#Id
private Integer id;
#JsonIgnore
#OneToOne
#JoinColumn(name = "id")
#MapsId
private Wrapper wrapper;
#OneToMany(mappedBy = "application", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#SortNatural
private SortedSet<Apartement> ownedApartements = new TreeSet<>();
}
#Entity
#Data
public class Apartement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "application_id", insertable = false, updatable = false)
private Application application;
}
#Repository
public interface WrapperRepository extends JpaRepository<Wrapper, Integer> {
}
The above entities generates the following create table statements:
create table Wrapper (
id int identity not null,
primary key (id)
)
create table Application (
id int not null,
primary key (id)
)
create table Apartement (
id int identity not null,
application_id int not null,
primary key (id)
)
alter table Apartement
add constraint FKsrweh1i1p29mdjfp03or318od
foreign key (application_id)
references Application
alter table Application
add constraint FKgn7j3pircupa2rbqn8yte6kyc
foreign key (id)
references Wrapper
Given the follow entities and the following code:
Apartement apartement1 = new Apartement()
Apartement apartement2 = new Apartement()
Wrapper wrapper = new Wrapper(new Application());
Application application = wrapper.getApplication();
application.getOwnedApartements().addAll(Arrays.asList(apartement1, apartement2));
apartement1.setApplication(application);
apartement2.setApplication(application);
WrapperRepository.saveAndFlush(wrapper);
I see three inserts in the log.
First wrapper, then application, and finally apartement. But for some reason application_id is null on the first save. But I know it has a bi-directional relationship.
The error I get is:
Caused by: org.h2.jdbc.JdbcSQLException: NULL not allowed for column "APPLICATION_ID"; SQL statement:
insert into Apartement (id) values (null) [23502-197]
Why does this happen? Do I need to store everything in the correct order? Do I need to first store wrapper and application, then finally store the apartement once I have application ID?
Cannot hibernate store all three in one go? Or figure this out it self?
Sorry I fixed it.
The problem was
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "application_id", insertable = false, updatable = false)
private Application application;
I removed insertable = false, updatable = false and added optional=false
That worked
#JoinColumn(name = "application_id", optional = false)
Try this:
Apartement apartement1 = new Apartement()
Apartement apartement2 = new Apartement()
Wrapper wrapper = new Wrapper(new Application());
Application application = wrapper.getApplication();
application.getOwnedApartements().addAll(Arrays.asList(apartement1, apartement2));
apartement1.setApplicationId(application.getId());
apartement2.setApplicationId(application.getId());
WrapperRepository.saveAndFlush(wrapper);

Jpa part of composite key mapping

I have the following table:
#Entity(name = 'STUDENT')
class Student {
#Id
#Column(name = 'STUDENT_ID')
String studentId
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "id.student")
Set<Disabilities> disabilities = []
}
#Entity(name = 'STUDENT_DISABILITY')
class Disability {
#EmbeddedId
DisabilityId id
#Nullable
#Column(name = 'MOD_DT')
LocalDateTime modifiedDate
}
#Embeddable
class DisabilityId implements Serializable {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = 'STUDENT_ID')
Student student
#Column(name = 'DISABILITY_CD')
String disabilityCode
}
This all works fine but I am trying to join a new table 'Disability_info' table to 'Disability' table. This is the Disability_info table:
#Entity
#Table(name = 'DISABILITY_INFO')
class DisabilityInfo {
#Id
#Column(name = 'DISABILITY_CD')
String id
#Column(name = 'DISABILITY_NAME')
String disabilityName
#Column(name = 'DISABILITY_DESC')
String disabilityDesc
}
The problem that I am having is that the DisabilityInfo's primary key is part of the composite key of the Disability class. All I want is the below with sql:
SELECT * FROM DISABILITY a INNER JOIN DISABILITY_INFO b on a.DISABILITY_CD = b.DISABILITY_CD
Can anybody please shed some light to how I can achieve that ?
Thank you in advance.
I figured it out. A many to one association was required. My initial thought was that one disability should have one description therefore there is a 1 : 1 relationship.
However, in reality, there are multiple disabilities that references the same description which means this is in fact a many to one relationship!
#Entity(name = 'STUDENT_DISABILITY')
class Disability {
#EmbeddedId
DisabilityId id
#Nullable
#Column(name = 'MOD_DT')
LocalDateTime modifiedDate
#ManyToOne
#JoinColumn(name = 'DISABILITY_ID, insertable = false, updatable = false)
DisabilityInfo disabilityInfo
}

Populate table in java using spring mvc

I have three entities: Account, Partner, and Referral.
The Partner table is already full and has a link to the Account table.
When registering a user, the Account table is filled out.
Then I need to fill out the Referral table in which there are links to the Account and the Partner.
In this case, I need to check if there is a Referral link in the request, I need to check that it is in the Partner table and write to the ID Referral table of the Partner. And also take the ID from the table Account and also write it to the Referral table.
I have entity and controller
#Entity
#Table(name = "partner")
public class Partner implements Serializable {
#Id
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToOne
#JoinColumn(name = "account_id")
private Account account;
#Column(name = "referral_link", nullable = false, unique = true)
#NotEmpty
private String referralLink;
Getter, Setter, and Constructor
#Entity
#Table(name = "referral")
public class Referral implements Serializable {
#Id
#Column(name = "id", unique = true, nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "partner", cascade = CascadeType.ALL)
#JoinColumn(name = "partner_id")
private Set<Partner> partnerId = new HashSet<>();
#OneToOne
#JoinColumn(name = "account_id", nullable = false, unique = true)
private Account accountId;
#Column(name = "device_id")
private String deviceId;
#Column(name = "setup_date", nullable = false)
private Date setupDate;
Getter, Setter, and Constructor
In the Controller, I wrote this code:
Long defaultId = 6L;
if (referralLink == null) {
referral = new Referral(defaultId, account.getId();
referralService.create(referral);
} else {
List<Partner> partnerList = partnerService.getAll();
if (partnerList.contains(referralLink)) {
// How to get partnerId?
referral = new Referral(partnerId, account.getId();
} else {
referral = new Referral(defaultId, account.getId();
referralService.create(referral);
}
referralService.create(referral);
}
Many questions turned out:
How to get the element's id on the sheet that the referralLink belongs to?
How to add a default ID if the referralLink is empty?
When creating a Referral, he asks me for an entity Account instead of my Long and Set partners, what am I doing wrong?
You have many problems,
First in your Refferal class
#OneToMany(fetch = FetchType.LAZY, mappedBy = "partner", cascade = CascadeType.ALL)
#JoinColumn(name = "partner_id")
private Set<Partner> partnerId = new HashSet<>();
you cannot have JoinColumn at the two sides of the relationship, since this is a bidirectional relation you should have joincolumn in one side and mappedBy in the other side.
In your persistence logic you should have a reference of each side of the relation in the other side since the relation is bidirectional, I am talking about the Refferal and Partner.
How to get the element's id on the sheet that the referralLink belongs
to?
I am not sure what you mean by element's id but I suppose It is entity which has association with Partner, in this case you only need to find the Partner for a specific referralLink and use normal simple getters
How to add a default ID if the referralLink is empty?
simply check if the referralLink is empty and add an id, if you are talking about the autogeneratedId then why would u need to add such thing and even if you do it will be just ignored, if you are talking about id of another entity (foreign key) set the entity you need if the referralLink.
When creating a Referral, he asks me for an entity Account instead of
my Long and Set partners, what am I doing wrong?
In your referral entity you are specifying not null constraint on the account
#OneToOne
#JoinColumn(name = "account_id", nullable = false, unique = true)
private Account accountId;
so just remove "nullable = false"

Categories