I'm making cart for my Spring project, I have User entity, cart and books, one user can have only one cart, so i made OneToOne relationship between user and cart, also many carts can contain many books, so i created manyToMany relationship between cart and book my code:
Book entity:
#ManyToMany
#JoinTable(
name = "books_in_cart",
joinColumns = { #JoinColumn(name = "cart_id")},
inverseJoinColumns = { #JoinColumn(name = "book_id")}
)
private Set<Cart> inCarts = new HashSet<>();
Cart entity:
#Entity
public class Cart {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "idUsers")
private User user;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
#ManyToMany
#JoinTable(
name = "books_in_cart",
joinColumns = { #JoinColumn(name = "book_id")},
inverseJoinColumns = { #JoinColumn(name = "cart_id")}
)
private Set<Book> books = new HashSet<>();
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Set<Book> getBooks() {
return books;
}
public void setBooks(Set<Book> books) {
this.books = books;
}
}
and User entity
#OneToOne(mappedBy = "user",cascade = CascadeType.MERGE,fetch = FetchType.EAGER,orphanRemoval = true)
private Cart userCart = new Cart();
Code that I use to add book to user's cart:
#PostMapping(value = "/addToCart")
#Secured("USER")
public String addToCart(#RequestParam(name = "ids") int id,Principal principal){
System.out.println(principal.getName());
User login = userDao.getByLogin(principal.getName());
Book book = service.getById(id);
login.getUserCart().setUser(login);
login.getUserCart().getBooks().add(book);
userDao.save(login);
return "redirect:/books";
}
I'm getting this exception :
java.lang.NullPointerException
controller.libraryController.addToCart(libraryController.java:170)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
The mapping is definitely wrong, probably the whole model is, because it look like you don't have a joining table at all:
joinColumns = { #JoinColumn(name = "cart_id")},
inverseJoinColumns = { #JoinColumn(name = "book_id")}
This means, that one entity has an id named cart_id in one table and book_id in the other one, which doesn't make any sense. That is why the exception is thrown.
You need to fix your model, both database and mapping. Here's a good read about it.
Related
I have a many to many relationship between users and questions for favouriting items, and when I trigger my /questionId/favourite api for a user, it should create an entry under the join table question_favourite that would map out user_id and question_id (which are both called 'id' within their own table).
I see the question_favourite table has been created, but it's always empty. I tried also using a put rather than post thinking that perhaps that's why I don't see an insert statement printed on the console but it didn't help much and I think I'm a bit stuck now.
I see similar questions posted here but being a beginner on Spring and having spent literally a few months now trying to figure out why I can't save data to my join table, I thought I'd need some more specific help please.
Here is what I have:
#Entity
#Table(name = "user_profile")
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "userEntity", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<QuestionEntity> questionEntities;
#ManyToMany
#JoinTable(
name = "question_favourite",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "question_id", referencedColumnName = "id"))
private Set<QuestionEntity> questionFavouriteEntity;
public UserEntity(UUID id) {
this.id = id;
}
}
#Entity
#Table(name = "question")
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
public class QuestionEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "title", nullable = false)
private String title;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.MERGE)
#JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false)
private UserEntity userEntity;
#ManyToMany(mappedBy = "questionFavouriteEntity")
private Set<UserEntity> userEntities;
public QuestionEntity(UUID id) {
this.id = id;
}
public QuestionEntity(UUID id, String title) {
this.id = id;
this.title = title;
}
}
#PostMapping(value = "/{id}/favourite", produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<QuestionFavouriteCreateDto> setFavouriteQuestion(#RequestHeader(required = false, value = "authorization") UUID userId, #RequestBody QuestionFavouriteCreateDto questionFavouriteCreateDto) {
if (userId == null) {
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
} else {
return new ResponseEntity<>(questionCommandService.favouriteQuestion(userId, questionFavouriteCreateDto), HttpStatus.OK);
}
}
#Override
public QuestionFavouriteCreateDto favouriteQuestion(UUID userId, QuestionFavouriteCreateDto qf) {
if (questionRepository.findById(qf.getQuestionId()).isPresent()) {
QuestionEntity questionEntity = questionRepository.findById(qf.getQuestionId()).get();
UserEntity userEntity = userRepository.findById(userId).get();
questionEntity.getUserEntities().add(userEntity);
userEntity.getQuestionEntities().add(questionEntity);
userRepository.save(userEntity);
questionRepository.save(questionEntity);
return new QuestionFavouriteCreateDto(questionEntity.getId(), true);
} else {
return null;
}
}
Here is the github link for the project:
https://github.com/francislainy/so/tree/master/backend/src/main/java/com/francislainy/so/backend
Thank you very much.
In your class QuestionCommandServiceImpl you should add a set of the question entity created as a Question Favourite like bellow :
#Override
public QuestionFavouriteCreateDto favouriteQuestion(UUID userId, QuestionFavouriteCreateDto qf) {
if (questionRepository.findById(qf.getQuestionId()).isPresent()) {
QuestionEntity questionEntity = questionRepository.findById(qf.getQuestionId()).get();
UserEntity userEntity = userRepository.findById(userId).get();
questionEntity.getUserEntities().add(userEntity);
userEntity.getQuestionEntities().add(questionEntity);
userEntity.getQuestionFavouriteEntity().add(questionEntity); // get the Set of favorite question and add the new question
userRepository.save(userEntity);
}
return null;
}
But preferably in the your case you can remplace in UserEntity the #OneToMany by joinColumn it's more perform:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "userEntity", cascade = CascadeType.ALL, orphanRemoval = true)
private Collection<QuestionEntity> questionEntities;
and try to unused the bidirectional or use with them the #JacksonBackReference
I hope that's help you ;)
In my system, User is the owner side of a many-to-many relationship.
User.class
#Entity
public class User {
#Id
private UUID id;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "user_rooms",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "room_id"))
private Set<Room> rooms = new HashSet<>();
}
Room.class
#Entity
public class Room {
#Id
private UUID id;
#ManyToMany(mappedBy = "rooms", fetch = FetchType.LAZY)
public Set<User> users = new HashSet<>();
}
With the JSON below, I can create a User and automatically associate him with an existing Room.
Creating a User
{
"nickname":"bob",
"rooms":[{
"id":"ca6eabb6-747e-47ec-9b52-c09483f7572a"
}]
}
But when I do the same thing on the non-owner side (Room), it doesn't work. The creation is successful, but the association does not happen.
Creating a Room
{
"name":"cool_room",
"users":[{
"id":"a5744044-1e6a-4279-8731-28f1e7dfc148"
}]
}
Is it possible to associate user_rooms from the non-owner side?
Yes, it's possible, but for correct saving both side of the bidirectional association should be in-sync. So, you should correct your entities in this way:
#Entity
public class User {
#Id
private UUID id;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "user_rooms",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "room_id"))
private Set<Room> rooms = new HashSet<>();
public void addRoom(Room room) {
rooms.add( room );
room.getUsers().add( this );
}
// public void removeRoom(Room room)
// can be implemented in the similar way if it's needed
}
#Entity
public class Room {
#Id
private UUID id;
#ManyToMany(mappedBy = "rooms", fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
public Set<User> users = new HashSet<>();
public void addUser(User user) {
users.add( user );
user.getRooms().add( this );
}
// public void removeUser(User user)
// can be implemented in the similar way if it's needed
}
See also this part of documentation.
I have the following code:
groupModel.getUserFormGroups().clear();
for(MemberDTO member : group.getMembers()){
User u = userRepository.findByEmail(member.getEmail());
System.out.println(member.getEmail() + " " + groupModel.getName() + " " + member.getRole());
if(u == null){
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
groupModel.getUserFormGroups().add(new UserFormGroup(u, groupModel, UserFormGroupRole.ADMIN));
}
try{
groupRepository.save(groupModel);
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
} catch (DataIntegrityViolationException e){
System.out.println(e.getMessage());
System.out.println(e.getClass());
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).build();
}
When I run this, the new UserFormGroups have an id and all the other fields are null. Is there something wrong with fully updating a ManyToOne relationship?
On the group entity I have the following OneToMany relation:
#OneToMany(targetEntity=UserFormGroup.class, cascade = { CascadeType.PERSIST, CascadeType.REMOVE }, fetch = FetchType.EAGER, mappedBy = "formGroup", orphanRemoval=true)
private Set<UserFormGroup> userFormGroups = new HashSet<>();
And the UserFormGroup relation looks like this:
#Entity
#Table(uniqueConstraints={
#UniqueConstraint(columnNames = {"user", "form_group"})
})
public class UserFormGroup implements Serializable{
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private long id;
#ManyToOne
#JoinColumn(name = "user",referencedColumnName = "id")
private User user;
#ManyToOne
#JoinColumn(name = "form_group", referencedColumnName = "id")
private FormGroup formGroup;
#Column(name = "role")
private UserFormGroupRole role;
public UserFormGroup() {
}
public UserFormGroup(User user, FormGroup group, UserFormGroupRole role) {
this.user = user;
this.formGroup = group;
this.role = role;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public FormGroup getFormGroup() {
return formGroup;
}
public void setFormGroup(FormGroup formGroup) {
this.formGroup = formGroup;
}
public UserFormGroupRole getRole() {
return role;
}
public void setRole(UserFormGroupRole role) {
this.role = role;
}
}
Not 100% but in my opinion, the problem might be following:
The CrudRepository's implementation of the save method checks whether the object you are saving is a new or existing entity. If it is already an existing entity, it performs a merge operation. This would be the scenario that's happening in your case as the groupModel is an existing entity.
Now on the #OneToMany dependency, you only have these cascade options:
cascade = { CascadeType.PERSIST, CascadeType.REMOVE }
If you add CascadeType.MERGE the operation should be propagated to the UserFromGroup entities and persist them (the default behavior of merge when the entities are new ones).
Assuming i have a unidirectional many-to-many relationship:
#Entity
#Table(name = "document")
public class Document {
private Integer id;
private Set<Role> roles;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "document_role",
joinColumns = {#JoinColumn(name = "Document_Id")},
inverseJoinColumns = {#JoinColumn(name = "Role_Id")})
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
#Entity
#Table(name = "role")
public class Role {
private Integer id;
...
}
Will the following insert into document_role if no Cascade type is defined and assuming no new Role entities will have to be created?
Role role1 = em.find(Role.class, 1);
Role role2 = em.find(Role.class, 2);
Role role3 = em.find(Role.class, 3);
Set<Role> roles = new HashSet<>();
roles.add(role1);
roles.add(role2);
roles.add(role3);
Document document = new Document();
document.setRoles(roles);
// Save document, will that save the association?
em.persist(document);
What if i also want to update the collection of roles and flush the changes? e.g. remove one role or add another one.
Short answer is no.
Long answer is each table is unique for CRUD.
Example:
If document table has a column id_role which is a foreign key to role table and you update this data, persisting document will update this column because is a property of Document table it will do nothing with Role objects.
I have a some problem.
I have 3 tables:
users (id, name, ..)
roles (id, name)
user-role (user_id, role_id)
When I do many-to-many relationship and do save() I have 3 inserts.
User:
#Entity
#Table(name = "user")
public class User implements Serializable {
public static final String UK_EMAIL = "uk_email";
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(
name = "system_user_role",
joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "role_id", referencedColumnName = id")}
)
private List<SystemRole> userRole;
public List<SystemRole> getUserRole() {
return userRole;
}
SystemRole;
#Entity
#Table(name = "system_role")
public class SystemRole implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column
private String name;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "system_user_role",
joinColumns = {#JoinColumn(name = "role_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")}
)
private List<User> users;
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
PLEASE, tell me, can I insert data into 2 tables only, only into (User and user_role?
I have roles list and I need not add a new role when I create a new user.
So, when I do:
SystemRole role1 = systemRoleService.findOne("ROLE_ADMIN");
userForm.setUserRole(Lists.newArrayList(role1));
....
final User saved = userRepository.save(user);
....
I get an error:
org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist:...
If i do:
#Service("userService")
#Transactional
public class UserServiceImpl implements UserDetailsService, ResourceService<User> {
private final static Logger LOGGER = LoggerFactory.getLogger(UserServiceImpl.class);
#Autowired
private UserDAO userRepository;
#Autowired
private SystemRoleDAO systemRoleRepository;
#Override
#Transactional(rollbackFor = ResourceException.class)
public User create(User user) throws ResourceException {
try {
SystemRole role1 = systemRoleRepository.findOne(6l);
user.setUserRole(Lists.newArrayList(role1));
user.setId(62l); // !!! if set user ID - it works CORRECT
final User saved = userRepository.save(user);
return saved;
} catch (DataIntegrityViolationException ex) {
...
UserDAO:
#Repository
public interface UserDAO extends JpaRepository<User, Long> {
...
SystemRoleDAO:
#Repository
public interface SystemRoleDAO extends JpaRepository<SystemRole, Long> {
It works, but I have 3 inserts.
When I create a new user, I need to select a role from list, add it to the user and save the new user.
Many thanks.
#Entity
#Table(name = "user")
public class User implements Serializable {
public static final String UK_EMAIL = "uk_email";
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE}) //Make CascadeType.Detached this should solver your problem
#JoinTable(
name = "system_user_role",
joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "role_id", referencedColumnName = id")}
)
private List<SystemRole> userRole;
public List<SystemRole> getUserRole() {
return userRole;
}
I guess that your code inside the userRepository.save() method is calling
entityManager.persist(user);
Instead you should be calling
entityManager.merge(user);
BY doing so, instead of inserting a new Role, Hibernate will check if a Role with the same ID already exists and if this is case, that Role will be attached to the User entity (provided that the cascade type includes Merge operation).
The reason why you need to call merge is that you are using a Role entity which has been loaded (via systemRoleService.findOne("ROLE_ADMIN")) and then detached from persistence context, so this entity is detached when you try to save the User. If Role had not been detached, you could call persist() instead of merge() on the User entity.