Java Spring Boot Rest: Search objects by specific parameters - java

I have this student app with grades and exams, what I need to do is to find all exams for specific date and function to list average grade for a specific exam.
Here are entity classes for Student, Exam and Grade
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Data
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstname;
private String lastname;
private String branch;
private int year;
private String studentsIndex;
}
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Data
public class Exam {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
private String code;
private LocalDate date;
private String time;
private String classroom;
}
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Data
public class Grade {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
private Exam exam;
#ManyToOne
private Student student;
#Column(name = "Grade")
private int grade;
}
I have repositories for all of them, but I have not added any other methods to them. Also, I have services for finding all, finding by id, deleting by id, etc...
So my question is how and what do I need to do so I can get all exams for a specific date and average grade for a certain exam, what do I need to add to the repository and service, and how to create that GET method in Controller class?
By the way, I am saving grades through the DTO object which contains an exam id and also one for student and grade.

First of all, I can't test the code at the moment. I would do both selection and aggregation for performance reasons in the database or via JPA. BTW, I assume you're using Spring Data JPA...
The first approach would be to turn a unidirectional JPA mapping into a bidirectional mapping. For this purpose, an association is created in the Exam class using #OneToMany.
#AllArgsConstructor
#NoArgsConstructor
#Data
public class Exam {
// Rest of your class...
#OneToMany(cascade = CascadeType.ALL, mappedBy = "exam")
private List<Grade> grades;
}
Next you have to write a custom query for your ExamRepository. For this, you should create projection.
public interface IExamGradeAverage {
Long getExamId();
Double getGradeAverage();
}
This projection is used as return typ of your custom query.
#Repository
public interface ExamRepository extends JpaRepository<Exam, Long> {
#Query("SELECT e.id AS examId, AVG(g.grade) AS gradeAverage FROM Exam e JOIN e.grades g WHERE e.date = ?1")
List<IExamGradeAverage> getAverageGradeOfExamByDate(LocalDate date);
}
After that, theoretically a rest endpoint could be created that uses the JPA repository implementation t fetch the aggregation.

Related

Extra association table is created in spring boot

I'm currently working on developing a recipe application and I'm having trouble with DB table generation.
Here are the Entity files I'm using:
// Recipe.java
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "recipes")
public class Recipe {
#Id
#GeneratedValue
private int id;
private String name;
private String description;
private String instruction;
#ManyToOne
private User user;
#OneToMany(cascade=CascadeType.ALL)
private List<RecipeIngredient> ingredients = new ArrayList<>();
}
// Ingredient.java
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "ingredients")
public class Ingredient {
#Id
#GeneratedValue
private int id;
private String name;
}
// RecipeIngredient.java
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
public class RecipeIngredient {
#Id
#GeneratedValue
private int id;
#ManyToOne
private Ingredient ingredient;
private String amount;
}
Spring Boot Automatically creates tables for me but I just wanna have one table for RecipeIngredient, but it creates two tables for them.
It works perfectly fine but the thing I want is just how to make these two tables into one or make spring boot not generate one of them.
If you want recipe_ingedients table only delete recipeIngredient Entity Class and if you want to keep recipe_ingredient table remove this:
#OneToMany(cascade=CascadeType.ALL)
private List<RecipeIngredient> ingredients = new ArrayList<>();

Spring data JPA save and updating parent entity

I have two entities called Student and Subject. They are stored in tables in the following format
student_id
name
grade
1
John
1
subject_id
name
1
English
2
Math
subject_id
student_id
mark
1
1
75
2
1
75
**Student:**
#Table(name = "student")
#Data
public class Student {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "grade")
private int grade;
//getters and setters left out for this example
}
**Subject:**
#Table(name = "subject")
#Data
public class Subject {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#Column(name = "name")
private String name;
//getters and setters left out for this example
}
**StudentRepository:**
public interface StudentRepository extends JpaRepository<Student, Long> {
}
How do I make it so that everytime I add a student using a StudentController, the subjects are automatically added to the student.
Create the third entity for the third table, create the student object and the subject object . put it in the third entity object, create the third repository and save that, all three tables will be updated together. Just make sure your relationships are correctly mentioned and you are done.
Update your Student entity to encapsulate Subject.
The idea is to explicitly define relationship between Student and Subject , and further leverag Cascade to propagate changes :
#Table(name = "student")
#Data
public class Student {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "grade")
private int grade;
#OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.MERGE}, orphanRemoval = true)
#JoinColumn(name = "SUBJECT_ID")
Subject subject;
}
Note : You need to make sure that you populate Subject when storing Student.
For more clarity , explore the examples presented here : https://vladmihalcea.com/a-beginners-guide-to-jpa-and-hibernate-cascade-types/

Implementing Composite (Embedded-ID) Foreign Key Relations using Spring Data JPA

Interestingly, I can't find any solution for a seemingly common scenario! So I'm asking here to learn from experienced professionals in Spring Data JPA. I'll consider using Lombok to make the sample codes more concise.
Consider a simple IMDB example web application. I've defined two simple entities as below:
#Data
#Entity
public class Movie {
#Id
#GeneratedValue
private long id;
private String title;
private int year;
private int rating;
}
#Data
#Entity
public class Actor {
#Id
#GeneratedValue
private long id;
private String firstName;
private String lastName;
private Date birthday;
private String gender;
}
Now we need a join-table to link these two entities; but this is not just a simple join-table. Other than the actor and movie columns, this table has some additional attributes. We didn't want to waste storage by adding an ID column here, instead we used a composite-key consisting of actor and movie:
#Data
#Embeddable
public class MovieActorId implements Serializable {
private Actor actor;
private Movie movie;
}
#Data
#Entity
public class MovieActor {
#EmbeddedId
private MovieActorId id;
private int salary;
private String characterName;
}
There are two Many-to-One relations here: MovieActor >-- Actor and MovieActor >-- Movie.
Now my main question is: "Assuming the above design, how should I define the #ManyToOne relationships in this design?"
NOTE: I believe if we add an additional ID column to the MovieActor join-table instead of the composite/embedded MovieActorId, the JPA code will become fairly straight-forward. But suppose we have some sort of limitation, and we need to stick to this design as much as possible.
You need to use #MapsId which provides the mapping for an EmbeddedId primary key in #ManyToOne relation
#Data
#Embeddable
public class MovieActorId implements Serializable {
private long actorId;
private long movieId;
// constructor, setter, etc
}
#Data
#Entity
public class MovieActor {
#EmbeddedId
private MovieActorId id;
#ManyToOne(cascade = CascadeType.ALL)
#MapsId("actorId")
private Actor actor;
#ManyToOne(cascade = CascadeType.ALL)
#MapsId("movieId")
private Movie movie;
...
}

JPA: How to remove unnecessary SELECT before save()?

I have a Entity Student which is #ManyToOne with another Entity School, Where School is pre-existing in the database and is fixed.
Entity User:
#Data
#Entity(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false)
private String username;
#ManyToOne
private School school;
}
Entity School:
#Data
#Entity(name = "school")
public class School {
#Id
#Column(unique = true, nullable = false)
private int id;
#Column(nullable = false)
private String name;
private String shorten;
#JsonProperty(value = "logo_url")
private String logoUrl;
private float longitude;
private float latitude;
#Column(nullable = false)
private boolean opened;
}
When adding a user, I POST the following json from Postman:
{
"username": "abcd",
"school_id": 2
}
Then,
School school = new School();
school.setId(2); //"school_id" above
User user = new User();
user.setUsername("abcd");
user.setSchool(school);
userRepository.save(user);
Because I think that to add a new user, only the School id is enough, and no other School parameters are required. But every time I run, it will run the select statement to select all fields of School by id before save().
My question is: how to remove this unnecessary operation so that before the save(), there is no need to select? (I know that custom sql statements can be implemented, but I feel like this will break the object orientation of JPA)
use below annotation on entity class
#SelectBeforeUpdate(value=false)
You need to use getReference method to avoid this issue
School school = entityManager.getReference(School.class, 2);
If you are using Spring Data JPA, this method is exposed as getOne on the repository.
See How do find and getReference EntityManager methods work when using JPA and Hibernate

Javers - DiffIgnore on bidirectional OneToMany

I am trying to integrate Javers with a Spring Data REST project. Currently I have the following entities in my domain.
Student.class
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private Long dob;
#OneToOne
private Gender gender;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "student", orphanRemoval = true)
private List<ContactNumber> contactNumbers = new ArrayList<>();
}
ContactNumber.class
#Entity
public class ContactNumber {
#Id
#GeneratedValue
private Long id;
private String phoneNumber;
private Boolean isPrimary;
#ManyToOne
private Student student;
}
In the javers docs it is mentioned that:
In the real world, domain objects often contain various kind of noisy
properties you don’t want to audit, such as dynamic proxies (like
Hibernate lazy loading proxies), duplicated data, technical flags,
auto-generated data and so on.
So does that mean I put a #DiffIgnore on the #ManyToOne student field in the contact number class or the #OneToMany contacts field in the student class?
It depends how you're logging the objects and what you want to log. Consider these two lines (suppose that you have a link between p and contactNumber)
//This logs p and contactNumber as part of the array part of p. If you want to ignore contactNumber here,
//add #DiffIgnore on the #OneToMany property. If you want to ignore
javers.commit("some_user", p);
//This would log contactNumber and p as the student. You can add #DiffIgnore here on the student property (#ManyToOne)
javers.commit("some_user", contactNumber);
Note that there is another annotation #ShallowReference that will log the id of the object instead of logging the entire object. E.g. if you add #ShallowReference to the student property it will not log the entire Person object, but only its ID. You can use that instead to have a link between those objects.
UPDATE:
Looking at your model, I'd recommend that you remove the student property. It doesn't make sense to have a link to the student from the phone number. The student has a number assigned, not the other way around. Thus your model would look like this.
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
private Long dob;
#OneToOne
private Gender gender;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "student", orphanRemoval = true)
private List<ContactNumber> contactNumbers = new ArrayList<>();
}
ContactNumber.class
#Entity
public class ContactNumber {
#Id
#GeneratedValue
private Long id;
private String phoneNumber;
private Boolean isPrimary;
}
If you really need to find a Person/Student starting with a phone number you can have a Repository for your Person class that enables you to do that search. That would look like this:
//extend from the corresponding Spring Data repository interface based on what you're using. I'll use JPA for this example.
interface PersonRepository extends JpaRepository<Person, Long> {
Person findByPhoneNumber(String phoneNumber);
}
With this, your model is cleaner and you don't need to use DiffIgnore at all.

Categories