My database contains a table with a composite-primary-key such that one of the keys is a foreign key, and the other is supposed to be used to get an entity from an external service. The code looks somewhat like this:
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#Embeddable
#EqualsAndHashCode
public class PrimaryKey implements Serializable {
#Column(name = "A_ID")
private Long aId;
#Column(name = "F_ID")
private Long fId;
}
#Entity
#Table(name = "JOIN_ENTITY")
#Getter
#Setter
#EqualsAndHashCode
#AllArgsConstructor
#NoArgsConstructor
public class JoinEntity {
#EmbeddedId
private PrimaryKey pk;
#MapsId("aId")
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "A_ID")
private EntityA a;
public getFId() { return pk.getFId(); }
}
#Entity
#Table(name = "ENTITY_A")
#Getter
#Setter
#EqualsAndHashCode
#AllArgsConstructor
#NoArgsConstructor
public class EntityA implements Serializable {
....
#OneToMany(mappedBy = "a", cascade = CascadeType.ALL, orphanRemoval = true)
List<JoinEntity> list = new ArrayList<>();
}
When I get a JoinEntity saved and try to get EntityA from the database the list is not being populated, but if I get some JoinEntity from the database the related EntityA is recovered correctly. What do I do to get the JoinEntity list to be recovered with the EntityA?
You need to use FetchType.EAGER on the #OneToMany association in the EntityA class:
#OneToMany(mappedBy = "a", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
List<JoinEntity> list = new ArrayList<>();
This way when you retrieve a EntityA from the database, its JoinEntitys will be automatically retrieved.
Solved the problem adding an ENTITY_F table with the id and switching to a simple ManyToMany relationship.
Related
I have 4 Entities, that a related to each other with #OneToMany relationships.
When I try to save Order that contains OrderItem - Orderitem has no backreference.
In the code below only important fields are showed for brevity ( usual strings and primitives are omitted ). I decided to include Dish and User Entities also.
Order:
#Entity
#NoArgsConstructor
#Getter
#Setter
#ToString
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
#OnDelete(action = OnDeleteAction.CASCADE)
#OneToMany(
mappedBy = "order",
cascade = CascadeType.ALL,
fetch = FetchType.EAGER,
orphanRemoval = true)
private List < OrderItem > orderItems;
}
Dish:
#Entity
#NoArgsConstructor
#Getter
#Setter
#ToString
public class Dish {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "dish")
#ToString.Exclude
private List < OrderItem > orderItems;
}
OrderItem:
#Entity
#NoArgsConstructor
#Getter
#Setter
#ToString
public class OrderItem {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#ToString.Exclude
private Dish dish;
#ManyToOne(fetch = FetchType.LAZY)
private Order order;
private int quantity;
}
User:
#Entity
#NoArgsConstructor
#Getter
#Setter
#ToString
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#OneToMany(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List < Order > orders;
}
The problem happens when I try to save Order with Spring data JPA.
Let's print Order to see OrderItem before saving.
public Order saveOrder(Order order) {
System.out.println("SERVICE saving order " + order);
return orderRepository.save(order);
}
As you can see, orderItems backreference is null before saving ( I though spring data jpa should deal with setting it ).
SERVICE saving order Order(id=0,
orderItems=[OrderItem(id=0, quantity=2, order=null)])
Here is what I have in DB ( Order and OrderItem entities ).
In your OrderItem class, add annotation below:
#ManyToOne(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.PERSIST}, fetch=FetchType.LAZY)
#JoinColumn(name="order_id", referencedColumnName="id", nullable = false)
Order order.
One more thing, I suggest you use SEQUENCE_GENERATOR, beacause IDENTITY means: I'll create the entity with a null ID and the database will generate one for me. I don't think Postgres even supports that, and even if it does, a sequence generator is a better, more efficient choice.
The best option that I found for this is doing something like:
order.getOrderItems().forEach(orderItem -> orderItem.setOrder(order));
Before your save() call. Even though order is not persisted at this point, it seems like Hibernate can resolve the relation and the back references will be set correctly.
If you do not want to bother setting the back reference in your business logic, you can add something like this to your entity:
class Order {
...
#PrePersist
public void prePersist() {
setMissingBackReferences();
}
private void setMissingBackReferences() {
orderItems.forEach(oderItem -> {
if (oderItem.getOrder() == null) {
oderItem.setOrder(this);
}
});
}
...
}
I have two tables that I want to join.
Lets say TableA and TableB.
TableA
#Entity
#Table(name = "TableA")
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class TableA {
#Id
#EmbeddedId
private TableA.PrimaryId id = new TableA.PrimaryId();
#Column(name = "COL1")
private Integer col1;
#ManyToOne
#JoinColumn(name = "ID1")
private TableB tableB;
#Data
#Embeddable
#Builder
#AllArgsConstructor
#NoArgsConstructor
public static class PrimaryId implements Serializable {
#Column(name = "ID1")
private Integer id1;
#Column(name = "ID2")
private Integer id2;
}
}
TableB
#Entity
#Table(name = "TableB")
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class TableB {
#Id
#EmbeddedId
private PrimaryId id = new PrimaryId();
#Column(name = "COLUMN1")
private String column1;
#Data
#Embeddable
#Builder
#AllArgsConstructor
#NoArgsConstructor
public static class PrimaryId implements Serializable {
#Column(name = "ID1")
private Integer id1;
#Column(name = "KEY2")
private Integer key2;
#Column(name = "KEY3")
private Integer key3;
#Column(name = "KEY4")
private Long key4;
}
}
The problem is that TableB has composite primary key (4 columns), but I need to do join only on 1 column (ID1).
It isnt a standart join, it is a partial primary key join.
So it causes an error:
nested exception is org.hibernate.AnnotationException: A Foreign key refering TableB from TableA has the wrong number of column. should be 4
If I try this
#ManyToOne
#JoinColumn(name = "ID1", referencedColumnName = "ID1", updatable=false, insertable=false)
I get another error
nested exception is org.hibernate.AnnotationException: referencedColumnNames(ID1) of TableA.TableB referencing TableB not mapped to a single property
I've looked for an answer in similar posts but found nothing useful :(
So I appreciate any help...
In my spring application , Lazy fetch is not working which is making fetch operation from db heavy. So,
when I fetch students using getStudentByRollNo() It is fetching fields EAGERLY which are even marked as Lazy. but when I am using findBySimpleNaturalId() it is loading lazy.
My classes are
Student.class
#Entity
#Builder
#ToString
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#FieldDefaults(level = AccessLevel.PRIVATE)
class Student{
#Id
Long id;
#NaturalId
String rollNo;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "tutor_fk", nullable = false)
Tutor tutor;
#OneToMany(fetch = FetchType.LAZY, mappedBy = Courses.student, cascade = CascadeType.ALL)
Set<Courses> courses = new HashSet<>();
}
Courses.class
#Entity
#Getter
#Setter
#Builder
#ToString
#NoArgsConstructor
#AllArgsConstructor
#FieldDefaults(level = AccessLevel.PRIVATE)
class Courses{
Long id;
String create_date_time;
#ManyToOne
#JoinColumn(name = "student_fk")
Student student;
}
Tutor.class
#Entity
#Getter
#Setter
#Builder
#ToString
#NoArgsConstructor
#AllArgsConstructor
#FieldDefaults(level = AccessLevel.PRIVATE)
class Tutor{
Long id;
String name;
}
studentService.class
#Transactional
class studentService{
Students getStudent(String rollNo ){
return studentRepository.findOneByRollNO(rollNo);
} }
when I run my code on org.hibernate.SQL: DEBUG.
querys hit are
select course0_.id as id1_43_0_, course0_.create_date_time as create_date_time2_43_0_
from course course0_ where course0_.id=?
select tutor0_.id as id1_43_0_, tutor0_.name as name2_43_0_
from tutor tutor0_ where tutor0_.id=?
it is hitting above query as eager when these fields are marked LAZY
But when I am running getByNaturalId it is fetching lazy
#Transactional
class studentService{
Students getStudent(String rollNo){
Map<String, Object> filter = new HashMap<>();
filter.put("rollNo",rollNo);
return studentRepository.findBySimpleNaturalId(rollNo);
} }
I want to know why is it fetching eager for getByRollNo() and fetching lazy for findBySimpleNaturalId() ?
I am trying to develop a system for managing dormitories. Since I don't have much experience with databases, I am stuck on a problem and I have a solution but I am not sure if this would be the right approach.
So I have Room and User. Each user can be accommodated in one room, but one room can accommodate more users. I would like to manage this relationship in one entity - Accommodation. Here I would have more properties, like start/end Date, etc.
I am using Hibernate to map the tables. From what I've read, persisting Collections from Java can be done in two ways, either by #OneToMany or by #ElementCollection. I am not quite sure if I should define this relationship in the Room entity or in the Accommodation entity? If I do it in the room entity then the Accommodation would hold just fk from the room/user tables?
Also, is it possible to only fetch the primary key when doing one-to-many relations instead of getting the whole object? I know that FETCH.LAZY does this, but in my Accommodation entity ideally I would want to store only Set studentsIds.
Thank you in advance.
#Table(name = "student")
#AllArgsConstructor
#Data
#Embeddable
#NoArgsConstructor
#javax.persistence.Entity
public class Student implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "role")
private String role;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "room", nullable = false)
private Room room_number;
}
Here is the Room entity
#javax.persistence.Entity
#Table(name = "room")
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Data
public class Room
{
#Id
#Column(name = "room_number")
private Long roomNumber;
#Column(name = "location_address")
private String locationAddress;
#Column(name = "dormitory_name")
private String dormitoryName;
}
Accommodation entity
#javax.persistence.Entity
#Table(name = "accommodation")
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Data
public class Accommodation extends Entity {
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "room_number")
private Room room_number;
#OneToMany(mappedBy = "room_number", // I am not sure about this
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
orphanRemoval = true) private List<Student> students;
#Column(name = "date_from")
private Date dateFrom;
#Column(name = "date_to")
private Date dateTo;
}
I have the following entity:
#Entity
#Getter
#Setter
#ToString
public class Team extends EntityBase {
......
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY,
orphanRemoval = true, optional = false)
#JoinColumn(name = "auth_id")
private TeamAuthentication auth;
......
}
so it has a TeamAuthentication table reference. The latter entity looks as follows:
#Entity
#Getter
#Setter
#ToString
public class TeamAuthentication {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String accessToken;
}
What I want is that when I fetch existing Team entity from the table and replace a reference to TeamAuthentication table there for field auth, then persist this Team entity with teamRepository.save(), I want that old TeamAuthentication would be deleted from its table. At the moment it stays in the table and becomes sort of a loitering entry that won't be ever used or queried.
How can I leverage Hibernate cascade in deleting OneToOne reference on change?