JPA #ManyToMany association not fetching owner class' collection - java

I have a simple Many-to-Many relationship between a User and Role entity. The tables are correctly created. Every time I add a role to a user, the USER_ROLE table is correctly updated with the user_id and role_id. But when fetching all the users, the roles collection is always empty.
User.java
#Entity
#Table(name = "USER")
#Builder
#Data
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)
public class User extends BaseEntity {
#Column(
name = "USERNAME",
unique = true,
nullable = false
)
private String username;
#Column(
name = "PASSWORD",
nullable = false
)
private String password;
#Column(name = "DEPOSIT")
private int deposit;
#ManyToMany(
fetch = FetchType.EAGER,
cascade = CascadeType.PERSIST
)
#JoinTable(
name = "USER_ROLE",
joinColumns = #JoinColumn(name = "USER_ID"),
inverseJoinColumns = #JoinColumn(name = "ROLE_ID")
)
private Collection<Role> roles = new HashSet<>();
}
Roles.java
#Entity
#Table(name = "ROLE")
#Builder
#Data
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)
public class Role extends BaseEntity {
#Column(
name = "NAME",
nullable = false,
unique = true
)
private String name;
#ManyToMany(mappedBy = "roles")
#JsonIgnore
private Collection<User> users = new HashSet<>();
}
BaseEntity.java
#MappedSuperclass
public abstract class BaseEntity implements Serializable {
#Id
#GeneratedValue(
generator = "UUID"
)
#GenericGenerator(
name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator"
)
#Column(
name = "ID",
updatable = false
)
private UUID id;
}
I saw this question many times on stackoverflow. I've tried multiple solutions but none of them worked and i can't seem to find the issue.
Edit: I have no ideas why, but it seems that the issue was coming from the fact that I was using UUID as primary keys. Now that I changed it to long values, it's working properly.

Related

Many to many jpa relationship creating duplicated data in entity with new IDs

I have two jpa entities User + profile with many to many relationship like below :
User Entity
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "t_user")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long Id;
#Column(nullable = false)
private String username;
#Column(nullable = false, length = 14)
private String siret;
#Cascade({
org.hibernate.annotations.CascadeType.SAVE_UPDATE,
org.hibernate.annotations.CascadeType.MERGE,
org.hibernate.annotations.CascadeType.PERSIST
})
#ManyToMany(fetch = FetchType.LAZY,cascade = CascadeType.MERGE)
#JoinTable(name = "t_user_profile", joinColumns = {#JoinColumn(name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "profile_id")})
private Set<ProfileEntity> profiles = new HashSet<>();
#OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinColumn(name = "ressource_id")
private RessourceEntity ressource;
}
and Profile entity :
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "t_profile")
public class ProfileEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "profile_id")
private Long Id;
#Column(nullable = false)
#Enumerated(EnumType.STRING)
private Profiles profile;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "profiles")
private Set<UserEntity> user = new HashSet<>();
}
The problem is when I send an http request contaning the same profiles with different users, I obtain new duplicated profiles records with new IDs in the Profile table in the database like below :
but the correct way is to add the informations ONLY in the joined table t_user_profile because profile already exists in profile table .
I tried cascade.All , persist and merge .... but the same result .
And if I remove cascade and i search for profile Id using findById and get the Id and insert... I got a problem when the profile is new and don't exist in DB and the problem is because I removed cascade ... So I want if the profile already exists , I want to add the relationship in the joined table ONLY.

JPA #JoinTable with composite (2 column) primary keys

In a spring-boot app, I've got the following entity definition:
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#Column(nullable = false, name = "username", length = 100)
private String username;
#JoinTable(name = "userrole",
joinColumns = { #JoinColumn(name = "username") },
inverseJoinColumns = { #JoinColumn(name = "role") }
)
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Role> roles;`
I'm using Spring-data-jpa,Hibernate with H2 as the database.
The trouble is that spring-data-jpa, hibernate always generate/creates the join table (DDL) 'userrole' with a single column primary key. e.g. 'username'.
Hence, if records such as {'username', 'user_role'} and {'username', 'admin_role'} is inserted in the join table ('userrole'), the next insert fails with an error due to the 'duplicate' primary key.
I've tried using both columns in the above definition, as well as the following variation:
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
#JoinColumns({
#JoinColumn(name = "username"),
#JoinColumn(name = "role") })
private List<Role> roles;`
But that they resulted in the same or worse problems, e.g. and in the latter, even table creation fails because only a single column is used as primary key for the jointable. Role is simply another table with 2 columns 'role' and 'description', basically a role catalog.
How do we specify to JPA that the #JoinTable should use both 'username' and 'role' columns as composite primary keys?
edit:
I tried using an independent table/entity as suggested, thanks #Kamil Bęben
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#Column(nullable = false, name = "username", length = 100)
private String username;
#OneToMany(
fetch = FetchType.EAGER,
cascade = CascadeType.ALL,
mappedBy = "username",
orphanRemoval = true
)
#ElementCollection
private List<UserRole> roles;
UserRole is defined as such
#Data
#NoArgsConstructor
#Entity
#Table(name = "userrole")
public class UserRole {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "userrole_seq")
Long id;
#Column(nullable = false, name = "username", length = 100)
private String username;
#Column(nullable = false, name = "role", length = 50)
private String role;
the repository for that user-roles join table is defined as
#Repository
public interface UserRoleRepository extends CrudRepository<UserRole, Long> {
UserRole findByUsernameAndRole(String username, String role);
List<UserRole> findByUsername(String username);
List<UserRole> findByRole(String role);
}
Admittedly, ugly, but that it works. And that somehow, it seemed to use the correct findByUsername() method to retrieve the roles as is relevant to the user, probably related to the 'mappedBy' clause. 'black magic'! There's lots more that I'd still need to find my way around JPA, Spring, Spring-data
edit2:
further update:
the original #JoinTable works as well.
But that the relations need to be specified as #ManyToMany
#ManyToMany(
fetch = FetchType.EAGER,
cascade = CascadeType.MERGE
)
#JoinTable(name = "usersroles",
joinColumns = { #JoinColumn(name = "username") },
inverseJoinColumns = { #JoinColumn(name = "role") }
)
private List<Role> roles = new ArrayList<Role>();
This creates 2 column primary keys as expected for the 'users-roles' table
Thanks to #Roman
If Role only has two columns, eg user_id and role, the way to map this in jpa would be as following
#ElementCollection
#CollectionTable(name = "user_roles", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "role")
List<String> roles = new ArrayList<>();
Otherwise, jpa really requires each entity's identifier and join columns to be separate columns, so Role entity would have to have columns like id, user_id and role_name. Could look like this .:
class Role {
#Id
Long id;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id");
User user;
String roleName;
// Other fields
}
And in the User entity
#OneToMany(mappedBy = "user") // user is Field's name, not a column
List<Role> roles = new ArrayList<>();
Further reading

Null point exception on set spring JPA relationship

I have already seen topics with this question, but they did not help me. Maybe I didn't see something.
Below I attach the code and the error.
#Entity
#Table(name = "department")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#EqualsAndHashCode
public class Department {
#Id
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#GeneratedValue(strategy = GenerationType.IDENTITY, generator = "uuid2")
#Column(length = 36, nullable = false, updatable = false)
private UUID id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "description", nullable = false)
private String description;
#OneToMany(mappedBy = "department", cascade = CascadeType.ALL, fetch = FetchType.LAZY,
orphanRemoval = true)
private Set<User> userSet = new HashSet<>();
}
AND user
#Entity
#Table(name = "user")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#EqualsAndHashCode
public class User {
#Id
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#GeneratedValue(strategy = GenerationType.IDENTITY, generator = "uuid2")
#Column(length = 36, nullable = false, updatable = false)
private UUID id;
private String firstName;
private String lastName;
private Integer age;
#ManyToOne
#JoinColumn(name = "dep_id", nullable = true)
private Department department;
#ManyToMany
#JoinTable(
name = "device_devices",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "device_id"))
Set<Device> devices = new HashSet<>();
}
and service
#Override
public List<DepartmentDto> getAllDepartment() {
List<Department> all = departmentRepository.findAll();
return all.stream().map(mapper::toDepartmentDto).collect(Collectors.toList());
}
#Override
public UUID createDepartment(DepartmentDto departmentDto) {
Department entity = mapper.DtoToDepartment(departmentDto);
return departmentRepository.save(entity).getId();
}
#Override
public void deleteDepartment(UUID id) {
departmentRepository.deleteById(id);
}
#Override
#Transactional
public void addUserToDepartment(UUID departmentId,UUID userId){
Department department = departmentRepository.findById(departmentId).orElseThrow(DepartmentNotFoundException::new);
User user = userRepository.findById(userId).orElseThrow(UserNotFoundException::new);
department.getUserSet().add(user); // ERROR java.lang.NullPointerException: null
user.setDepartment(department);
}
I think I did something wrong. I tried writing Cascade.ALL but it didn't help me. I don't think I fully understand the concept of a link collection yet. I don't like that in my code, I add the user to the department, and then I add the department to the user. Probably it can be done in one action.
Task.
I want to make a department and 2 functions. Add a user to the department and remove users from the department. in such a way that the contempt of the users from the department the user himself was not removed.
I will be glad to hear your comments
if you use #Builder annotation on top of the class
and you want to set default value for a field,
you must put annotation #Builder.Default on top of field
#Builder.Default
Set<Device> devices = new HashSet<>();
otherwise devices will always be NULL when the builder builds the object

Unique constraint on combination of more than two columns

I have two tables book and page
I' trying to setup a bidirectional one to many relation between them.
page has unique constraint on multiple columns which isn't working correctly. When i insert book_item with unique page_item it works as its supposed to but when i try to delete the book which should also delete the pages, i get unique constraint violation exception which i don't really understand.
i'm using jpa repository to perform insert/delete.
When i remove #UniqueConstraint the delete works but also the constraint doesn't so i don't really understand what i'm doing wrong.
#Entity
#Table(name = "book")
#NoArgsConstructor
#RequiredArgsConstructor
public class Book implements Serializable {
#Id
#GeneratedValue(generator = "book_uuid")
#GenericGenerator(name = "book_uuid", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id", updatable = false, nullable = false, columnDefinition = "VARCHAR(255)")
#Type(type="uuid-char")
#Getter
private UUID id;
#OneToMany(mappedBy = "book", cascade = CascadeType.ALL, orphanRemoval = true)
#Getter
#Setter
private List<Page> pages;
}
#Entity
#Table(name = "page", uniqueConstraints = #UniqueConstraint(columnNames = {"book_id", "chapter", "volume", "name"}))
#NoArgsConstructor
#RequiredArgsConstructor
public class Page implements Serializable {
#Id
#GeneratedValue(generator = "page_uuid")
#GenericGenerator(name = "page_uuid", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id", updatable = false, nullable = false, columnDefinition = "VARCHAR(255)")
#Type(type="uuid-char")
#Getter
private UUID id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "book_id", nullable = false)
#Getter
#Setter
#NonNull
private Book book;
#Column(name = "chapter", nullable = false)
#Getter
#Setter
private int chapter = 0;
#Column(name = "volume", nullable = false)
#Getter
#Setter
private int volume = 0;
#Column(name = "name", nullable = false)
#Getter
#Setter
#NonNull
private String name;
}
// Code used for insertion/update /deletion
#Repository
public interface BookRepository extends JpaRepository<Book, UUID> {
}
public void test() {
Book book = bookRepository.findById("9c7b2ab2-1c78-4e9f-adc5-99c7da42a7c6");
List<Page> pages = IntStream.range(0, 5)
.mapToObj(i -> new Page(book, "Page" + i, ".png"))
.collect(Collectors.toList());
book.setPages(pages);
bookRepository.save(book);
bookRepository.delete(book);
The error message i get :
org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["UKOBQWYIY89PIIE6GP2NB4PVQ8W_INDEX_2 ON PUBLIC.PAGE(BOOK_ID, CHAPTER, VOLUME, NAME) VALUES ('acb0bb92-be2f-4dd3-9653-98f8d890e6b4', 0, 0, 'Page0', 1)"; SQL statement:
insert into page (book_id, chapter, extension, name, volume, id) values (?, ??, ?, ?, ?, ?) [23505-197]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
You could try deleteById
deleteById("9c7b2ab2-1c78-4e9f-adc5-99c7da42a7c6");

ORA-01722: invalid number when using Hibernate

I have an entity Job as below.
#Entity
#Getter
#Setter
#NoArgsConstructor
#Immutable
#ToString
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
#Table(name = "JOB")
public class Job extends BaseEntity implements IEntity, IDto {
#Id
#Column(name = "JOB_ID", unique = true, nullable = false)
private Long id;
#Column(name = "PRINT_JOB_ID", length = 30)
private String printJobId;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "PRINT_JOB_ID", nullable = false, insertable = false, updatable = false)
private Set<PrintFile> printFileInfos = new HashSet<PrintFile>();
}
I also have another entity PrintFile.
#Entity
#Getter
#Setter
#NoArgsConstructor
#Immutable
#Table(name = "PRINT_FILE")
public class PrintFile implements Serializable {
#Id
#Column(name = "FILE_ID", unique = true, nullable = false, length = 50)
private String fileId;
#Column(name = "PRINT_JOB_ID", nullable = false, length = 30)
private String printJobId;
}
Here are my tables.
Job
JOB_ID NOT NULL NUMBER
PRINT_JOB_ID VARCHAR2(30)
Print_File
PRINT_JOB_ID NOT NULL VARCHAR2(30)
FILE_ID NOT NULL VARCHAR2(50)
When trying fetch Job data using Sprint boot rest API, I'm getting java.sql.SQLSyntaxErrorException: ORA-01722: invalid number error. All the dataype mapping seems to be correct, what else could have gone wrong ?
EDIT:
The Job entity fetches without any issues when I get rid of the join. i.e, the entire declaration of printFileInfos in Job entity. This makes me think the issue is either with the join or in PrintFile entity.
I would recommend you to try below given code. After adding referencedColumnName attribute, it worked for me.
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "PRINT_JOB_ID", referencedColumnName = "PRINT_JOB_ID", nullable = false, insertable = false, updatable = false)
private Set<PrintFile> printFileInfos = new HashSet<PrintFile>();

Categories