Problem with update id - mysql

I'm gonna try to explain my problem as well I can
I have some user groups to manage user rights.
Some users may be customers. There's a OneToOne connection between the user table and the customers table. And a OneToOne connection between users and groups. When I delete a client, I would like the user group changes from customer to user, the default group (Id=4).
With this code:
public static void delete(Long id) {
Customer entity = Customer.findById(id);
User entityUser = User.findById(entity.user.id);
entity.delete();
entityUser.groups.id = (long)4;
entityUser.merge();
entityUser.save();
flash.success(Messages.get("Users.deleted"));
Customers.list();
}
Group model:
#Entity
#Table(name = "groups")
public class Group extends Model{
#Required
public String name;
#Required
public String description;
public Group(String name, String description)
{
this.name = name;
this.description = description;
}
User model:
#Entity
#Table(name = "users")
public class User extends Model{
#Required
public String firstname;
#Required
public String lastname;
#As("dd/MM/yyyy")
#Required
public Date birthday;
#Required
public String avatar;
#Required
public String adress;
#Required
public String phonenumber;
#Required
#Email
public String email;
#Required
public String username;
#Required
#Password
public String password;
#OneToOne
#Required
public Group groups;
public User(
String firstname,
String lastname,
#As("dd/MM/yyyy") Date birthday,
String avatar,
String adress,
String phonenumber,
String email,
String username,
String password,
Group groups
)
{
if(groups == null){
groups.id = (long)4;
}
else
{
this.groups = groups;
}
this.firstname = firstname;
this.lastname = lastname;
this.birthday = birthday;
this.avatar = avatar;
this.adress = adress;
this.phonenumber = phonenumber;
this.email = email;
this.username = username;
this.password = password;
}
Customer model:
#Entity
#Table(name = "customers")
public class Customer extends Model{
#As("dd/MM/yyyy")
public Date dateinscription;
public Double amountdue;
public Double amountpaid;
#Required
public String picture;
#OneToOne
public User user;
public Customer(#As("dd/MM/yyyy") Date dateinscription, Double amountdue, Doubleamountpaid, String picture, User user)
{
this.dateinscription = dateinscription;
this.amountdue = amountdue;
this.amountpaid = amountpaid;
this.picture = picture;
this.user = user;
}
}
But I've got an error:
PersistenceException occured : org.hibernate.HibernateException: identifier of an instance of models.Group was altered from 3 to 4
In /app/controllers/Customers.java (around line 69)
65:
66:
entityUser.groups.id = (long)4;
67:
68:
entityUser.merge();
69:
entityUser.save();
70:
71:
flash.success(Messages.get("Users.deleted"));
72:
Customers.list();
73:
74:
}
75:
I tried with GenericModel but it didn’t work.
Please help me !!
Thank you.

In your code, you are explicitly changing the ID of the group, not the group. Hibernate assumes you want to change that field. But when you try to save the change, it won't let you, since it's the ID. It doesn't assume you mean "change the group to the one that has an ID of 4."
Instead, you'll need to load the default group, and set the user's group that that group.
Group group = Group.findById(4);
entityUser.groups = group;

Related

JPA: Many to many relationship - JsonMappingException: Infinite recursion

I'm having trouble with a many to many relation with JPA.
My code looks as follows:
The Sensor class:
#Entity
#Table(name = "sensor")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Sensor {
#Id
private long chipId;
#OneToMany(mappedBy = "sensor")
#JsonBackReference
private Set<Link> userLinks;
private String firmwareVersion;
private long creationTimestamp;
private String notes;
private long lastMeasurementTimestamp;
private long lastEditTimestamp;
private double gpsLatitude;
private double gpsLongitude;
private double gpsAltitude;
private String country;
private String city;
private boolean indoor;
private boolean published;
}
The user class:
#Entity
#Table(name = "user")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonManagedReference
private int id;
private String firstName;
private String lastName;
private String email;
private String password;
#OneToMany(mappedBy = "user")
private Set<Link> sensorLinks;
private int role;
private int status;
private long creationTimestamp;
private long lastEditTimestamp;
}
And the Link class (relation class):
#Entity
#Table(name = "link")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Link {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name = "user_id")
#MapsId("user_id")
private User user;
#ManyToOne
#JoinColumn(name = "sensor_id")
#MapsId("sensor_id")
private Sensor sensor;
private boolean owner;
private String name;
private int color;
private long creationTimestamp;
}
The controller:
...
#RequestMapping(method = RequestMethod.GET, path = "/user/{email}", produces = MediaType.APPLICATION_JSON_VALUE)
#ApiOperation(value = "Returns details for one specific user")
public User getUserByEmail(#PathVariable("email") String email) {
return userRepository.findByEmail(email).orElse(null);
}
...
The UserRepository:
public interface UserRepository extends JpaRepository<User, Integer> {
Optional<User> findByEmail(String email);
#Modifying
#Query("UPDATE User u SET u.firstName = ?2, u.lastName = ?3, u.password = ?4, u.role = ?5, u.status = ?6 WHERE u.id = ?1")
Integer updateUser(int id, String firstName, String lastName, String password, int role, int status);
}
I want to achieve, that the user endpoint shows all linked sensors with that particular user.
What I get is only an error message:
JSON mapping problem:
com.chillibits.particulatematterapi.model.db.main.User["sensorLinks"];
nested exception is
com.fasterxml.jackson.databind.JsonMappingException: Infinite
recursion (StackOverflowError) (through reference chain:
com.chillibits.particulatematterapi.model.db.main.User["sensorLinks"])
How can I fix this issue?
Thanks in advance
Marc
------------------------------------ Edit -----------------------------------
According to Abinash Ghosh's answer, I added following DTOs:
UserDto:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class UserDto {
private int id;
private String firstName;
private String lastName;
private Set<LinkDto> sensorLinks;
private int role;
private int status;
private long creationTimestamp;
private long lastEditTimestamp;
}
LinkDto:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class LinkDto {
private Integer id;
private SensorDto sensor;
private boolean owner;
private String name;
private int color;
private long creationTimestamp;
}
And the mapper (I realized it a bit different, but it should be the same):
public UserDto getUserByEmail(#PathVariable("email") String email) {
User user = userRepository.findByEmail(email).orElse(null);
return convertToDto(user);
}
private UserDto convertToDto(User user) {
return mapper.map(user, UserDto.class);
}
This leads to following Exception:
2020-04-13 14:22:24.383 WARN 8176 --- [nio-8080-exec-2] o.h.e.loading.internal.LoadContexts : HHH000100: Fail-safe cleanup (collections) : org.hibernate.engine.loading.internal.CollectionLoadContext#68ab57c7<rs=HikariProxyResultSet#2017009664 wrapping Result set representing update count of -1>
1) Error mapping com.chillibits.particulatematterapi.model.db.main.User to com.chillibits.particulatematterapi.model.io.UserDto
1 error] with root cause
java.lang.StackOverflowError: null
at com.mysql.cj.NativeSession.execSQL(NativeSession.java:1109) ~[mysql-connector-java-8.0.19.jar:8.0.19]
...
It's working!
This post helped: https://stackoverflow.com/a/57111004/6296634
Seems that you should not use Lombok #Data in such cases.
When User serialized for the response, all getter methods of User's fields are called.
So, User relational field sensorLinks's getter are also called to set value. This happened recursively. That's cause of infinite recursion.
It's better to not use Entity as a response. Create a DTO class for User then map User entity value into DTO then send response. Don't use any Enity class again into DTO then it will result same problem
For dynamically map one model to another you can use ModleMapper
public class UserDTO {
//Fields you want to show in response & don't use enity class
private Set<LinkDTO> sensorLinks;
}
public class LinkDTO{
//Fields you want to show in response &don't use enity class
}
public User getUserByEmail(#PathVariable("email") String email) {
User user = userRepository.findByEmail(email).orElse(null);
UserDTO userDto = merge(user,UserDTO.class)
return userDto;
}
public static <T> void merge(T source, T target) {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
modelMapper.map(source, target);
}

Spring JWT, missing information about role in database

Architecture: Spring Boot, Spring JWT, Hibernate, MySQL
Current situation:
Properly generated jwt token during sign up user. Also I have no problem with login.
Problem:
I have problem with user roles. Properly was sent etc., but does not save to database.
I have user table:
#Entity
#Table(name="user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="login")
private String login;
#Column(name="password")
private String password;
#Column(name="email")
private String email;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "user_roles", joinColumns = #JoinColumn(name = "id"))
#Column(name="role")
List<Role> roles;
public User(){}
public User(String login, String password, String email, List<Role> roles) {
this.login = login;
this.password = password;
this.email = email;
this.roles = roles;
}
//getters and setters, toString();
}
Role enum:
public enum Role implements GrantedAuthority {
ROLE_ADMIN,
ROLE_USER
#Override
public String getAuthority() {
return name();
}
}
But where I use findByLogin User, there is no information about Role. But another information exist.
#Override
public User findByLogin(String loginUser) {
Session currentSession = entityManager.unwrap(Session.class);
Query findLoginQuery = currentSession
.createQuery("from User where login=:login")
.setParameter("login", loginUser);
return (User) findLoginQuery.uniqueResult();

How to solve HHH000346 Error using hibernate 5 and mysql?

I'm studying restful service and views.
Regarding it, I use mysql and hibernate 5.
My data tables are two and have reference relation.
The problem occurs when I update the primary key.
When I add new one then update existing data in another table (they have reference relation), HHH000346: Error during managed flush occurs.
I already search on google, but I couldn't find the answer.
This is my Entity classes.
#Entity
#Table(name = "users")
#EntityListeners(AuditingEntityListener.class)
public class User {
private long serial;
private String username;
private String password;
public User() {
}
public User(long serial, String username, String password) {
setSerial(serial);
setUsername(username);
setPassword(password);
}
#Column(name = "serial", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
public long getSerial() {
return serial;
}
public void setSerial(long serial) {
this.serial = serial;
}
#Id
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#Column(name = "password", nullable = false)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#Override
public String toString() {
return "serial: " + this.serial + ", username: " + this.username + ", password: " + this.password;
}
}
Entity
#Table(name = "sites")
#EntityListeners(AuditingEntityListener.class)
#IdClass(Site.class)
public class Site implements Serializable{
#ManyToOne
#JoinColumn(name="username",foreignKey=#ForeignKey(name="username"))
private String username;
private String siteURL;
#Id
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#Id
public String getSiteURL() {
return siteURL;
}
public void setSiteURL(String siteName) {
this.siteURL = siteName;
}
}
And this is class had problem.
public class UserController {
#Autowired
private UserRepository userRepository;
#Autowired
private SiteRepository siteRepository;
private CryptoUtil passwordEncoder = new CryptoUtil();
...
#PutMapping("/users/{username}")
public User updateUser(#PathVariable(value = "username") String username, #Valid #RequestBody User userDetails)
throws ResourceNotFoundException {
User user = userRepository.findById(username)
.orElseThrow(() -> new ResourceNotFoundException("User not found on :: " + username));
List<Site> sites = siteRepository.findByUsername(user.getUsername());
userDetails.setPassword(passwordEncoder.encryptSHA256(userDetails.getPassword()));
final User updateUser = userRepository.save(userDetails);
for (Site site : sites)
{
site.setUsername(userDetails.getUsername());
site = siteRepository.save(site);
}
userRepository.delete(user);
return updateUser;
}
....
}
The for-each statement occurs error.
PLEASE HELP ME
Why did you do this?
#ManyToOne
#JoinColumn(name="username",foreignKey=#ForeignKey(name="username"))
private String username;
It should be:
#ManyToOne
#JoinColumn(name="username",foreignKey=#ForeignKey(name="username"))
private User user;
I'll also suggest you to use the primary key as foreign key.
And you can't have multiple #Id in an entity.

Springboot - empty get rest response

I am building a simple get rest call from MySQL database, the problem is that it returns an empty object.
The call itself is takes in an email (I know this is not the best approach), here is my code:
Entity:
#Entity
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int id;
private String email;
private String password;
private String firstName;
private String userName;
private String lastName;
private boolean active;
#Temporal(TemporalType.DATE)
private Date createDate;
#Temporal(TemporalType.DATE)
private Date updateDate;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"), inverseJoinColumns = #JoinColumn(name = "role_id"))
private Collection<Role> roles;
// constructor
// get and setter
}
Repository:
public interface UserRepository extends JpaRepository<User, Long> {
// User findById (Integer Id);
#Query("SELECT u.id FROM User u where u.id = :id")
User findById(#Param("id") Integer id);
User findByEmail (String email);
}
Service:
#Service("userService")
public class UserService {
private String status, message;
private final HashMap map = new HashMap();
#Autowired
private UserRepository userRepository;
// #Autowired
// private RoleRepository roleRepository;
public User findByUserEmail (String email) {
return userRepository.findByEmail(email);
}
}
Controller:
#RestController("userControllerService")
#RequestMapping("/user/account")
public class UserController {
#Autowired
private UserService userService;
#GetMapping("/test-get/{email}")
public User jj(#PathVariable("email") String email){
return userService.findByUserEmail(email);
}
}
And my database happens to have the following data:
And here is the response I get after hitting the URL
I have no clue why my response is empty!
You cannot have the #in the URI path. Encode it with %40.
Reference: Can I use an at symbol (#) inside URLs?
Also, right way is to use as a query param as that's more of a good identifier and allows # as it parses as string
#GetMapping("/test-get")
public User jj(#RequestParam("email") String email){
return userService.findByUserEmail(email);
}
Either ways, hit as encoded url as /test-get/email=a#b.com ? or /test-get/a%40b.com for your previous code.

Trying to extend a class in java and running into problems

I was wondering if anyone could help me with a problem I am encountering when trying to extend a class. I want to be able to add a first name and a last name which is part of my base class to an extended class.
Here is a snippet from my base class Person.java
public class Person
{
private String firstName = "";
private String lastName = "";
private String id = "";
public Person()
{
}
public Person(String firstName, String lastName, String id)
{
this.firstName = firstName;
this.lastName = lastName;
this.id = id;
}
And here is the class where I am trying to extend the base class:
public TeamMember(String firstName, String lastName, String team, String id, String role)
{
super(firstName, lastName);
this.team = team;
this.id = id;
this.role = role;
}
The error I receive is:
Error:(25, 9) java: constructor Person in class xxx.xxxx.xxx.xx
cannot be applied to given types;
required: no arguments
found: java.lang.String,java.lang.String
reason: actual and formal argument lists differ in length
I had to create a constructor that matched what was in the super() in the extended class in the base class.
This is what I added to the base class to make it work.
public Person(String firstName, String lastName)
{
this.firstName = firstName;
this.lastName = lastName;
}
Try this.
public TeamMember(String firstName, String lastName, String team, String id, String role)
{
super(firstName, lastName, id);
this.team = team;
this.role = role;
}
Id is a private property of base class, so cannot be accessed in derived class.
private String id = "";