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
Related
I make method (acceptUseroffermapping) in a REST-controller (UserOfferController) in which I want to delete record in the DB (UserOfferMapping table). But the problem is that record not deleted and relation also saved after I run this method.
I have also UserOfferMapping class which maps to User class. In UserOfferController I manipulate with UserOfferMapping: creating, selecting records from DB and also trying to delete records but have fail.
UserOfferController.java:
/*...*/
#POST
#RequestMapping("/acceptUserOfferMapping")
public void acceptUseroffermapping(#RequestBody Map<String,
String> body) throws ParseException {
String userId = body.get("userId");
String offerId = body.get("offerId");
Optional<User> user = userRepository.findById(Integer.parseInt(userId));
UserOfferMapping mapping = userOfferMappingRepository.getById(Integer.parseInt(userId));
user.get().getUserOfferMapping().remove(mapping);
userRepository.save(user.get());
userOfferMappingRepository.deleteById(Integer.parseInt(offerId));
}
/*...*/
User.java:
/*some imports*/
#Entity
#Table(name = "User")
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
/* ...
* a lot of fields
* ...
*/
// Important section which describes all Role Project and Skill mapping
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<UserUserrolemapping> userrolemapings = new HashSet<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<Userprojectmapping> userprojectmappings = new HashSet<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<UserOfferMapping> userOfferMapping = new HashSet<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#OrderBy
private Set<Userskillmapping> userskillmappings = new HashSet<>();
/* ...
* a lot of fields too
* ...
*/
/* getter and setters */
}
UserOfferMappingRepository.java:
public interface UserOfferMappingRepository extends JpaRepository<UserOfferMapping, Integer> {
public List<UserOfferMapping> getAllByUser(Optional<User> user);
public UserOfferMapping getUserOfferMappingByUserAndProjectAndUserRole(User user, Userproject userproject, Userrole userrole);
public UserOfferMapping getById(int id);
public void deleteById(int id);
}
UserOfferMapping.java:
#Entity
public class UserOfferMapping {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne
#JoinColumn(name = "userid")
#JsonBackReference
private User user;
#ManyToOne
#JoinColumn(name = "roleid")
private Userrole userRole;
#ManyToOne
#JoinColumn(name = "projectid")
private Userproject project;
#Column(name = "fromdate", nullable = true)
private Date fromdate;
#Column(name = "todate", nullable = true)
private Date todate;
#Column(name = "chance")
private int chance;
#Column(name = "percent")
private int percent;
public int getId() {
return id;
}
public User getUser() {
return user;
}
public Userrole getUserRole() {
return userRole;
}
public Userproject getProject() {
return project;
}
public Date getFromdate() {
return fromdate;
}
public int getChance() {
return chance;
}
public int getPercent() {
return percent;
}
public void setId(int id) {
this.id = id;
}
public void setUser(User user) {
this.user = user;
}
public void setUserRole(Userrole userRole) {
this.userRole = userRole;
}
public void setProject(Userproject project) {
this.project = project;
}
public void setFromdate(Date fromdate) {
this.fromdate = fromdate;
}
public void setChance(int chance) {
this.chance = chance;
}
public void setPercent(int percent) {
this.percent = percent;
}
public void setTodate(Date todate) {
this.todate = todate;
}
public Date getTodate() {
return todate;
}
}
Can you try to use this
public interface UserOfferMappingRepository extends JpaRepository<UserOfferMapping, Integer> {
public List<UserOfferMapping> getAllByUser(Optional<User> user);
public UserOfferMapping getUserOfferMappingByUserAndProjectAndUserRole(User user, Userproject userproject, Userrole userrole);
public UserOfferMapping getById(int id);
// public void deleteById(int id);
#Modifying(clearAutomatically = true)
#Query(value = "Delete from UserOfferMapping c WHERE c.id=:id")
public void deleteById(#Param("id") int id);
}
So, you have bidirectional entity association.
Try to add mapping.setUser(null); before userRepository.save.
Persisting and deleting objects requires a transaction in JPA so that is why you have to define #Transactional annotation before the method in Repository for example `
#Transactional
public void deleteById(#Param("id") int id);
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).
I have following domain mapping:
#Entity
#Table(name = "terminal_admin_role")
public class AdminRole {
#Id
#Column(name = "role_id", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id")
#SequenceGenerator(name = "user_id", sequenceName = "user_id")
private Long adminId;
#Column(name = "role")
private String role;
public AdminRole(String role) {
this.role = role;
}
public AdminRole() {
}
// get set
#Override
public String toString(){
return role;
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof AdminRole)) {
return false;
}
AdminRole adminRole = (AdminRole) o;
if (!role.equals(adminRole.role)) {
return false;
}
return true;
}
#Override
public int hashCode() {
return role.hashCode();
}
}
and
#Entity
#Table(name = "terminal_admin")
public class TerminalAdmin {
#ManyToMany(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
#JoinTable(name = "admin_role", joinColumns = {
#JoinColumn(name = "admin_id", nullable = false) },
inverseJoinColumns = { #JoinColumn(name = "role_id",
nullable = false) })
private Set<AdminRole> adminRoles;
//...
}
and following code to execute:
controller:
#RequestMapping(value = "/admin/addNewAdmin")
public String adminUsers(#ModelAttribute #Valid TerminalAdmin terminalAdmin,
BindingResult bindingResult, ModelMap model, Principal principal, HttpSession session) {
...
terminalAdmin.setCreateDate(Calendar.getInstance());
terminalAdminService.saveTerminalAdmin(terminalAdmin);
...
}
service:
#Override
#Transactional
public void saveTerminalAdmin(TerminalAdmin newAdmin) {
String rawPassword = newAdmin.getPassword();
newAdmin.setPassword(passwordEncoder.encode(newAdmin.getPassword()));
terminalAdminDao.save(newAdmin);
emailService.sendAdminCreatedEmail(rawPassword, newAdmin.getEmail(), newAdmin.getAdminRoles());
emailService.sendAdminRegisteredForAdminEmail(newAdmin);
}
dao:
#Override
#Transactional
public void save(TerminalAdmin terminalAdmin) {
sessionFactory.getCurrentSession().save(terminalAdmin);
}
After it I see that admin roles binded to user duplicated in AdminRole table in database.
What Do I wrong? I have wrote equals method.
P.S.
before saving in debug I see following values:
Because in your new TerminalAdmin, the AdminRole it is referring does not contain ID. ID is the identity of entities. It does not use equals() to identify the identity. Without IDs, Hibernate simply treat it as a new AdminRole to be persisted to DB (as you have set corresponding cascade options in TerminalAdmin)
There are some choices you may take
Change the ID of your AdminRole to the role String, or/and
Lookup the correct AdminRole by role string, and set them in your new TerminalAdmin entity, or/and
Contains the AdminRole ID in the incoming request, etc...
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.
I'm trying to implement to following : Many to Many Hibernate Mapping for additional property in the join table
for creating many to many connection with extra fields. I keep getting this exception:
org.hibernate.MappingException: Repeated column in mapping for entity:
UserRole column: id (should be mapped with insert="false" update="false")
Why?
I'm using spring 4 and MySQL
My code:
#Entity
#AssociationOverrides({ #AssociationOverride(name = "pk.user", joinColumns = #JoinColumn(name = "id")),
#AssociationOverride(name = "pk.role", joinColumns = #JoinColumn(name = "id")) })
public class UserRole extends AbstractEntity {
private UserRoleId pk;
public UserRole(User user, Role role) {
super();
this.pk = new UserRoleId(user, role);
}
#EmbeddedId
public UserRoleId getPk() {
return pk;
}
public Long getUserId() {
return pk.getUser().getId();
}
public Long getRoleId() {
return pk.getRole().getId();
}
}
#Embeddable
public class UserRoleId implements Serializable {
private User user;
private Role role;
public UserRoleId(User user, Role role) {
super();
this.user = user;
this.role = role;
}
#ManyToOne
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
#ManyToOne
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
#MappedSuperclass
public abstract class AbstractEntity {
#Column(nullable = false)
protected Date timeCreated;
#Column
private Long modifiedBy;
public AbstractEntity() {
super();
this.timeCreated = DateUtil.now();
this.modifiedBy = 0l;
}
public Date getTimeCreated() {
return timeCreated;
}
public void setTimeCreated(Date timeCreated) {
this.timeCreated = timeCreated;
}
#Override
public abstract String toString();
}
You can't define same column as more than times. If two columns present there, You need to mention insert = "false" and update = "false"
#Entity
#AssociationOverrides({ #AssociationOverride(name = "pk.user", joinColumns = #JoinColumn(name = "id")),
#AssociationOverride(name = "pk.role", joinColumns = #JoinColumn(name = "id", insertable = false, updatable = false)) })
public class UserRole extends AbstractEntity {
private UserRoleId pk;
public UserRole(User user, Role role) {
super();
this.pk = new UserRoleId(user, role);
}
#EmbeddedId
public UserRoleId getPk() {
return pk;
}
public Long getUserId() {
return pk.getUser().getId();
}
public Long getRoleId() {
return pk.getRole().getId();
}
}