JPA #OneToMany get latest record by date from Join - spring

Getting stuck trying to fetch the latest record from a Join
I have the following classes
Author
#Entity
#Table(name = "author")
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "name")
private String name;
#OneToMany
#JoinColumn(name = "author_id", referencedColumnName = "id")
#OrderBy("id Desc")
private List<Book> books;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Book> getBooks() {
return books;
}
public void setBooks(List<Book> books) {
this.books = books;
}
}
Book
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "author_id")
private Integer authorId;
#Column(name = "date_published")
private Date datePublished;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Integer getAuthorId() {
return authorId;
}
public void setAuthorId(Integer authorId) {
this.authorId = authorId;
}
public Date getDatePublished() {
return datePublished;
}
public void setDatePublished(Date datePublished) {
this.datePublished = datePublished;
}
}
Repository
#Repository
public interface AuthorRepository extends
JpaRepository<Author, Long> {
public Page<Author> findALL(int id, Pageable pageable);
}
Current results
{
"id": 1,
"name": "James",
"books":[
{
"id": 1,
"name": "book1",
"datePublished": '12/12/2012'
},
{
"id": 1,
"name": "book2",
"datePublished": '01/02/2013'
}]
},
{
"id": 2,
"name": "Tim",
"books":[
{
"id": 5,
"name": "book5",
"datePublished": '12/12/2014'
},{
"id": 6,
"name": "book6",
"datePublished": '01/02/2015'
}]
}
Expected Result
{
"id": 1,
"name": "James",
"books":[
{
"id": 1,
"name": "book2",
"datePublished": '01/02/2013'
}]
},
{
"id": 2,
"name": "Tim",
"books":[
{
"id": 6,
"name": "book6",
"datePublished": '01/02/2015'
}]
}
From this a list of Authors are being returned with all their respective books.
Question is how can JPA assist me to pick only the latest book from the collection based on date published.

If you are using hibernate you can achieve this using #JoinFormula to map the latest record by date. Something like:
#ManyToOne(fetch = FetchType.LAZY)
#JoinFormula("(" +
"SELECT b.id " +
"FROM book b " +
"WHERE b.author_id = id " +
"ORDER BY b.date_published DESC " +
"LIMIT 1" +
")")
private Book latestBook;

I had similar problem. The other solution is with #Where annotation:
#OneToMany
#JoinColumn(name = "author_id", referencedColumnName = "id")
#Where(clause = "date_published = (SELECT MAX(book.date_published) " +
"FROM book" +
"where author_id = book.author_id)")
#OrderBy("datePublished Desc")
private List<Book> books;
My post on stack: Get applications with their latest status only

if you want to get last book for each author , you can add transient field to get it :
#Entity
#Table(name = "author")
public class Author {
.......
#Transient
private Book lastBook;
#PostLoad
private void setLastBook() {
if(books==null || books,isEmpty())
this.lastBook=null;
}else{
this.lastBook=books.get(0);
}
}
or make it one to one and save it in db by same method annotated with #PostPersist and #PostUpdate. it will save in db automatically last book

the answer by #Seldo97 is correct, but he missed the space between "book" and "where" in the Select query, which will throw an error.
so basically it should be
#OneToMany
#JoinColumn(name = "author_id", referencedColumnName = "id")
#Where(clause = "date_published = (SELECT MAX(book.date_published) " +
"FROM book" +
" where author_id = book.author_id)")
#OrderBy("datePublished Desc")
private List<Book> books;
author_id -> basically refers to foreign key column name in the child.
entity
date_published -> this refers to the column by which we want to
sort(in this case the date column name).
so, the above query will take the record with the latest date and put it in the list object:
List<book> book;

Related

Is there a way to update and send the name to the response after calling the save method in spring?

There are employees and department class as below.
Employee
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToOne
private Department department;
}
Department
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
I want to save the employee and return the name of the department together.
Service
public ResponseEntity<Employee> create(Employee employee) throws URISyntaxException {
Employee savedEmployee = repository.save(employee);
URI location = new URI(String.format("/employee/%s", savedEmployee.getId()));
return ResponseEntity.created(location).body(savedEmployee);
}
Controller
#PostMapping
public ResponseEntity<Employee> postEmployee(#RequestBody Employee employee) throws URISyntaxException {
return service.create(employee);
}
I want to receive the response as below.
Request Body :
{
"name":"David",
"department":{
"id":1
}
}
Actual Response Body:
{
"id": 1,
"name": "David",
"department": {
"id": 1,
"name": null
}
}
Wanted Response Body :
{
"id": 1,
"name": "David",
"department": {
"id": 1,
"name": "HR"
}
}
Is there an easy way?

How to fix update process in Spring Boot (One-to-Many , One-to-One) via Postman?

I have a problem about updating the movie.
I wrote a function that is named for "update" in MovieService.
Here is that function which is shown below.
public void update(Long id,Movie movie) {
boolean isUpdatingEmployee = (movie.getId() == id);
if (isUpdatingEmployee) {
Movie existingMovie = movieRepository.findById(movie.getId()).get();
existingMovie.setId(id);
existingMovie.setName(movie.getName());
existingMovie.setRating(movie.getRating());
existingMovie.setDirector(movie.getDirector());
existingMovie.setGenres(movie.getGenres());
existingMovie.setCreatedAt(movie.getCreatedAt());
movieRepository.save(existingMovie);
}
}
When ı try to update a movie after saving it, I got this kind of JSON result and that's why the update process cannot be done.
http://localhost:8082/api/v1/movie/update/1
Body Request
{
"name": "MovieC",
"genres": [
{
"name" : "Adventure"
},
{
"name" : "Action"
}
],
"createdAt": "2021-04-28",
"rating" : 9,
"director" : {
"name" : "Director 2"
}
}
The result of JSON after updating the process.
{
"id": null,
"name": "MovieC",
"genres": [
{
"id": null,
"name": "Action"
},
{
"id": null,
"name": "Adventure"
}
],
"rating": 9.0,
"createdAt": "2021-04-28",
"director": {
"id": null,
"name": "Director 2"
}
}
Here is my Movie entity which is shown below.
#Entity
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
public class Movie implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#JsonManagedReference
#OneToMany(mappedBy="movie",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set<Genre> genres;
private Double rating;
private LocalDate createdAt;
#ManyToOne(cascade=CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn
private Director director;
}
Here is my Director entity which is shown below.
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
#NoArgsConstructor
#JsonIgnoreProperties({"movies"})
public class Director implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#OneToMany(mappedBy="director",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set<Movie> movies;
}
Here is my Genre entity which is shown below.
#Entity
#Getter
#Setter
#ToString
#RequiredArgsConstructor
#NoArgsConstructor
#JsonIgnoreProperties({"movie"})
public class Genre implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#JsonBackReference
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn
private Movie movie;
}
Here is my sample project link : Project Link
How can I fix it?
As per your code, this is your request:
http://localhost:8082/api/v1/movie/update/1
{
"name": "MovieC",
"genres": [
{
"name" : "Adventure"
},
{
"name" : "Action"
}
],
"createdAt": "2021-04-28",
"rating" : 9,
"director" : {
"name" : "Director 2"
}
}
Now consider this snippet from your code:
public void update(Long id,Movie movie) {
boolean isUpdatingEmployee = (movie.getId() == id);
if (isUpdatingEmployee) {
...
Your id will be 1 as you've set this in your path variable.
However, movie.getId() will be null since I don't see it in your RequestBody.
And so:
isUpdatingEmployee = (movie.getId() == id)`
isUpdatingEmployee = ( null == 1)
isUpdatingEmployee = false
this will always give you false so I don't think this will enter in your update logic.
I think the problem because you are returning the same object movie you passed in the body of the post method in the controller - https://github.com/Rapter1990/springboothazelcast/blob/3157f354a628d418cccb99cfdbd188f594c24e9c/src/main/java/com/springboot/hazelcast/controller/MovieController.java#L64
You should rewrite it to something like this:
#PostMapping("/save")
public Movie saveMovie(#RequestBody Movie movie) throws ParseException {
LOG.info("MovieController | Saving Movie.");
return movieService.save(movie);
}
Here is the link to CRUDRepository javadocs:
https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html#save-S-

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

How to select specific fields and return the map as the result on a one to many query with spring data jpa?

There are two entities including Country and City whose relationship is one to many. The query result I want is a Map and child entity city as a Country field cityList should be also returned in one query, no N+1 query.
Entity code:
City
#Entity
#Data
#NoArgsConstructor
public class City {
private Integer cityId;
private String cityName;
private Timestamp lastUpdate;
#Id
#Column(name = "city_id", nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getCityId() {
return cityId;
}
public void setCityId(int cityId) {
this.cityId = cityId;
}
#Basic
#Column(name = "city_name", nullable = true, length = 255)
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
#Basic
#Column(name = "last_update", nullable = true)
public Timestamp getLastUpdate() {
return lastUpdate;
}
public void setLastUpdate(Timestamp lastUpdate) {
this.lastUpdate = lastUpdate;
}
}
Country
#Entity
public class Country {
private Integer countryId;
private String countryName;
private Timestamp lastUpdate;
private List<City> cityList;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "country_id", nullable = false)
public Integer getCountryId() {
return countryId;
}
public void setCountryId(int countryId) {
this.countryId = countryId;
}
#Basic
#Column(name = "country_name", nullable = true, length = 255)
public String getCountryName() {
return countryName;
}
public void setCountryName(String countryName) {
this.countryName = countryName;
}
#Basic
#Column(name = "last_update", nullable = true)
public Timestamp getLastUpdate() {
return lastUpdate;
}
public void setLastUpdate(Timestamp lastUpdate) {
this.lastUpdate = lastUpdate;
}
#OneToMany
#JoinColumn(name = "country_id")
public List<City> getCityList() {
return cityList;
}
public void setCityList(List<City> cityList) {
this.cityList = cityList;
}
}
Repo
public interface CountryRepo extends JpaRepository<Country, Integer> {
#Query(value = "select new map(c.countryId as countryId , c.countryName as countryName , c.cityList as cityList) from Country c left join fetch c.cityList")
List findAllCountry3();
}
Expected result:
[
{
countryId: 1,
countryName: "China",
cityList: [
{
cityId: 1,
cityName: "Dalian",
lastUpdate: "2020-04-03T10:28:57.000+0000"
},
{
cityId: 2,
cityName: "Beijing",
lastUpdate: "2020-04-03T10:29:15.000+0000"
}
]
},
{
countryId: 2,
countryName: "US",
cityList: [
{
cityId: 3,
cityName: "New York",
lastUpdate: "2020-04-03T10:29:47.000+0000"
},
{
cityId: 4,
cityName: "Florda",
lastUpdate: "2020-04-03T10:30:05.000+0000"
}
]
},
{
countryId: 3,
countryName: "Japan",
cityList: [
{
cityId: 5,
cityName: "Tokyo",
lastUpdate: "2020-04-05T06:47:16.000+0000"
}
]
}
]
When I started spring boot, I got below error.
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=null,role=org.fxbird.entity.Country.cityList,tableName=city,tableAlias=citylist1_,origin=country country0_,columns={country0_.country_id ,className=org.fxbird.entity.City}}]
Any idea? Thanks

Getting a tag as additional object returned in json while searching images by that tag

Okay first of all I don't even know how to describe it in one sentence to ask google ( yes I did and I failed).
I have this gallery spring boot back-end application.
So there is Image class and Tag class between which is manytomany relationship.
Here are these classes:
#Entity
#Table(name = "image")
#JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class)
public class Image {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "image_id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#Column(name = "height")
private int height;
#Column(name = "width")
private int width;
#Column(name = "date")
private Date date;
#ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(
name="image_tag",
joinColumns = #JoinColumn(name="image_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id")
)
private Set<Tag> tags = new HashSet<>();
// getters / setters further
And this is my tag class:
#Entity
#Table(name = "tag")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "tag_id")
private int id;
#Column(name ="name", length= 50)
private String name;
#Column(name = "createddate", length = 100)
private Date createdDate;
public Tag(String name) {
this.name = name;
}
public Tag() {
}
#Override
public String toString() {
return "Tag{" +
"id=" + id +
", name='" + name + '\'' +
", createdDate=" + createdDate +
'}';
}
#ManyToMany(mappedBy = "tags")
private Set<Image> images = new HashSet<>();
This is how I get my Images by tag's name:
#Transactional
#Override
public List<Image> findByTag(String tag) {
Session session = entityManager.unwrap(Session.class);
Query<Image> query = session.createQuery("from Image as img inner join
img.tags as tags where tags.name=:tagname");
query.setParameter("tagname", tag);
List<Image> images = query.getResultList();
return images;
}
The problem is that whenever I make a get request with tag's name to get Images. Next to every Image object I receive there will also be that tag object with the name I used to look for images.
{
"#id": 2,
"id": 6,
"name": "peoples",
"description": "some people hanging",
"height": 501,
"width": 780,
"date": "2019-09-02T12:34:23.000+0000",
"tags": [
// there are more tags here I'm just saving space
{
"id": 7,
"name": "joy",
"createdDate": "2019-09-03T12:27:49.000+0000",
"images": [
2
]
}
],
"categories": [],
"picture": " byte array of picture"
},
Aand this is where magic happens, this object will be next to every Image object in Json response.
{
"id": 7,
"name": "joy",
"createdDate": "2019-09-03T12:27:49.000+0000",
"images": [
2
]
}
I'm using Angular for front end and I can't fetch Images like this because I'll get an error because there will be tag object there.
Really hoping to figure this out!
#GetMapping("/tags/{tagname}")
public List<Image> getImageByTag(#PathVariable("tagname") String tagName){
return imageService.findByTag(tagName);
}
And this is that service:
#Override
public List<Image> findByTag(String tag) {
return imageDao.findByTag(tag);
}
It's more like a json serialize problem. If you are using jackson as I guessed, you can try to manually control the output by following:
public class Image {
#JsonBackReference
private Set<Tag> tags = new HashSet<>();
}
If you do need to include tag in some occasions, you might have to implement a JSON view

Resources