FetchType.LAZY in ManyToMany doesnot work - spring

I'm having a problem with, fetch = FetchType.LAZY, it just doesn't work. I've already spent a lot of time solving this problem, can anyone help with this? I'll be very thankful. I have a genre and a country that are associated with movie manyTomany. No matter how hard I try to initialize the LAZY download, it doesn't work.I need the movie to have EAGER, and the genre and country to have LAZY.
I expect to get movie with its genre and country, But with SELECT * FROM movie WHERE id = 1 - I get an endless loop, although genre and country has LAZY download.
Sample code - below
Entities:
Movie
#Entity
#Getter
#Setter
#ToString(of = {"id", "year", "name"})
#EqualsAndHashCode(of = {"id", "year"})
#NoArgsConstructor
#AllArgsConstructor
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
**********
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "movie_genre",
joinColumns = {
#JoinColumn(name = "movie_id")},
inverseJoinColumns = {
#JoinColumn(name = "genre_id")})
private Set<Genre> genres = new HashSet<>();
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "movie_country",
joinColumns = {
#JoinColumn(name = "movie_id")},
inverseJoinColumns = {
#JoinColumn(name = "country_id")})
private Set<Country> countries = new HashSet<>();
}
Genre
#Entity
#Getter
#Setter
#ToString(exclude = "movies")
#EqualsAndHashCode(exclude = "movies")
#NoArgsConstructor
#AllArgsConstructor
public class Genre {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Size(max = 20)
private String name;
#ManyToMany(mappedBy = "genres")
private Set<Movie> movies = new HashSet<>();
}
Country
#Entity
#Getter
#Setter
#ToString(exclude = "movies")
#EqualsAndHashCode(exclude = "movies")
#NoArgsConstructor
#AllArgsConstructor
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Size(max = 20)
private String name;
#ManyToMany(mappedBy = "countries")
private Set<Movie> movies = new HashSet<>();
}
Controller
#RestController
public class TestController {
#Autowired
private MovieService movieService;
#Autowired
private CountryService countryService;
#Autowired
private GenreService genreService;
#GetMapping("movie")
public List<Movie> getMovieMovie(){
return movieService.getAll();
}
#GetMapping("movie/add")
public Movie create(){
Movie movie = new Movie();
movie.setName("test");
movie.setImg("test");
movie.setTime("test");
movie.setYear((short) 2332);
movie.setMovieLink("test");
movie.getCountries().add(countryService.getCountry(1));
movie.getGenres().add(genreService.getGenre(1));
return movieService.create(movie);
}
}
Service
#Service
public class MovieService {
#Autowired
private MovieRepository movieRepository;
public List<Movie> getAll(){
return movieRepository.findAll();
}
#Transactional
public Movie create(Movie mocie){
return movieRepository.save(mocie);
}
}

Lazy loading works as expected, as it loads all data lazy.
What you are looking for is a way to break loop in the bi-directional mapping.
There you can use #JsonManagedReference and #JsonBackReference that you have to set on the relationships.
Please also read this: https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion

Related

How to save list of children data in onetomany in Springboot jpa

Parent data is saving but list of children data is not saving in table.
data from postman
{"billno":"nur-1001", "grandTotal": 5000,"billcart":[{"itemcode":"SU10027", "soldPrice":0},{"itemcode":"SU10027","soldPrice":1100}]}
Bill is parent Entity & billcart is child entity
#NoArgsConstructor #AllArgsConstructor #Data #Entity
public class Bill {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String billno;
private Long grandTotal;
#OneToMany(mappedBy = "bill", cascade = CascadeType.ALL)
#JsonIgnore
private List<Billcart> billcart = new ArrayList<>();
public Bill( String billno, Long grandTotal, List<Billcart> billcart) {
this.billno = billno;
this.grandTotal = grandTotal;
this.billcart = billcart;
this.billcart.forEach(e -> e.setBill(this));
}
}
child entity
#NoArgsConstructor #AllArgsConstructor #Data #Entity
public class Billcart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String itemcode;
private Integer soldPrice;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "bill_id")
private Bill bill;
controller
public void saveBill(#RequestBody Bill request) {
List<Billcart> billscart = request.getBillcart().stream()
.map(e -> new Billcart(e))
.collect(Collectors.toList());
Bill bill = new Bill(request.getBillno(), request.getGrandTotal() , billscart);
billRepository.save(bill);

How to implements entity with 2 entity as primary key with jpa annotation and repository

i want to implement a many to many association with quantity information in it . like this :
#Entity
#Table(name = "reserves")
#Getter #Setter #NoArgsConstructor
public class Reserve {
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "groupe_id")
private GroupeSanguin bloodGroup;
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Banque banque;
private int quantity;
}
the GroupSanguin and the Banque are two class stored in the database two . here is the code for the two if you need :
#Entity
#Table(name = "groupe_sanguins")
public class GroupeSanguin {
#Id
private String groupe;
#OneToMany(mappedBy = "groupeSanguin")
private List<Donneur> donneurs;
}
#Entity #Getter #Setter #NoArgsConstructor
public class Banque {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true,nullable = false)
private String nom;
private String adresse;
#Column(unique = true)
private String telephone;
private String localisation;
}
so my i want to know how to annotate the JpaRepository to take the two as primary key like this and is my annotation good for it to work ?
public interface ReserveRepository extends JpaRepository<
Reserve,
//what to put here ?
>
This isn't a JPA question in fact, it's a relationnal database conception.
If Reserve has is own data and links with other entity it has it own Id
You can add unicity constraint
#Entity
#Table(name = "reserves", uniqueConstraints={
#UniqueConstraint(columnNames = {"banque_id", "groupe_id"})
#Getter #Setter #NoArgsConstructor
public class Reserve {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "groupe_id")
private GroupeSanguin bloodGroup;
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "banque_id")
private Banque banque;
private int quantity;
}
i've found this solutions too.
#Entity
#Table(name = "reserves")
#Getter #Setter #NoArgsConstructor
#IdClass(ReserveId.class) //this annotation will tell that id that the
// the id will be represented by a class
public class Reserve {
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "groupe_id")
private GroupeSanguin groupeSanguin;
#Id
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "banque_id")
private Banque banque;
private int quantity;
}
and the id class should implements Serializable like this :
#Getter #Setter
public class ReserveId implements Serializable {
private Banque banque;
private GroupeSanguin groupeSanguin;
}
and finally the repository will be like that :
#Repository
public interface ReserveRepo extends JpaRepository<Reserve, ReserveId>{}
See your Reserve class has nowhere mentioned composite primary key. First you need to fix the model, You can refer to the solution here How to create and handle composite primary key in JPA

Add extra custom column to auto mapped Table in Spring JPA ManyToMany

#Getter #Setter #NoArgsConstructor #AllArgsConstructor
#Table(name = "my_users")
public class MyUsers {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false)
private Long id;
#Column(nullable = false, unique = true)
private String userName;
private String password;
#ManyToMany
private List<MyUsers> connections;
}
This is my MyUsers Model Class. I am using Hibernate and MySQL.
#ManyToMany
private List<MyUsers> connections;
This ManyToMany relationship is automatically creating the table 'my_users_connections' with 'my_users_id' and 'connections_id' colums. How can I add extra columns to this auto mapped table?
It's not ideal solution...
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "my_users")
public class MyUsers implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false)
private Long myUsersId;
#Column(nullable = false, unique = false)
private String userName;
private String password;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "my_users_connections",
joinColumns = { #JoinColumn(name = "my_users_id") },
inverseJoinColumns = { #JoinColumn(name = "connections_id") })
private List<MyUsers> connections;
}
Create embedded id MyUsersConnectionsPK:
#Data
#Embeddable
public class MyUsersConnectionsPK implements Serializable {
#Column(name = "my_users_id")
private Long myUsersId;
#Column(name = "connections_id")
private Long connectionsId;
}
Create MyUsersConnections, which represent ManyToMany
#Data
#Entity
#Table(name = "my_users_connections")
public class MyUsersConnections implements Serializable {
#EmbeddedId
private MyUsersConnectionsPK id;
#ManyToOne
#MapsId("my_users_id")
#JoinColumn(name = "my_users_id")
private MyUsers myUsersId;
#ManyToOne
#MapsId("connections_id")
#JoinColumn(name = "connections_id")
private MyUsers connectionsId;
#Column(name = "extra_column")
private String extraColumn;
}
Create JPA repository
#Repository
public interface MyUsersConnectionsRepository extends JpaRepository<MyUsersConnections, MyUsersConnectionsPK> {
List<MyUsersConnections> findMyUsersConnectionsByMyUsersIdMyUsersId(Long id);
}
And simple sample for using:
#Service
public class Test {
#Autowired
private MyUsersConnectionsRepository myUsersConnectionsRepository;
#Autowired
private MyUsersRepository myUsersRepository;
public void test() {
MyUsers myUsers = new MyUsers();
myUsers.setUserName("user name");
myUsers.setPassword("password");
MyUsers myUsers2 = new MyUsers();
myUsers2.setUserName("user name 2");
myUsers2.setPassword("password 2");
myUsers.setConnections(Collections.singletonList(myUsers2));
myUsers = myUsersRepository.saveAndFlush(myUsers);
List<MyUsersConnections> myUsersConnections = myUsersConnectionsRepository.findMyUsersConnectionsByMyUsersIdMyUsersId(myUsers.getMyUsersId());
MyUsersConnections item = myUsersConnections.get(0);
item.setExtraColumn("Extra column");
myUsersConnectionsRepository.saveAndFlush(item);
}
}

(Do not display relationship values)

I have two entity with name of the article and article Category.
and they have one-to-many relationships.
I use #JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class,property = "id")
but I cant see data of article category(category_id) in spring data rest.
ArticleCategory.class
#Entity
#Table(name = "article_category")
#Getter
#Setter
public class ArticleCategory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "category_name")
private String categoryName;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "articleCategory", fetch = FetchType.LAZY)
private Set<Article> articles = new HashSet<>();
}
Article.class
#Entity
#Table(name = "article")
#Getter
#Setter
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Article implements Serializable {
public Article() {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", nullable = false)
private ArticleCategory articleCategory;
#Column(name = "title")
private String title;
#Column(name = "image_url")
private String image_url;
#Column(name = "short_description")
private String short_description;
#Column(name = "text")
private String text;
#Column(name = "keywords", nullable = true)
private String keywords;
#Column(name = "visit", nullable = false)
private int visit;
#Column(name = "code", nullable = false)
private UUID code;
#Column(name = "date_created")
#CreationTimestamp
private Date dateCreated;
#Column(name = "date_updated", nullable = false)
#UpdateTimestamp
private Date dateUpdated;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
public Article(String title, String image_url, String short_description, String text, String keywords, int visit, UUID code) {
this.title = title;
this.image_url = image_url;
this.short_description = short_description;
this.text = text;
this.keywords = keywords;
this.visit = visit;
this.code = code;
}
}
Article Repository
#CrossOrigin("http://localhost:4200")
#RepositoryRestResource(collectionResourceRel = "article", path = "article")
public interface ArticleRepository extends JpaRepository<Article,Long> {
Article findByCode(UUID uuid);
}
And this is output of spring data rest
enter image description here
That is exactly because you used #JsonManagedReference and #JsonBackReference. Keep in mind the following when using them:
#JsonManagedReference is the forward part of the relationship and is the one that gets serialized normally.
#JsonBackReference is the back part of the relationship and it will be omitted from serialization.
The serialized Article object does not contain a reference to the ArticleCategory object.
If you want to have any ArticleCategory data when serializing Article you can either use #JsonIdentityInfo so that one of the properties is serialized (in this case I've chosen id for both):
#Entity
#Table(name = "article")
#Getter
#Setter
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Article implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", nullable = false)
private ArticleCategory articleCategory;
}
#Entity
#Table(name = "article_category")
#Getter
#Setter
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class ArticleCategory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "category_name")
private String categoryName;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "articleCategory" ,fetch = FetchType.LAZY)
private Set<Article> articles=new HashSet<>();
}
If you are only interested in categoryId another possibility would be to use #JsonIgnore on private Set<Article> articles property so that it is not serialized:
#Entity
#Table(name = "article_category")
#Getter
#Setter
public class ArticleCategory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "category_name")
private String categoryName;
#JsonIgnore
#OneToMany(cascade = CascadeType.ALL,mappedBy = "articleCategory" ,fetch = FetchType.LAZY)
private Set<Article> articles=new HashSet<>();
}
If none of those suits your needs you might need to implement your own custom serializer. You can read more about all those options at https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion.
I solved the problem using the controller
And that's why #JsonManageRefrence and #JsonBackRefrence do not work
I replaced the lazy load with the eager load in both entity
#ManyToOne(fetch = FetchType.Eager)
#JoinColumn(name = "user_id")
#JsonManageRefrence
private User user;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "articleCategory",
fetch = FetchType.Eager)
#JsonBackRefrence
private Set<Article> articles = new HashSet<>();
and then add a controller
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
#RestController
#RequestMapping("/getAllArticle")
public class MyController {
private ArticleRepository articleRepository;
// you must do constructor injection
#GetMapping("/getAllArticle")
public List<Article> allArticle()
{
return articleRepository.findAll();
}
}

JPA ManyToMany - empty list in findById

In my Spring Boot project I have an entity class User
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#Builder
#Entity
#Table(name = "applicationusers")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
[...]
#Singular
#ManyToMany(
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
fetch = FetchType.EAGER)
#Fetch(value = FetchMode.SUBSELECT)
#JoinTable(
name = "applicationusers_roles",
joinColumns = { #JoinColumn(name = "applicationuser_id") },
inverseJoinColumns = { #JoinColumn(name = "role_id") }
)
private Set<Role> roles;
}
and a second entity class role
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#Builder
#Entity
#Table(name = "roles")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, unique = true)
#Length(min = 3, max = 15)
private String name;
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "roles")
#Fetch(value = FetchMode.SUBSELECT)
#Singular
private Set<User> applicationUsers;
}
In one of my tests, I try to store a Role with a User to the repository like this
#Test
public void createRoleWithUsers() {
User newUser = User.builder()
.name("name")
.password("1234567")
.email("hello#world.net")
.enabled(true)
.build();
User savedUser = userRepository.save(newUser);
Set<User> users = new HashSet<>();
users.add(savedUser);
Role role = Role.builder()
.name("TestRole")
.applicationUsers(users)
.build();
Role createdRole = roleRepository.save(role);
Role foundRole = roleRepository.findRoleById(createdRole.getId()).get();
[...]
}
Debugging this code, I found out, that createdRole has the users set as expected but foundRole doesn't.
How can I get the users in foundRole too?
Please tell me if you need the repository-code - it's a very simple interface so I just skipped it.
Simplified response, read the fine manual: 6.3.10. Configuring Fetch- and LoadGraphs.
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#Builder
#Entity
#Table(name = "applicationusers")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Singular
#ManyToMany
private Set<Role> roles;
}
and
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#Builder
#Entity
#Table(name = "roles")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(mappedBy = "roles")
#Singular
private Set<User> applicationUsers;
}
The manual specifies:
public interface RoleRepository extends JpaRepository<Role, Long>{
#Query("select r from Role r where r.id = :id")
#EntityGraph(attributePaths = {"applicationUsers"})
Role findByIdFetchUsers(#Param("id") Long id);
}
The second System.out.println will give you a org.hibernate.LazyInitializationException.
#Override
public void run(String... args) throws Exception {
User u = save();
Role r2 = roleRepository.findByIdFetchUsers(u.getId());
System.out.println("R: " + r2 + " : " + r2.getApplicationUsers());
Role r1 = roleRepository.findById(u.getId()).get();
System.out.println("R: " + r1 + " : " + r1.getApplicationUsers());
}

Resources