Java Spring JPA magical update - java

I am having two Entities:
Aircraft
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.jpa.domain.AbstractPersistable;
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
//#ToString
public class Aircraft extends AbstractPersistable<Long> {
private String name;
#ManyToOne
private Airport airport;
#Override
public String toString(){
return name;
}
}
And Airport:
public class Airport extends AbstractPersistable<Long> {
private String identifier;
private String name;
#OneToMany(mappedBy = "airport")
private List<Aircraft> aircrafts = new ArrayList<>();
#Override
public String toString(){
String aircraftString = aircrafts.stream().map(r -> r.toString()).collect(Collectors.joining(", "));
return "airport " + name + " " + "[" + aircraftString + "]";
}
}
And a #PostMapping in #Controller like this:
#PostMapping("/aircrafts/{aircraftId}/airports")
public String assignAirport(#RequestParam Long airportId,#PathVariable Long aircraftId){
Aircraft aircraft = aircraftRepository.getOne(aircraftId);
Airport airport = airportRepository.getOne(airportId);
aircraft.setAirport(airport);
aircraftRepository.save(aircraft)
System.out.println(airport.toString());
System.out.println(aircraft.toString());
return "redirect:/aircrafts";
}
This works. But I can not figure out how does this
private List<Aircraft> aircrafts = new ArrayList<>();
List get updated?
I first figured it should work like this in #Controller
airport.getAircrafts().add(aircraft);
But that does not do anything.

The OneToMany relation in the airport class only reflecting what is persisted in the ManyToOne relation in the aircraft class so whenever you will add a new plane that has an airport, this airport planes List will be updated.
Side Note:
If you want to achieve the behavior you were expecting you can add
#OneToMany(mappedBy = "airport", cascade = CascadeType.ALL)
private List<Aircraft> aircrafts = new ArrayList<>();
public void addAircraft(Aircraft aircraft){
aircraft.setAirport(this);
aircrafts.add(aircraft);
}
to your airport class and then save the airport entity.
but I think this isn't what you want and also it's not beneficial in your current case

Related

repository.save function changes id of it's children entity

I'm trying to update existing entry in parent Entity and I encounter error I can't understand nor resolve.
I have two entities in a simple crud repository - Parent(User) and Children(movie). I am trying to pass a favourite movie to an user. The goal is that the movie doesn't have to be already in database, and the #PostMapping has to accept an user_id and movie name as parameters, other method uses the movie name, goes through the OMDBapi, parses data from json to fields and then gives the user at user_id the movie as a favourite. The PostMapping sort of works, because it gets the user at user_id, the movie is also added, but when the url looks like this - http://localhost:8080/users/2/fight+club the user at user_id 2 gets the movie as his favourite, but the movie gets it's id also as 2, even if it's first movie being added to repository. What I don't understand is why when I try to debug this every line of code is acting as I expect it to do -
wUser(id=2, name=Jan, favouriteMovies=[Movie(id=1, title=Fight Club, plot=An insomniac office worker and a devil-may-care soap maker form an underground fight club that evolves into much more., genre=Drama, director=David Fincher, posterURL=https://m.media-amazon.com/images/M/MV5BNDIzNDU0YzEtYzE5Ni00ZjlkLTk5ZjgtNjM3NWE4YzA3Nzk3XkEyXkFqcGdeQXVyMjUzOTY1NTc#._V1_SX300.jpg)])
but after it passes repository.save(user) line I get redirected to InvocableHandlerMethod class, into doInvoke method, into
return KotlinDetector.isSuspendingFunction(method) ? this.invokeSuspendingFunction(method, this.getBean(), args) : method.invoke(this.getBean(), args);
this line, and after that it's just deep into the rabbit hole. As I am quite an inexperienced in coding in Java, what probably can be deducted, I don't really understand nor can find solution to this problem.
The entities and controller classes below
package com.example.omdbapirest.movie;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="movie_id")
private Integer id;
private String title;
private String plot;
private String genre;
private String director;
private String posterURL;
public Movie(String title, String plot, String genre, String director, String posterURL) {
this.title = title;
this.plot = plot;
this.genre = genre;
this.director = director;
this.posterURL = posterURL;
}
}
package com.example.omdbapirest.user;
import com.example.omdbapirest.movie.Movie;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
public class wUser {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
// #OneToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH})
#OneToMany(cascade =CascadeType.ALL)
#JoinColumn(name = "movie_id")
private List<Movie> favouriteMovies;
public wUser(String name) {
this.name = name;
}
}
UserController
package com.example.omdbapirest.user;
import com.example.omdbapirest.movie.Movie;
import com.example.omdbapirest.movie.MovieService;
import lombok.RequiredArgsConstructor;
import org.json.simple.parser.ParseException;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
#RestController
#RequestMapping("/users")
#RequiredArgsConstructor
public class UserController {
private final MovieService movieService;
private final UserRepository repository;
private final UserService service;
#GetMapping
public List<wUser> getUsers(){
return repository.findAll();
}
#PostMapping("/{id}/{moviename}")
public void addMovieAsFavorite (#PathVariable (name= "id") int id,
#PathVariable (name="moviename") String moviename)
throws ParseException{
String url = "https://www.omdbapi.com/?t="+moviename+"&apikey=30ccf40c";
wUser user = repository.getById(id);
List<Movie> movies = user.getFavouriteMovies();
List<Movie>moviesToAdd = new ArrayList<>();
Movie movie = movieService.getDataFromOMDBAsMovie(url);
movies.add(movie);
moviesToAdd.addAll(movies);
user.setFavouriteMovies(moviesToAdd);
repository.save(user);
}
}
I'm also adding MovieService class in case there is some error in the JSON parser
package com.example.omdbapirest.movie;
import lombok.RequiredArgsConstructor;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.List;
#Service
#RequiredArgsConstructor
public class MovieService {
private final MovieRepository repository;
public String getJSONFromURL(String strUrl) {
String jsonText = "";
try {
URL url = new URL(strUrl);
InputStream is = url.openStream();
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(is));
String line;
while ((line = bufferedReader.readLine()) != null) {
jsonText += line + "\n";
}
is.close();
bufferedReader.close();
} catch (Exception e) {
e.printStackTrace();
}
return jsonText;
}
public Movie getDataFromOMDBAsMovie(String strURL) throws ParseException {
String json = getJSONFromURL(strURL);
Movie movie = new Movie();
JSONParser parser = new JSONParser();
Object object = parser.parse(json);
JSONObject mainJsonObject = (JSONObject) object;
String title = (String)mainJsonObject.get("Title");
movie.setTitle(title);
String plot = (String)mainJsonObject.get("Plot");
movie.setPlot(plot);
String genre = (String)mainJsonObject.get("Genre");
movie.setGenre(genre);
String director = (String)mainJsonObject.get("Director");
movie.setDirector(director);
String posterURL = (String)mainJsonObject.get("Poster");
movie.setPosterURL(posterURL);
repository.save(movie);
return movie;
}
public Movie addMovie(Movie movie){
return repository.save(movie);
}
}
I tried adding movies to db, reworking the favourite saving class, all to no avail, I was getting different errors when not debuging, including
org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Pole nie może być NULL"MOVIE_ID"(Field cannot be NULL)
NULL not allowed for column "MOVIE_ID"; SQL statement:
update movie set movie_id=null where movie_id=? [23502-214]
and
org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Naruszenie ograniczenia Klucza Głównego lub Indeksu Unikalnego: "PRIMARY KEY ON PUBLIC.MOVIE(MOVIE_ID)(translating to- Unique Index or primary key violated)
( /* key:2 */ 2, 'David Fincher', 'Drama', 'An insomniac office worker and a devil-may-care soap maker form an underground fight club that evolves into much more.', 'https://m.media-amazon.com/images/M/MV5BNDIzNDU0YzEtYzE5Ni00ZjlkLTk5ZjgtNjM3NWE4YzA3Nzk3XkEyXkFqcGdeQXVyMjUzOTY1NTc#._V1_SX300.jpg', 'Fight Club')"
Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.MOVIE(MOVIE_ID) ( /* key:2 */ 2, 'David Fincher', 'Drama', 'An insomniac office worker and a devil-may-care soap maker form an underground fight club that evolves into much more.', 'https://m.media-amazon.com/images/M/MV5BNDIzNDU0YzEtYzE5Ni00ZjlkLTk5ZjgtNjM3NWE4YzA3Nzk3XkEyXkFqcGdeQXVyMjUzOTY1NTc#._V1_SX300.jpg', 'Fight Club')"; SQL statement:
insert into movie (director, genre, plot, posterurl, title, movie_id) values (?, ?, ?, ?, ?, ?) [23505-214]
Both of these errors appear when I try to add another movie to given user, I mean I was able to give all users 1 movie, but never more since it tries to always add the movie with id of the user
Let's focus on the relevant part of your mapping:
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="movie_id")
private Integer id;
}
and
public class wUser {
#OneToMany(cascade =CascadeType.ALL)
#JoinColumn(name = "movie_id")
private List<Movie> favouriteMovies;
}
The id property of Movie is mapped to the table column movie_id by the configuration in the Movie class.
But for the wUser.favouriteMovies you use #JoinColumn to make it use movie_id the join column, i.e. the column in the Movie table that references the wUser.
By this that column is mapped to two completely different values and it seems in your scenario the second one wins.
To fix this simply choose a different column for the join column. user_id might be a good choice.

How to handle duplicates in M-M (manytomany) relations in Java's Quarkus framework?

I'm working on a Quarkus codebase where 'many-to-many' relation is required. I'm using hibernate's #ManyToMany annotation for this and checking duplicates in the setter function of the parent entity.
For e.g Let's assume there are two entities Fruits and Categories.
Fruits.java
package org.acme.hibernate.orm.panache;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import io.quarkus.panache.common.Parameters;
import javax.persistence.*;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
#Entity
#Cacheable
public class Fruit extends PanacheEntity {
#Column(length = 40, unique = true)
public String name;
#ManyToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST }, mappedBy = "fruits")
public List<Category> categories;
public Fruit () {
}
public Fruit (String name) {
this.name = name;
}
public void setCategories (List<Category> categories) {
this.categories = categories.stream().map(category -> {
Category category1 = Category.find("name = :name", Parameters.with("name", category.name)).firstResult();
if (category1 != null) {
category1.fruits.add(this);
return category1;
}
return category;
}).filter(Objects::nonNull)
.collect(Collectors.toList());
}
}
Category.java
package org.acme.hibernate.orm.panache;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import javax.persistence.*;
import java.util.List;
#Entity
#Cacheable
public class Category extends PanacheEntity {
#Column(length = 40, unique = true)
public String name;
#ManyToMany(cascade = { CascadeType.MERGE, CascadeType.REFRESH })
public List<Fruit> fruits;
public Category() {
}
public Category(String name) {
this.name = name;
}
}
When I'm trying to save fruit entity with categories, it is successfully getting saved the first time. But the second time I'm getting
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: org.acme.hibernate.orm.panache.Category
What is the correct way to do this?
Find the code for above example here

findAll from org.springframework.data.jpa.repository.support.SimpleJpaRepository returning duplicates

So I have repository defined as follows:
public interface PersonRepository extends CrudRepository<Person, Long>
My entity is as follows:
#Entity
#Table(name="Person")
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="PERSON_GENERATOR", sequenceName="PERSON_SEQ")
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="PERSON_GENERATOR")
private Long id;
#Column(name="ssn")
private String socialSecurityNumber;
private String name;
public Person() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
I have ommitted the getters and setters for socialSecurityNumber and Name. In the database there are 1000 records.
I have a Spring bean annotated with #Component that uses the PersonRepo by calling its findAll(). When findAll() gets called I get the list of 1000 records with their UNIQUE ID however, when I loop through the list of Persons returned by findAll() I find unexpected results.
#Component
public class PersonComponent {
#Autowired
PersonRepository personRepo;
public void printPerson() {
List<Person> people = personRepo.findAll();
for(Person person : people) {
System.out.println("id=" + person.getId() + ", ssn=" + person.getSocialSecurityNumber() + ", name=" + person.getName());
}
}
}
So if in my db i have the records
id, ssn, name
1, ssn1, Bob
2, ssn2, Mary
3, ssn3, Joe
when I call the findAll() I constantly get this back
id=1, ssn=ssn1, name=Bob
id=2, ssn=ssn2, name=Mary
id=3, ssn=ssn2, name=Mary
Notice that i get the correct Ids (ie. 1, 2, 3) but for some reason id 3 is mapped to ssn2,Mary and NOT ssn3, Joe
This behaviour happens only after the first call to findAll() (ie. the first findAll, works fine but subsequent once show the behaviour explained above). In other words, when the application starts up and the Spring bean that uses the PersonRepo gets called for the first time, findAll() seems to work fine. But when a subsequent call is made to the Spring Bean then findAll behaves as described.
Lastly, When i call the web service http://localhost/persons (which calls `findAll() under the covers) I get the correct behaviour every time.
Any thoughts?
The problem is probably relating to omitting getters and setters for name and social security variables ? After all those are declared private so we need a way to access them, hence why getters ?
I have replicated your app and if you use the following, you should not be getting duplicates at all.
Appllication.properties file:
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/findall_db
spring.datasource.username=YOUR_DATABASE_USERNAME
spring.datasource.password=YOUR_DATABASE_PASSWORD
Person Entity Java Class:
package com.example.demo;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
#Entity
#Table(name="Person")
public class PersonInfo implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="PERSON_GENERATOR", sequenceName="PERSON_SEQ")
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="PERSON_GENERATOR")
private Long id;
#Column(name="ssn")
private String socialSecurityNumber;
private String name;
public PersonInfo() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getSocialSecurityNumber() {
return socialSecurityNumber;
}
public void setSocialSecurityNumber(String socialSecurityNumber) {
this.socialSecurityNumber = socialSecurityNumber;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "PersonInfo [id=" + id + ", socialSecurityNumber=" + socialSecurityNumber + ", name=" + name + "]";
}
}
Person Repository:
package com.example.demo;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface PersonInfoRepository extends CrudRepository<PersonInfo, Long>{
}
Person Controller:
package com.example.demo;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
#Controller
public class PersonController {
#Autowired
PersonInfoRepository personRepo;
#ResponseBody
#GetMapping("/people")
public List printPersonInfo() {
List<PersonInfo> people = (List<PersonInfo>) personRepo.findAll();
System.out.println(people.toString());
return people ;
}
}

Not able to map fields using Hibernate Mapping

I want to establish one to many relation between table vendor detail and product detail. like one vendor can have multiple products. but when i am inserting data into table its inserting all the four fields but not mapping vendorid into ProductDetail Table
and query generated is this.
Hibernate: insert into ProductInfo (productCategory, productDetails, productPrice, VendorId) values (?, ?, ?, ?) It shuld map vendor ID also but in table its empty.
VendorDetail.java
package com.cts.entity;
import javax.persistence.*;
#Entity
#Table(name = "VendorInfo")
public class VendorDetails {
#Id
#Column
private Long VendorId;
#OneToMany
private ProductDetails productdetail;
#Column
private String VendorName;
#Column
private String Password;
public String getVendorName() {
return VendorName;
}
public void setVendorName(String vendorName) {
VendorName = vendorName;
}
public Long getVendorId() {
return VendorId;
}
public void setVendorId(Long vendorId) {
VendorId = vendorId;
}
public String getPassword() {
return Password;
}
public void setPassword(String password) {
Password = password;
}
}
ProductDetails.java
package com.cts.entity;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.Table;
#Entity#Table(name = "ProductInfo")
public class ProductDetails {
#ManyToOne(cascade = CascadeType.ALL)#JoinColumn(name = "VendorId")
private VendorDetails vendordetails;
public ProductDetails() {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int productId;
#Column
private String productCategory;
#Column
private String productDetails;
#Column
private String productPrice;
public VendorDetails getVendordetails() {
return vendordetails;
}
public void setVendordetails(VendorDetails vendordetails) {
this.vendordetails = vendordetails;
}
public int getProductId() {
return productId;
}
public void setProductId(int productId) {
this.productId = productId;
}
public String getProductCategory() {
return productCategory;
}
public void setProductCategory(String productCategory) {
this.productCategory = productCategory;
}
public String getProductDetails() {
return productDetails;
}
public void setProductDetails(String productDetails) {
this.productDetails = productDetails;
}
public String getProductPrice() {
return productPrice;
}
public void setProductPrice(String productPrice) {
this.productPrice = productPrice;
}
}
DAO class ProductDetailDaoImpl.java
package com.cts.Dao;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.cts.entity.ProductDetails;
import com.cts.entity.to.ProductDetailsTo;
#Repository
public class ProductDetailDaoImpl implements ProductDetailDao {
#Autowired
SessionFactory sessionFactory;
#Transactional
public boolean saveProductInfo(ProductDetailsTo productTo) {
System.out.println("M in Registration DAO");
System.out.println(productTo.getProductCategory());
System.out.println(productTo.getProductDetails());
System.out.println(productTo.getProductId());
System.out.println(productTo.getProductPrice());
//getting productTo data to entity class
ProductDetails prodet = productTo.getEntity();
System.out.println("Value of product details is:" + prodet.getProductDetails());
sessionFactory.getCurrentSession().save(prodet);
return false;
}
}
VendorDetails has many ProductDetails so you need to make one to many annotation like this:-
#OneToMany(mappedBy="vendordetails") //mappedBy value will be what you declared //in ProductDetails class.
private Collection<ProductDetails> productdetail=new ArrayList<ProductDetails>;
and create the setter and getter of this.
Now in ProductDetails class you need to annotate many to one like this:-
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "VendorId")
private VendorDetails vendordetails;
Then a new column named 'VendorId' will be create in table 'ProductInfo' and since declare mappedBy value="vendordetails" so each vendor id would be insert.
I think you should replace the code
#OneToMany
private ProductDetails productdetail;
to
#OneToMany
private Set productdetailSet;
And create setter and getter for this.
You can visit the blog http://gaurav1216.blogspot.in/2014/01/hibernate-tutorial-day-5.html for one to many using annotation.

How to create an #Entity with a parent/child relationship

I am creating a website that will have articles; each article will have comments. These comments will be stored in a "comment" table with a field called "parent_id" that is a foreign key to the field "id" in the same table.
I am hoping to use Hibernate to recursively grab all of the comments for a specific article.
Here is my Entity:
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import org.hibernate.annotations.IndexColumn;
#Entity
public class Comment implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#ManyToOne
#JoinColumn(name="user_id")
private User user;
#OneToMany(targetEntity=Comment.class)
#JoinColumn(name="parent_id")
#IndexColumn(name="id", base=0)
private List<Comment> comments = new ArrayList<Comment>();
#Column(name="article_id", length=10)
private int articleId;
#Column(name="text", length=8192)
private String text;
public int getArticleId() {
return articleId;
}
public void setArticleId(int articleId) {
this.articleId = articleId;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
#Override
public String toString() {
return "Comment [" + "articleId " + articleId + " " + "id " + id + " " + "text " + text + " " + "]";
}
}
The code is "kinda" working, however, when all of the comments are retrieved the "child" list of comments contains the same number of elements as there are total comments (if that makes sense. For instance, assume I have only three comments in the table for article with id number 1... this article with 2 comments, and one of the comments has a child comment.. the array with the child comment has 3 entries, the first 2 are null and the last is the child comment.
Is this code correct?
I wonder if the problem is not coming from the fact that you are using the PK column as index column. I'd recommend to use a dedicated column for the index column. Something like this:
#OneToMany
#JoinColumn(name="parent_id")
#IndexColumn(name="comments_index", base=0)
private List<Comment> comments = new ArrayList<Comment>();
Could you give this a try?

Categories

Resources