ManyToMany relationship removes all entities - spring

I have two entities, user and movies. They're manytomany bidirectional relationship. My problem is that when I delete a movie via my controller it also removes all related with that movie entities. My user and this user roles and the movie entity. What have I to do, to get rid just off movie entity from the table when delete it and keep the user with his roles instead of removing them all.
#Data
#ToString
#EqualsAndHashCode
#Entity
#Table(name = "movies")
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "title")
private String title;
#Column(name = "description")
private String description;
#Column(name = "release_date")
#Temporal(TemporalType.DATE)
private Date release_date;
#Column(name = "country")
private String country;
#Column(name = "category")
private String category;
#ManyToMany(mappedBy = "movies")
#ToString.Exclude
#EqualsAndHashCode.Exclude
private Set<User> users = new HashSet<>();
#Data
#ToString
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Transient
private String confirmPassword;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "users_roles", joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "role_id", referencedColumnName = "id")})
#ToString.Exclude
#EqualsAndHashCode.Exclude
private Set<Role> roles = new HashSet<>();
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "users_movies", joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "movie_id", referencedColumnName = "id")})
#ToString.Exclude
#EqualsAndHashCode.Exclude
private Set<Movie> movies = new HashSet<>();

this is the expected behavior when you use CascadeType.ALL, which implicitly includes CascadeType.REMOVE - that removes all the related entities on removing the owning entity.
Since you are using CascadeType.ALL on both sides of the association, as a result you end up deleting records more than what you actually intended.
To avoid this:
You need to remove the CascadeType.ALL or change it to something appropriate like MERGE / PERSIST
Handle the deletion of entities on your own.
Consider using batch delete option specifying the associated entity id.

Problem solved, instead of using mappedBy I added a JoinTable annotation also to Movie entity and now it works as it should.

Well, I had to guess what your Role entity looks like so include complete examples. You didn't specify whether you were using Spring-Data-Jpa or just Jpa so who knows. Edit: in your examples you have finally clarified that you are using spring-data-jpa, but the concepts are the same as here anyway.
#Data
#ToString
#Entity
#Table(name = "roles")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(mappedBy="roles")
#ToString.Exclude
#EqualsAndHashCode.Exclude
private Set<User> users = new HashSet<>();
}
and to use
tx.begin();
User u1 = new User();
Role r1 = new Role();
u1.setRoles(Collections.singleton(r1));
Movie m1 = new Movie();
m1.setDescription("movie 1");
Movie m2 = new Movie();
m2.setDescription("movie 2");
u1.setMovies(Stream.of(m1, m2).collect(Collectors.toSet()));
em.persist(u1);
tx.commit();
// now to query
em.clear();
tx.begin();
User u = em.createQuery("from User u left outer join fetch u.movies where u.id = 1", User.class).getSingleResult();
Movie m = u.getMovies().stream().filter(mv->mv.getDescription().equals("movie 1")).findFirst().get();
u.getMovies().remove(m);
em.remove(m);
tx.commit();
and this creates the log
create table users (id bigint generated by default as identity (start with 1), password varchar(255), username varchar(255), primary key (id))
create table users_movies (user_id bigint not null, movie_id bigint not null, primary key (user_id, movie_id))
create table users_roles (user_id bigint not null, role_id bigint not null, primary key (user_id, role_id))
alter table users_movies add constraint FKt4hasm7tvj0vor58ql33xptjy foreign key (movie_id) references movies
alter table users_movies add constraint FKhhj9vi206o88q0typfntk3fek foreign key (user_id) references users
alter table users_roles add constraint FKj6m8fwv7oqv74fcehir1a9ffy foreign key (role_id) references roles
alter table users_roles add constraint FK2o0jvgh89lemvvo17cbqvdxaa foreign key (user_id) references users
insert into users (id, password, username) values (default, ?, ?)
insert into movies (id, category, country, description, release_date, title) values (default, ?, ?, ?, ?, ?)
insert into movies (id, category, country, description, release_date, title) values (default, ?, ?, ?, ?, ?)
insert into roles (id) values (default)
insert into users_movies (user_id, movie_id) values (?, ?)
insert into users_movies (user_id, movie_id) values (?, ?)
insert into users_roles (user_id, role_id) values (?, ?)
select user0_.id as id1_2_0_, movie2_.id as id1_0_1_, user0_.password as password2_2_0_, user0_.username as username3_2_0_, movie2_.category as category2_0_1_, movie2_.country as country3_0_1_, movie2_.description as descript4_0_1_, movie2_.release_date as release_5_0_1_, movie2_.title as title6_0_1_, movies1_.user_id as user_id1_3_0__, movies1_.movie_id as movie_id2_3_0__ from users user0_ left outer join users_movies movies1_ on user0_.id=movies1_.user_id left outer join movies movie2_ on movies1_.movie_id=movie2_.id where user0_.id=1
delete from users_movies where user_id=? and movie_id=?
delete from movies where id=?

Related

Multiple tables with QueryDSL

I have three tables connected together and I am trying to pull the data from three tables using queryDSL.
DROP TABLE IF EXISTS countries;
CREATE TABLE countries(id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100), data VARCHAR(100));
DROP TABLE IF EXISTS states;
CREATE TABLE states(id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100), count VARCHAR(100), co_id BIGINT );
DROP TABLE IF EXISTS towns;
CREATE TABLE towns(town_id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100), people_count VARCHAR(100), st_id BIGINT);
Data
INSERT INTO countries (id, name, data) VALUES (1, 'USA', 'wwf');
INSERT INTO countries (id, name, data) VALUES (2, 'France', 'football');
INSERT INTO countries (id, name, data) VALUES (3, 'Brazil', 'rugby');
INSERT INTO countries (id, name, data) VALUES (4, 'Italy', 'pizza');
INSERT INTO countries (id, name, data) VALUES (5, 'Canada', 'snow');
INSERT INTO states (id, name, count, co_id) VALUES (1, 'arizona', '1000', 1);
INSERT INTO states (id, name, count, co_id) VALUES (2, 'texas', '400', 4);
INSERT INTO states (id, name, count, co_id) VALUES (3, 'ottwa', '3000', 1);
INSERT INTO states (id, name, count, co_id) VALUES (4, 'paulo', '222', 3);
INSERT INTO states (id, name, count, co_id) VALUES (5, 'paris', '544', 1);
INSERT INTO towns (town_id, name, people_count, st_id) VALUES (1, 'arizona', '1000', 1);
INSERT INTO towns (town_id, name, people_count, st_id) VALUES (2, 'texas', '400', 2);
INSERT INTO towns (town_id, name, people_count, st_id) VALUES (3, 'fff', '3000', 1);
INSERT INTO towns (town_id, name, people_count, st_id) VALUES (4, 'fsdd', '222', 3);
INSERT INTO towns (town_id, name, people_count, st_id) VALUES (5, 'fsfdds', '544', 3);
Entities
#Entity
#Table(name = "countries")
#Setter
#Getter
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long countryId;
#Column(name = "name")
private String name;
#Column(name = "data")
private String data;
#OneToOne(mappedBy = "country")
private State stateJoin;
}
#Entity
#Table(name = "states")
#Setter
#Getter
public class State {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long stateId;
#Column(name = "name")
private String name;
#Column(name = "count")
private String count;
#Column(name = "co_id")
private Long countryId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "co_id", referencedColumnName = "id", updatable = false, insertable = false)
private Country country;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "state_id", referencedColumnName = "id")
private Set<Town> towns;
}
#Entity
#Table(name = "towns")
#Setter
#Getter
public class Town {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "town_id")
private Long townId;
#Column(name = "name")
private String name;
#Column(name = "people_count")
private String peopleCount;
#Column(name = "st_id")
private Long stateId;
}
I am trying to run the below QueryDSL to get the whole data from the above tables especially since I am looking for the data from the state table with a set of records of towns.
Case-1
Projection1
#Component
#Data
#AllArgsConstructor
#NoArgsConstructor
public class SecondDto {
private String name;
private State state;
}
Query
QCountry country = QCountry.country;
QState state = QState.state;
JPAQuery query = new JPAQuery(entityManager);
List<SecondDto> result1 = query
.select(Projections.constructor(SecondDto.class, country.name, country.stateJoin))
.from(country)
.join(country.stateJoin, QState.state)
.join(state.towns, QTown.town)
.fetch();
System.out.println("*** "+ result1);
Result logs and generated queries
Hibernate:
/* select
country.name,
country.stateJoin
from
Country country
inner join
country.stateJoin as state
inner join
state.towns as town */ select
country0_.name as col_0_0_,
country0_.id as col_1_0_,
state1_.id as id1_1_,
state1_.count as count2_1_,
state1_.co_id as co_id3_1_,
state1_.name as name4_1_
from
countries country0_
inner join
states state1_
on country0_.id=state1_.co_id
inner join
towns towns2_
on state1_.id=towns2_.state_id
*** [] --- No response even though I have the data in DB.
Case-2
Projecton2
#Component
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Dto {
private String name;
private Set<Town> towns;
}
Query
List<Dto> result = query
.select(Projections.constructor(Dto.class, state.name, state.towns))
.from(state)
.join(state.towns, QTown.town)
.fetch();
this one creates the below logs with an exception.
/* select
state.name,
state.towns
from
State state
inner join
state.towns as town */ select
state0_.name as col_0_0_,
. as col_1_0_, <--- Here I am getting issue
towns2_.town_id as town_id1_2_,
towns2_.name as name2_2_,
towns2_.people_count as people_c3_2_,
towns2_.st_id as st_id4_2_
from
states state0_
inner join
towns towns1_
on state0_.id=towns1_.state_id
inner join
towns towns2_
on state0_.id=towns2_.state_id
07-01-2023 14:33:02 [restartedMain] WARN org.hibernate.engine.jdbc.spi.SqlExceptionHelper.logExceptions - SQL Error: 42001, SQLState: 42001
07-01-2023 14:33:02 [restartedMain] ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper.logExceptions - Syntax error in SQL statement "/* select state.name, state.towns\000afrom State state\000a inner join state.towns as town */ select state0_.name as col_0_0_, [*]. as col_1_0_, towns2_.town_id as town_id1_2_, towns2_.name as name2_2_, towns2_.people_count as people_c3_2_, towns2_.st_id as st_id4_2_ from states state0_ inner join towns towns1_ on state0_.id=towns1_.state_id inner join towns towns2_ on state0_.id=towns2_.state_id"; expected "*, INTERSECTS (, NOT, EXISTS, UNIQUE, INTERSECTS"; SQL statement:
/* select state.name, state.towns
from State state
inner join state.towns as town */ select state0_.name as col_0_0_, . as col_1_0_, towns2_.town_id as town_id1_2_, towns2_.name as name2_2_, towns2_.people_count as people_c3_2_, towns2_.st_id as st_id4_2_ from states state0_ inner join towns towns1_ on state0_.id=towns1_.state_id inner join towns towns2_ on state0_.id=towns2_.state_id [42001-214]
07-01-2023 14:33:02 [restartedMain] INFO org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener.logMessage -
My main goal is to use case-1 and get combined data from three entities. The complete SpringBoot project can be found in here
Can anyone help me with how can I achieve this with queryDSL?
First, your model is incorrect. There can be multiple states in a country, so you should rather model the Country<->State relationship with a one-to-many association:
#Entity
#Table(name = "countries")
#Setter
#Getter
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long countryId;
#Column(name = "name")
private String name;
#Column(name = "data")
private String data;
#OneToMany(mappedBy = "country")
private Set<State> states;
}
#Entity
#Table(name = "states")
#Setter
#Getter
public class State {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long stateId;
#Column(name = "name")
private String name;
#Column(name = "count")
private String count;
#Column(name = "co_id")
private Long countryId;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "co_id", referencedColumnName = "id", updatable = false, insertable = false)
private Country country;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "state_id", referencedColumnName = "id")
private Set<Town> towns;
}
I would recommend you to look into Blaze-Persistence Entity Views for this though as that will simplify the querying.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Country.class)
public interface CountryDto {
#IdMapping
Long getId();
String getName();
Set<StateDto> getStates();
#EntityView(State.class)
interface StateDto {
#IdMapping
Long getId();
String getName();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
CountryDto a = entityViewManager.find(entityManager, CountryDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<CountryDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!

Soft delete but cannot prevent delete cascade with foreign key table

My entity user with #SQLDelete
#Data
#Entity
#Table(name = "`user`")
#SQLDelete(sql = "UPDATE `user` SET status = 0 WHERE id = ?")
public class User {
private Integer id;
private String username;
private String password;
#ManyToMany
#JoinTable(
name = "`user_role`",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private List<Role> roles = new ArrayList<>();
}
When I delete by method userRepository.deleteAllById(ids). My row user_role table also deleted. I only want soft delete a row at user table and no effect to user_role

Why OneToMany JPA association is failing while insert statement executes

Hi below is my schema definition
CREATE TABLE LOANS (
LOAN_ID NUMBER(9,0) PRIMARY KEY,
CORR_ID VARCHAR(5) NULL
);
CREATE TABLE DV_LOAN_PARTICIPANTS (
LOAN_ID NUMBER(9,0) ,
DVP_PARTICIPANT_NAME VARCHAR(50) NOT NULL,
DVP_PARTICIPANT_TYPE VARCHAR(50) NOT NULL,
PRIMARY KEY ("LOAN_ID", "DVP_PARTICIPANT_NAME")
);
LOANS Entity
#Table(name = "LOANS")
#Entity
public class Loans {
#Id
#Column(name = "LOAN_ID")
private Long loanId;
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "LOAN_ID")
#MapKey(name = "dvpParticipantName")
private Map<String, DVLoanParticipants> dvLoanParticipantsMap;
// getter and setters
}
DV_LOAN_PARTICIPANTS Entity
#Table(name = "DV_LOAN_PARTICIPANTS")
#Entity
public class DVLoanParticipants implements Serializable {
#Id
#Column(name = "LOAN_ID")
private Long loanId;
#Id
#Column(name = "DVP_PARTICIPANT_NAME")
private String dvpParticipantName;
#Column(name = "DVP_PARTICIPANT_TYPE")
private String dvpParticipantType;
// getters and setters
}
Service Class is
DVLoanParticipants dvLoanParticipants = new DVLoanParticipants();
dvLoanParticipants.setLoanId(Long.valueOf("196801758"));
dvLoanParticipants.setDvpParticipantName("VKP");
dvLoanParticipants.setDvpParticipantType("Developer");
Loans loanInsert = new Loans();
loanInsert.setLoanId(Long.valueOf("196801758"));
Map<String,DVLoanParticipants> partyMap = new HashMap<>();
partyMap.put("VKP",dvLoanParticipants);
loanInsert.setDvLoanParticipantsMap(partyMap);
repository.save(loanInsert);
But when i am executing the save i am getting error as
NULL not allowed for column "LOAN_ID"; SQL statement:
insert into dv_loan_participants (dvp_participant_type, loan_id, dvp_participant_name) values (?, ?,
?)
Git Hub Code
https://github.com/vinoykp/spring-jpa/tree/master/spring-boot-hibernate-crud-demo
I had the similar question
Why Value is not getting assigned in JPA for insert statement
What is the issue in association?

creating relationship table at startup MySQL Spring Boot from data.sql

Hello i have a task to build an application using spring boot and hibernate. The thing is i'm building the database through a data.sql file to persist data and create the database. Now i need to create a relationship table between my two tables. This should be a one to many relationship given that a worker can have a user account or may not have an account. The problem lies on how to build this relationship table when the application starts because the references should be the foreign keys user_id and worker_id which i do not have at startup because they are created automatically by hibernate so there's no way to hardcode them. So the question is how would i go about creating this relationchip table from data.sql.
data.sql:
INSERT INTO worker (name, last_name, status) VALUES
('manuel', 'dias', 'active'),
('sailin ', 'nal', 'active'),
('abraham', 'lincon', 'active'),
('mick', 'smith', 'active'),
('jose', 'perez', 'inactive'),
('luis', 'nuñez', 'inactive'),
('ruben', 'puertas', 'inactive'),
('anders', 'stone', 'inactive'),
('luis', 'alvarez', 'deleted'),
('claudio', 'martinez', 'deleted'),
('morfeo', 'rodriguez', 'active'),
('onetys', 'estrada', 'inactive'),
('rene', 'fajardo', 'active');
INSERT INTO users (username, password, user_type, status) VALUES
('madi', 'madi', 'worker', 'active'),
('sana', 'sana', 'worker', 'active'),
('abli', 'abli', 'worker', 'active'),
('mism', 'mism', 'worker', 'active'),
('jope', 'jope', 'worker', 'active'),
('lunu', 'lunu', 'worker', 'active'),
('rupu', 'rupu', 'worker', 'active'),
('anst', 'anst', 'worker', 'active'),
('lual', 'lual', 'worker', 'active'),
('clma', 'clma', 'worker', 'active');
Users.class:
#Entity
#Table(name = "users")
public class Users implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", nullable = false)
private int userId;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "userType")
private String userType;
#Column(name = "status")
private String status;
#ManyToOne
#JoinColumn(name = "worker_id")
private Worker worker;
Worker.class:
#Entity
#Table(name = "worker")
public class Worker implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "worker_id")
private int workerId;
#Column(name = "name")
private String name;
#Column(name = "last_name")
private String lastName;
#Column(name = "status")
private String status;
#OneToMany(mappedBy = "worker", cascade = CascadeType.ALL)
private Collection<Users> users;
So do i have to:
1: Create another Entity to be able to make a new table in the database holding the foreign keys to user and worker entities?
2: Can it be done with a script in data.sql.
I would create a column in users table referencing the worker id but the task explicitly says:
"make a third table to hold the relationship"
Thanks in advance
In OneToMany relationship, you can have all needed information on the entity of the many side. Creating another join table only makes sense on ManyToMany relationship, it would only add redundant data on OneToMany and violates normalization.
That being said, if you really need another table to define the two entities relationship, you can achieve this without creating another entity using #JoinTable.
#Entity
#Table(name = "users")
public class Users implements Serializable {
//....
#ManyToOne
#JoinTable(name = "worker_users",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "worker_id"))
private Worker worker;
}
#Entity
#Table(name = "worker")
public class Worker implements Serializable {
//...
#OneToMany(cascade = CascadeType.ALL) //remove mappedBy
#JoinTable(name = "worker_users",
joinColumns = #JoinColumn(name = "worker_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private Collection<Users> users;
}
It would create new worker_user table with this structure
|WORKER_ID |USER_ID |

Get Hibernate single iteration values only in an entity

I need to get values from user table, in that user have an manager id,
Manager is an user so again manager id is mapped with user entity.
This will cause continuous iteration until the manager ID get null.
\
The entity get more on inside on it > User entity > manager id> --> user entity ....
Is there any possible to get single entity with single manager using hibernate query?
USER_NAME (PK)
CREATE_DATE
UPDATED_DATE
ROLE_ID
USER_ID
MANAGER_ID
REGION_ID
USER_GROUP
Created_By
User class
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USER_ID", unique = true, nullable = false)
private Long userId;
#ManyToOne( targetEntity = User.class, cascade = CascadeType.ALL)
#JoinColumn(name = "MANAGER_ID", referencedColumnName = "USER_ID")
private User managerId;
You can relate to entity only managerId(I mean Long type), or you can mark Many to one relation with fetch = FetchType.LAZY and call this field only if needed.

Resources