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).
Related
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.
I have 3 tables, User, Role, and User_role. User has a OneToMany relationship mapped by "user" with a CascadeType.Merge and user_role has 2 ManyToOne Relationships with cascadeTypes.All however the user_table never populates with data when running hibernate. Instead values are only populated in the user and role tables, but never the user_role table.
User Table Definition
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="Id", nullable=false, updatable = false)
private Long id;
private String username;
private String password;
private String firstName;
private String lastName;
private String email;
private String phone;
private boolean enabled = true;
#OneToMany(mappedBy = "user", cascade=CascadeType.MERGE, fetch =
FetchType.EAGER)
#JsonIgnore
private Set<UserRole> userRoles = new HashSet<>();
UserRole Table:
#Entity
#Table(name="user_role")
public class UserRole implements Serializable {
private static final long serialVersionUID = 890345L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long userRoleId;
#ManyToOne(fetch = FetchType.EAGER, cascade=CascadeType.ALL,
optional=false)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(fetch = FetchType.EAGER, cascade=CascadeType.ALL,
optional=false)
#JoinColumn(name = "role_id")
private Role role;
public UserRole () {}
public UserRole (User user, Role role) {
this.user = user;
this.role = role;
}
public long getUserRoleId() {
return userRoleId;
}
public void setUserRoleId(long userRoleId) {
this.userRoleId = userRoleId;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
Call To userRepository.save() in userServiceImpl that is called from a commandLine Runner.
#Service
public class UserServiceImpl implements UserService {
private static final Logger LOG = LoggerFactory.getLogger(UserSecurityService.class);
#Autowired
private UserRepository userRepository;
#Autowired
private RoleRepository roleRepository;
// Indicates a Database Transaction
#Transactional
public User createUser(User user, Set<UserRole> userRoles) {
User localUser = userRepository.findByUsername(user.getUsername());
if(localUser != null) {
LOG.info("User with username {} already exist. Nothing will be done. ", user.getUsername());
} else {
for (UserRole ur : userRoles) {
roleRepository.save(ur.getRole());
}
Set<UserRole> currentRoles =user.getUserRoles();
currentRoles.addAll(userRoles);
user.setUserRoles(currentRoles);
localUser = userRepository.save(user);
}
return localUser;
}
}
Main Class Run()
public void run(String... args) throws Exception {
User user1 = new User();
user1.setFirstName("John");
user1.setLastName("Adams");
user1.setUsername("j");
user1.setPassword(SecurityUtility.passwordEncoder().encode("p"));
user1.setEmail("JAdams#gmail.com");
Set<UserRole> userRoles = new HashSet<>();
Role role1 = new Role();
role1.setRoleId(1);
role1.setName("ROLE_USER");
userRoles.add(new UserRole(user1, role1));
userService.createUser(user1, userRoles);
}
CascadeType.MERGE will only cascade merge events. Persist events wont cascade so if you try to save a new user, the persist event will not cascade to User_role and no entries will be inserted into the user_role table.
Try to add the CascadeType.PERSIST or change to CascadeType.ALL in order to cascade save the record in the database.
#OneToMany(mappedBy = "user", cascade={CascadeType.MERGE, CascadeType.PERSIST}, fetch = FetchType.EAGER)
You can find out more about Cascade events in this answer: What do REFRESH and MERGE mean in terms of databases?
I was able to persist the data to the user_role associative table by implementing the JPA entity manager. A new EntityManager class was instantiated and the data was persisted through the javax.persistence .merge() method.
I have a Spring project that uses JPA with Hibernate and MySQL database. Database includes three tables: Users, Roles, and many-to-many join table UserRoles.
Below are the corresponding classes for the tables.
ApplicationUser.java
#Entity
#Table(name = "APPLICATION_USER")
public class ApplicationUser extends ExtAuditInfo {
public static final Long SYSTEM_USERID = 1000L;
#Id
#Column(name = "APPLICATION_USER_ID")
private long applicationUserId;
#Column(name = "LOGIN_NAME")
private String loginName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "MIDDLE_NAME")
private String middleName;
#OneToMany(mappedBy = "id.applicationUser", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<UserRole> roles =new ArrayList<>();
//get and set methods
Role.java
#Entity
#Table(name = "ROLE")
#NamedQueries({
#NamedQuery(name = "Role.getRoleById", query = "select r from Role r where r.roleId =:roleId"))}
public class Role extends AuditInfo {
#Id
#Column(name="ROLE_ID")
private long roleId;
#Column(name="ACTIVE_FLAG")
private String activeFlag;
#Column(name="ROLE_NAME")
private String roleName;
#OneToMany(mappedBy = "id.role", fetch = FetchType.LAZY)
private List<UserRole> users = new ArrayList<>();
//get and set methods
UserRole.java
#Entity
#AssociationOverrides({
#AssociationOverride(name = "id.applicationUser",
joinColumns = #JoinColumn(name = "APPLICATION_USER_ID")),
#AssociationOverride(name = "id.role",
joinColumns = #JoinColumn(name = "ROLE_ID")) })
#Table(name = "USER_ROLE")
public class UserRole extends ExtAuditInfo implements Serializable{
#EmbeddedId
private UserRoleID id = new UserRoleID();
#Column(name="USER_ROLE_VER")
private long userRoleVer;
public UserRole(){
}
#Transient
public ApplicationUser getApplicationUser() {
return this.id.getApplicationUser();
}
public void setApplicationUser(ApplicationUser applicationUser) {
this.id.setApplicationUser(applicationUser);
}
public long getUserRoleVer() {
return this.userRoleVer;
}
public void setUserRoleVer(long userRoleVer) {
this.userRoleVer = userRoleVer;
}
#Transient
public Role getRole() { return this.id.getRole(); }
public void setRole(Role role) { this.id.setRole(role); }
}
UserRoleID.java
#Embeddable
public class UserRoleID implements Serializable {
#ManyToOne(cascade = CascadeType.ALL)
private ApplicationUser applicationUser;
#ManyToOne(cascade = CascadeType.ALL)
private Role role;
private static final long serialVersionUID = 1L;
public UserRoleID() {
}
public ApplicationUser getApplicationUser() {
return this.applicationUser;
}
public void setApplicationUser(ApplicationUser applicationUser) {
this.applicationUser = applicationUser;
}
public Role getRole() {
return this.role;
}
public void setRole(Role role) {
this.role = role;
}
}
Now, when I create a sample user with viewer role, the record is being inserted into the Application_user and User_Role tables, but when I try to update the role of the user it is adding a new role to the user instead of updating the existing role.
This is what I'm doing
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void updateRole(ApplicationUser appUser, long roleId){
EntityManager em=getEntityManager();
TypedQuery<Role> query = em.createNamedQuery("Role.getRoleById", Role.class);
query.setParameter("roleId",roleId);
Role r = query.getSingleResult();
UserRole userRole= appUser.getRole().get(0);
userRole.setRole(r);
em.merge(userRole);
em.flush();
em.detach(userRole);
}
Any idea, what to do to update the existing role instead of creating a new role in user_role table?
You are assigning new role to user, so a new record is added in user_role table, and old user_role entry is deleted. That's the right behavior.
So it's not you called "update the role of user".
Update:
You should delete role manually when many-to-many relationship.
appUser.getRoles().remove(userRole);
em.remove(userRole);
UserRole newUserRole = new UserRole();
newUserRole.setRole(r);
appUser.getRoles().add(newUserRole);
I have this domain class in Spring:
#Entity
#Table(name="Like")
public class Like {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#ManyToOne(cascade= CascadeType.MERGE, targetEntity = User.class)
#JoinColumn(name = "user_id")
#OnDelete(action= OnDeleteAction.CASCADE)
Set<User> user;
#OneToMany(mappedBy = "like", orphanRemoval = true ,cascade= CascadeType.ALL, targetEntity = Picture.class)
#OnDelete(action= OnDeleteAction.CASCADE)
Set<Picture> pictures;
public Like() {
}
public Like(Set<User> user) {
this.user = user;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#JsonIgnore
public Set<User> getUser() {
return user;
}
#JsonIgnore
public void setUser(Set<User> user) {
this.user = user;
}
#JsonIgnore
public Set<Picture> getPictures() {
return pictures;
}
#JsonIgnore
public void setPictures(Set<Picture> pictures) {
this.pictures = pictures;
}
}
}
and I have this table in my sql script
CREATE TABLE IF NOT EXISTS `like` (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`user_id` BIGINT,
FOREIGN KEY (user_id) REFERENCES `user`(id)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
And this is a problem when I send post request in postman:
java.lang.IllegalArgumentException: Can not set java.lang.Long field
com.nyters.webapp.domain.User.id to java.util.HashSet
ControllerLike.java
#RestController
#RequestMapping("api/like")
public class LikeController {
private LikeService likeService;
#Autowired
public LikeController(LikeService likeService){
this.likeService = likeService;
}
#RequestMapping(path = "/{id}", method = RequestMethod.GET)
public ResponseEntity<LikeDTO> findOne(#PathVariable Long id) {
LikeDTO pictureDTO = likeService.findOne(id);
if (pictureDTO != null) {
return new ResponseEntity<>(pictureDTO, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<LikeDTO>> findAll() {
List<LikeDTO> likeDTOs = likeService.findAll();
if (likeDTOs != null) {
return new ResponseEntity<>(likeDTOs, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<LikeDTO> save(#RequestBody String likeDtoString)
throws
IOException {
LikeDTO likeDTO = new ObjectMapper().readValue(likeDtoString,
LikeDTO.class);
LikeDTO saved = likeService.save(likeDTO);
if (saved != null) {
return new ResponseEntity<>(saved, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
I guess prob is with your mapping
Like.java
#Fetch(value = FetchMode.SELECT)
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "user_Id")
#JsonIgnore
private List<User> userList;
//based on user_Id u can fetch userList from DB
User.java
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_Id")
private Long user_Id;
At first #JsonIgnore make your users not accessiable for entity from json.
At second you need to change #ManyToOne to #OneToMany or #ManyToMany for filed Set<User> user or change type to User user
UPDATE
Well, ManyToOne means that in current class you have just one element (in our case User. it`s looks as following:
class Like {
// init, class and bla-bla-bla
#ManyToOne(/*properties*/)
User user; // important - not Collection, Set or something else
and in this case your user class looks as follows:
class User {
// blablabla
#OneToMany(/*properties*/
Set<Like> likes;
So, you can imagine (?) it as follows: #external_filed To current_field. I hope you understand
So I have a one to many and many to one relation between two classes. When I try to update an entity, the parent is updated and child throws an error. In that case I expect the parent update to be rolled back but it is not. Since I have a one to many relation, an update on the parent is expected to insert a child but when the child throws an error shouldnt the parent's update be rolled back? If it is of any relation the child's error is thrown due to the unique constraints on the child/account entity.
Below are my two models:
/** User model **/
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", unique = true, nullable = false)
private int id;
#Column(name = "type")
private String type;
#Column(name = "username", nullable = false)
private String username;
...
// define one to many relation between User and Account
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private Set<Account> accounts;
public User() {
}
#PrePersist
void preInsert() throws ParseException {
...
}
// field getters and setters
...
// returns Account list associated with User
public Set<Account> getAccount() {
return accounts;
}
// set Account list associated with User
public void setAccount(Set<Account> accounts) {
this.accounts = accounts;
}
}
Model 2:
/** Account model **/
#Entity
#Table(name = "account", uniqueConstraints =
#UniqueConstraint(columnNames = {"user_id", "entity_id", "branch_id", "type"}))
public class Account {
private int id;
#Column(name = "user_id", nullable = false)
private int user_id;
...
private User user;
// constructor
public Account() {
}
#PrePersist
void preInsert() throws ParseException {
...
}
// field getters and setters
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", nullable = false)
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
...
// define many to one relation between Account and User
// get User associated with Account
#ManyToOne
#JoinColumn(name = "user_id", insertable = false, updatable = false)
public User getUser() {
return user;
}
// set User associated with Account
public void setUser(User user) {
this.user = user;
}
}
UserDAO:
#Repository("userDao")
#Transactional(propagation = Propagation.REQUIRED)
public class UserDAO {
#PersistenceContext
private EntityManager entityManager;
public EntityManager getEntityManager() {
return entityManager;
}
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
public void insert(User user) {
entityManager.persist(user);
}
public void update(User user) {
entityManager.merge(user);
}
....
}
User service (where i am calling the update)
#Service
public class UserService {
private UserDAO userDAO;
public UserDAO getUserDao() {
return userDAO;
}
#Autowired
public void setUserDao(UserDAO userDAO) {
this.userDAO = userDAO;
}
public boolean addUser(SignupComponent signupComponent) {
....
else {
// case (4)
// get user object
User userObj = getUserDao().findUser(user.getPhone());
// update user object, adding account and account details
Set<Account> accounts = userObj.getAccount();
Account a = new Account();
a.setBranch_id(signupComponent.branch_id);
a.setEntity_id(signupComponent.entity_id);
if (signupComponent.type != -1) {
a.setType(signupComponent.type);
}
a.setUser(userObj);
userObj.setAccount(accounts);
userObj.setEmail(signupComponent.user.getEmail());
AccountDetails ad = new AccountDetails(); //never mind this line, i have another one to one relation with another entity
ad.setAccount(a);
a.setAccountDetails(ad);
accounts.add(a);
try {
getUserDao().update(userObj);
return true;
}
catch(Exception e) {
signupComponent.error = e.toString();
return false;
}
}
}
}
You are defining JoinColumn at both the sides.You need to define at one side. How would it store an arbitrary number of foreign keys in a single row? Instead, it must let the tables of the entities in the collection have foreign keys back to the source entity table.
Try this:
public class User{
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL,mappedBy="user")
private Set<Account> accounts;
}
User class
/** User model **/
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", unique = true, nullable = false)
private int id;
#Column(name = "type")
private String type;
#Column(name = "username", nullable = false)
private String username;
...
// FetchType should be Lazy to improve performance
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL,mappedBy="user")
private Set<Account> accounts;
//mappedBy says that this side is inverse side of relation and source is user which is mapped by user field name in Account class
public User() {
}
#PrePersist
void preInsert() throws ParseException {
...
}
// field getters and setters
...
// returns Account list associated with User
public Set<Account> getAccount() {
return accounts;
}
// set Account list associated with User
public void setAccount(Set<Account> accounts) {
this.accounts = accounts;
}
}
Account class
/** Account model **/
#Entity
#Table(name = "account", uniqueConstraints =
#UniqueConstraint(columnNames = {"user_id", "entity_id", "branch_id", "type"}))
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", nullable = false)
private int id;
#Column(name = "user_id", nullable = false)
private int user_id;
...
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "user_id", insertable = false, updatable = false)
private User user;
// constructor
public Account() {
}
#PrePersist
void preInsert() throws ParseException {
...
}
// field getters and setters
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
...
// define many to one relation between Account and User
// get User associated with Account
public User getUser() {
return user;
}
// set User associated with Account
public void setUser(User user) {
this.user = user;
}
}
See now, when you save User then Account class will not be updated as User is at inverse side. But when you save Account class then user_id which is present in account table will get update as it is source side of relation.