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
Related
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.
how can I create the work_with class in springBoot. I have tried my approch is that I have created the manytomany mapping between employee and client class but I was not able to create new colunm which is total_sales within work_with. Is there any way I can Create the new colunm during mapping and is my approch is right or wrong ? if not what should I do to create the works_with table class/entity and also want to add ON DELETE CASCADE.
I want to create the Entity class just like I have done in below image.
#Entity
#Table(name="posts")
#NoArgsConstructor
#Getter
#Setter
public class **Post** {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name="post_tittle",length = 100, nullable = false)
private String tittle;
#Column(name="post_image", nullable = false)
private String imageName;
private String content;
private Date addedDate;
#ManyToOne
#JoinColumn(name="category_id")
private Category category;
#ManyToOne
private User user;
#OneToMany(mappedBy = "post",cascade = CascadeType.ALL)
private Set<Comment> comments = new HashSet<>();
}
You can find a lot of examples and articles online that explain how ManyToMany mappings work. Just search with your favorite search engine for "JPA ManyToMany" and you will find an article like the following: https://en.wikibooks.org/wiki/Java_Persistence/ManyToMany
I'm creating rating system for simple web application that allows users to post text, similiar to twitter's wall. I've got two entities:
First one:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(unique = true)
private String login;
private String hashPassword;
private String name;
private String surname;
#Column(unique = true)
private String email;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "connectedUser", fetch = FetchType.EAGER)
private List<Post> userPosts = new ArrayList<>();
Second one:
#Entity
#Table(name = "post")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String text;
#NotNull
private LocalDateTime postDate;
#ManyToOne(optional = false)
#JoinColumn(name = "user_id")
private User connectedUser;
And I'm trying to figure out, where/how to add something responsible for rating. Every single post can be rated once per user (Plus or minus) and total sum of rate should be displayed nearby. It seems simple, I need separate table in database with user_id, post_id, and rate, but how could I do that in Hibernate (Hibernate creates database by itself)? Is there any simple solution for this?
If you need additional table - you need additional Entity.
For storing the user actions related to post:
#Entity
#Table(name = "user_post_rate")
public class UserPostRate {
#OneToMany
private Post post;
#OneToOne
private User user;
private boolean upvote;
// ...
}
It could be just boolean value if you have two fixed actions related to the post. You can replace it with some integer values, let's say, for example if privileged user can upvode it for + n, or user can upvote it again after some time and etc.
However you still need sum of rated values to be stored somewhere (not to calculate it time after time).
The overall post score is not a good place to be stored in the same table when user-post related actions are stored, because you will keep many unnecessary duplicates here (until you'll need to keep track of post score history). You can store it in Post entity, because the score of the post is part of its state:
#Entity
#Table(name = "post")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
// skipped
private long score;
}
Every time a user will rate the post, update for the post entity score should be triggered.
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.
I have a versioning on an entity as part of its primary key. The versioning is done via a timestamp of the last modification:
#Entity
#Table(name = "USERS")
#IdClass(CompositeKey.class)
public class User {
#Column(nullable = false)
private String name;
#Id
#Column(name = "ID", nullable = false)
private UUID id;
#Id
#Column(name = "LAST_MODIFIED", nullable = false)
private LocalDateTime lastModified;
// Constructors, Getters, Setters, ...
}
/**
* This class is needed for using the composite key.
*/
public class CompositeKey {
private UUID id;
private LocalDateTime lastModified;
}
The UUID is translated automatically into a String for the database and back for the model. The same goes for the LocalDateTime. It gets automatically translated to a Timestamp and back.
A key requirement of my application is: The data may never update or be deleted, therefore any update will result in a new entry with a younger lastModified. This requirement is satisfied with the above code and works fine until this point.
Now comes the problematic part: I want another object to reference on a User. Due to versioning, that would include the lastModified field, because it is part of the primary key. This yields a problem, because the reference might obsolete pretty fast.
A way to go might be depending on the id of the User. But if I try this, JPA tells me, that I like to access a field, which is not an Entity:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#OneToOne(optional = false)
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
#Column(nullable = false)
private boolean married;
// Constructors, Getter, Setter, ...
}
What would be the proper way of solving my dilemma?
Edit
I got a suggestion by JimmyB which I tried and failed too. I added the failing code here:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#OneToMany
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private List<User> users;
#Column(nullable = false)
private boolean married;
public User getUser() {
return users.stream().reduce((a, b) -> {
if (a.getLastModified().isAfter(b.getLastModified())) {
return a;
}
return b;
}).orElseThrow(() -> new IllegalStateException("User detail is detached from a User."));
}
// Constructors, Getter, Setter, ...
}
What you seem to require seems to be on the lines of a history table, to keep track of the changes. See https://wiki.eclipse.org/EclipseLink/Examples/JPA/History on how EclipseLink can handle this for you while using normal/traditional JPA mappings and usage.
What you have here is a logical 1:1 relationship which, due to versioning, becomes a technical 1:n relationship.
You have basically three options:
Clean JPA way: Declare an 'inverse' #ManyToOne relationship from user to the "other object" and make sure you always handle it whenever a new User record is created.
'Hack-ish' way: Declare a #OneToMany relationship in the "other object" and force it to use a specific set of columns for the join using #JoinColumn. The problem with this is that JPA always expects unique reference over the join columns so that reading the UserDetail plus referenced User records should work, whereas writing UserDetail should not cascade onto User to avoid unwanted/undocumented effects.
Just store the user's UUID in the "other object" and resolve the reference yourself whenever you need it.
The added code in your question is wrong:
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
More correct, albeit not with the result you want, would be
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private User user;
This won't work though, because, as I said above, you may have more than one user record per UserDetail, so you'd need a #OneToMany relationship here, represented by a Collection<User>.
Another 'clean' solution is to introduce an artificial entity with a 1:1 cardinality w.r.t. to the logical User to which you can refer, like
#Entity
public class UserId {
#Id
private UUID id;
#OneToMany(mappedBy="userId")
private List<User> users;
#OneToOne(mappedBy="userId")
private UserDetail detail;
}
#Entity
public class User {
#Id
private Long _id;
#ManyToOne
private UserId userId;
}
#Entity
public class UserDetail {
#OneToOne
private UserId userId;
}
This way, you can somewhat easily navigate from users to details and back.
I came to a solution, that is not really satisfying, but works. I created a UUID field userId, which is not bound to an Entity and made sure, it is set only in the constructor.
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#Column(nullable = false)
// no setter for this field
private UUID userId;
#Column(nullable = false)
private boolean married;
public UserDetail(User user, boolean isMarried) {
this.id = UUID.randomUUID();
this.userId = user.getId();
this.married = isMarried;
}
// Constructors, Getters, Setters, ...
}
I dislike the fact, that I cannot rely on the database, to synchronize the userId, but as long as I stick to the no setter policy, it should work pretty well.