How deal with child entites with unique attributes in JPA+Hibernate+Spring? - spring

Saving a many-to-one association only works the first time.
How save father with appended child if child entries already exist and prevent the "Unique index or primary key violated" error message ?
Note: that the name attribute is unique!
The main program.
// This works
Car car1 = new Car();
Car car2 = new Car();
Car car3 = new Car();
Model model1 = new Model();
Model model2 = new Model();
model1.setName("911");
model2.setName("Vito");
car1.setLicense("S-TU-123");
car2.setLicense("S-GH-124");
car3.setLicense("S-BN-123");
car1.setModel(model1);
car2.setModel(model2);
car3.setModel(model1);
carRepository.saveAll(Arrays.asList(car1, car2, car3));
//// wont work because of "Unique index or primary key violated"
Car car4 = new Car();
Model model4 = new Model();
car4.setModel(model4);
model4.setName("911"); // <== exists in database already
car4.setLicense("L-QQ-001");
carService.save(car4);
model code:
import javax.persistence.*;
import lombok.*;
#Entity
#Table(name = "cars")
#Getter
#Setter
#EqualsAndHashCode
#NoArgsConstructor
public class Car {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String license;
#EqualsAndHashCode.Exclude
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "model_id", referencedColumnName = "id")
private Model model;
public void setModel(Model model) {
this.model = model;
model.getCars().add(this);
}
public void removeModel(Model model) {
this.model = model;
model.getCars().add(null);
}
}
car code:
import javax.persistence.*;
import lombok.*;
import java.util.HashSet;
import java.util.Set;
#Entity
#Table(name = "models")
#Getter
#Setter
#EqualsAndHashCode
#NoArgsConstructor
public class Model {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String name;
#EqualsAndHashCode.Exclude
#OneToMany(mappedBy = "model", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Car> cars = new HashSet<>();
public void setCars(Set<Car> cars) {
this.cars = cars;
for (Car car : cars) {
car.setModel(this);
}
}
}
car repository:
public interface CarRepository extends JpaRepository<Car, Long> {
}
model repository:
public interface ModelRepository extends JpaRepository<Model, Long> {
Optional<Model> findByLicense(String license);
}

Related

How to add the IDs of foreign tables to another table passing the parameters by constructor?

Hi everyone I'm working with SpringBoot and I want to send the ID's of table Producto and Cliente to Pedidos, I'm using the constructor for to pass of parametrs
I tried to create a List as String to hold the values ​​and then use it to send the data to the other method
Class Product
package com.example.demo.model;
import java.util.Set;
import javax.persistence.*;
#Entity
#Table(name = "Productos")
public class Producto {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nombreProducto;
private String precioProducto;
/*Here i send of FK of this table to Pedidos*/
#OneToMany(mappedBy = "producto",cascade = CascadeType.ALL)
private Set<Pedido> pedidos;
public Producto(String nombreProducto, String precioProducto) {
this.nombreProducto = nombreProducto;
this.precioProducto = precioProducto;
}
//Getters and Setters
}
Class Cliente
package com.example.demo.model;
import java.util.Set;
import javax.persistence.*;
#Entity
#Table(name="Clientes")
public class Cliente {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String nombreCliente;
private String correoElectronico;
/*Here i send of FK of this table to Pedidos*/
#OneToMany(mappedBy = "cliente",cascade = CascadeType.ALL)
private Set<Pedido> pedidos;
public Cliente(String nombreCliente, String correoElectronico) {
this.nombreCliente = nombreCliente;
this.correoElectronico = correoElectronico;
}
//Getters and Setters
}
Class Pedido
package com.example.demo.model;
import javax.persistence.*;
#Entity
#Table(name = "Pedido")
public class Pedido {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String fechaPedido;
private String direccion;
/*
Here I create the atribute of FK of the tables Cliente and Producto
*/
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "cliente_id", referencedColumnName = "id")
private Cliente cliente;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "producto_id", referencedColumnName = "id")
private Producto producto;
public Pedido(String fechaPedido, String direccion, Cliente cliente, Producto producto) {
this.fechaPedido = fechaPedido;
this.direccion = direccion;
this.cliente = cliente;
this.producto = producto;
}
//Getters and Setters
}
And the last Class it's the RunnClass
package com.example.demo;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
//import java.util.stream.Stream;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import com.example.demo.model.Cliente;
import com.example.demo.model.Pedido;
import com.example.demo.model.Producto;
import com.example.demo.repository.ClienteRepository;
import com.example.demo.repository.PedidosRepository;
import com.example.demo.repository.ProductoRepositroy;
import com.github.javafaker.Faker;
#Component
public class SampleDataLoader implements CommandLineRunner {
private final ClienteRepository clienteRepository;
private final ProductoRepositroy productoRepositroy;
private final PedidosRepository pedidosRepository;
private final Faker faker; //It's a ASI of DataFaker
public SampleDataLoader(ClienteRepository clienteRepository,
ProductoRepositroy productoRepositroy,
PedidosRepository pedioPedidosRepository) {
this.clienteRepository = clienteRepository;
this.productoRepositroy = productoRepositroy;
this.pedidosRepository = pedioPedidosRepository;
this.faker = new Faker(); //It's a ASI of DataFaker
}
#Override
public void run(String... args) throws Exception {
ejecutarClases();
}
private void ejecutarClases() {
List<Cliente> clientes = IntStream.rangeClosed(1, 20)
.mapToObj(i -> new Cliente(faker.name().fullName(),
faker.internet().emailAddress()))
.collect(Collectors.toList());
clienteRepository.saveAll(clientes);
List<Producto> productos = IntStream.rangeClosed(1, 100)
.mapToObj(i -> new Producto(faker.commerce().productName(), "$"+faker.commerce().price()))
.collect(Collectors.toList());
productoRepositroy.saveAll(productos);
//I don't know how to send two ID's to this table,
//if you can see I have two values as null
//I want to send the ID's the other tables
List<Pedido> pedidos = IntStream.rangeClosed(1, 30)
.mapToObj(i -> new Pedido(faker.backToTheFuture().date(),
faker.address().streetAddress(), null, null))
.collect(Collectors.toList());
pedidosRepository.saveAll(pedidos);
}
}
I hope someone can help me please.

FetchType.LAZY in ManyToMany doesnot work

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

ManyToOne mapping and findingBySchoolId

I am trying to create teacher and school objects where 1 school can have multiple teachers, but each teacher works at one school. I am tying to make a query where I get only teachers that work at certain school by getting its id as a parameter.
School object
#Getter
#Setter
#Entity
#Table(name="school")
#NoArgsConstructor
#AllArgsConstructor
#ToString
public class School {
#Id
#SequenceGenerator(
name = "school_sequence",
sequenceName = "school_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "school_sequence"
)
#Column(name="id")
private Long id;
private String name;
#JsonIgnore
#OneToMany//(mappedBy = "school")
private List<Teacher> teacher;
}
Teacher object
#Getter
#Setter
#Entity
#Table(name="teacher")
#NoArgsConstructor
#AllArgsConstructor
#ToString
public class Teacher {
#Id
#SequenceGenerator(
name = "teacher_sequence",
sequenceName = "teacher_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "teacher_sequence"
)
#Column(name="id")
private Long id;
private String name;
private String email;
/* #Column(name="schoolId")
private Long schoolId;*/
//(cascade = CascadeType.ALL) Don't use this! it will prevent you to have different teacher queries with
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "schoolId", referencedColumnName = "id") //the same school ids.
private School school;
}
Teacher controller
#RestController
#RequestMapping(path = "api/v1/teacher")
public class TeacherController {
private final TeacherService teacherService;
#Autowired
public TeacherController(TeacherService teacherService) {
this.teacherService = teacherService;
}
#GetMapping
public List<Teacher> getTeacher(){
return teacherService.getTeacher();
}
#GetMapping(path = "schoolID")
public List<Teacher> getTeacherBySchool(#PathVariable("schoolId") Long schoolId, School school){
return teacherService.getTeacherBySchool(schoolId, school);
}
Teacher Service
#Service
public class TeacherService {
private final TeacherRepository teacherRepository;
#Autowired
public TeacherService(TeacherRepository teacherRepository) {
this.teacherRepository = teacherRepository;
}
public List<Teacher> getTeacher(){
return teacherRepository.findAll();
}
//public List<Teacher> getTeacherBySchool
How would I implement this on teacher controller and teacher service?
Update on my progress!!!
Teacher Controller
#GetMapping(path = "/teachers/{schoolID}")
public List<Teacher> getTeacherBySchool(#PathVariable("schoolID") Long schoolId){
School school = new School();
school.setId(schoolId);
return teacherService.getTeacherBySchool(school);
}
Teacher Service
public List<Teacher> getTeacherBySchool(Long schoolId){
return teacherRepository.findBySchool(school);
}
Teacher Repository
#Repository
public interface TeacherRepository extends JpaRepository<Teacher, Long> {
#Query
List<Teacher> findBySchool(List<School> school);
These changes do what I want them to do, but as I said. It is not a good coding practice. What I want is that less code on TeacherController, and make my TeacherService communicate with SchoolService to get the right schools.
I think you can just add the following to your repository:
public List<Teacher> findBySchoolId(Long schoolId);
Or just write the query yourself:
entityManager.createQuery("FROM Teacher t WHERE t.school.id = :schoolId")
.setParameter("schoolId", schoolId)
.getResultList();

Entity not mapped to a single property error with inherited entites of one table

I have two entities SuperAlbumEntity and AlbumEntity reflecting the same table "albums".
SuperAlbumEntity:
#Entity
#Table(name = "albums")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class SuperAlbumEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
//other fields
}
AlbumEntity:
#EqualsAndHashCode(callSuper = true)
#Entity
#Table(name = "albums")
public class AlbumEntity extends SuperEntity{
//some fields
#Column(name = "country")
private String country;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "country_name", referencedColumnName = "country")
private Set<CountryEntity> countrySet = new HashSet<>();
}
AlbumEntity has #OneToMany mapping to CountryEntity:
#Entity
#Table(name = "countries")
public class CountryEntity implements Serializable {
#Id
String id;
String country_name;
//other fields
}
Running my application I get the folowing error:
...
Caused by: org.hibernate.AnnotationException: referencedColumnNames(country) of CountryEntity.countrySet referencing AlbumEntity not mapped to a single property
...
What's interesting is that if I move country field from SuperAlbumEntity to AlbumEntity everything just works fine...
Can someone explain me why I get this error?
I'm not sure but I think is connected with the type of inherence that you used it. Try to modify your superclass to something like this:
SuperAlbumEntity:
#MappedSuperclass
public abstract class SuperAlbumEntity {
}
AlbumEntity:
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#Table(name = "albums")
public class AlbumEntity extends SuperEntity {
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "country_name", referencedColumnName = "country")
private Set<CountryEntity> countrySet = new HashSet<>();
}

Saving multiple child takes too much time (5 sec +)

saving table with multiple children takes too much time to execute (more than 5 sec)
tried this,
spring.jpa.properties.hibernate.jdbc.batch_size=30
spring.jpa.properties.hibernate.order_inserts=true
Timesheet Model
package com.Erp.Model.TimeSheetModel;
#Data
#Getter
#Setter
#Entity
#Table(name = "timesheet")
#JsonIdentityInfo(generator =
ObjectIdGenerators.PropertyGenerator.class,property="id", scope =
Timesheet.class)
public class Timesheet extends BaseEntity{
#ManyToOne
#JoinColumn(name = "employee_personal_information_id")
private EmployeePersonalInformation employeePersonalInformation;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "timesheet_id")
private Collection<TimesheetBudget> timesheetBudget;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "timesheet_id")
private Collection<TimesheetDateType> timesheetDateType;
}
TimesheetBudget Model
package com.Erp.Model.TimeSheetModel;
#Setter
#Getter
#Entity
#Table(name = "timesheet_budget")
#JsonIdentityInfo(generator =
ObjectIdGenerators.PropertyGenerator.class,property="id", scope =
TimesheetBudget.class)
public class TimesheetBudget extends BaseEntity {
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "timesheet_budget_id")
private Collection<TimesheetHours> timesheetHours;
#ManyToOne(fetch= FetchType.LAZY)
#JsonIgnore
private Timesheet timesheet;
}
TimesheetHours Model
package com.Erp.Model.TimeSheetModel;
#Entity
#Getter
#Setter
#Table(name = "timesheet_hours")
public class TimesheetHours extends BaseEntity {
#ManyToOne(fetch=FetchType.LAZY )
#JsonIgnore
private TimesheetBudget timesheetBudget;
}
TimesheetDateType Model
package com.Erp.Model.TimeSheetModel;
#Setter
#Getter
#Entity
#Table(name="timesheet_date_type")
#JsonIdentityInfo(generator =
ObjectIdGenerators.PropertyGenerator.class,property="id", scope =
TimesheetDateType.class)
public class TimesheetDateType extends BaseEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JsonIgnore
private Timesheet timesheet;
}
Timesheet Service
package com.Erp.Service.TimeSheetService;
#Service
public class TimesheetService {
#Autowired
TimesheetRepository timesheetRepository;
public ResponseEntity<String> saveTimesheet(Timesheet timesheet) {
timesheetRepository.save(timesheet);
return ResponseEntity.ok().body("Timesheet Saved Successfully.");
}
}
Timesheet Repository
package com.Erp.Repository.TimesheetRepository;
#Repository
public interface TimesheetRepository extends JpaRepository<Timesheet,
Integer>{
}

Resources