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...
Related
what is the best way which can I add to the following method in a spring boot controller class to allow the user just delete the restaurant he created .
I don't want to add the user id to the path, I want the logged in user to not allowed deleting restaurant which he didn't create.
Note that I extend Auditable to add createdBy to the database mysql
#DeleteMapping("/restaurant/{restaurantId}")
public String deleteRestaurantById(#PathVariable("restaurantId") Long restaurantId) {
if(restaurantService.existsById(id) == false){
logger.info("Error occurred because this restaurant is not found!");
throw new InternalServerErrorException("There is no restaurant with this id");
}
restaurantService.deleteById(id);
return "deleted";
}
Restaurant.java
#Entity
#NoArgsConstructor
#RequiredArgsConstructor
#Getter
#Setter
#ToString
public class Restaurant extends Auditable{
#Id
#GeneratedValue
private long id;
#NonNull
#NotEmpty(message = "The restaurant must have a name")
private String name;
....
}
User.java
#Entity
#RequiredArgsConstructor
#Getter
#Setter
#ToString
#NoArgsConstructor
#PasswordMatch
public class User implements UserDetails {
#Id #GeneratedValue
private Long id;
#NonNull
#Size(min = 8, max = 20)
#Column(nullable = false, unique = true)
private String email;
#NonNull
#Column(length = 100)
private String password;
#Transient
#NotEmpty(message = "Please enter Password Confirmation.")
private String confirmPassword;
#NonNull
#Column(nullable = false)
private boolean enabled;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "users_roles",
joinColumns = #JoinColumn(name = "user_id",referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "role_id",referencedColumnName = "id")
)
private Set<Role> roles = new HashSet<>();
#NonNull
#NotEmpty(message = "You must enter First Name.")
private String firstName;
#NonNull
#NotEmpty(message = "You must enter Last Name.")
private String lastName;
#Transient
#Setter(AccessLevel.NONE)
private String fullName;
#NonNull
#NotEmpty(message = "Please enter alias.")
#Column(nullable = false, unique = true)
private String alias;
private String activationCode;
public String getFullName() {
return firstName + " " + lastName;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream().map(role -> new SimpleGrantedAuthority(role.getName())).collect(Collectors.toList());
}
public void addRole(Role role) {
roles.add(role);
}
public void addRoles(Set<Role> roles) {
roles.forEach(this::addRole);
}
#Override
public String getUsername() {
return email;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return enabled;
}
}
You can implement One-to-One relationship between User and Restaurant with join table if User is able to create the only Restaurant.
it is impossible for spring security to magically know what restaurants that are created by a user.
There has to be some form of relationship between the user and the restaurant.
You can either as mentioned before have a relationship between created restaurants and the user that created them.
Or you can have a column named created_by in the restaurant entity and then store the users id in that column. So that when you fetch restaurants you use the id in the Principal to filter on restaurants created by said user.
But expecting spring security to solve this for you is not going to happen.
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
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'm trying to generate Hibernate mapping to my H2 database.
I have 2 tables for test, called users and users_groups.
They look like:
users table:
user_id integer PK
login varchar
password varchar
user_group_id integer FK
users_groups
user_group_id integer PK
name varchar
And the problem is that hibernate generate entities like that:
#Entity
public class Users {
private int userId;
private int userGroupId;
#Id
#Column(name = "USER_ID", nullable = false)
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
#Basic
#Column(name = "USER_GROUP_ID", nullable = false)
public int getUserGroupId() {
return userGroupId;
}
public void setUserGroupId(int userGroupId) {
this.userGroupId = userGroupId;
}
#Entity
#Table(name = "USERS_GROUPS", schema = "PUBLIC", catalog = "DATABASE")
public class UsersGroups {
private int userGroupId;
#Id
#Column(name = "USER_GROUP_ID", nullable = false)
public int getUserGroupId() {
return userGroupId;
}
public void setUserGroupId(int userGroupId) {
this.userGroupId = userGroupId;
}
So no relation annotations are generated, like #OneToMany or #ManyToMany etc. What am I doing wrong? Thanks for your help.
p.s. I want it to generate mapping like
Users class with field of UserGroup type
If the classes were auto generated like this check your relation in the database between the two tables and make sure you choose the right schema your mapping is completely wrong the for example :-
1-the auto generated classes your mapping are missing some columns, class User doesn't contain password and login columns and class UsersGroups doesn't contain name column.
2- class User doesn't have #table annotation
They should look something like this :-
Class UserGroups
#Entity
#Table(name = "USERS_GROUPS", schema = "PUBLIC", catalog = "DATABASE")
public class UsersGroups implements java.io.Serializable {
private int userGroupId;
private String name;
private Set<Users> users = new HashSet<Users>(0);
public UsersGroups() {
}
#Id
#GeneratedValue(strategy = IDENTITY) //this to make the id auto increment
#Column(name = "user_group_id", nullable = false)
public int getUserGroupId() {
return userGroupId;
}
public void setUserGroupId(int userGroupId) {
this.userGroupId = userGroupId;
}
// if name column is not unique / nullable remove values from annotation
#Column(name = "name", unique = true, nullable = false, length = 10)
public String getName() {
return this.name;
}
public void setName(String name) {
this.name= name;
}
#OneToMany(fetch = FetchType.EAGER, mappedBy = "users_groups")
public Set<Users> getUsers() {
return this.users;
}
public void setUsers(Set<Users> users) {
this.users= users;
}
}
Class Users
#Entity
#Table(name = "users", schema ="PUBLIC" , catalog ="DATABASE")
public class Users implements java.io.Serializable {
private Integer userId;
private UsersGroups usersGroups;
private String password;
private String login;
public Users() {
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "user_id", unique = true, nullable = false)
public Integer getUserId() {
return this.userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_group_id", nullable = false)
public UsersGroups getUsersGroups() {
return this.usersGroups;
}
public void setUsersGroups(UsersGroups usersGroups) {
this.usersGroups = usersGroups;
}
#Column(name = "password",length = 10)
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
#Column(name = "login",length = 10)
public String getLogin() {
return this.login;
}
public void setLogin(String login) {
this.login = login;
}
}
Check this full example for one to many mapping
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.