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
Related
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.
I have two tables. A Users table and an Artists table. Users can be associate with many artists, and vice versa. I have an API call that adds an Artist to the User's list. The API seems to work correctly, but when I check the User afterward, my postman return shows an endless list of the artist I added.
The user entity:
#Entity
#Table(name = "users")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Slf4j
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", nullable = false)
Integer id;
#Column(name = "username")
String username;
#Column(name = "picture_link")
String pictureLink;
#ManyToMany
#JoinTable(
name = "user_artist",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "artist_id"))
Set<Artist> artists = new HashSet<>();
The artist entity:
#Entity
#Table(name = "artists")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Slf4j
public class Artist {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", nullable = false)
Integer id;
#Column(name = "name")
String name;
#Column(name = "description")
String description;
#Column(name = "picture_link")
String pictureLink;
#Column(name = "genres")
String genres;
#ManyToMany(mappedBy = "artists")
Set<User> users = new HashSet<>();
#Transient
List<Album> albums = new ArrayList<>();
}
The api call that causes the infinite loop:
#Override
public String addArtistToFaveList(int user_id, int artist_id) {
try{
User foundUser = new User();
Artist foundArtist = new Artist();
Optional<User> resultUser = userRepo.findById(user_id);
Optional<Artist> resultArtist = artistRepo.findById(artist_id);
if(resultUser.isPresent() && resultArtist.isPresent()){
foundUser = resultUser.get();
foundArtist = resultArtist.get();
}
Set<Artist> userFaveSet = foundUser.getArtists();
userFaveSet.add(foundArtist);
userRepo.save(foundUser);
return "THIS WORKED!";
}catch(Exception e){
return "Failed completely.";
}
}
i have Three entities User, Institution and Role.
1)one to many between user and institution
2)and many to many between User and Role
-------user-------
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="user_Id")
private int userId;
#Column(name="name")
private String name;
#Column(name="lastname")
private String lastname;
#Column(name="email")
private String email;
#Column(name="password")
private String password;
#Column(name="isActive")
private boolean isActive;
#Column(name="lastActive")
private String lastActive;
#Column(name="createdDate")
private String createdDate;
#Column(name="isBlocked")
private boolean isBlocked;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "institution_id", nullable = false)
#JsonIgnoreProperties(value = {"user"})
private Institution institution;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "user_has_role",
joinColumns = {
#JoinColumn(name = "user_id", referencedColumnName = "user_id",
nullable = false, updatable = true)},
inverseJoinColumns = {
#JoinColumn(name = "role_id", referencedColumnName = "role_id",
nullable = false, updatable = true)})
#JsonIgnoreProperties(value = {"users"})
private Set<Role> roles = new HashSet<>();
}
-------institution-------
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
#Table(name = "institution")
public class Institution {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="institution_Id")
private int institutionId;
#Column(name="name")
private String name;
#Column(name="type")
private String type;
#Column(name="location")
private String location;
#OneToMany(mappedBy = "institution", fetch = FetchType.LAZY)
#JsonIgnoreProperties(value = {"institution" , "user"})
private Set<User> user;
}
-------role-------
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
#Table(name = "role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="role_Id")
private int roleId;
#Column(name="name")
private String name;
#Column(name="description")
private String description;
#ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY)
#JsonIgnoreProperties(value = {"roles"})
private Set<User> users = new HashSet<>();
}
Those are my 3 entities and tables in MySql
i have 7 roles
• Super-User
• Bank-Admin
• Bank-Support
• Bank-Service
• Merchant-Admin
• Merchant-Support
• Merchant-service
The super-User can create a user of any role
#PostMapping("/addUser")
public String addUser(#RequestBody User user) {
String rawpassword = user.getPassword();
String encodedpasswrod = passwordencoder.encode(rawpassword);
user.setPassword(encodedpasswrod);
userrepository.save(user);
return "user saved with name: " + user.getName();
}
this api works and i can set the role to anything in my api json body
But want that if the User is Bank-Admin he can only create Bank-Support and Bank-Service
im trying to create a new API which can only create a user with those 2 specific roles.
and then restrict the bank admin to access the other API that can create users of any kind.
is there any other way to do it and if no how can i do that...
You have to implement your custom implementation of User Entitlement.
Like according to login person, you will get that login person role, and according to your criteria just put validation like check that entity he is trying to add is he eligible to create it.
Map<String, List<String>> roleUserAccessMap = new HashMap<>();
roleUserAccessMap.put("Bank-Admin", Arrays.asList("Bank-Support", "Bank-Service"));
Just check like below
String loginPersonRole="Bank-Admin"; //This value should get from logged-in person context
if(roleUserAccessMap.containsKey(loginPersonRole) && roleUserAccessMap.get(loginPersonRole).contains(newuserrole) ){
//proceed ahead with Add api
}else{
System.out.println("You do not have enough privileage to create Use");
}
This will help you.
I have a spring service class where I'm loading a JPA object (target) via CRUD. This target class has a one-to-may mapping that is set to lazy loading.
I would like to query this object inside a spring service method that is annotated with #Transactional and avoid that the childs are being loaded.
When I execute the following code all child data is loaded and laziness is ignored.
#Override
#Transactional
public boolean changeState(boolean enabled, final EventType eventType, final String deviceSerialNumber) {
final UniqueUser user = userService.getUser();
final Target target = targetRepository.findEventsByUserIdAndTenantIdAndTargetDeviceId(user.getUserId(), user.getTenantId(), deviceSerialNumber);
//here everything gets loaded
if (target == null) {
return false;
}
final List<EventHistory> events = target.getEvents().stream()
.filter(event -> event.getEventType() == eventType)
.filter(event -> event.isActive() != enabled)
.collect(Collectors.toList());
events.forEach(event -> event.setActive(enabled));
if (events.isEmpty()) {
return false;
}
return true;
}
Mappings:
#ToString
#Entity
#Table(name = "target")
public class Target {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id", unique = true)
private UUID id;
#Column(name = "user_id")
private String userId;
#Column(name = "tenant_id")
private String tenantId;
#Column(name = "target_device_id")
private String targetDeviceId;
#Column(name = "target_type")
private TargetType targetType;
#OneToMany(mappedBy = "target", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JsonManagedReference
private List<EventHistory> events = new ArrayList<>();
public void addEvents(EventHistory event) {
events.add(event);
event.setTarget(this);
}
}
#Entity
#Data
#Table(name = "event_history")
public class EventHistory {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id", unique = true)
private UUID id;
#Column(name = "active")
private boolean active;
#OneToMany(mappedBy = "event", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonManagedReference
private List<EventTimestamp> timestamps = new ArrayList<>();
#ManyToOne(optional = false, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "target_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonBackReference
private Target target;
public void addTimestamps(EventTimestamp eventTimestamp) {
timestamps.add(eventTimestamp);
eventTimestamp.setEvent(this);
}
}
#Entity
#Data
#Table(name = "event_timestamp")
public class EventTimestamp {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id", unique = true)
private UUID id;
#Column(name = "due_timestamp")
private Timestamp dueTimestamp;
#Column(name = "period")
private String period;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "event_id", nullable = false)
#JsonBackReference
private EventHistory event;
So my question is how to keep lazy loading inside transaction annotated functions?
My first assumption that the root cause had been the wrongly implemented repository function was wrong. The real issue was the #ToString annotation. This added the one-to-many event collection to toString(). While being inside the transaction and accessing the object, toString got invoked and the the collection was loaded.
Solution was to exclude the collection from toString via.
#OneToMany(mappedBy = "target", cascade = CascadeType.ALL, orphanRemoval = true)
#ToString.Exclude
private List<EventHistory> events;
I figured it out. The problem was ins the Repository code. The findBy method is expecting a List instead of a single object.
My original repository looked like this
#Repository
public interface TargetRepository extends CrudRepository<Target, UUID> {
Target findEventsByUserIdAndTenantIdAndTargetDeviceId(String userId, String tenantId, String targetId);
}
Changing it to the below version fixed it.
#Repository
public interface TargetRepository extends CrudRepository<Target, UUID> {
List<Target> findEventsByUserIdAndTenantIdAndTargetDeviceId(String userId, String tenantId, String targetId);
}
In my application every customer can have several accounts. I have the following data structure (a lot omitted for brevity):
#Entity
#Table(name = "CUSTOMER")
public class Customer {
#Id
#Column(length = 36, name = "CUSTOMER_ID", nullable = false, unique = true)
private String id;
#OneToMany
#JoinColumn(name = "OWNER_ID", referencedColumnName = "CUSTOMER_ID")
private List<Account> accounts;
}
#Entity
#Table(name = "ACCOUNT")
public class Account {
#Id
#Column(length = 36, name = "ACCOUNT_ID", nullable = false, unique = true)
private String id;
#Column(name = "OWNER_ID", nullable = false)
private String ownerId;
}
If I use JPA to delete a Customer, such as
entityManager.remove(customer);
it tries to update the related ACCOUNT.OWNER_ID fields with null. OWNER_ID is not nullable, so it throws a JDBCException and rolls back the transaction.
What I need to achieve is that the related ACCOUNT rows get deleted (if any). How can I do that?
Thank you
Update: I tried it with
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
but it does not change the behavior: still tries to update with null.
I think you need to be using cascading in order to remove the child elements. Try this:
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "OWNER_ID", referencedColumnName = "CUSTOMER_ID")
private List<Account> accounts;
You should also reference the Customer in your account by a ManyToOne relationship and not the String id. I think this should solve your issue:
#Column(name = "OWNER_ID", nullable = false)
#ManyToOne
private Customer owner;