With spring boot - can't get all the fields when querying on multiples tables - spring-boot

I'm trying to get information from both tables model and user_model but without success.
Tables have relations like these :
So i use HQL request to get this informations, here is the request :
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
#Query("SELECT m, um.hasWriteAccess FROM Model as m, UserModel as um "
+ "INNER JOIN m.userModel "
+ "INNER JOIN um.user as u "
+ "WHERE u.username=:username")
List<Model> findModelsForUser(#Param("username") String username);
}
But i get duplicate information without the hasWriteAccess field? Hum, what did i forget ? I don't understand why it doesn't work while the same SQL query gives me the expected result...
Here is the result i get :
{
"models": [
{
"displayName": "model1",
"type": "type1",
"description": "description model1",
...
=> Missing : "hasWriteAccess" : "1"
},
{
"displayName": "model1",
"type": "type1",
"description": "description model1",
...
=> Missing : "hasWriteAccess" : "1"
},
{
"displayName": "model4",
"type": "type2",
"description": "description model4",
...
=> Missing : "hasWriteAccess" : "1"
},
{
"displayName": "model4",
"type": "type2",
"description": "description model4",
...
=> Missing : "hasWriteAccess" : "1"
},
...
],
"CENTRAL_GIT_URL": "xxxx",
"CENTRAL_GIT_USER": "xxxx",
"CENTRAL_GIT_PASS": "xxxx"
}
This is the controller with which I call the service that initiates the request :
#PostMapping("/authentication")
public ResponseEntity<HashMap> process(#RequestBody Map<String, String> request) throws Exception {
List<Model> models = new ArrayList<Model>();
models = genericService.findModelsForUser( request.get("username"));
HashMap<String, Object> map = new HashMap<>();
map.put("CENTRAL_GIT_USER", env.getProperty("spring.constant.git.central.user"));
map.put("CENTRAL_GIT_PASS", env.getProperty("spring.constant.git.central.password"));
map.put("CENTRAL_GIT_URL", env.getProperty("spring.constant.git.central.url"));
map.put("models",models);
return new ResponseEntity<HashMap>(map, HttpStatus.OK);
}
It may be useful to show my entities. So I have entity User :
#Entity
#Table(name = "user")
#JsonIgnoreProperties(value = { "password" })
public class User implements Serializable {
private static final long serialVersionUID = 7182498683194688448L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long id;
...
#ElementCollection
#CollectionTable(name = "user_role", joinColumns = #JoinColumn(name = "user_id"))
#Column(name = "role_name")
private List<String> roles = new ArrayList<>();
#OneToMany(mappedBy="user",cascade = CascadeType.ALL)
#JsonBackReference
private List<UserModel> userModel;
... getters ans setters
And entity Model :
#Entity
#Table(name="model")
public class Model implements Serializable {
private static final long serialVersionUID = 1125066441066336997L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="model_id")
private Long id;
...
#OneToMany(mappedBy="model",cascade = CascadeType.ALL)
#JsonBackReference
private List<UserModel> userModel;
... getters and setters
And entity UserModel :
#Entity
#Table(name="user_model")
public class UserModel implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "user_model_id")
private long id;
#ManyToOne
#JoinColumn(name="user_id")
#JsonManagedReference
private User user;
#ManyToOne
#JoinColumn(name="model_id")
#JsonManagedReference
private Model model;
#Column(name="is_activated")
private boolean isActivated;
#Column(name="has_write_access")
private boolean hasWriteAccess;
... getters and setters
Can you tell me where is my mistake ? Thanks

To workaround this issue try to use 'distinct' in the query:
#Query("select distinct m from Model m join m.userModel um join um.user u where u.username = ?1")
List<Model> findModelsByUserName(String userName);
I think there is a bug in Spring Data JPA that leads to this situation.
See my 'investigation' about this problem.
Also to workaround the issue try to use Hibernate 5.2.12 instead of 5.0.12 (by default)...

With your help i modified my request like this :
#Query("select distinct um from UserModel as um inner join um.user as u where u.username =:username")
List<Object[]> findModelsForUser(#Param("username") String username);
And i get the result that i expected. Thanks

Related

How to insert an object inside another in Spring

I'm trying to insert the Insurance object into **User **through an endpoint in Spring, but either the request asks me for all User attributes or the object is not inserted.
**User Class **
#Entity
#Table(name = "users",
uniqueConstraints = {
#UniqueConstraint(columnNames = "username"),
#UniqueConstraint(columnNames = "email")
})
#AllArgsConstructor
#NoArgsConstructor
#Data
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 20)
private String username;
private String documento;
private String telefone;
#Value("${some.key:false}")
private boolean first_acess;
private String formacao;
private String sexo;
private String data_nascimento;
#NotBlank
#Size(max = 50)
#Email
private String email;
#NotBlank
#Size(max = 120)
private String password;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable( name = "user_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles = new HashSet<>();
#Embedded
private Address address;
#ManyToOne
#JoinColumn(name = "insuranceId")
private Insurance insurance;
Insurance class
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "insurance")
public class Insurance {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long insuranceId;
private String name;
private String valor;
private String maximoDependentes;
private String vencimento;
private String descricao;
}
Request to insert into class
#PostMapping("/User/{id}/addPlan")
public ResponseEntity<User> addPlanToUser(#PathVariable("id") long id, #RequestBody SignInsuranceRequest signInsuranceRequest) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Not found User with id = " + id));
return new ResponseEntity<>(userRepository.save(user), HttpStatus.OK);
}
Custom request for the solution
public class SignInsuranceRequest {
#ManyToOne
#JoinColumn(name = "insuranceId", insertable = false, updatable = false)
private Insurance insurance;
public Insurance getInsurance() {
return insurance;
}
public void setInsurance(Insurance insurance) {
this.insurance = insurance;
}
}
I already tried to put the User in #RequestBody and just ask for the insurance and it didn't work either
Example of how it was done
#PutMapping("/User/{id}/addPlan")
public ResponseEntity<User> addPlanToUser(#PathVariable("id") long id, #RequestBody User user) {
User _user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Not found User with id = " + id));
_user.setInsurance(user.getInsurance());
return new ResponseEntity<>(userRepository.save(_user), HttpStatus.OK);
}
In this attempt, I would pass the id of an insurance in the body of the request and the ID of the user in the Header but the id of the database was not retrieved.
Response
{
"id": 1,
"username": "user",
"documento": null,
"telefone": null,
"first_acess": false,
"formacao": null,
"sexo": null,
"data_nascimento": null,
"email": "user#user.com",
"password": "$2a$10$DUkVRxFFiR7L2OpwwpxHyOCC.7v/iFLqp66BBqy3AC0lHrVa9lnQK",
"roles": [],
"address": null,
"insurance": {
"insuranceId": 1,
"name": "string",
"valor": "string",
"maximoDependentes": "string",
"vencimento": "string",
"descricao": "string"
}
}
I expected to insert an User class into a Insurance Class(Already created) and return the object of User completed.

Spring Boot JPA Using Many-to-Many relationship with additional attributes in the join table

I have two simple classes Student and Course. I am trying to set up many to many relationship between these classes. I want to use additional table whose PRIMARY KEY is the combination of the primary keys of student and course tables (student_id and course_id).
The student class:
#Entity
#Table(name = "student")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#OneToMany(mappedBy = "student")
private Set<CourseStudent> courses;
}
The course class:
#Entity
#Table(name = "course")
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String courseName;
#OneToMany(mappedBy = "course")
Set<CourseStudent> students;
}
The entity that stores the relationship between course and the student:
#Entity
#NoArgsConstructor
#Data
public class CourseStudent {
#EmbeddedId
CourseStudentKey id;
#ManyToOne
#MapsId("studentId")
#JoinColumn(name = "student_id")
Student student;
#ManyToOne
#MapsId("courseId")
#JoinColumn(name = "course_id")
Course course;
public CourseStudent(Student student, Course course) {
this.student = student;
this.course = course;
this.rating = 0;
}
int rating;
}
Attention: Since I want to have additional features in this entity (for example, storing the rating of the students for courses), I don't want to use #JoinTable idea that we implement in the Student class.
Since I have multiple attributes in the primary key of CourseStudent entity, I used the following class
#Embeddable
#Data
public class CourseStudentKey implements Serializable {
#Column(name = "student_id")
Long studentId;
#Column(name = "course_id")
Long courseId;
}
I have the following POST request to insert the student into a course:
#PostMapping("/insert/students/{studentId}/courses/{courseId}")
public CourseStudent insertStudentIntoCourse(#PathVariable(value = "studentId") Long studentId,
#PathVariable(value = "courseId") Long courseId) {
if (!studentRepository.existsById(studentId)) {
throw new ResourceNotFoundException("Student id " + studentId + " not found");
}
if (!courseRepository.existsById(courseId)) {
throw new ResourceNotFoundException("Course id " + courseId + " not found");
}
CourseStudent courseStudent = new CourseStudent(
studentRepository.findById(studentId).get(),
courseRepository.findById(courseId).get()
);
return courseStudentRepository.save(courseStudent);
}
I have manually added Student and the Course into my local database and send this request by using Postman.
http://localhost:8080/insert/students/1/courses/1
However, I get the following error:
{
"timestamp": "2022-08-04T12:33:18.547+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/insert/students/1/courses/1"
}
In the console, I get NullPointerException. What is the thing I am doing wrong here?

DTO and Entities mapping

I am building a Spring Rest Application, I need help with DTO's and parsing a result to a endpoint
This is json that I return at the moment to the endpoint:
{
"id": 1,
"name": "Ella - IPA Is Dead",
"description": "2015 IPA is Dead Series. Supremely floral, this hugely under-rated hop is related to Galaxy and was first cultivated in the Australian state of Victoria.",
"method": {
"mash_temp": [
{
"temp": {
"value": 65
}
}
]
}
}
I don't want to return "method" from this json, I just need "id", "name", "description", "mash_temp" - so it should look like this:
{
"id": 1,
"name": "Ella - IPA Is Dead",
"description": "2015 IPA is Dead Series. Supremely floral, this hugely under-rated hop is related to Galaxy and was first cultivated in the Australian state of Victoria. Initially given the same name as a certain Eurolager, their lawyers got involved and the St- prefix was dropped. Ella displays subtle notes of spice, but is fundamentally a truly floral bouquet, redolent of the Southern Hemisphere.",
"mash_temp": [
{
"temp": {
"value": 65
}
}
]
}
Those are the entities that I am using now:
Beer Entity:
#Entity
public class Beer implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "beer_id", unique = true, nullable = false)
private Integer id;
#Column(name = "name", nullable = false)
private String name;
#JsonProperty("description")
#Column(name = "description", nullable = false, columnDefinition = "TEXT")
private String description;
#JsonProperty("method")
#OneToOne(cascade = CascadeType.ALL)
private Method method;
}
Method Entity:
#Entity
public class Method implements Serializable
{
#JsonIgnore(value = true)
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#JsonProperty("mash_temp")
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "mash_temp")
private List<MashTemp> mash_temp = new ArrayList<>();
}
MashTemp Entity:
#Entity
public class MashTemp implements Serializable
{
#JsonIgnore(value = true)
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToOne(cascade = CascadeType.ALL)
private Temp temp;
#ManyToOne
private Method method;
}
Temp Entity:
#Entity
public class Temp implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Integer value;
#JsonIgnore(value = true)
private String unit;
#OneToOne
private MashTemp mashTemp;
}
Does anyone know how to create DTO's from this Entities but without "method" field?
Also this is my Controller:
#GetMapping("/beers")
public ResponseEntity<Set<Beer>> getAllBeers()
{
return new ResponseEntity<>(beerService.getAllBeers(), HttpStatus.OK);
}
#GetMapping("/beers/{id}")
public ResponseEntity<Beer> getById(#PathVariable Integer id) {
Beer beer = beerService.findById(id);
return new ResponseEntity<>(beer, HttpStatus.OK);
}
Have a look at the #JsonUnwrapped annotation (https://fasterxml.github.io/jackson-annotations/javadoc/2.8/com/fasterxml/jackson/annotation/JsonUnwrapped.html). You can put it on the method field in the Beer class, and then the properties of the Method class are serialized directly on the same level as the ones from Beer.

JPA Repository many to one

I have Student entity and Course entity. This is #ManyToOne relationship i.e. Student may attend only one course at a time, but courses may have multiple students.
#Entity
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String studentId;
private String firstName;
private String secondName;
#ManyToOne
#JoinColumn(name = "course_id")
//#JsonIgnore
private Course course;
#Entity
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String courseName;
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "course", orphanRemoval = true, targetEntity = Student.class)
private List<Student> students = new ArrayList<>();
I post my data with the following json:
{
"id": 1,
"courseName": "course134",
"students" : [
{
"id" : 1,
"studentId": "123",
"firstName": "John1",
"secondName": "Name1"
},
{
"id" : 2,
"studentId": "1234567",
"firstName": "John2",
"secondName": "Name2"
}
then, as I get courses I receive:
{
"id": 1,
"courseName": "course134",
"students": []
}
How to list Students attending specific course?
I made up a Query in StudentRepository
#Query("SELECT s from Student s where s.id = :courseName")
Optional<Student> getStudentByCourseName(String courseName);
Still not working.
this is my Repository code:
#Repository
public interface CourseRepository extends JpaRepository<Course, Long> {
Optional<Course> findCourseByCourseName(String courseName);
#Query("SELECT c.students FROM Course c WHERE c.courseName = :courseName")
Optional<Student> getStudentsByCourseName(String courseName);
}
this is my Service method
public Optional<Student> findStudentByCourse(String courseName){
return courseRepository.getStudentsByCourseName(courseName);
}
and finally my Controller:
#GetMapping("/student/course/{courseName}")
public ResponseEntity<Student> findCoursesWithStudentId(#PathVariable String courseName) {
Optional<Student> byCourseName = studentService.findStudentByCourse(courseName);
if (byCourseName.isPresent()) {
return ResponseEntity.ok(byCourseName.get());
} else {
return ResponseEntity.notFound().build();
}
}
You should query the Course table, not the Student table. Also, the query will return the list, not just one entity, so change your method's return type also...
#Query("SELECT c.students FROM Course c WHERE c.courseName = :courseName")
List<Student> getStudentsByCourseName(String courseName) {}
edit
You can always do it like so:
Excute the simple method:
Course findByCourseName(String courseName) {}
and then just get its Students by a simple:
course.getStudents();

OneToOne ConstraintViolation while saving a new Record, PK Provided

We have an Entity called Customers that has a OneToOne relationship to the Entity Address.
The Customer's PK should be manually defined. The Address' PK should be automatically defined.
So, in Customer I omitted the #GeneratedValue and I'm providing is value manually. But, when trying to save I'm getting the following error:
2018-11-07 10:42:17.810 ERROR 1257 --- [nio-8080-exec-2] o.h.i.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [Validation failed for classes [br.com.customers.entity.Address] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='não pode ser nulo', propertyPath=street, rootBeanClass=class br.com.customers.entity.Address, messageTemplate='{javax.validation.constraints.NotNull.message}'}
The problem is that the address.street is being provided and I can't realize why JPA is complaining that it's null...
Here are the JSON body that I'm trying to save. (It's being deserialized correctly, as, Address is not NULL)
{
"customer_Id": 50,
"name": "name",
"company_name": "company_name",
"email": "email#provider.com",
"business_phone": "(00) 1111-2222",
"mobile_phone": "(00) 1111-2222",
"document": "123456789",
"state_registration_number": "ISENTO",
"state_registration_type": "NO_CONTRIBUTOR",
"city_registration_number": "ISENTO",
"classification": "AUTO",
"address": {
"street": "STREET NAME",
"number": "NUMBER",
"complement": "COMPLEMENT",
"zip_code": "ZIP_CODE",
"neighborhood": "NEIGHBORHOOD",
"city": "CITY",
"state": "STATE"
}
}
Here are the Customer Entity:
#Data
#Entity(name = "X_CUSTOMERS")
public class Customer {
#Id
private int customer_Id;
#NotNull
private String name;
private String company_name;
private String email;
private String business_phone;
private String mobile_phone;
#NotNull
private String document;
private String state_registration_number;
private String state_registration_type;
private String city_registration_number;
#NotNull
private String classification;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
#JoinColumn(name = "address_id")
private Address address;
}
And here, Address Entity:
#Data
#Entity(name = "X_ADDRESS")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int address_Id;
#NotNull
private String street;
private String number;
private String complement;
private String zip_code;
private String neighborhood;
private String city;
private String state;
}
What Am I doing wrong?
Thanks!!!
Adding the code do persist the entities:
Customer Repository:
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
}
To persist:
#RestController
#RequestMapping("/customers")
public class CustomersController {
private CustomerRepository customerRepository;
public CustomersController(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
#PostMapping
public Customer postCustomer(#RequestBody Customer customer) {
return customerRepository.save(customer);
}
}
From reading the Hibernate documentation, the save operation only persist entities with auto generated ids. So, if you intend to set the id yourself, then what you need, is to change your insert method for persist. And since you customer has an id that is not auto generated, maybe this could be the issue. You can read more in this blog.
#PostMapping
public Customer postCustomer(#RequestBody Customer customer) {
return customerRepository.persist(customer);
}
Hope it helps.
If you add CascadeType.MERGE, it will work
#OneToOne(fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "address_id")
private Address address;
you set the customer id(50) so the following line of SimpleJpaRepository will be executed.
return this.em.merge(entity);

Resources