Hibernate : How to Join 3 tables in one join table by Annotation? - java

I have some roles, users and applications
I want to create a mapping hibernate of this table :
CREATE TABLE role_application_user (
role_identifier INTEGER not null,
application_identifier INTEGER not null,
user_identifier INTEGER not null,
KEY FK_role_identifier (role_identifier),
KEY FK_application_identifier(application_identifier),
KEY FK_user_identifier (user_identifier),
CONSTRAINT FK_role_identifier FOREIGN KEY (role_identifier) REFERENCES role (identifier),
CONSTRAINT FK_application_identifier FOREIGN KEY (application_identifier) REFERENCES application (identifier),
CONSTRAINT FK_user_identifier FOREIGN KEY (user_identifier) REFERENCES users (login)
);
for an application, a role can have many users and a user can of many roles.
I try this mapping :
Application.java
#JoinTable(name = "role_application_user",
joinColumns = #JoinColumn(name = "application_identifier"),
inverseJoinColumns = #JoinColumn(name = "user_identifier"))
#MapKeyJoinColumn(name = "role_identifier")
#ElementCollection
private Map<Role, User> userByRole = new HashMap<>();
Unfortunately, this is not working in my case because in java, the key of a Map must be unique.
With this mapping, we can have only one user for a role and an application.

try this implementation :
#Entity
public class User {
#OneToMany
private List<RoleInApplication> rolesInApplications;
}
#Entity
public class Role {
#OneToMany
private List<RoleInApplication> rolesInApplications;
}
#Entity
public class RoleInApplication {
#ManyToOne
private User user;
#ManyToOne
private Role role;
#ManyToOne
private Application application;
}
#Entity
public class Application {
#OneToMany
private List<RoleInApplication> rolesInApplications;
}

Related

JPA one-to-one mapping creates an extra column. How to remove it?

I have one-to-one mapping JPA table in my springboot application which works fine.
The Users is the parent table and in the account_no column, it stores the foreign key. Which is, child's primary key. The child is Account table.
However, when the application is started, I can see that there is one additional column (user_id) that has been created in H2 DB. I think it is something wrong with my JPA mapping. Pls help to figure it out. Below are the two classes.
#Entity
public class User extends AbstractEntity {
// Other fields related to user entity go here ..
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "account_no", referencedColumnName = "account_num")
private Account account;
}
#Entity
public class Account extends AbstractEntity{
// fields like account#, balance etc goes here..
#Column(name="account_num", unique = true)
#NotNull
private long accountNo;
#OneToOne (fetch = FetchType.LAZY)
private User user;
}
Startup log.
create table account (id bigint not null, account_num bigint not null, bal float not null, user_id bigint, primary key (id))
2021-12-22 00:09:28.765 DEBUG 25380 --- [ main] org.hibernate.SQL :
Decide which side should contain the extra column and use the mappedBy attribute. Then JPA will do what's needed
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false, mappedBy = "user")
private Account account;
Considering that you have bidirectional mapping you don't need the #JoinColumn that you have used.
Just both #OneToOne annotations and the decision which would be owner entity of the relation by using the mappedBy attribute on one of those annotations.
It depends on you in which side you store the extra column which was a
foreign key.Because When You started to established oneToOne
relationship between two table it is mandatory to store user's
primary key in account table as a foreign key, or account table
primary key in user table as foreign key.You should be need to declare
mappedBy and your tables reference name.for example if you declare
mappedBy="user" in account table it create an extra account_id column
in user table as a foreign key, Same as for account
#OneToOne(optional = false, mappedBy = "user")
private Account account;
Don't need to declare these things in side OneToOne annotation cascade
= CascadeType.ALL, fetch = FetchType.LAZY, By default it always support lazy fetch
.

How to make a reflexive one-to-many relationship inside a model?

In the database there is a table having a reflexive one-to-many relationship :
create table structure
(
struct_code varchar2(15) not null,
str_struct_code varchar2(15),
struct_lib varchar2(255),
struct_sigle varchar2(10),
struct_comment clob,
struct_interne smallint default 1,
constraint pk_structure primary key (struct_code)
);
alter table structure add constraint fk_structur_associati_structur foreign key (str_struct_code) references structure (struct_code);
I created the corresponding model :
#Entity
#Table(name = "structure")
public class Structure {
#Id()
#Column(name="struct_code")
private String code;
#Column(name="struct_sigle")
private String sigle;
#Column(name="struct_lib")
private String lib;
#Column(name="struct_interne")
private Integer interne;
#ManyToOne
#JoinColumn(name = "struct_code")
private Structure sousStructure;
public Structure() {
super();
}
public Structure(String code) {
super();
}
// getters and setters
}
But when I built the project then I got the error : mappingexception repeated column in mapping for entity : com.ambre.pta.model.Structure column: struct_code (should be mapped with insert="false" update="false")
So how to write correctly the reflexive relation ?
I do have something like this in place:
#ManyToOne
#JoinColumn(name = "parent_struct_code", nullable = true)
private Structure parentStructure;
#OneToMany(mappedBy = "parentStructure", cascade = CascadeType.REMOVE, fetch=FetchType.LAZY)
private List<Structure> sousStructures = new ArrayList<>();

JPA ManyToOne Cascade On UPDATE with JPQL

I have this Parent class
#Entity
#Table(name = "category")
#NamedQuery(name = "category.findAll", query = "SELECT c FROM Category c")
public class Category implements Serializable {
public Category(){}
#Column(name = "name", nullable = false)
#Id
private String name;
#Column(name = "col2")
private Boolean col2;
}
And i have referenced the parent table in child table as follows:
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "cat_name")
private Category category
when i run this JPQL query
update Category c SET c.name=:newName ,c.termsCanHaveChildren=:canHaveChdrn where c.name=:oldName
it's return with foreign key constraint error while i have put Cascade All in child field
Cannot delete or update a parent row: a foreign key constraint fails (`terms`.`term`, CONSTRAINT `FKaykenypxci167nqioh4xx9p3a` FOREIGN KEY (`cat_name`) REFERENCES `category` (`name`))
The problem lays at the constraint being generated by your persistence provider (hibernate), for the #JoinColumn(name = "cat_name") at the child table (and not with the CascadeType that you're defining)...
The generated constraint should indicated that when the PK of Category is Updated, any reference to such column should be updated also...
I believe this configuration should work (but you need to test it first, because I always generated my database model using scripts and not using hibernate features):
#ManyToOne
#JoinColumn(
name = "cat_name",
foreignKey = #ForeingKey(
name = "fk_child_category",
foreignKeyDefinition = "FOREIGN KEY (cat_name) REFERENCES category ON UPDATE CASCADE"
)
)
private Category category;
Also you need to check if your database supports "ON UPDATE CASCADE"... According to this link, oracle does not... (What database are you using?)
If this does not work, try the suggestion of Michelle...
That's expected: you are changing the Primary Key (#Id), that's used in a Foreign Key (#JoinColumn).
Use a surrogated immutable primary key.

How insert automatic ManyToMany with OpenJPA

I have 2 classes mapped with OpenJPA. One class is User and has relationship ManyToMany with the AppProfile. In the database I have the table relations USER_APP_PROFILES (ID,User_ID,App_ID).
My class Open JPA User
#Table(name = "USER_PROFILE",schema = "BPMS")
public class UserProfile {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="USER_ID")
private Integer userID;
#ManyToMany(mappedBy="listUserProfile", fetch=FetchType.EAGER)
private List<AppProfile> listAppProfile;
}
My class AppProfile
#Table(name = "APP_PROFILE",schema = "BPMS")
public class AppProfile {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="APP_ID")
private Integer appID;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(
name="USER_APP_PROFILES",
joinColumns={#JoinColumn(name="APP_ID", referencedColumnName="APP_ID")},
inverseJoinColumns={#JoinColumn(name="USER_ID", referencedColumnName="USER_ID")})
private List<UserProfile> listUserProfile;
}
I fetch a user into the database by EntitiyManager, after I add the List AppProfile.
Example:
UserProfile userProfile //(populate with fetch in database)
AppProfile app = new App();
app.setAppID(11);
List<AppProfile> listApp = new ArrayList<AppProfile>();
listApp.add(app);
userProfile.setListAppProfile(listApp);
em.merge(userProfile)
How do I merge, if I need JPA automatic insert in table USER_APP_PROFILES:
User_App_Profile_ID : new register
UserID : 1
AppID : 11
Try moving the #JoinTable to UserProfile (changing the parameters accordingly) and adding cascade={CascadeType.ALL} to the #ManyToMany annotation.
That should do the trick.

Cannot add or update a child row: a foreign key constraint fails - Bidirectional mapping in hibernate

Hi I have a problem with JPA...
DB:
Java classes (irrelevant fields are ommited):
User:
#Entity
public class User{
#Id
private int iduser;
//bi-directional many-to-one association to UserInfo
#OneToMany(mappedBy="user", cascade= {CascadeType.ALL}, fetch=FetchType.EAGER)
private List<UserInfo> userInfos;
}
UserInfo:
#Entity
#Table(name="user_info")
public class UserInfo {
#Id
#Column(name="iduser_info")
private int iduserInfo;
//bi-directional many-to-one association to User
#ManyToOne
private User user;
}
Currently when I try to do this (again I omitted setting irrelevant fields):
User u = new User();
UserInfo info = new UserInfo();
u.addUserInfo(info);
em.persist(u); // save user
I get this error:
Cannot add or update a child row: a foreign key constraint fails (`webstore`.`user_info`, CONSTRAINT `fk_user_info_user` FOREIGN KEY (`user_iduser`) REFERENCES `user` (`iduser`) ON DELETE NO ACTION ON UPDATE NO ACTION)
I have been banging my head all day and I can't figure it out... I have also searched for solutions here but they all suggest that this error shows that I want to enter UserInfo without user_iduser value entered, but even if I add this before persist:
info.setUser(u);
it still doesn't work - is bidirectional mapping even supported with cascading? The desired effect is that User should be inserted and then all the UserInfos in the list after it refering the original User. How can I achieve that?
I don't want to do
SET foreign_key_checks = 0/1;
everytime =(
Try:
#ManyToOne
#JoinColumn(name="user_iduser", nullable=false)
private User user;
Then see if this works. I'm assuming that in user_info table, the user_id field is NOT NULL in your RDBMS. Also, since it's bidirectional, you will have to setXXX on UserInfo and User.
User u = new User();
UserInfo info = new UserInfo();
info.setUser(u);
u.addUserInfo(info);
em.persist(u); // save user
Update: Are you sure you're specifying an ID for both User and UserInfo? It looks like the ID is not set hence there is no reference to link UserInfo to User.
If the ID are AUTO_INCREMENT, then add the following after #Id annotation:
#GeneratedValue(strategy=GenerationType.IDENTITY)
I also had this error, fixed this error by adding #GeneratedValue(strategy = GenerationType.IDENTITY) annotation on top of iduser and you must have foreign keys CASCADE ON DELETE, UPDATE.
this is worked for me in my example ,
i created books and library.
#Entity
#Table(name = "book")
public class Books{
#Id
private int library_id;
private String libraryname;
//add getters and setters bellow
}
#Entity
#Table(name = "library")
public class Book {
#Id
private int book_id;
private String book_title;
#JsonIgnore
#ManyToOne
#JoinColumn(name="library_id" , nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private Library library;
//set getters and setters
}
in controller i used this method
#RequestMapping(value = "/{libraryId}/book", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public Book createBook(#PathVariable(value = "libraryId") Integer libraryId, #RequestBody Book book) {
List<Book> books = new ArrayList<Book>();
Library author1 = new Library();
Optional<Library> byId = LibraryRepository.findById(libraryId);
Library author = byId.get();
book.setLibrary(author);
Book book1 = bookRepository.save(book);
books.add(book1);
author1.setBooks((List<Book>) books);
return book1;
}

Categories