I have two entity classes.User and FriendStatus.Friend status keeps data about friend requests that have come from another users.
User:
#Entity
#Table(name = "USERS")
#XmlRootElement
public class User implements Serializable {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "acceptor")
#LazyCollection(LazyCollectionOption.FALSE)
private List<Message> acceptedMessages;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "sender")
#LazyCollection(LazyCollectionOption.FALSE)
private List<Message> sentMessages;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
#Column(name = "phone_number")
private String phoneNumber;
#OneToMany(cascade = CascadeType.ALL)
#LazyCollection(LazyCollectionOption.FALSE)
private List<User> friends;
#OneToMany(cascade = CascadeType.ALL,mappedBy="requestAcceptor")
#LazyCollection(LazyCollectionOption.FALSE)
#JsonIgnoreProperties("requestAcceptor")
private List<FriendStatus> acceptedFriendRequests;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "requestSender")
#LazyCollection(LazyCollectionOption.FALSE)
#JsonIgnoreProperties("requestSender")
private List<FriendStatus> sentFriendRequests;
#Column(name = "profile_status")
private String profileStatus;
#Enumerated(EnumType.STRING)
#Column(name = "activation_status")
private UserActivationStatus activationStatus;
FriendStatus:
#Entity
#Table(name="FRIEND_STATUS")
public class FriendStatus implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long Id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="request_sender_id")
#JsonIgnoreProperties("sentFriendRequests")
private User requestSender;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="request_acceptor_id")
#JsonIgnoreProperties("acceptedFriendRequests")
private User requestAcceptor;
#Enumerated(EnumType.STRING)
#Column(name = "request_status")
private FriendRequestStatus friendRequestStatus;
When i first time save FriendStatus object to the database it works fine.But when i save object second time with the same requestAcceptor object,hibernate deletes previous id from request_acceptor_id column and writes it to the new row.HELP ME PLEASE.
EDIT:
This is the method which i save my object to db.
public T create(T object) {
T objectFromDB = object;
Session session = NewHibernateUtil.getSessionFactory().openSession();
Transaction transaction = null;
try {
transaction = (Transaction) session.beginTransaction();
session.save(object);
transaction.commit();
} catch (HibernateException e) {
if (session != null){
session.getTransaction().rollback();
}
e.printStackTrace();
} finally {
session.close();
}
return objectFromDB;
}
This is the method where i call create method:
public void sendFriendRequest(FriendStatus object) {
FriendStatus status = fDao.create(object);//fDao is the object from Dao class which includes create method.
}
This is my controller:
#RequestMapping(value="/sendFriendRequest",method = RequestMethod.POST,consumes = MediaType.APPLICATION_JSON,produces = MediaType.APPLICATION_JSON)
public String sendFriendRequest(#RequestBody FriendStatus status) {
serviceUser.sendFriendRequest(status);//serviceUser is the object from class that includes sendFriendRequest method.
return "OK";
}
This is my table in db:
+====+================+=====================+===================+
| id | request_status | request_acceptor_id | request_sender_id |
+====+================+=====================+===================+
| 18 | WAITING | NULL | 29 |
+----+----------------+---------------------+-------------------+
| 19 | WAITING | 23 | 30 |
+----+----------------+---------------------+-------------------+
When i save FriendStatus object(it comes from client) with requestSender which id is 29 and requestAcceptor object which id is 23 hibernate saves it to the column which id is 18.After that when i save second FriendStatus object with requestSender which id is 30 and requestAcceptor object which id is 23,hibernate replaces request_acceptor_id with the NULL in the row which id is 18 and then creates new row in db which request_acceptor_id is 23.But i want that when i add second object,first object don't change.I don't want to replace request_acceptor_id with NULL when i create new column with the same request_acceptor_id.
Its to do with your cascade setting,
try using the following to save:
public void sendFriendRequest(FriendStatus status) {
User requester=userDao.findOne(status.getRequestAcceptor.getId());
User sender=userDao.findOne(status.getRequestSender.getId());
......
statusDao.create(status);
status.getRequestAcceptor(requester);
status.getRequestSender(sender);
statusDao.update(status);
}
the users that status is carrying have private List sentFriendRequests and private List acceptedFriendRequests; set to empty lists
Ofcause it works out even better if your transaction is outside the whole function then you can use:
sender.getSentFriendRequests().add(status) and reqester.getacceptedFriendRequests().add(status); which i guess was the point of the #OneToMany, Cascade.All
otherwise you can just remove the cascade rules from the collections
Related
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?
I have 2 entity ,
the parent entity
Entity
#Table(name = "parent")
#Data
#NoArgsConstructor
public class Parent {
#Column(name = "uuid")
private UUID uuid;
#Column(name = "type")
private String type;
#Column(name = "gateway")
private String gateway;
#OneToMany(fetch = FetchType.EAGER,orphanRemoval = true)
#JoinColumn(name = "example")
#OnDelete(action = OnDeleteAction.CASCADE)
private List<Child> childs;
...
other paramerters
}
and my child entity
#Entity
#Table(name = "child")
#Data
#NoArgsConstructor
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "id")
private Long id;
#Column(name = "unit")
private String unit;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "example")
private Parent parent;
...
other paramerters
}
so when i call the Get method which is under #Transactional it always execute a update operation?? why is that ? and how can i prevent that?
the get method
public class PublicImp implements CRUD {
#Transactional(isolation = Isolation.READ_COMMITTED)
#Override
public Parent getParent(UUID uuid) {
List<Parent> parents = repository.findByUUID(uuid);
return parents.get(0);
}
}
this is from the log
update
child
set
unit=?
where
id=?
Hibernate:
update
child
set
unit=?
where
id=?
2020-11-26 | 20:16:16.592 | http-nio-9797-exec-1 | TRACE | o.h.t.d.sql.BasicBinder | binding parameter [1] as [VARCHAR] - [kWh]
2020-11-26 | 20:16:16.592 | http-nio-9797-exec-1 | TRACE | o.h.t.d.sql.BasicBinder | binding parameter [2] as [BIGINT] - [2493]
EDIT :
my repository
public interface DeviceInfoRepository extends JpaRepository<Parent,String> {
List<Parent> findByUUID(UUID uuid);
}
Usually this happens when an entity that is part of the persistence context is considered dirty and the query you are using touches the tables of these dirty entities. In order for such queries to return correct results, Hibernate must first flush the dirty state to the database.
I fixed it , the issue was that i was using a UserType i.e is user implementation of Postgres datatype for json storage and retrival , the issue was the equals(Object x , Object y) was giving false as described by #Christian
Usually this happens when an entity that is part of the persistence context is considered dirty and the query you are using touches the tables of these dirty entities.
so jpa thought it was dirty as equals always returned false , after fixing the equals function the code worked fine
I have spring web app (JPA/Hibernate + MySQL).
I have two DAO classes.
CustomerDAO
#Entity
#Table(name = "customers")
public class Customer {
#Id
#Column(name = "customer_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "name", length = 50)
private String name;
#Column(name = "surname", length = 50)
private String surname;
#OneToMany(mappedBy = "customer")
private Set<Order> orders = new HashSet<>();
}
OrderDAO
#Entity
#Table(name = "orders")
public class Order {
#Id
#Column(name = "order_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "date")
private Date date;
#Digits(integer = 5, fraction = 2)
#Column(name = "amount")
private BigDecimal amount;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "customer_id")
private Customer customer;
#OneToMany(mappedBy = "order")
private Set<OrderDetail> ordersDetails = new HashSet<>();
And i have a class for retrieving data from DB:
#Repository
public interface OrderDAO extends JpaRepository<Order, Long> {
#Query("select o.customer.surname, sum(o.amount) as s from Order as o group by o.customer")
List<Customer> findCustomersBySumOfAmount();
}
It is giving me result like this:
+---------+---------------+
| surname | sum of amount |
+---------+---------------+
|Bielecki | 141.49 |
|Bielen | 130.34 |
......
Now i want 'unbox' data from DB with this method List<Customer> findCustomersBySumOfAmount()
I have method for this in my spring controller class:
List<Customer> findCustomersBySumOfAmount = orderService.findCustomersBySumOfAmount();
model.addAttribute("findCustomersBySumOfAmount", findCustomersBySumOfAmount);
for(Customer c : findCustomersBySumOfAmount) {
String s = c.getSurname();
System.out.println(c);
}
And i have error:
Failed to convert from type [java.lang.Object[]] to type
[com.twistezo.models.Customer] for value '{Bielecki, 141.49}'; nested
exception is
org.springframework.core.convert.ConverterNotFoundException: No
converter found capable of converting from type [java.lang.String] to
type [com.twistezo.models.Customer]
I suppose it's because I'm getting List<Object[]>. I know that I can iterate between this List<Object[]> of my data but maybe there is some simpler way to retrieve data directly to <Customer> ? I'm new in this stuff. Since now I used methods like List<Customer> findAll() without #Query annotation and i'm looking for similar "unboxing".
I was trying do something like this (add Customer.class in query) without effect:
#Query("select o.customer.surname, sum(o.amount) as s from Order as o group by o.customer", Customer.class)
List<Customer> findCustomersBySumOfAmount();
I already had this error message. In my case, I was doing something like the code below:
#Repository
public interface RepositoryA extends CrudRepository<EntityA, Long> {
#Query(nativeQuery = true, value = " ...SQL_1... ")
List<EntityA> getListOfEntityA(...PARAMS_1...);
#Query(nativeQuery = true, value = " ...SQL_2... ")
List<EntityB> getListOfEntityB(...PARAMS_2...);
}
I was parameterizing the repository interface with an "EntityA" and had an other method returning other type parametrized with an "EntityB".
In your case you are parameterizing the JpaRepository with "Order" and then using a method that returns a list of "Customer"s... Maybe this is causing this exception.
I would suggest creating a POJO class just to store the results of that query:
package com.mypackage;
public class CustomerAmountResult{
private String surname;
private BigDecimal amountSum;
public CustomerAmountResult(String surname, BigDecimal amountSum){
this.surname = surname;
this.amountSum = amountSum;
}
// getters / setters
}
Then change your query to the following:
#Query("select NEW com.mypackage.CustomerAmountResult(
o.customer.surname, sum(o.amount))
from Order as o
group by o.customer.surname")
List<CustomerAmountResult> findCustomersBySumOfAmount();
Thanks to that you will not need to parse the result set manually.
I have also had a similar scenario, but in my case, there wasn't any need for mapping(Orders into customers).
If you have all the fields in the same Domain POJO(here it was CustomerDAO), No need to create a new POJO or change the return type to Object. All you need is a parameterized Constructor(with the same fields which you are fetching in the JPQL query) in your Domain POJO.
An Example
package com.mypackage.domain
#Entity
#Table(name = "TABLENAME")
public class UserDO {
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "DOB")
private String dob;
public UserDO(){
}
public UserDO(String firstName, String lastName){
this.firstName = firstName;
this.lastName = lastName;
}
}
And your repo should contain a method like this
#Query("Select NEW com.mypackage.domain.UserDO(user.firstName, user.lastName) from UserDO as user where user.lastName =?1")
UserDO findByLastName(String lastName);
Your query select o.customer.surname, sum(o.amount) as s from Order as o group by o.customer doesn't return Customer. The Customer entity expects the result set to at least look like:
| id | name | surname |
-----------------------------
| 1 | john | smith |
| 2 | james | white |
| 3 | sara | black |
That's how the Result Set gets mapped into Customer entity. If you want to stick to your query, then change the return type to List<Object[]>, and run through the list and extract the data that you want.
I was also facing this issue since long.
Another way to fix this one by below
#Query("select entity from com.package.info.EntityName entity where entity.ClientID=:ClientID")
List<EntityName> findByClientID(#Param("clientID") Long clientID);
At first I thought this solution might solve my problem:
#Entity
public class User {
#JoinTable(name = "user_permission",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "permission_id"))
#MapKeyJoinColumn(name = "project_id")
#ElementCollection
private Map<Project, Permission> permissions = new HashMap<>();
}
#Entity
public class Project {
...
}
#Entity
public class Permission {
...
}
But in this implementation there can only be one Permission set per Project. I'd like to accomplish the ability to set multiple permissions for a project such that the following could be true:
| user_id | project_id | permission_id |
|---------|------------|---------------|
| 1 | 1 | 1 |
|---------|------------|---------------|
| 1 | 1 | 2 |
|---------|------------|---------------|
| 1 | 2 | 1 |
|---------|------------|---------------|
| 1 | 2 | 2 |
|---------|------------|---------------|
| 2 | 1 | 1 |
|---------|------------|---------------|
| 2 | 1 | 2 |
|---------|------------|---------------|
| 2 | 2 | 1 |
|---------|------------|---------------|
| 2 | 2 | 2 |
You can use an entity dedicated to your relation table. It's the way we declare relations with their own attributes for instance.
This would result in the following implementation:
#Entity
#IdClass(PermissionAssignation.class)
public class PermissionAssignation {
#Id
#ManyToOne
#JoinColumn(name="user_id")
private User user;
#Id
#ManyToOne
#JoinColumn(name="project_id")
private Project project;
#Id
#ManyToOne
#JoinColumn(name="permission_id")
private Permission permission;
...
}
I used the solution found in this post: Hibernate and no PK
It explains how to create the PK with field (I did not test it).
If it does not work, you'd better use a EmbeddedId class.
And if you want your relation to be bidirectional, you can then use a Set<PermissionAssignation> (or List, as you prefer/need):
#Entity
public class User {
#OneToMany(mappedBy="user")
private Set<PermissionAssignation> permissions;
}
Since I recently ran into this and still struggled, I wanted to share a complete code example. This example uses a separate #EmbeddedId class which will still create a table with 3 PK/FK columns. My example makes use of Lombok to fill in a bunch of boiler-plate code such as getters/setters, constructors, etc. It was also necessary to override the equals and hashcode methods. This was written using Spring framework, which wires up the repos & tests. Hopefully someone finds this a useful guide.
/* ENTITY CLASSES */
#Entity
#Data
#Table(name = "_Who")
public class Who {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(mappedBy = "who", fetch = FetchType.EAGER)
#JsonManagedReference
List<WhoWhatWhere> storage;
}
#Entity
#Data
#Table(name = "_What")
public class What {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String thing;
}
#Entity
#Data
#Table(name = "_Where")
public class Where {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String place;
}
#Data
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Entity
#NoArgsConstructor
#Table(name = "_WhoWhatWhere")
public class WhoWhatWhere {
public WhoWhatWhere(Who who, What what, Where where) {
this.who = who;
this.what = what;
this.where = where;
this.setId(new WhoWhatWhereId(who.getId(), what.getId(), where.getId()));
}
#EmbeddedId
WhoWhatWhereId id;
#ManyToOne(fetch = FetchType.EAGER)
#JsonBackReference
#JoinColumn(name = "who_id", insertable = false, updatable = false)
private Who who;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "what_id", insertable = false, updatable = false)
private What what;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "where_id", insertable = false, updatable = false)
private Where where;
}
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class WhoWhatWhereId implements Serializable {
#Column(name = "who_id")
Long whoId;
#Column(name = "what_id")
Long whatId;
#Column(name = "where_id")
Long whereId;
}
/* REPOSITORIES */
#Repository
public interface WhoRepository extends PagingAndSortingRepository<Who, Long> {
Iterable<Who> findWhoByName (String name);
}
#Repository
public interface WhatRepository extends PagingAndSortingRepository<What, Long> {
}
#Repository
public interface WhereRepository extends PagingAndSortingRepository<Where, Long> {
}
#Repository
public interface WhoWhatWhereRepository extends PagingAndSortingRepository<WhoWhatWhere, WhoWhatWhereId> {
}
/* TEST CLASS */
#SpringBootTest
#Slf4j
public class ThreeWayAssocTest {
private final WhoRepository whoRepository;
private final WhatRepository whatRepository;
private final WhereRepository whereRepository;
private final WhoWhatWhereRepository whoWhatWhereRepository;
#Autowired
public ThreeWayAssocTest(WhoRepository whoRepository, WhatRepository whatRepository, WhereRepository whereRepository, WhoWhatWhereRepository whoWhatWhereRepository) {
this.whoRepository = whoRepository;
this.whatRepository = whatRepository;
this.whereRepository = whereRepository;
this.whoWhatWhereRepository = whoWhatWhereRepository;
}
#Test
public void attemptPersistence() {
/*
* the commented pieces can be used to do the initial inserts. Later, fetch existing values so as not to fill
* up the database
*/
Who who =
/* new Who();
who.setName("Carl");
whoRepository.save(who);*/
whoRepository.findById(1L).get();
What what =
/* new What();
what.setThing("strawberry");
whatRepository.save(what);
what.setThing("salad");
whatRepository.save(what);*/
whatRepository.findById(2L).get();
Where where =
/* new Where();
where.setPlace("plate");
whereRepository.save(where);*/
whereRepository.findById(1L).get();
WhoWhatWhere whoWhatWhere = new WhoWhatWhere(who, what, where);
whoWhatWhereRepository.save(whoWhatWhere);
LOGGER.debug("finished");
}
#Test
public void testSerializing() throws JsonProcessingException {
Iterable<Who> examples = whoRepository.findWhoByName("Carl");
Who carl = examples.iterator().next();
LOGGER.debug("Carl: {}", carl);
LOGGER.debug("found some: \n {}", new ObjectMapper().writeValueAsString(examples));
}
}
I am working on a Spring mvc app in which I am using Trip model and TripStop model. Trip model has list of trip stop models. Following is my Trip model:
#Entity
#Table(name = "Trip")
public class TripModel {
#Id
#Column(name = "tripid")
#GeneratedValue
private int tripId;
#Column(name = "tripname")
private String tripName;
#Column(name = "tripdesc")
private String tripDesc;
#Column(name = "createdate")
private Date createDate;
#OneToMany(mappedBy = "tripModel", fetch = FetchType.EAGER)
#Fetch(value = FetchMode.SUBSELECT)
private List<TripStopModel> tripStopList;
}
Following is my trip stop model:
#Entity
#Table(name="TripStop")
public class TripStopModel {
#Id
#Column(name="tripstopid")
#GeneratedValue
private int tripStopId;
#Column(name="datetime")
private String tripStopDateTime;
#Column(name="createts")
private Date tripStopCreateTime;
#ManyToOne(optional=true)
#JoinColumn(name="locationid")
private LocationModel locationModel;
public LocationModel getLocationModel() {
return locationModel;
}
public void setLocationModel(LocationModel locationModel) {
this.locationModel = locationModel;
}
#ManyToOne(optional=true)
#JoinColumn(name="userid")
private UserModel userModel;
#ManyToOne(optional=true)
#JoinColumn(name="tripid")
private TripModel tripModel;
}
This works fine. But when trip id is 0 in TripStop table, it shows following exception:
02:32:43,784 ERROR [stderr] (http--0.0.0.0-8080-5) org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.example.scm.model.TripModel#0]
Is there any option with which we can used trip id = 0 in TripStop table, without any exception? How can I allow this?
The tripID is defaulting to 0 because you are using primitives. Switch to primitive wrappers so these values can default to null and this should solve it