I have a OneToMany relationship (two tables bi-directional).
When I save the doctor's specialties, it does work, but when I remove any specialty and update the doctor, it doesn't.
Doctor
#Entity
#Table(name = "doctors")
public class Doctor implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer doctorId;
#Column(length = 20)
private String doctorName;
#Column(length = 9)
private String doctorPhoneNo;
#Column(length = 30)
private String doctorEmailAddress;
private String doctorProfileImage;
#Enumerated(EnumType.STRING)
private Status status;
#Column(length = 6)
private String doctorCmp;
#OneToMany(mappedBy = "doctor", cascade = CascadeType.ALL)
// #JsonIgnore
private Set<DoctorSpecialties> doctorSpecialties;
public Doctor() {
this.doctorSpecialties = new HashSet<>();
}
public Doctor(Integer id){
this();
this.doctorId = id;
}
// getters y setters
}
Specialty
#Entity
#Table(name = "specialties")
public class Specialty implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer specialtyId;
private String specialtyName;
#OneToMany(mappedBy = "specialty")
#JsonIgnore
private Set<DoctorSpecialties> doctorSpecialties;
public Specialty() {
}
public Specialty(Integer id) {
this.specialtyId = id;
}
// getters and setters
}
DoctorSpecialties
#Entity
#Table(name = "doctor_specialties")
public class DoctorSpecialties implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "doctor_id")
private Doctor doctor;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "specialty_id")
private Specialty specialty;
#OneToMany
#JoinColumn(name = "doctor_specialties_id")
private Set<Appointment> appointments;
#OneToMany
#JoinColumn(name = "doctor_specialties_id")
private Set<DoctorSchedule> schedules;
public DoctorSpecialties(){
}
public DoctorSpecialties(Specialty specialty, Doctor doctor){
this.specialty = specialty;
this.doctor = doctor;
}
getters / setters
}
Controller
#PostMapping(value = "/saveSpecialties/{id}")
public String saveDoctorSpecialties(#RequestParam(required = false) String[] specialtiesId,
#PathVariable Integer id, RedirectAttributes message) {
if (id != null && id > 0) {
Doctor doctor = doctorService.findOne(id);
if (doctor != null) {
// It does not work
doctor.getDoctorSpecialties().forEach(ds -> doctorSpecialtiesService.delete(ds.getId()));
doctor.getDoctorSpecialties().clear();
if (specialtiesId != null) {
for (String specialtyId : specialtiesId) {
DoctorSpecialties ds = new DoctorSpecialties();
ds.setSpecialty(new Specialty(Integer.parseInt(specialtyId)));
ds.setDoctor(doctor);
doctor.getDoctorSpecialties()
.add(ds);
}
}
doctorService.update(doctor);
message.addFlashAttribute("success", "Specialties successfully saved.");
return "redirect:/doctors/profile/{id}/specialties";
}
}
// specialtiesId = new String[]{};
message.addFlashAttribute("error", "Doctor doesn't exists");
return "redirect:/doctors/list";
}
console:
2021-10-30 21:19:13.330 DEBUG 44504 --- [nio-8080-exec-7] org.hibernate.SQL : select doctor0_.doctor_id as doctor_i1_3_0_, doctor0_.doctor_cmp as doctor_c2_3_0_, doctor0_.doctor_email_address as doctor_e3_3_0_, doctor0_.doctor_name as doctor_n4_3_0_, doctor0_.doctor_phone_no as doctor_p5_3_0_, doctor0_.doctor_profile_image as doctor_p6_3_0_, doctor0_.status as status7_3_0_ from doctors doctor0_ where doctor0_.doctor_id=?
2021-10-30 21:19:13.339 DEBUG 44504 --- [nio-8080-exec-7] org.hibernate.SQL : select doctorspec0_.doctor_id as doctor_i2_2_0_, doctorspec0_.id as id1_2_0_, doctorspec0_.id as id1_2_1_, doctorspec0_.doctor_id as doctor_i2_2_1_, doctorspec0_.specialty_id as specialt3_2_1_ from doctor_specialties doctorspec0_ where doctorspec0_.doctor_id=?
2021-10-30 21:19:13.401 DEBUG 44504 --- [nio-8080-exec-8] org.hibernate.SQL : select doctor0_.doctor_id as doctor_i1_3_0_, doctor0_.doctor_cmp as doctor_c2_3_0_, doctor0_.doctor_email_address as doctor_e3_3_0_, doctor0_.doctor_name as doctor_n4_3_0_, doctor0_.doctor_phone_no as doctor_p5_3_0_, doctor0_.doctor_profile_image as doctor_p6_3_0_, doctor0_.status as status7_3_0_ from doctors doctor0_ where doctor0_.doctor_id=?
2021-10-30 21:19:13.404 DEBUG 44504 --- [nio-8080-exec-8] org.hibernate.SQL : select specialty0_.specialty_id as specialt1_7_0_, doctorspec1_.id as id1_2_1_, doctor2_.doctor_id as doctor_i1_3_2_, specialty0_.specialty_name as specialt2_7_0_, doctorspec1_.doctor_id as doctor_i2_2_1_, doctorspec1_.specialty_id as specialt3_2_1_, doctorspec1_.specialty_id as specialt3_2_0__, doctorspec1_.id as id1_2_0__, doctor2_.doctor_cmp as doctor_c2_3_2_, doctor2_.doctor_email_address as doctor_e3_3_2_, doctor2_.doctor_name as doctor_n4_3_2_, doctor2_.doctor_phone_no as doctor_p5_3_2_, doctor2_.doctor_profile_image as doctor_p6_3_2_, doctor2_.status as status7_3_2_ from specialties specialty0_ inner join doctor_specialties doctorspec1_ on specialty0_.specialty_id=doctorspec1_.specialty_id inner join doctors doctor2_ on doctorspec1_.doctor_id=doctor2_.doctor_id where doctor2_.doctor_id=?
2021-10-30 21:19:13.565 DEBUG 44504 --- [nio-8080-exec-4] org.hibernate.SQL : select specialty0_.specialty_id as specialt1_7_0_, doctorspec1_.id as id1_2_1_, doctor2_.doctor_id as doctor_i1_3_2_, specialty0_.specialty_name as specialt2_7_0_, doctorspec1_.doctor_id as doctor_i2_2_1_, doctorspec1_.specialty_id as specialt3_2_1_, doctorspec1_.specialty_id as specialt3_2_0__, doctorspec1_.id as id1_2_0__, doctor2_.doctor_cmp as doctor_c2_3_2_, doctor2_.doctor_email_address as doctor_e3_3_2_, doctor2_.doctor_name as doctor_n4_3_2_, doctor2_.doctor_phone_no as doctor_p5_3_2_, doctor2_.doctor_profile_image as doctor_p6_3_2_, doctor2_.status as status7_3_2_ from specialties specialty0_ inner join doctor_specialties doctorspec1_ on specialty0_.specialty_id=doctorspec1_.specialty_id inner join doctors doctor2_ on doctorspec1_.doctor_id=doctor2_.doctor_id where doctor2_.doctor_id=?
There is no delete statement...
------------------------
EDIT 1
------------------------
Doctor find = doctorRepository.findById(1).get();
DoctorSpecialties ds1 = new DoctorSpecialties();
ds1.setSpecialty(specialtyRepository.findById(1).get());
ds1.setDoctor(find);
DoctorSpecialties ds2 = new DoctorSpecialties();
ds2.setSpecialty(specialtyRepository.findById(2).get());
ds2.setDoctor(find);
find.getDoctorSpecialties().add(ds1);
find.getDoctorSpecialties().add(ds2);
doctorRepository.save(find);
I have done some tests and I can not understand completely. I did and it only saves once when actually I am adding two objects.
insert into doctor_specialties (id, doctor_id, specialty_id) values (null, ?, ?)
------------------------
EDIT 2
------------------------
DoctorSpecialties (Modify the constructor)
#Entity
#Table(name = "doctor_specialties")
public class DoctorSpecialties implements Serializable {
public DoctorSpecialties(Integer specialtyId, Doctor doctor) {
this.specialty = new Specialty(specialtyId);
this.doctor = doctor;
}
}
Controller
#PostMapping(value = "/saveSpecialties/{id}")
public String saveDoctorSpecialties(#RequestParam(required = false) String[] specialtiesId,
#PathVariable Integer id, RedirectAttributes message) {
if (id != null && id > 0) {
doctorService.saveDelete(id);
Doctor doctor = doctorService.findOne(id);
if (specialtiesId != null && specialtiesId.length > 0) {
for(String idSpecialty : specialtiesId){
doctorSpecialtiesService.save(new DoctorSpecialties(Integer.parseInt(idSpecialty), doctor));
}
}
message.addFlashAttribute("success", "Specialties successfully saved.");
return "redirect:/doctors/profile/{id}/specialties";
}
message.addFlashAttribute("error", "Doctor doesn't exists");
return "redirect:/doctors/list";
}
Service
#Override
#Transactional
public void saveDelete(Integer doctorId) {
Doctor doctor = this.doctorRepository
.findById(doctorId).get();
doctor.getDoctorSpecialties().clear();
}
Console:
select doctor0_.doctor_id as doctor_i1_3_0_, doctor0_.doctor_cmp as doctor_c2_3_0_, doctor0_.doctor_email_address as doctor_e3_3_0_, doctor0_.doctor_name as doctor_n4_3_0_, doctor0_.doctor_phone_no as doctor_p5_3_0_, doctor0_.doctor_profile_image as doctor_p6_3_0_, doctor0_.status as status7_3_0_ from doctors doctor0_ where doctor0_.doctor_id=?
select doctorspec0_.doctor_id as doctor_i2_2_0_, doctorspec0_.id as id1_2_0_, doctorspec0_.id as id1_2_1_, doctorspec0_.doctor_id as doctor_i2_2_1_, doctorspec0_.specialty_id as specialt3_2_1_ from doctor_specialties doctorspec0_ where doctorspec0_.doctor_id=?
update appointments set doctor_specialties_id=null where
doctor_specialties_id=?
update doctor_schedules set
doctor_specialties_id=null where doctor_specialties_id=?
delete from doctor_specialties where id=?
For the one-many relationship in a transaction, once you get the parent (Doctor) and loop over its children (DoctorSpecialties) [In other words, once you load entire the parent and its children into Persistent state], you won't be able to delete your DoctorSpecialties directly by its repository.
You could try something like the below example to see it:
#Transactional
public void removeLine(Long doctorId, Long specId) {
Doctor doctor = this.doctorRepository // (1)
.findById(doctorId)
.orElseThrow(IllegalArgumentException::new);
this.doctorSpecialtiesRepository.deleteById(specId); // (2)
}
At (1), we load doctor into the persistent state. So here, if fetch = FetchType.EAGER, this means that it will load doctor and its all doctorSpecialties into the persistent state and this cause (2) won't give you any effect.
Otherwise, if fetch = FetchType.LAZY, it only load doctor into the persistent state, and at (2) it will be deleted successfully.
Your case was similar, although fetch = FetchType.LAZY, but you looped over the children by using forEach and that loads doctorSpecialties into the persistent state. That's why you cannot delete them.
Suggestion: Using orphanRemoval = true in your parent entity
#OneToMany(mappedBy = "doctor", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<DoctorSpecialties> dss;
and just simply clear its children in your method (In #Transactional method)
doctor.getDoctorSpecialties().clear();
Whichever side has mappedBy is owning side.
This means something like: "modifications on this side of the relation
are already Mapped By the other side of the relation
, so no need to track it here separately in an extra
table."
Check this url for more information What is the "owning side" in an ORM mapping?
Related
I have two entity booking and travelAgentBooking, booking could exist by itself while travelAgentBooing must have one booking of it.
TABookingEntity is below
#Entity
#ApplicationScoped
#Table(name = "TABooking")
#NamedQuery(name = "TABooking.findAll", query = "SELECT t FROM TABookingEntity t ORDER BY t.id ASC")
public class TABookingEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TABookingId_seq")
#SequenceGenerator(name = "TABookingId_seq", initialValue = 1, allocationSize = 1)
private Long id;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "booking_id", nullable = false)
private BookingEntity flightbooking;
// belong to upstream booking so we just store id here
private Long taxibookingid;
private Long hotelbookingid;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public BookingEntity getFlightbooking() {
return flightbooking;
}
public void setFlightbooking(BookingEntity flightbooking) {
this.flightbooking = flightbooking;
if (flightbooking != null) {
flightbooking.setTravelAgentBooking(this);
}
}
public Long getTaxibookingId() {
return taxibookingid;
}
public void setTaxibookingId(Long taxibookingid) {
this.taxibookingid = taxibookingid;
}
public Long getHotelbookingId() {
return hotelbookingid;
}
public void setHotelbookingId(Long hotelbookingid) {
this.hotelbookingid = hotelbookingid;
}
BookingEntity is below
#Entity
#ApplicationScoped
#Table(name = "booking")
#NamedQueries({ #NamedQuery(name = "Booking.findAll", query = "SELECT b FROM BookingEntity b ORDER BY b.d ASC"),
#NamedQuery(name = "Booking.findByFlight", query = "SELECT b FROM BookingEntity b WHERE b.flight = :flight"),
#NamedQuery(name = "Booking.findByDate", query = "SELECT b FROM BookingEntity b WHERE b.d = :d") })
public class BookingEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "bookingId_seq")
#SequenceGenerator(name = "bookingId_seq", initialValue = 1, allocationSize = 1)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "customer_id", nullable = false)
private CustomerEntity customer;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "flight_id", nullable = false)
private FlightEntity flight;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "travelAgentBooking_id", nullable = true)
private TABookingEntity travelAgentBooking;
#NotNull
#Column(name = "date")
private Date d;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public CustomerEntity getCustomer() {
return customer;
}
public void setCustomer(CustomerEntity customer) {
this.customer = customer;
if(customer != null)
customer.addBooking(this);
}
public FlightEntity getFlight() {
return flight;
}
public void setFlight(FlightEntity flight) {
this.flight = flight;
}
public Date getDate() {
return new Date(d.getTime());
}
public void setDate(Date d) {
this.d = d;
}
public TABookingEntity getTravelAgentBooking() {
return travelAgentBooking;
}
public void setTravelAgentBooking(TABookingEntity travelAgentBooking) {
this.travelAgentBooking = travelAgentBooking;
}
here is the code I creating booking firstly, and then set it to tabooking.
then I'm trying to update the booking since when it is created, there is no travelAngentBooking for it to associate.
Booking booking = flightService.createBooking(tabooking.getFlightbooking());
tabooking.setFlightbooking(booking);
,,,,,,,,,,,
,,,,,,,,,,,
tabookingService.create(tabooking);
flightService.updateBooking(tabooking.getFlightbooking().getId(), tabooking.getFlightbooking());
After running it the table of travelAgentBooking is perfect.
But booking table column referred to travelAgentBooking is always null for any booking object.
UPDATE:
#PUT
#Path("/{id:[0-9]+}")
#Operation(description = "Update a Booking in the database")
#APIResponses(value = { #APIResponse(responseCode = "200", description = "Booking updated successfully"),
#APIResponse(responseCode = "400", description = "Invalid Booking supplied in request body"),
#APIResponse(responseCode = "404", description = "Booking with id not found"),
#APIResponse(responseCode = "409", description = "Booking details supplied in request body conflict with another existing Booking"),
#APIResponse(responseCode = "500", description = "An unexpected error occurred whilst processing the request") })
#Transactional
public Response updateBooking(
#Parameter(description = "Id of Booking to be updated", required = true) #Schema(minimum = "0") #PathParam("id") Integer id,
#Parameter(description = "JSON representation of Booking object to be updated in the database", required = true) Booking booking) {
Customer customer = customerService.findById(booking.getCustomer().getId())
.orElseThrow(() -> new RestServiceException("We can't found customer", Response.Status.BAD_REQUEST));
if (!customer.equals(booking.getCustomer()))
throw new RestServiceException("use custoemr's own API for it update", Response.Status.BAD_REQUEST);
Flight flight = flightService.findById(booking.getFlight().getId())
.orElseThrow(() -> new RestServiceException("We can't found flight", Response.Status.BAD_REQUEST));
if (!flight.equals(booking.getFlight()))
throw new RestServiceException("use custoemr's own API for it update", Response.Status.BAD_REQUEST);
try {
bookingService.validateBooking(booking);
} catch (ConstraintViolationException ce) {
// Handle bean validation issues
Map<String, String> responseObj = new HashMap<>();
for (ConstraintViolation<?> violation : ce.getConstraintViolations()) {
responseObj.put(violation.getPropertyPath().toString(), violation.getMessage());
}
throw new RestServiceException("Bad Request", responseObj, Response.Status.BAD_REQUEST, ce);
} catch (UniqueFlightWithDateException e) {
// we are updating an existence flight, so ignore this as expected
}
try {
bookingService.update(id);
} catch (ServiceException e) {
Map<String, String> responseObj = new HashMap<>();
responseObj.put("id", "please ensure the id is associated with this number");
throw new RestServiceException("Bad Request", responseObj, Response.Status.NOT_FOUND, e);
}
bookingService.update(id);
return Response.ok(booking).build();
}
BookingEntity update(BookingEntity booking) {
log.info("BookingRepository.update() - Updating " + booking.getId());
em.merge(booking);
return booking;
}
From the original posted code, the problem is that you have two very independent unidirectional relationships and only setting one of them. Since they are independent, the other remains null and can not be anything other than null until it is set.
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "booking_id", nullable = false)
private BookingEntity flightbooking;
The join column sets a foreign key in the "TABooking" table to point at the bookingEntity. It requires this relationship reference be set to populate that foreign key value. Same thing with:
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "travelAgentBooking_id", nullable = true)
private TABookingEntity travelAgentBooking
It creates its own travelAgentBooking_id foreign key column in the "booking" table that will remain null until you update a booking instance and set this reference. If you only set one side, the other will always remain null in the database.
But there are two problems with the model and your expectations. First, from the comments, you didn't intend this to be two separate relationships and instead expect it to be a single bidirectional relationship. For that, you need a single foreign key, and to pick a side that 'owns' it. The side that owns it controls it:
#OneToOne(mappedBy "flightbooking", fetch = FetchType.LAZY)
private TABookingEntity travelAgentBooking
Using mappedBy tells JPA that the other side owns the relationship. The foreign key column then is only set when you set the TABookingEntity.flightbooking reference and save/merge the TABookingEntity instance.
Second is you are using JSON and so Json serialization and assuming it abides by your object model and the JPA mappings. It does not. JPA annotations are for your persistence provider to tell it how to serialize/deserialize your model into the database but mean nothing for JSON serialization (or xml or any other REST formats). You need to tell your JSON tool how to handle your relationships, and that completely depends on how you are going to be expecting and sending the JSON. There are many tutorials and different strategies to deal with this (see this link for a good primer), but easiest is usually just to pick parts of the graph and exclude them with #JsonIgnore:
#OneToOne(mappedBy "flightbooking", fetch = FetchType.LAZY)
#JsonIgnore
private TABookingEntity travelAgentBooking
This will mean that any JSON you receive representing a booking will have a null travelAgentBooking. So if you need to see or set this relationship, your api would have to send/receive TABookingEntity which would still have the flightbooking reference serialized. I picked this way because flightbooking owns the relationship, so it matches JPA, but it doesn't need to. You can and should figure out what works for your client application and it may be different from the JPA mappings. I would expect that bookings always need to know the TABookingEntity and you'll want that sent to the client, so you might put the #JsonIgnore annotation on the other side. If you do, you'll just have to be sure that when you want to change or add TABookingEntity, that you fix the TABookingEntity.flightbooking reference appropriately so that you don't null out the foreign key.
I'm trying to see whether a Cab is assigned to any Employee so I can fetch that Employee Id and simply to set cab as null, but #OneToMany mapping, simply returning a list of Employees & by this I'm not getting any method like a cab.getEmp() to fetch employee details
Employee.java
#Data
#Entity
#Table(name = "employee")
public class Employee {
#Id
private Integer id;
private String username;
private String password;
private String role;
private String dropLocation;
#ManyToOne(
cascade = CascadeType.ALL,
fetch = FetchType.EAGER
)
#JoinColumn(
name = "empCab",
referencedColumnName = "cabId"
)
public Cab cab;
}
Cab.java
#Data
#Entity
#Table(name = "cab")
public class Cab {
#Id
private Integer cabId;
private Integer cabNumber;
private String cabShift;
#OneToMany(
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
mappedBy= "cab"
)
private List<Employee> emp = new ArrayList<>();
}
Controller
#GetMapping("deleteCab")
public ModelAndView deleteCab(#RequestParam("id") Integer id, ModelAndView mvc){
Cab cab = cabRepo.findById(id).orElse(null);
if(cab!=null){
List<Employee> emp = cab.getEmp();
if(!cab.getEmp().isEmpty()){
//e1.setCab(null);
//empRepo.save(e1);
mvc.addObject("msg", "Cab deleted & an employee cab detail also got changed");
mvc.setViewName(NOTHING_JSP);
} else {
cabRepo.deleteById(id);
mvc.addObject("msg", "Cab removed from the database");
mvc.setViewName(NOTHING_JSP);
}
}
}
In your code, Cab is perent class and Employee is child class and your goal is to remove the child class dependency with parent class. For that, You have to extract the Employee from Cab and remove the relationship with Cab using employee.setCab(null).
#GetMapping("/deleteCab")
#ResponseBody
public ModelAndView deleteCab(#RequestParam("id") Integer id, ModelAndView mvc){
Cab cab = cabRepo.findById(id).orElse(null);
if(cab != null){
// Extract employee from cab
for(Employee emp: cab.getEmp())
{
// Remove the relationship with cab
emp.setCab(null);
}
cabRepo.save(cab);
mvc.addObject("msg", "Cab deleted & an employee cab detail also got changed");
mvc.setViewName(NOTHING_JSP);
} else {
cabRepo.deleteById(id);
mvc.addObject("msg", "Cab removed from the database");
mvc.setViewName(NOTHING_JSP);
}
return mvc;
}
I use PostgreSQL and I have these tables, product and product_media with relation OneToMany on product with product_media. I want to retrieve a list with product which each of them contains a list of product_media.
And I have two options in my mind in order to retrieve them from DB.
First solution is initially retrieve the list of product and then iterate the retrieved list and execute query in order to retrieve the list of product_media.
Query1:
select * from product as p where p.status=1;
Retrieve List and then iterate this list and execute this query:
select * from product_media as pm where pm.product_id=?
Second is to implement join in query and retrieve all data from my DB.
Query:
select * from product as p Join product_media as pm on (p.id=pm.product_id)
Retrieve a complex list with all data.
The problem of second option is to do not know an elegant way to map this list into an object which has the format below. Do you know how can map automatically the results into this format?
product:[
{
id:1,
name:'Pro1',
medias:[
{
id:1,
uuid:'asdfi-asdf-rg-fgsdf-do'
},
{
id:2,
uuid:'asdfi-asdf-rg-fgsdf-do'
}
]
},
{
id:2,
name:'Pro2',
medias:[
{
id:5,
uuid:'asdfi-asdf-rg-fgsdf-do'
},
{
id:7,
uuid:'asdfi-asdf-rg-fgsdf-do'
}
]
}
]
I think the second variant is the better option. After fetching the object tree from the database you can do something like the following to achieve what you are posted above:
Assuming your entities are defined as follows:
Product.java
public class Product {
private long id;
private String name;
private List<ProductMedia> mediaList;
public Product() {
mediaList = new ArrayList<ProductMedia>();
}
public Product(long id, String name) {
this.id = id;
this.name = name;
mediaList = new ArrayList<ProductMedia>();
}
// getters + setters
}
ProductMedia.java
public class ProductMedia {
private long id;
private String uuid;
public ProductMedia() { }
public ProductMedia(long id, String uuid) {
this.uuid = uuid;
}
// getters + setters
}
Using the Jackson library you can generate output as follows:
public class JsonTest {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
Product prod = new Product(1, "p1");
ProductMedia pm = new ProductMedia(1, "uuid1");
ProductMedia pm2 = new ProductMedia(2, "uuid2");
prod.getMediaList().add(pm);
prod.getMediaList().add(pm2);
Product prod1 = new Product(2, "p2");
ProductMedia pm3 = new ProductMedia(3, "uuid3");
ProductMedia pm4 = new ProductMedia(4, "uuid4");
prod1.getMediaList().add(pm3);
prod1.getMediaList().add(pm4);
Product[] pList = {prod, prod1};
mapper.writeValue(System.out, pList);
}
}
In this example, I am writing the output onto the console. But you are not restricted to it; you can write to a file passing in a FileOutputStream.
To be able to run this example you need to add the dependency; if you use Maven you can add the following into your POM:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.4</version>
</dependency>
Otherwise add the jar of the dependency into your project build path.
If your response is not in json format you can try below
There is a many-to-many relationship between Product and Media.
Product_Media is a helper table to maintain many-to-many relationship between Product and Media entities.
Product entity:
#Entity(name = "product")
public class Product {
#Id
#GeneratedValue
private Long product_id;
#Column
private String name;
#ManyToMany(cascade = { CascadeType.MERGE }, fetch = FetchType.EAGER)
#JoinTable(name = "product_media", joinColumns = {
#JoinColumn(name = "product_id", table = "product") }, inverseJoinColumns = {
#JoinColumn(name = "media_id", table = "media") })
List<Media> medias;
}
Media entity
#Entity(name = "media")
public class Media {
#Id
#GeneratedValue
private Long media_id;
#Column
private String name;
}
SQL generated by Hibernate
select
product0_.product_id as product_1_1_0_,
product0_.name as name2_1_0_,
medias1_.product_id as product_1_1_1_,
media2_.media_id as media_id2_2_1_,
media2_.media_id as media_id1_0_2_,
media2_.name as name2_0_2_
from
product product0_
left outer join
product_media medias1_
on product0_.product_id=medias1_.product_id
left outer join
media media2_
on medias1_.media_id=media2_.media_id
where
product0_.product_id=?
If the relationship is one-to-many, change entities like below
Media Entity
#Entity(name = "media")
public class Media {
#Id
#GeneratedValue
private Long id;
#Column
private String name;
#ManyToOne
#JoinColumn(name = "product_id", referencedColumnName = "id", nullable = false, updatable = false)
private Product product;
public Media() {
}
}
Product Entity
#Entity(name = "product")
public class Product {
#Id
#GeneratedValue
private Long id;
#Column
private String name;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "product")
List<Media> medias;
}
Hibernate generated SQL
select
product0_.id as id1_2_0_,
product0_.name as name2_2_0_,
medias1_.product_id as product_3_2_1_,
medias1_.id as id1_0_1_,
medias1_.id as id1_0_2_,
medias1_.name as name2_0_2_,
medias1_.product_id as product_3_0_2_
from
product product0_
left outer join
media medias1_
on product0_.id=medias1_.product_id
where
product0_.id=?
I have a problem with deleting (by unassign only) roles which are assigned to user through association table. Only assignment should be deleted.
I need to have a User entity with relation ManyToMany to Rolle entity but this relation (assignment) needs to have additional information like expiriation_date (because assignment can expire). So, I cannot use automatically created and managed association table by hibernate (#JoinTable - which was working really great when I didn't need an additional column) but now I need to extend the association with an extra column and that's why instead of that #JoinTable I need to create such a association table manualy. Below my model relation which is working when I am adding some roles to user, but not working when I try to delete some role.
USER:
#Entity
#Table(name = "USER")
public class User {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "NAME")
private String name;
#OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<UserRole> userRoleAssignments = new LinkedHashSet<>();
//getters, setters
public List<BrugersystemrolleDB> getRoles() {
return this.userRoleAssignments.stream().
map(roleAssignment -> roleAssignment.getRole()).
collect(Collectors.toList());
}
public void setRoles(List<Role> roles) {
this.userRoleAssignments = roles.stream().
map(role -> useExistingOrNewRoleAssignment(role)).
collect(Collectors.toSet()));
}
private UserRole useExistingOrNewRoleAssignment(Role role) {
Optional<Role> roleAssignmentOpt = getRoleAssignment(role);
if (roleAssignmentOpt.isPresent()) {
return roleAssignmentOpt.get();
} else {
return new UserRole(role, this);
}
}
private Optional<UserRole> getRoleAssignment(Role role) {
return roleAssignments.stream().
filter(roleAssignment ->
roleAssignment.getRole().getId() == role.getId()).findFirst();
}
}
ROLE:
#Entity
#Table(name = "ROLE")
public class Role {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "NAME")
private String name;
#OneToMany(mappedBy = "role", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<UserRole> userRoleAssignments = new LinkedHashSet<>();
//getters, setters
}
USER_ROLE:
#Entity
#Table(name = "USER_ROLE")
public class UserRole {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "USER_ID")
private User user;
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "ROLE_ID")
private Role role;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "EXPIRE_DATE", nullable = true)
private Date expireDate;
public UserRole (Role role, User user) {
this.role = role;
this.user = user;
}
//getters, setters
}
Usage:
#Stateless
public class UserLogic {
#EJB private UserDao userDao;
#EJB private UserRoleDao userRoledao;
//... just some generic solution for update requests
public User updateUser(User user, UserVo, userVo) {
user.setName(userVo.getName());
assignRollesToUser(user, userVo.getRoles());
return userDao.update(user); // <-- is causing an error when some role is deleted!
}
// this method is responsible for roles management and is causing a problem during update user list of roles, but only when some of them needs to be deleted.
public void assignRollesToUser(User user, List<Integer> roles) {
// 1 step: remove all existing assignments:
user.getRoleAssignments().stream().
forEach(roleAssignment -> {
// 1) solution with userRoleDao is causing a problem with deleted/detached object passed to merge:
userRoleDao.delete(roleAssignment);
// 2) solution with removing role from list gives me an error saying: NULL not allowed for column 'USER_ID' which is true, but why hibernate is not deleting this entry and wants to to set null for user_id which breaks a constraint?
// user.getRoleAssignments().remove(userAssignment);
});
// 2 step: assign new set of roles
List<Role> newRolesToAssign = getRolesFromDB(roles);
user.setRoles(newRolesToAssign )
}
private List<Role> getRolesFromDB(List<Integer> rolles) {
return rolles.stream().
map(userRoleDao::read).
collect(Collectors.toList());
}
}
I am able to add new roles to user but cannot delete any role and when try to delete then I am receiving
deleted instance passed to merge
or
detached instance passed to merge
It depends on way of deleting. I tried to delete roles via dao.delete() or via removing from collection only without removing by dao. Both solutions didn't work. Can you give me please some idea what am I doing wrong? Am I something missing?
EDIT:
Below the error which appears now after change in forEach in UserLogic to:
forEach(roleAssignment -> {
user.getRoleAssignments().remove(userAssignment);
roleAssignment.setUser(null);
roleAssignment.setRole(null);
});
Error:
[Server:server-one] Caused by: java.util.ConcurrentModificationException
[Server:server-one] at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429) [rt.jar:1.8.0_45-internal]
[Server:server-one] at java.util.HashMap$KeyIterator.next(HashMap.java:1453) [rt.jar:1.8.0_45-internal]
[Server:server-one] at org.hibernate.collection.internal.AbstractPersistentCollection$IteratorProxy.next(AbstractPer
sistentCollection.java:789) [hibernate-core-4.3.7.Final.jar:4.3.7.Final]
[Server:server-one] at java.util.Iterator.forEachRemaining(Iterator.java:116) [rt.jar:1.8.0_45-internal]
[Server:server-one] at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801) [rt.jar:1
.8.0_45-internal]
[Server:server-one] at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512) [rt.jar:1.8.0_45-intern
al]
[Server:server-one] at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502) [rt.jar:1.8.0_45
-internal]
[Server:server-one] at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) [rt.jar:1.8.0_4
5-internal]
[Server:server-one] at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) [rt.jar:1
.8.0_45-internal]
[Server:server-one] at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) [rt.jar:1.8.0_45-intern
al]
[Server:server-one] at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) [rt.jar:1.8.0_45-internal]
[Server:server-one] at com.myapp.rest.logic.UserLogic.assignRollesToUser(UserLogic.java:280)
Why am I getting java.util.ConcurrentModificationException?
You must first remove the dependent entities. So if you have an entity Students and each Student has zero or more Classes, and students and classes are joined many-to-many in the table StudentsClasses, you would have a collection of classes for each student and a collection of students for each class. To delete a student, you must first remove their classes from the many-to-many table. So if you have a method RemoveStudentClass that takes a student and a class as a parameter, you do something like
public void RemoveSudent(student deleteStudent)
{
foreach (Class c in deleteSudent.Classes)
{
removeStudentClass(deleteSudent,c);
}
Students.Delete(deleteStudent);
}
I have the following two POJOs:
#Entity(name = "project")
#Table(name = "project")
public class Project {
#Column(name = "identifier")
#Id
#GeneratedValue
private Integer identifier;
#ManyToMany(cascade = CascadeType.ALL)
private Set<Member> members = new HashSet<Member>();
// --- constructors ---
public Project() {
}
// --- getters ---
public Integer getIdentifier() {
return identifier;
}
public Set<Member> getMembers() {
return members;
}
}
and
#Entity(name = "member")
#Table(name = "member")
public class Member {
#Column(name = "identifier")
#Id
#GeneratedValue
private Integer identifier;
#Column(name = "name", columnDefinition = "text")
private String name;
#ManyToMany(mappedBy = "members")
private Set<Project> projects = new HashSet<Project>();
// --- constructors ---
public Member() {
}
// --- getters ---
public Integer getIdentifier() {
return identifier;
}
public String getName() {
return name;
}
public Set<Project> getProjects() {
return projects;
}
}
What is common practise do delete from a many-to-many relation? The following code works perfectly, but it generates a lot of overhead. First, there are some queries:
1 query to get the projects, triggered by member.getProjects()
N queries to get all members of the each project, triggered by project.getMembers()
Second, the complete data set of the project (query 1) and member (query 2) is fetched, and not only the identifier.
final Member member = ...;
for (final Project project : member.getProjects()) {
project.getMembers().remove(member);
}
db.delete(member);
Is it possible to do this more efficient without being forced to write ad-hoc queries?
Take a look here:
How to remove entity with ManyToMany relationship in JPA (and corresponding join table rows)?
Additionally you have the option to use jpql statements to delete data:
http://docs.oracle.com/html/E24396_01/ejb3_overview_query.html#ejb3_overview_query_delete
But I think the first link is what you are looking for.