Hibernate + Spring Boot: any way to use a jointable as an entity? - java

Let's say I have tables PERSON and COMPANY, connected by the table PERSON_COMPANY in which aside from the foreign keys I also store information about update_date, create_date and status of the connection:
This is how I've realized the connection in Person class, which works well:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "person_contact",
joinColumns = #JoinColumn(name = "person_id"),
inverseJoinColumns = #JoinColumn(name = "company_id"))
private List<Company> companyList;
The problem comes when I try to create an entity for PERSON_COMPANY table as there is information that I will need in some cases:
#Entity
#Table(name="person_company")
public class PersonCompany {
...
}
I get an SchemaManagementException: Export identifier [person_company] encountered more than once.
Is there a way to use a join table as an entity as well?
I'm using springboot and hibernate with mariaDB.

Related

Deleting referencing side entity in #ManyToMany relationship: JPA

I am working on a Spring Boot Project in which I have used a Many TO Many relationship between two entities- User and Category.
For User-
//Set of categories a user is following
#ManyToMany(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST, CascadeType.REFRESH}, fetch = FetchType.EAGER)
#JoinTable(name = "user_category",
joinColumns = #JoinColumn(
name = "user_id",
referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(
name = "category_id",
referencedColumnName = "categoryId"))
private Set<Category> categories=new HashSet<>();
For Category-
// set of users who follow the category
#ManyToMany(mappedBy = "categories",cascade={CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
Set<User> users = new HashSet<>();
What I want-
When I delete a User only user should be deleted and not the Category.
When I delete a Category only category should be deleted and not the User.
Problem-
When I delete a User, it gets deleted successfully without affecting category. But, when I delete a Category, I get an error-
java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (`blog_application_database`.`user_category`, CONSTRAINT `FK4x6ipr43cfvhqc3aqda6j8c2l` FOREIGN KEY (`category_id`) REFERENCES `categories` (`category_id`))
Now, to remove the associated users, in the category I use-
#PreRemove
public void removeUsers() {
System.out.println("Removing users for " + this.categoryName + " before deleting");
this.users=null;
this.users=new HashSet<>();
}
But, the problem is not resolved.
One approach I came across from several other posts was to manually remove each and every mapping from each user (from user side) but it would be highly inefficient.
I am adding a screenshot of the db tables-
Please help me understand what is going wrong here and how to resolve it.
Changing the Category#users collections has no effect on the relationship, because that is not the owning side of it. The owning side is the one which has no use of mappedBy.
So if you want to remove a category, you will first have to remove the category from every User#categories collection.
Maybe it's easier if you don't map that relationship as a #ManyToMany association and instead introduce an entity for the join table. Then you can use #OneToMany on both sides with DELETE cascading.

JPQL Inserting new entity violates foreign key constraint

UPDATE: Changed code to be coherent to db model and changed details of the model itself. Also added code of the section that causes the error.
I am trying to implement a small database consulting system in Hibernate with PostgreSQL and having issues with one specific pair of tables. As you can see, it's a system for car rental services, and the tables store drivers and rentals. A driver is supposed to be able to have multiple rentals (but not the other way around).
Problem Tables
CREATE TABLE Driver(
cod_driver SERIAL PRIMARY KEY,
cod_client INTEGER,
num_license BIGINT UNIQUE,
expiration_license DATE,
ident_driver BIGINT,
FOREIGN KEY (cod_client)
REFERENCES Client(cod_client)
ON UPDATE CASCADE
ON DELETE CASCADE
);
CREATE TABLE Rental(
cod_rental SERIAL PRIMARY KEY,
cod_plate VARCHAR(10),
cod_dest VARCHAR(10),
cod_driver INTEGER,
date_delivery DATE,
FOREIGN KEY (cod_plate)
REFERENCES Vehicle(cod_plate)
ON UPDATE CASCADE
ON DELETE NO ACTION,
FOREIGN KEY (cod_dest)
REFERENCES Location(cod_location)
ON UPDATE CASCADE
ON DELETE NO ACTION,
FOREIGN KEY (cod_driver)
REFERENCES Driver(cod_driver)
ON UPDATE CASCADE
ON DELETE CASCADE
);
I did my implementation using Hibernate as follows (short of getters/setters for brevity):
Driver
#Entity
public class Driver {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer cod_driver;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cod_client")
private Client client;
private Long num_license;
private Long ident_driver;
private LocalDate expiration_license;
}
Rental
#Entity
public class Rental {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer cod_rental;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cod_plate")
private Vehicle vehicle;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cod_dest")
private Location location_dest;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cod_driver")
private Driver driver;
private LocalDate date_delivery;
Context of PSQLException
Persist function call in Main.java (clientGet is obtained through successfull queries, and inserts is just a class for queries):
Driver d = new Driver(clientGet, 3294324792L, 321312931L, LocalDate.of(2030, 10, 01));
inserts.insertEntity(d);
insertEntity function:
public void insertEntity(Object o) // Basic insertion of any persistent object
{
em.getTransaction().begin();
em.persist(o);
em.getTransaction().commit();
}
Error
The error I get is this:
Caused by: org.postgresql.util.PSQLException:
ERROR: insert or update on table "driver" violates foreign key
constraint "fkdfq0qhvpkw1dqguk6dv1dsj0t"
Detail: Key (cod_driver)=(3) is not present in table "rental".
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2497)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2233)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:310)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:446)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:370)
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:149)
at org.postgresql.jdbc.PgPreparedStatement.executeUpdate(PgPreparedStatement.java:124)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:175)
What I've Considered
From my understanding, the relationship didn't need to be bidirectional (though I did try to use mappedBy), given that I don't store rentals in driver table.
I just don't understand what constraint could possibly be violated by this. It's as if it expects the value of cod_driver to be already in the rentals table, but a rental entity depends on the pre-existence of the driver existence. SQL for the database doesn't seem to have any constraint like that.
Can someone help me understand what I'm doing wrong? I tried all things I found, but nothing shed any light on this.
use many to many relationship between rental and driver.the same problem i faced once in my PurchaseOrder and PaymentMethods relationship.
#ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "rental_driver", joinColumns = #JoinColumn(name = "rental_id"), inverseJoinColumns = #JoinColumn(name = "driver_id"))
private List driver = new List();

Why does persisting entity twice avoid errors around cascading for entity with join columns

Why does this unit test fail if i do not perform the setup of the entity Role in two steps (two persists).
The error being:
java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: io.osram.olt.extension.jpa.Role#16daa399.
private Role addRoleWithId(String roleId){
Role myRole = new Role();
myRole.setRoleId(roleId);
myRole.setRealmId("my");
myRole.setDescription("role-description-0");
myRole.setExternalCreator(true);
myRole.setName("role-name-0");
em.persist(myRole); //<--- Without this persisting the role fails with the error above.
//Setup joins:
myRole.setAContext(getApplications().get(0));
myRole.setAnotherContext(getTenants().get(0));
em.persist(myRole);
return myRole;
}
...
The Role Entity:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ANOTHER_CONTEXT_ID")
private AnotherContext anotherContext;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ACONTEXT_ID")
private AContext aContext;
...
public Role setAContext(AContext aContext) {
this.aContext = aContext;
if(aContext != null) {
aContext.addRole(this);
}
return this;
}
public Role setAnotherContext(AnotherContext anotherContext) {
this.anotherContext = anotherContext;
if(anotherContext != null){
anotherContext.addRole(this);
}
return this;
}
...
The AContext and AnotherContext both contain similar relations towards role:
#OneToMany
#JoinTable(
name="OLT_ROLES_ACONTEXT",
joinColumns = #JoinColumn( name="ACONTEXT_ID"),
inverseJoinColumns = #JoinColumn( name="ROLE_ID")
)
private Set<Role> roles = new HashSet<Role>();
It seems by creating the object in two steps I can avoid using cascading.
In your setAContext and setAnotherContext methods, you are trying to set the Role object which is not yet persisted.
So It's clear that it will not work without em.persist(myRole); before you set contexts since you have not specified CaseCadeType.PERSIST.
The default setting for cascading is cascade NONE , which causes the relationships in the persisted entity not to be persisted by default.
the corollary is that if you try to persist an entity without cascade.PERSIST to its relationship while the relationship is not managed , you will get the above exception.
An exception of the corollary is that if the entity you are persisting is the owner of the relation and the attribute in the relation is already in the database, yo will be able to persist it.
One small thing that I noticed in your mapping : It's a double unidirectional, one with a join column and the reverse with a join table, so is this intended?

Delete hibernate entity which is referenced by #ManyToMany in other entity

I want to delete Recipe (using spring data DAO) but I got SQL exception: org.postgresql.util.PSQLException: ERROR: update or delete on table "recipe" violates foreign key constraint "fkacys689tmdmfggtf4thdoc83k" on table "favourite_recipes"
Detail: Key (id)=(76823) is still referenced from table "favourite_recipes".
My entities:
#Entity
public class Account {
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "favourite_recipes",
joinColumns = #JoinColumn(name = "account_id"),
inverseJoinColumns = #JoinColumn(name = "recipe_id"))
private Set<Recipe> favouriteRecipes = new HashSet<>(0);
...
}
#Entity
public class Recipe {
...
}
How to remove recipe instance?
You need to handle the cascade type, by default is set to ALL.
For example you can work around the contraints like this:
#ManyToMany(cascade = CascadeType.DETACH)
more info : cascade type docs
in you need to delete from the owning entity side which is the Account.
So first remove the recipe from recipe list in Account and save the account, then remove the recipe itself.
As Amer Qarabsa metioned I had to remove recipe from Account.
I added new field in Recipe to get bidirectional mapping
#ManyToMany(cascade = CascadeType.MERGE, mappedBy = "favouriteRecipes")
private Set<Account> recipeLovers = new HashSet<>(0);
Code in service class to remove recipe from all accounts + clear lovers in recipe (recipe and recipeId variables are not initialized here)
Set<Account> recipeLovers = recipe.getRecipeLovers();
recipeLovers.forEach(account ->
account.getFavouriteRecipes()
.removeIf(r -> r.getId() == recipeId));
recipeLovers.clear();
recipeDao.delete(recipe);

JoinTable is lost

I have a the two following classes:
#Entity
class A {
#Id
private aId;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "AB", joinColumns = #JoinColumn(name = "aId", referencedColumnName = "aId"), inverseJoinColumns = #JoinColumn(name = "bId", referencedColumnName = "bId"))
private Set<B> bSet;
}
#Entity
class B {
#Id
private bId;
}
I load the complete object structure from one database and then enters a new transaction on the second database to persist the structure again. However the "AB" table is left empty. This is very strange as "B" is persisted though I only explicitly persist "A". I have check that A-objects contains non empty sets of B, so that is not a problem.
This leaves me with the conclusion that Hibernate believes that "AB"-table should exist as both "A" and "B" already have their primary keys. Is there a way around this so I can get Hibernate to persist the join-table in the second database?
I guess this is happening because you are using proxy object.That is if you create instances of A and B with new operator and then call persist ,Join table record will be created .But you are using object from obtained from entitymanager(these are proxy objects) so you have to merge object that way entitymanager will create new proxies of this objects.

Categories