Spring JWT, missing information about role in database - mysql

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();

Related

How do you fetch an object from MySQL that contains a property with a value of another object in Spring Boot?

I have a few models that has relationship to each other within this object I am tryin to save and fetch from the database, but I can't seem to get it to work properly. The object contains a property that also contains another object. My approach was to save to all related models and for those models, I would also save this current model to them as well so it shows relation on the database table.
Below the models for the app.
#Entity
#Table(name = "daily_entry")
#Setter
#Getter
#AllArgsConstructor
#NoArgsConstructor
public class DailyEntry {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "daily_entry_id", unique = true)
private Long id;
#Column(unique = true)
private LocalDate date;
private int weight;
#ManyToOne
#JoinColumn(name="user_id")
private User user;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "daily_macros_id", unique = true)
private DailyMacros dailyMacros;
#OneToMany(mappedBy = "dailyEntry", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Exercise> exercise = new ArrayList<Exercise>();
}
#Entity
#Table(name = "daily_macros")
#Setter
#Getter
#AllArgsConstructor
#NoArgsConstructor
public class DailyMacros {
#Id
#GeneratedValue
#Column(name = "daily_macros_id")
private Long id;
private int calories;
private int protein;
private int fat;
private int carbs;
#OneToOne(mappedBy = "dailyMacros", cascade = CascadeType.ALL, orphanRemoval = true)
private DailyEntry dailyEntry;
}
#Setter
#Getter
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name= "exercises")
public class Exercise {
#Id
#GeneratedValue
#Column(name = "exercise_id")
private Long id;
private String name;
private int sets;
private int reps;
private int weight;
#ManyToOne
#JoinColumn(name = "daily_entry_id", unique = true)
private DailyEntry dailyEntry;
}
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long id;
#Column(nullable = false)
private String firstName;
#Column(nullable = false)
private String lastName;
private String role = "user";
private String token;
#Column(unique = true, nullable = false)
protected String username;
#Column(unique = true ,nullable = false)
private String emailAddress;
#Column(nullable = false)
private String password;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "macros_goal_id")
private MacrosGoal macrosGoal;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<DailyEntry> dailyEntry = new ArrayList<>();
}
Im tryin to save the model of DailyEntry to the database.
User is the owner of DailyEntry
DailyEntry owns MacrosGoal (#OneToOne) and Exercise (#OneToMany). How exactly can I save and fetch this model into the database?
This is what I've tried -
For my PutMapping, I get this error : Cannot call sendError() after the response has been committed
My GetMapping just returns a empty response although http status code 200
DailyEntryController file
#RequestMapping(path = "/api/v1/")
#RequiredArgsConstructor
public class DailyEntryController {
#Autowired
DailyEntryService dailyEntryService;
#GetMapping("/getDailyEntry")
public ResponseEntity<DailyEntry> getDailyEntry(#RequestParam("username") String username, #RequestParam String date) throws ResponseStatusException {
DailyEntry dailyEntry = dailyEntryService.getDailyEntry(username, date);
return new ResponseEntity<>(dailyEntry, HttpStatus.OK);
}
#PutMapping("/addDailyEntry")
public ResponseEntity<DailyEntry> addDailyEntry(#RequestBody DailyEntry dailyEntry, #RequestParam("username") String username) throws ResponseStatusException {
DailyEntry dailyEntryInfo = dailyEntryService.createDailyEntry(dailyEntry, username);
return new ResponseEntity<>( dailyEntryInfo, HttpStatus.OK);
}
}
#Slf4j
#Service
public class DailyEntryServiceImpl implements DailyEntryService {
#Autowired
DailyEntryRepository dailyEntryRepository;
#Autowired
ExerciseRepository exerciseRepository;
#Autowired
UserRepository userRepository;
#Autowired
DailyMacrosRepository dailyMacrosRepository;
#Override
public DailyEntry addExercise(Exercise exercise) {
return null;
}
#Override
public DailyEntry getDailyEntry(String username, String date) {
DailyEntry entry = null;
Optional<User> userFromDatabase = userRepository.findByUsername(username);
User user = userFromDatabase.get();
LocalDate localDate = LocalDate.parse(date);
List<DailyEntry> dailyEntryList = user.getDailyEntry();
for(DailyEntry e : dailyEntryList) {
if(e.getDate() == localDate){
entry = e;
}
}
return entry;
}
#Override
public DailyEntry createDailyEntry(DailyEntry dailyEntry, String username) {
DailyEntry entry = new DailyEntry();
// Find user
Optional<User> userFromDatabase = userRepository.findByUsername(username);
User user = userFromDatabase.get();
entry.setUser(user);
entry.setDate(LocalDate.parse(dailyEntry.getDate().toString()));
entry.setDailyMacros(dailyEntry.getDailyMacros());
entry.setWeight(dailyEntry.getWeight());
entry.setExercise(dailyEntry.getExercise());
DailyMacros dailyMacros = dailyEntry.getDailyMacros();
dailyMacros.setDailyEntry(entry);
dailyMacrosRepository.save(dailyMacros);
List<Exercise> exercise = dailyEntry.getExercise();
for (Exercise e : exercise) {
e.setDailyEntry(entry);
exerciseRepository.save(e);
}
List<DailyEntry> dailyEntryList = user.getDailyEntry();
dailyEntryList.add(entry);
user.setDailyEntry(dailyEntryList);
userRepository.save(user);
return entry;
}
I also tried with Query inside repository, but I may have done it wrong.
You should probably add #Transactional to createDailyEntry.
I don't really understand what's happening in createDailyEntry but I think it should look something like this:
#Override
#Transactional
public DailyEntry createDailyEntry(DailyEntry dailyEntry, String username) {
User user = userRepository
.findByUsername(username)
.get();
dailyEntryRepository.save(dailyEntry);
user.getDailyEntry().add(dailyEntry);
dailyEntry.setUser(User);
DailyMacros dailyMacros = dailyEntry.getDailyMacros();
dailyMacros.setDailyEntry(dailyEntry);
dailyMacrosRepository.save(dailyMacros);
dailyEntry.getExcercise()
.forEach( e -> {
e.setDailyEntry(dailyEntry);
exerciseRepository.save(e);
});
// I don't think you need this because user is already managed
// and userFromDatabase.get() will throw an exception
// if the user does not exist.
// userRepository.save(user);
return dailyEntry;
}
I think getDailyEntry doesn't work because you are using e.getDate() == localDate and it's always going to be false.
One solution is to add a method to the DailyEntryRepository that accept username and date:
interface DailyEntryRepository extends JpaRepository<DailyEntry, Long> {
#Query("select de from DailyEntry de where de.user.username = :username and de.date = :localDate")
DailyEntry findByUsernameAndDate(#Param("username") String username, #Param("localDate") LocalDate date);
}
...
#Override
public DailyEntry getDailyEntry(String username, String date) {
LocalDate localDate = LocalDate.parse(date);
return dailyEntryRepository.findByUsernameAndDate(username, localdate);
}
This solution will run a query and load only the entry you need.
But, if you need to validate the username, this should work:
#Override
public DailyEntry getDailyEntry(String username, String date) {
User user = userRepository
.findByUsername(username)
.get();
LocalDate localDate = LocalDate.parse(date);
for(DailyEntry e : user.getDailyEntry()) {
if (localDate.equals(e.getDate)){
// No need to continue the loop, you've found the entry
return entry;
}
}
// daily entry not found
return null;
}
Another thing that's missing is that in your model you are not handling the bidirectional association during convertion to JSON.
The solution is to use I end up using #JsonManagedReference and #JsonBackReference where you have the association.

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);
}

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.

In Spring Data how to read entity by foreign key value instead of join?

I am using Spring Data and I am in doubt why even after declaring foreign entities as Lazy loaded they are getting eagerly loaded for this method:
findByReportingManager_IdAndAndLevel(Long reporterManagerId, Integer level)
On logs, I can see the query as:
select userhierar0_.id as id1_28_,
userhierar0_.LEVEL as LEVEL5_28_,
userhierar0_.REPORTING_MANAGER_ID as REPORTIN9_28_,
userhierar0_.USER_ID as USER_ID10_28_
from USER_HIERARCHY userhierar0_
left outer join
USER_V3 user1_ on userhierar0_.REPORTING_MANAGER_ID=user1_.id
where user1_.id=? and userhierar0_.CUSTOMER_ID=? and userhierar0_.LEVEL=?
why extra join even if I am passing reporting manager id ?
UserHierarchy Class:
#Entity
#Table(name = "USER_HIERARCHY")
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class UserHierarchy {
#Id
#GeneratedValue
private Long id;
#ManyToOne(fetch = FetchType.LAZY) // LAZY LOADING
#JoinColumn(name = "USER_ID",referencedColumnName = "ID")
private User user;
#ManyToOne(fetch = FetchType.LAZY) //LAZY LOADING
#JoinColumn(name = "REPORTING_MANAGER_ID", referencedColumnName = "ID")
private User reportingManager;
#Column(name = "LEVEL")
private Integer level;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public User getReportingManager() {
return reportingManager;
}
public void setReportingManager(User reportingManager) {
this.reportingManager = reportingManager;
}
public Integer getLevel() {
return level;
}
public void setLevel(Integer level) {
this.level = level;
}
#Override
public String toString() {
return ReflectionToStringBuilder.toStringExclude(this, "user", "reportingManager");
}
User Entity class
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue
private Long id;
#Column(name = "EMAIL")
private String email;
#Column(name = "CUSTOMER_ID")
private Long customerId;
#Column(name = "STATUS")
private String status;
// Getter and Setter
As per Spring's doc:
At query creation time you already make sure that the parsed property
is a property of the managed domain class.
So does that mean in order to make User object in "managed state" it uses join or I am wrong in the implementation ?
I stumbled across the same problem recently and it seems that there is no solution for this at the moment in Spring Data.
However I've created a ticket for it.
If you go with Criteria API or JPQL for this particular query, then it will work properly.