I'm getting this error when I try to save my user entity to the database
org.hibernate.PersistentObjectException: detached entity passed to persist: kpi.diploma.ovcharenko.entity.user.AppUser
Some more information where where does this error appear
at kpi.diploma.ovcharenko.service.user.LibraryUserService.createPasswordResetTokenForUser(LibraryUserService.java:165) ~[classes/:na]
at kpi.diploma.ovcharenko.service.user.LibraryUserService$$FastClassBySpringCGLIB$$ca63bf4b.invoke(<generated>) ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.10.jar:5.3.10]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.3.10.jar:5.3.10]
at kpi.diploma.ovcharenko.service.user.LibraryUserService$$EnhancerBySpringCGLIB$$6160822e.createVerificationTokenForUser(<generated>) ~[classes/:na]
at kpi.diploma.ovcharenko.service.activation.RegistrationListener.confirmRegistration(RegistrationListener.java:39) ~[classes/:na]
at kpi.diploma.ovcharenko.service.activation.RegistrationListener.onApplicationEvent(RegistrationListener.java:32) ~[classes/:na]
at kpi.diploma.ovcharenko.service.activation.RegistrationListener.onApplicationEvent(RegistrationListener.java:16) ~[classes/:na]
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176) ~[spring-context-5.3.10.jar:5.3.10]
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169) ~[spring-context-5.3.10.jar:5.3.10]
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143) ~[spring-context-5.3.10.jar:5.3.10]
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:421) ~[spring-context-5.3.10.jar:5.3.10]
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:378) ~[spring-context-5.3.10.jar:5.3.10]
at kpi.diploma.ovcharenko.controller.UserController.registerUserAccount(UserController.java:96) ~[classes/:na]
As you can see error appeared in this method. In this method I create a verification token for user
#Override
#Transactional
public void createVerificationTokenForUser(final AppUser user, final String token) {
final VerificationToken myToken = new VerificationToken(token, user);
verificationTokenRepository.save(myToken);
}
I call this method in my RegistrationListener
private void confirmRegistration(OnRegistrationCompleteEvent event) {
AppUser user = event.getUser();
log.info(user.toString());
String token = UUID.randomUUID().toString();
userService.createVerificationTokenForUser(user, token);
final SimpleMailMessage email = constructEmailMessage(event, user, token);
mailSender.send(email);
}
And how I call my confirmRegistration method in the RegistrationListener
#Override
public void onApplicationEvent(OnRegistrationCompleteEvent event) {
this.confirmRegistration(event);
}
And this RegistrationListener I used in my Controller like this
#PostMapping("/registration")
public String registerUserAccount(#ModelAttribute("user") #Valid UserModel userModel, BindingResult result, HttpServletRequest request) {
AppUser existing = userService.findByEmail(userModel.getEmail());
if (existing != null) {
result.rejectValue("email", null, "There is already an account registered with that email");
}
if (result.hasErrors()) {
return "registration";
}
AppUser registeredUser = userService.save(userModel);
log.info(registeredUser.toString());
String appUrl = request.getContextPath();
eventPublisher.publishEvent(new OnRegistrationCompleteEvent(registeredUser, request.getLocale(), appUrl));
return "redirect:/regSuccessfully";
}
As you can see I have log.info() in the controller and in the listener, its because I thought that the problem can be because something wrong with user ID, but, when my logs showed that with user and user id is everything is ok
2022-05-16 21:33:27.522 INFO 65143 --- [nio-8080-exec-7] k.d.o.controller.UserController : AppUser{id=42, firstName='ovcharenko.messor#gmail.com', lastName='ovcharenko.messor#gmail.com', email='ovcharenko.messor#gmail.com', telephoneNumber='', password='$2a$10$4ao20SYR2QJsQ.Fj50Jek.ZBRG0R0g9N8t3iaksUx2.byIb0fj6Y6', registrationDate=2022-05-16 21:33:27.492, enabled=false, roles=[kpi.diploma.ovcharenko.entity.user.UserRole#bbe3026f], bookCards=[]}
2022-05-16 21:33:27.523 INFO 65143 --- [nio-8080-exec-7] k.d.o.s.activation.RegistrationListener : AppUser{id=42, firstName='ovcharenko.messor#gmail.com', lastName='ovcharenko.messor#gmail.com', email='ovcharenko.messor#gmail.com', telephoneNumber='', password='$2a$10$4ao20SYR2QJsQ.Fj50Jek.ZBRG0R0g9N8t3iaksUx2.byIb0fj6Y6', registrationDate=2022-05-16 21:33:27.492, enabled=false, roles=[kpi.diploma.ovcharenko.entity.user.UserRole#bbe3026f], bookCards=[]}
And another point is that after I try to register user, i am getting the error as above, but my user is successfully saved into the database
[
Here is my AppUser class and VerificationToken class
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(uniqueConstraints = #UniqueConstraint(columnNames = "email"), name = "library_user")
public class AppUser {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotEmpty
#Column(name = "first_name")
private String firstName;
#NotEmpty
#Column(name = "last_name")
private String lastName;
#NotEmpty
#Email
#Column(name = "email")
private String email;
#Column(name = "number")
private String telephoneNumber;
#NotEmpty
#Column(name = "password")
private String password;
#CreationTimestamp
#Column(name = "create_time")
private Timestamp registrationDate;
#Column(name = "enabled")
private boolean enabled;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(
name = "user_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private Collection<UserRole> roles = new HashSet<>();
#EqualsAndHashCode.Exclude
#OnDelete(action = OnDeleteAction.CASCADE)
#OneToMany(mappedBy = "book", fetch = FetchType.EAGER)
private Set<BookCard> bookCards = new HashSet<>();
public void addBookCard(BookCard bookCard) {
bookCards.add(bookCard);
bookCard.setUser(this);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AppUser user = (AppUser) o;
return Objects.equals(id, user.id) &&
Objects.equals(firstName, user.firstName) &&
Objects.equals(lastName, user.lastName) &&
Objects.equals(email, user.email) &&
Objects.equals(password, user.password);
}
#Override
public int hashCode() {
return Objects.hash(id, firstName, lastName, email, password);
}
public void setRoles(Collection<UserRole> roles) {
this.roles = roles;
}
#Override
public String toString() {
return "AppUser{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", telephoneNumber='" + telephoneNumber + '\'' +
", password='" + password + '\'' +
", registrationDate=" + registrationDate +
", enabled=" + enabled +
", roles=" + roles +
", bookCards=" + bookCards +
'}';
}
}
#Getter
#Setter
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Table(name = "verification_token")
public class VerificationToken {
private static final int EXPIRATION = 60 * 24;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String token;
#OneToOne
#JoinColumn(nullable = false, name = "user_id", foreignKey = #ForeignKey(name = "FK_VERIFY_USER"))
private AppUser user;
private Date expiryDate;
public VerificationToken(final String token, final AppUser user) {
super();
this.token = token;
this.user = user;
this.expiryDate = calculateExpiryDate();
}
public Long getId() {
return id;
}
public String getToken() {
return token;
}
public void setToken(final String token) {
this.token = token;
}
public AppUser getUser() {
return user;
}
public void setUser(final AppUser user) {
this.user = user;
}
public Date getExpiryDate() {
return expiryDate;
}
public void setExpiryDate(final Date expiryDate) {
this.expiryDate = expiryDate;
}
private Date calculateExpiryDate() {
final Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(new Date().getTime());
cal.add(Calendar.MINUTE, VerificationToken.EXPIRATION);
return new Date(cal.getTime().getTime());
}
public void updateToken(final String token) {
this.token = token;
this.expiryDate = calculateExpiryDate();
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
VerificationToken that = (VerificationToken) o;
return Objects.equals(token, that.token) && Objects.equals(user, that.user) && Objects.equals(expiryDate, that.expiryDate);
}
#Override
public int hashCode() {
return Objects.hash(token, user, expiryDate);
}
}
I tried different variants with of cascade type , for example (cascade = CascadeType.ALL, cascade = CascadeType.MERGE). Also as you can see in VerificationToken class, I tried to delete any type of cascade, but it doesn't help me. I don't have any idea how can I solve this problem
You probably load the user within a transaction and then create and publish an event on which you set the user. After the transactional method where you created and published the event has ended, the transaction ends as well and the AppUser entity becomes detached from the persistence context. In order to use it further like an entity that you have just obtained from a repository or the EntityManager, you need to "reattach" it to the context. You can read more about the lifecycle of a Hibernate entity in the Hibernate docs or in this Baldung article.
Alternatively, you could load the AppUser again within the same transaction where you want to persist the VerificationToken. You could for example change your method to:
private final AppUserRepository appUserRepository;
#Override
#Transactional
public void createVerificationTokenForUser(final String userId, final String token) {
final AppUser user = appUserRepository.findById(userId).orElseThrow();
final VerificationToken myToken = new VerificationToken(token, user);
verificationTokenRepository.save(myToken);
}
Related
I can't insert the id of the currently logged-in user while making the patient data.
I want the user to be able to add his own patients, but the problem is that when I add a new patient, the column id_user is null
I tried lots of ways but couldn't add id_user to the patient.
What do I miss?
This is my User Entity:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int idUser;
private String firstName;
private String lastName;
private String username;
...
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
#JoinTable(name = "users_roles",
joinColumns = #JoinColumn(name = "id_user"),
inverseJoinColumns = #JoinColumn(name = "id_role"))
#JsonManagedReference
private Set<Role> roles = new HashSet<>();
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<Patient> patients = new ArrayList<>();
public void add(Patient patient) {
if (patient != null) {
if (patients == null) {
patients = new ArrayList<>();
}
patients.add(patient);
patient.setUser(this);
}
}
Patient Entity:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_patient")
private int idPatient;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
...
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private User user;
}
Controller
#RequestMapping("/addPatient")
public String addPatient(Model theModel, HttpServletRequest request) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
UserDetails userD = (UserDetails) auth.getPrincipal();
User u = userService.findByUsername(userD.getUsername());
request.getSession().setAttribute("id_user", u.getIdUser());
// int userId = user.getIdUser();
int userId = (int) request.getSession().getAttribute("id_user");
User user = new User();
user.setIdUser(userId);
Patient patient = new Patient();
patient.setUser(user);
theModel.addAttribute("patient", patient);
return "user/patients/add-patient-dashboard";
}
#PostMapping("savePatient")
public String savePatient(#ModelAttribute("patient") Patient thePatient, Model model) {
patientService.save(thePatient);
return "redirect:/user/allPatients";
}
I try editing the User service from:
#Override
public void save(User user) {
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
userRepository.save(user);
}
to:
#Override
public void save(User user) {
List<Patient> patients = user.getPatients();
patients.forEach(patient -> user.add(patient));
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
userRepository.save(user);
}
if someone can help me resolve this I would appreciate it since I'm struggling for a very long time with this
Thanks to M Denium I finally solve the issue. I have moved the whole code from addUser to saveUser method:
#RequestMapping("/addPatient")
public String addPatient(Model theModel) {
Patient patient = new Patient();
theModel.addAttribute("patient", patient);
return "user/patients/add-patient-dashboard";
}
#PostMapping("savePatient")
public String savePatient(#ModelAttribute("patient") Patient thePatient, Model model, HttpServletRequest request) {
patientService.save(thePatient);
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
UserDetails userD = (UserDetails) auth.getPrincipal();
User u = userService.findByUsername(userD.getUsername());
request.getSession().setAttribute("id_user", u.getIdUser());
int userId = (int) request.getSession().getAttribute("id_user");
User user = userService.findById(userId);
thePatient.setUser(user);
patientService.save(thePatient);
return "redirect:/user/allPatients";
}
I am taking this question Perform multi column search on Date, Integer and String Data type fields of Single Table? and This method must return a result of type Specification<Employee> in Java 8 further ahead.
Actually I wanted to search within association entity as well as a part of global search. Will that be possible using JPA 2 Specifications API ?
I've Employee and Department #OneToMany bi-directional relationship.
Employee.java
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "EMAIL_ID")
private String email;
#Column(name = "STATUS")
private String status;
#Column(name = "BIRTH_DATE")
private LocalDate birthDate;
#Column(name = "PROJECT_ASSOCIATION")
private Integer projectAssociation;
#Column(name = "GOAL_COUNT")
private Integer goalCnt;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DEPT_ID", nullable = false)
#JsonIgnore
private Department department;
}
Department.java
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "DEPT_ID")
private Long departmentId;
#Column(name = "DEPT_NAME")
private String departmentName;
#Column(name = "DEPT_CODE")
private String departmentCode;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "department")
#JsonIgnore
private Set<Employee> employees;
}
and I saved Data like below.
MyPaginationApplication.java
#SpringBootApplication
public class MyPaginationApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(MyPaginationApplication.class, args);
}
#Autowired
private EmployeeRepository employeeRepository;
#Autowired
private DepartmentRepository departmentRepository;
#Override
public void run(String... args) throws Exception {
saveData();
}
private void saveData() {
Department department1 = Department.builder()
.departmentCode("AD")
.departmentName("Boot Depart")
.build();
departmentRepository.save(department1);
Employee employee = Employee.builder().firstName("John").lastName("Doe").email("john.doe#gmail.com")
.birthDate(LocalDate.now())
.goalCnt(1)
.projectAssociation(2)
.department(department1)
.build();
Employee employee2 = Employee.builder().firstName("Neha").lastName("Narkhede").email("neha.narkhede#gmail.com")
.birthDate(LocalDate.now())
.projectAssociation(4)
.department(department1)
.goalCnt(2)
.build();
Employee employee3 = Employee.builder().firstName("John").lastName("Kerr").email("john.kerr#gmail.com")
.birthDate(LocalDate.now())
.projectAssociation(5)
.department(department1)
.goalCnt(4)
.build();
employeeRepository.saveAll(Arrays.asList(employee, employee2, employee3));
}
}
EmployeeController.java
#GetMapping("/employees/{searchValue}")
public ResponseEntity<List<Employee>> findEmployees(#PathVariable("searchValue") String searchValue) {
List<Employee> employees = employeeService.searchGlobally(searchValue);
return new ResponseEntity<>(employees, HttpStatus.OK);
}
EmployeeSpecification.java
public class EmployeeSpecification {
public static Specification<Employee> textInAllColumns(Object value) {
return (root, query, builder) -> builder.or(root.getModel().getDeclaredSingularAttributes().stream()
.filter(attr -> attr.getJavaType().equals(value.getClass()))
.map(attr -> map(value, root, builder, attr))
.toArray(Predicate[]::new));
}
private static Object map(Object value, Root<?> root, CriteriaBuilder builder, SingularAttribute<?, ?> a) {
switch (value.getClass().getSimpleName()) {
case "String":
return builder.like(root.get(a.getName()), getString((String) value));
case "Integer":
return builder.equal(root.get(a.getName()), value);
case "LocalDate":
return builder.equal(root.get(a.getName()), value);//date mapping
default:
return null;
}
}
private static String getString(String text) {
if (!text.contains("%")) {
text = "%" + text + "%";
}
return text;
}
}
When I hit the /employees/{searchValue}, I want searching to be happened in Department Table along with Employee table (may be using Joins something like that). Is that possible ? If yes, how can we do that ?
Or:
Will this be good approach to put like here? Got reference from Using #Query
#Query("SELECT t FROM Todo t WHERE " +
"LOWER(t.title) LIKE LOWER(CONCAT('%',:searchTerm, '%')) OR " +
"LOWER(t.description) LIKE LOWER(CONCAT('%',:searchTerm, '%'))")
List<Todo> findBySearchTerm(#Param("searchTerm") String searchTerm);
Any pointers?
If you take a look at my post actually I have a solution for join
#Override
public Specification<User> getFilter(UserListRequest request) {
return (root, query, cb) -> {
query.distinct(true); //Important because of the join in the addressAttribute specifications
return where(
where(firstNameContains(request.search))
.or(lastNameContains(request.search))
.or(emailContains(request.search))
)
.and(streetContains(request.street))
.and(cityContains(request.city))
.toPredicate(root, query, cb);
};
}
private Specification<User> firstNameContains(String firstName) {
return userAttributeContains("firstName", firstName);
}
private Specification<User> lastNameContains(String lastName) {
return userAttributeContains("lastName", lastName);
}
private Specification<User> emailContains(String email) {
return userAttributeContains("email", email);
}
private Specification<User> userAttributeContains(String attribute, String value) {
return (root, query, cb) -> {
if(value == null) {
return null;
}
return cb.like(
cb.lower(root.get(attribute)),
containsLowerCase(value)
);
};
}
private Specification<User> cityContains(String city) {
return addressAttributeContains("city", city);
}
private Specification<User> streetContains(String street) {
return addressAttributeContains("street", street);
}
private Specification<User> addressAttributeContains(String attribute, String value) {
return (root, query, cb) -> {
if(value == null) {
return null;
}
ListJoin<User, Address> addresses = root.joinList("addresses", JoinType.INNER);
return cb.like(
cb.lower(addresses.get(attribute)),
containsLowerCase(value)
);
};
}
private String containsLowerCase(String searchField) {
return "%" + searchField.toLowerCase() + "%";
}
Here you can see how I search the users by their address columns (city and street).
EDIT: Also you cannot use the #Query annotation that much dinamically (you van insert parameter values dinamically, but not parameters. That's where Specificaion is handy)
EDIT2: I know this is not the 2.x.x Spring version, but 1.5.x, but the idea is the same for joins.
I am creating a user registration flow with spring boot (2.1.3.RELEASE) . With the help of few articles I am able to successfully add a user along with its roles and user is able to login into the system. The problem is when user is successfully loged-in, the authentication obect has empty role even when I can see th correct role mapping in mysql database (honestly I am not able to get exactly how roles are fetched from database when findByUserName method is called.
Below is my code:
Entity objects
1. User.java
public class User implements UserDetails {
private static final long serialVersionUID = 1L;
public User() {
//Verification flow 2. set enabled = false
this.enabled = false;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_unique_number")
private long id;
#UniqueUser(groups = UniqueUserOrder.class)
#Column(name = "username", length = 60,nullable = false, unique = true)
private String username;
#Column(name = "email", nullable = false, unique = true)
private String email;
#Column(name = "password", nullable = false)
private String password;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<UserRole> userRoles = new HashSet<>();
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> authorities = new HashSet<>();
userRoles.forEach(ur -> authorities.add(new Authority(ur.getRole().getName())));
return authorities;
}
public Set<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(Set<UserRole> userRoles) {
this.userRoles = userRoles;
}
...//OTHER GETTERS AND SETTERS
}
Roles.java
public class Role
{#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
private String name;
#OneToMany(mappedBy = "role", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<UserRole> userRoles = new HashSet<>();
public Role() {
}
public Role(RolesEnum rolesEnum) {
this.id = rolesEnum.getId();
this.name = rolesEnum.getRoleName();
}
public Set<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(Set<UserRole> userRoles) {
this.userRoles = userRoles;
}
...//OTHER GETTERS AND SETTERS }
UserRole.java
public class UserRole {
public UserRole() {}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
public UserRole(User user, Role role) {
this.user = user;
this.role = role;
}
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "role_id")
private Role role;
...//OTHER GETTERS AND SETTERS
}
4.RolesEnum.java
public enum RolesEnum {
ADMIN(1, "ROLE_ADMIN"),
BASIC(2, "ROLE_BASIC");
private final int id;
private final String roleName;
...//OTHER GETTERS AND SETTERS
}
New user is getting created as below:
...
...
String encryptedPassword = passwordEncoder.encode(adminPassword);
user.setPassword(encryptedPassword);
user.setUsername(adminUsername);
user.setEmail(adminEmail);
user.setUserCreateTime(LocalDateTime.now());
Set<UserRole> userRoles = new HashSet<>();
UserRole userRole = new UserRole();
userRole.setUser(user);
userRole.setRole(new Role(RolesEnum.ADMIN));
userRoles.add(userRole);
user.getUserRoles().addAll(userRoles);
user.setAccountNonLocked(true);
user.setEnabled(true);
user.setAccountNonExpired(true);
user.setCredentialsNonExpired(true);
user = userRepository.save(user);
...
...
At this point user is added successfully along with the roles in database
User is also able to successfully log-in but the problem is after logging, authentication object has an empty list of roles
Below is the code which is failing
public class SecurityConfig extends WebSecurityConfigurerAdapter {
......
......
private AuthenticationSuccessHandler loginSuccessHandler() {
return (request, response, **authentication**) -> {
Collection<? extends GrantedAuthority> authorities
= authentication.getAuthorities();
for (GrantedAuthority grantedAuthority : authorities) {
if (grantedAuthority.getAuthority().equals("ROLE_ADMIN")) {
isAdmin = true;
break;
} else if (grantedAuthority.getAuthority().equals("ROLE_ADMIN")) {
isBasic = true;
break;
}
}
if (isAdmin) { return "/admin/"; } else if (isBasic) { return
"/profile.html"; } else { throw new IllegalStateException(); }
response.sendRedirect("/");
};
}
......
......
When i inspect authentication object using eclipse the roles are not getting fetched from database
Here is the code for fetching user#Override
public User findByUserName(String username) {
return userRepository.findByUsername(username);
}
Do I need to add some additional logic to fetch roles along with user or Spring handles it behind the scene? Please let me know what I am doing wrong here... Thanks
So all I'm trying to do is allow users to delete their own comments and posts in my application, and I have a form that's supposed to run a controller method that should delete it, but it's not working.
I'll show you my controller and repository to show you guys what I'm trying to do.
So here's my Controller method
#RequestMapping(value="userEdits/editComment/{commentId}/deleteComment", method=RequestMethod.POST)
public String deleteComment (#PathVariable Long commentId, #AuthenticationPrincipal User user)
{
Comment comment = commentRepo.findOne(commentId);
User savedUser = userRepo.findUserByUsername(user.getUsername());
savedUser.getCourses().remove(comment);
commentRepo.delete(comment);
return "redirect:/userEdits";
}
And I can even run this in debug mode and see that the right comment is in the commentRepo.delete(comment); line. And it runs all the through and returns the userEdits screen, just like it should, without any errors, but the comment is still there after it runs through everything.
Here's my repository class, it's pretty simple, but who knows, I could be missing something.
public interface CommentRepository extends PagingAndSortingRepository <Comment, Long>{
public Page<Comment> findByPostOrderByIdDesc(Post post, Pageable pageable);
public List<Comment> findByUserOrderByIdDesc(User user);
}
I'm confused because this should be a simple task and it appears that it's running through and returning the view I tell it to, without error.
So if anyone can see where I'm going wrong that would be great. Thanks in advance.
UPDATE
User Entity
#Entity
#Table(name = "users")
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id")
public class User {
private Long id;
#ValidEmail
#NotNull
#NotEmpty
private String email;
private String username;
private String password;
private University university;
private Set<Authorities> authorities = new HashSet<>();
private Set<Course> courses = new HashSet<>();
private Set<Post> posts = new HashSet<>();
private Set<Comment> comments = new HashSet<>();
private Set<StudySet> studySet = new HashSet<>();
private Set<Course> myCourses = new HashSet<Course>();
public User() {
}
public User(User user) {
this.id = user.getId();
this.email = user.getEmail();
this.username = user.getUsername();
this.password = user.getPassword();
this.university = user.getUniversity();
this.authorities = user.getAuthorities();
this.courses = user.getCourses();
this.posts = user.getPosts();
this.comments = user.getComments();
this.studySet = user.getStudySet();
this.myCourses = user.getMyCourses();
}
#Id
#GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "user", orphanRemoval = true)
public Set<Course> getCourses() {
return courses;
}
public void setCourses(Set<Course> courses) {
this.courses = courses;
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "user", orphanRemoval = true)
public Set<Post> getPosts() {
return posts;
}
public void setPosts(Set<Post> posts) {
this.posts = posts;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#ManyToOne
public University getUniversity() {
return university;
}
public void setUniversity(University university) {
this.university = university;
}
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user")
#JsonManagedReference
#JsonIgnoreProperties(allowGetters = true, value = "user")
public Set<Comment> getComments() {
return comments;
}
public void setComments(Set<Comment> comments) {
this.comments = comments;
}
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user")
public Set<Authorities> getAuthorities() {
return authorities;
}
public void setAuthorities(Set<Authorities> authorities) {
this.authorities = authorities;
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "user", orphanRemoval = true)
public Set<StudySet> getStudySet() {
return studySet;
}
public void setStudySet(Set<StudySet> studySet) {
this.studySet = studySet;
}
#ManyToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER)
#JoinTable(name = "user_myCourses")
public Set<Course> getMyCourses() {
return myCourses;
}
public void setMyCourses(Set<Course> myCourses) {
this.myCourses = myCourses;
}
}
Comment Entity
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id")
public class Comment {
public Long id;
#Size(min = 1, max = 140)
public String comment;
public Post post;
public User user;
#Temporal(TemporalType.DATE)
#DateTimeFormat(pattern = "dd-MMM-YYYY")
private LocalDate date;
#Temporal(TemporalType.TIME)
#DateTimeFormat(pattern = "HH:mm:ss")
private LocalTime time;
#Temporal(TemporalType.DATE)
#DateTimeFormat(pattern = "MM/dd/yyyy HH:mm:ss")
private LocalDateTime dateTime;
public Comment() {
}
#Id
#GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Size(min = 1, max = 140)
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
#ManyToOne
public Post getPost() {
return post;
}
public void setPost(Post post) {
this.post = post;
}
#ManyToOne
#JsonBackReference
#JsonIgnoreProperties(value = { "comments" }, allowGetters = true)
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public LocalDate getDate() {
return date;
}
public void setDate(LocalDate date) {
this.date = date;
}
public LocalTime getTime() {
return time;
}
public void setTime(LocalTime time) {
this.time = time;
}
public LocalDateTime getDateTime() {
return dateTime;
}
public void setDateTime(LocalDateTime dateTime) {
this.dateTime = dateTime;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Comment other = (Comment) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
public Comment(Long id, String comment, Post post, User user, LocalDate date, LocalDateTime dateTime) {
this.id = id;
this.comment = comment;
this.post = post;
this.user = user;
this.date = date;
this.dateTime = dateTime;
}
}
UPDATE
So I realized I needed to addorphanRemoval = true to the comment of the user, now I get the error the entity must not be null when I run the controller method, however it does delete the comment. But I need my app to run the method and return the view I ask it to, without the error message popping up.
The problem is in your CascadeType. You've specified:
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "user", orphanRemoval = true)
CascadeType.ALL means that comments are managed by user entity and you can't delete comment directly. You should read about cascade types and change it for your needs or you have orphanRemoval = true so you can simply save user after deletion and comment should be deleted for example
savedUser.getCourses().remove(comment);
userRepo.save(savedUser);
I am new to Spring Data Jpa and able to get the result using proper function names on a single table. But now I am facing problem to get the result based on join. I have two tables Invoice Table (columns : accountNumber, courierId), Account Table (columns : number, clinetId). Now I need to join these two tables and get Invoice results based on courierId and clientId. So in the repository I have formed the query as shown below:
#Query("select Invoice from Invoice i left join Account a on i.accountNumber = a.number where i.courierId=?1 and a.clientId=?2")
List<Invoice> findByCourierIdAndClientId(Long courierId, Long clientId);
But I am getting the following error in my debugging log:
[ERROR] org.hibernate.hql.internal.ast.ErrorCounter - Path expected for join!
[ERROR] org.hibernate.hql.internal.ast.ErrorCounter - Path expected for join!
antlr.SemanticException: Path expected for join!
[ERROR] org.hibernate.hql.internal.ast.ErrorCounter - Invalid path: 'a.clientId'
[ERROR] org.hibernate.hql.internal.ast.ErrorCounter - Invalid path: 'a.clientId'
org.hibernate.hql.internal.ast.InvalidPathException: Invalid path: 'a.clientId'
[ERROR] org.hibernate.hql.internal.ast.ErrorCounter - left-hand operand of a binary operator was null
[ERROR] org.hibernate.hql.internal.ast.ErrorCounter - left-hand operand of a binary operator was null
antlr.SemanticException: left-hand operand of a binary operator was null
In Account Table I have client_id field in mysql table and in Account.java, I have
#ManyToOne
private Client client;
public Client getClient() {
return client;
}
public void setClient(Client client) {
this.client = client;
}
if I use the suggested solution,
#Query("SELECT i from Invoice i WHERE i.courierId =?1 AND i.clientId =?2")
List<Invoice> findByCourierIdAndClientId(Long courierId, Long clientId);
I am getting the following error:
java.lang.IllegalArgumentException: org.hibernate.QueryException: could not resolve property: clientId of: com.trace.domain.Invoice [SELECT i from com.trace.domain.Invoice i WHERE i.courierId =?1 AND i.clientId =?2]
The following are my mappings:
In Account.java,
#OneToMany(mappedBy = "account")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Invoice> invoices = new HashSet<>();
And in Invoice.java,
#ManyToOne
private Account account;
Is there any changes I need to make in these mappings. Moreover in the solution
#Query("SELECT i from Invoice i WHERE i.courierId =?1 AND i.clientId =?2")
I don't follow how invoice table is gets only the joined details with Account, moreover there is no i.clientId in Invoice Table. clientId is present in Account Table only.
The following is my Invoice.java
/**
* A Invoice.
*/
#Entity
#Table(name = "invoice")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Invoice implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "account_number")
private String accountNumber;
#Column(name = "invoice_number")
private String invoiceNumber;
#Column(name = "invoice_amount")
private Double invoiceAmount;
#Column(name = "status")
private String status;
#Column(name = "edi_number")
private String ediNumber;
#Column(name = "bill_date")
private Date billDate;
#Column(name = "courier_id")
private Long courierId;
// #JoinColumn(name="owner_id", nullable=false)
#ManyToOne
private Account account;
#OneToMany(mappedBy = "invoice")
//#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<InvoiceDetails> invoiceDetailss = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public String getInvoiceNumber() {
return invoiceNumber;
}
public void setInvoiceNumber(String invoiceNumber) {
this.invoiceNumber = invoiceNumber;
}
public Double getInvoiceAmount() {
return invoiceAmount;
}
public void setInvoiceAmount(Double invoiceAmount) {
this.invoiceAmount = invoiceAmount;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getEdiNumber() {
return ediNumber;
}
public void setEdiNumber(String ediNumber) {
this.ediNumber = ediNumber;
}
public Date getBillDate() {
return billDate;
}
public void setBillDate(Date billDate) {
this.billDate = billDate;
}
public Long getCourierId() {
return courierId;
}
public void setCourierId(Long courierId) {
this.courierId = courierId;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public Set<InvoiceDetails> getInvoiceDetailss() {
return invoiceDetailss;
}
public void setInvoiceDetailss(Set<InvoiceDetails> invoiceDetailss) {
this.invoiceDetailss = invoiceDetailss;
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Invoice invoice = (Invoice) o;
if ( ! Objects.equals(id, invoice.id)) return false;
return true;
}
#Override
public int hashCode() {
return Objects.hashCode(id);
}
#Override
public String toString() {
return "Invoice{" +
"id=" + id +
", accountNumber='" + accountNumber + "'" +
", invoiceNumber='" + invoiceNumber + "'" +
", invoiceAmount='" + invoiceAmount + "'" +
", ediNumber='" + ediNumber + "'" +
", status='" + status + "'" +
", billDate='" + billDate + "'" +
", courierId='" + courierId + "'" +
'}';
}
}
And the following is my Account.java,
/**
* Account.
*/
#Entity
#Table(name = "account")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Account implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
#Column(name = "number", nullable = false)
private String number;
#NotNull
#Column(name = "name")
private String name;
#Column(name = "currency_code")
private String currencyCode;
#Column(name = "edi_type")
private String ediType;
#Column(name = "is_fedex_express_gsr")
private Boolean isFedexExpressGsr;
#Column(name = "is_fedex_ground_gsr")
private Boolean isFedexGroundGsr;
#Column(name = "is_ups_gsr")
private Boolean isUpsGsr;
#Column(name = "electronic_voiding")
private Boolean electronicVoiding;
#Column(name = "activate_signature_service")
private Boolean activateSignatureService;
#Column(name = "reject_invoices")
private Boolean rejectInvoices;
#Column(name = "notify_client_services")
private Boolean notifyClientServices;
#Column(name = "is_active")
private Boolean isActive;
#Column(name = "address")
private String address;
#Column(name = "city")
private String city;
#Column(name = "state")
private String state;
#Column(name = "postal_code")
private String postalCode;
#OneToMany(mappedBy = "account")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Invoice> invoices = new HashSet<>();
#ManyToOne
private Courier courier;
#ManyToOne
private Client client;
#ManyToMany
#JoinTable(
name = "account_group_members",
joinColumns = {#JoinColumn(name = "account_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id")})
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<AccountGroup> accountGroups = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCurrencyCode() {
return currencyCode;
}
public void setCurrencyCode(String currencyCode) {
this.currencyCode = currencyCode;
}
public String getEdiType() {
return ediType;
}
public void setEdiType(String ediType) {
this.ediType = ediType;
}
public Boolean getIsFedexExpressGsr() {
return isFedexExpressGsr;
}
public void setIsFedexExpressGsr(Boolean isFedexExpressGsr) {
this.isFedexExpressGsr = isFedexExpressGsr;
}
public Boolean getIsFedexGroundGsr() {
return isFedexGroundGsr;
}
public void setIsFedexGroundGsr(Boolean isFedexGroundGsr) {
this.isFedexGroundGsr = isFedexGroundGsr;
}
public Boolean getIsUpsGsr() {
return isUpsGsr;
}
public void setIsUpsGsr(Boolean isUpsGsr) {
this.isUpsGsr = isUpsGsr;
}
public Boolean getElectronicVoiding() {
return electronicVoiding;
}
public void setElectronicVoiding(Boolean electronicVoiding) {
this.electronicVoiding = electronicVoiding;
}
public Boolean getActivateSignatureService() {
return activateSignatureService;
}
public void setActivateSignatureService(Boolean activateSignatureService) {
this.activateSignatureService = activateSignatureService;
}
public Boolean getRejectInvoices() {
return rejectInvoices;
}
public void setRejectInvoices(Boolean rejectInvoices) {
this.rejectInvoices = rejectInvoices;
}
public Boolean getNotifyClientServices() {
return notifyClientServices;
}
public void setNotifyClientServices(Boolean notifyClientServices) {
this.notifyClientServices = notifyClientServices;
}
public Boolean getIsActive() {
return isActive;
}
public void setIsActive(Boolean isActive) {
this.isActive = isActive;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
public Courier getCourier() {
return courier;
}
public void setCourier(Courier courier) {
this.courier = courier;
}
public Client getClient() {
return client;
}
public void setClient(Client client) {
this.client = client;
}
public Set<Invoice> getInvoices() {
return invoices;
}
public void setInvoices(Set<Invoice> invoices) {
this.invoices = invoices;
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Account shipper = (Account) o;
if (!Objects.equals(id, shipper.id))
return false;
return true;
}
#Override
public int hashCode() {
return Objects.hashCode(id);
}
#Override
public String toString() {
return "Shipper{" + "id=" + id + ", number='" + number + "'" + ", name='" + name + "'" + ", currencyCode='"
+ currencyCode + "'" + ", ediType='" + ediType + "'" + ", isFedexExpressGsr='" + isFedexExpressGsr + "'"
+ ", isFedexGroundGsr='" + isFedexGroundGsr + "'" + ", isUpsGsr='" + isUpsGsr + "'"
+ ", electronicVoiding='" + electronicVoiding + "'" + ", activateSignatureService='"
+ activateSignatureService + "'" + ", rejectInvoices='" + rejectInvoices + "'"
+ ", notifyClientServices='" + notifyClientServices + "'" + ", isActive='" + isActive + "'"
+ ", address='" + address + "'" + ", city='" + city + "'" + ", state='" + state + "'" + ", postalCode='"
+ postalCode + "'" + '}';
}
}
After making the following entry in InvoiceRepository.java,
#Query("from Invoice i " + " where i.courierId = :courierId " + " and i.account.client.id = :clientId ")
List<Invoice> findByCourierIdAndClientId(#Param("courierId") Long courierId, #Param("clientId") Long clientId);
I am not getting any error but I am getting any result set from mysql database. My debugging log is as follows:
[DEBUG] com.sample.aop.logging.LoggingAspect - Enter: com.sample.web.rest.InvoiceResource.getInvoicesByCourierIdAndClientId() with argument[s] = [1, 1]
[DEBUG] com.sample.web.rest.InvoiceResource - REST request to get Invoices By Courier Id and Client Id 1 1
[DEBUG] com.sample.aop.logging.LoggingAspect - Enter: com.sample.service.InvoiceService.findByCourierIdAndClientId() with argument[s] = [1, 1]
[DEBUG] com.sample.aop.logging.LoggingAspect - Exit: com.sample.service.InvoiceService.findByCourierIdAndClientId() with result = []
[DEBUG] com.sample.aop.logging.LoggingAspect - Exit: com.sample.web.rest.InvoiceResource.getInvoicesByCourierIdAndClientId() with result = []
In InvoiceResource.java, I have the following mapping:
// Get Invoices By Courier Id and CustomerId
#RequestMapping(value = "/invoices/byCourierAndClient", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#Timed
public List<Invoice> getInvoicesByCourierIdAndClientId(#RequestParam(value = "courierId") Long courierId,
#RequestParam(value = "clientId") Long clientId) {
log.debug("REST request to get Invoices By Courier Id and Client Id " + courierId + " " + clientId);
return invoiceService.findByCourierIdAndClientId(courierId, clientId);
}
The Id field in Client.java is as follows:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
The following query I have used to verify my data manually on mysql database tables which is returning result set containing on record.
select * from invoice i, account a where i.account_number = a.number
and i.courier_id = 1
and a.client_id = 1
A brief glance on your code seems suggested that you already have proper relationship mapping between Invoice and Account. In JPQL/HQL, you do not join like SQL by providing the join criteria. So your query should be
#Query("from Invoice i "
+ " where i.courierId = :courierId "
+ " and i.account.client.id = :clientId ")
List<Invoice> findByCourierIdAndClientId(#Param("courierId") Long courierId,
#Param("clientId") Long clientId);
(Assume the ID field in Client is called id)
Looks intuitive right?
And, given your query is really straight-forward, you can even let Spring Data generate your query by properly naming your finder method:
// no more #Query needed, works magically
List<Invoice> findByCourierIdAndAccountClientId(Long courierId, Long clientId);
You can try something like this. If you have the relation mapped correctly (#OneToMany/ManyToOne), you don't need to explicitly join the tables.
#Query("SELECT i from Invoice i WHERE i.courierId =?1 AND i.clientId =?2")
List<Invoice> findByCourierIdAndClientId(Long courierId, Long clientId);
You can even pass objects to a query directly, e.g.
#Query("SELECT i from Invoice i WHERE i.courierId= :courier AND i.clientId = :client")
List<Invoice> findByCourierAndClient(#Param("courier") Courier courier, #Param("client") Client client);