I have got three entities, Session, Order and User (part of my online movie tickets project). In my domain model, Order keeps fk of both User and Session. As you can see in my code:
#Table(name="Orders")
#Entity
public class Order {
#ManyToOne
#JoinColumn(nullable = false)
private User user;
#ManyToOne
private Session session;
...
}
#Entity
#Table(name="Session")
public class Session {
#OneToMany(fetch=FetchType.LAZY,
cascade = CascadeType.ALL,
mappedBy = "session")
private List<Order> orders = new ArrayList<Order>();
...
}
#Table(name="User")
#Entity
public class User {
#OneToMany(cascade = { CascadeType.PERSIST,
CascadeType.MERGE,
CascadeType.REMOVE },
mappedBy = "user")
private #Getter Set<Order> orders = new HashSet<>();
...
}
My question is, Can I use CascadeType.ALL in both Session and User?
Are there potential conflicts when update Order with both Session and User?
As you can see, I use fetchType.Lazy, Can it guarantee that orders in both Session and User are up-to-date?
Question 1: It's a good question, but in order to answer it you need to understand the concept of the owning entity. The Entity with the #ManyToOne annotation is the owner of the relationship. This is important for the developer because no relationship will be persisted unless it's done on the owning side, in this case that means setting Order.user. However, since you have the cascade annotation on the non-owning User, you have to do extra work to use the cascade functionality:
// create Order
Order order = new Order();
// create User and Set of orders
User user = new User();
Set<Order> userOrders = new HashSet<Order>();
user.setOrders(userOrders);
userOrders.add(order);
// and set Order.user
order.setUser(user);
// persist with cascade
em.persist(user);
Notice that you must create a Set of orders as well as set Order.user to persist with cascade. However, if you put the cascade annotation on the owning entity Order, then your job becomes much simpler:
// create User
User user = new User();
// create Order
Order order = new Order();
// and set Order.user
order.setUser(user);
// persist with cascade
em.persist(order);
Now just persisting order will persist the new User and the Order with one call. Without the cascade annotation on the Order entity, persisting Order before User will give you an exception.
References: What is the “owning side” in an ORM mapping?, In a bidirectional JPA OneToMany/ManyToOne association, what is meant by “the inverse side of the association”?
Question 2: FetchType.LAZY means you have to get the children by specific query, so if I understand your question, the answer is no, it doesn't guarantee anything. With FetchType.LAZY when you get a Session you will not have access to the Session.orders when the entity becomes detached, typically after you have left your Session Bean or Service Layer. If you need access to orders, you will need to get them in the select query:
"select distinct s from Session s join fetch s.orders"
EDIT: As noted, by default this query does a sql "inner join", which will return nothing if there are no orders. Instead, do
"select distinct s from Session s left join fetch s.orders"
so that you always get the sessions that are in the database.
Reference: Difference between FetchType LAZY and EAGER in Java Persistence API?
Related
I'm working on a Spring Boot Application with Hibernate and I'm just trying to understand the correct way to approach a OneToOne mapping when it comes to using cascade delete.
So, we have a User table and a PasswordResetToken table. A user has standard user columns: id, username, password, email.
A password reset token has an id, a FK to userId, and a string for a token.
So, my question now is: how do I correctly model this so we can properly cascade delete?
My thought process is that we have a unidirectional mapping since password reset token has a FK to user, and user does NOT have a FK to password reset token.
So I would think that we would place the #OneToOne on our PasswordResetToken class in Java and not have a reference to PasswordResetToken in our User class, but then the PasswordResetToken class will have a reference to a User object.
But, through some stackoverflowing, I found that people would have the child object (PasswordResetToken) inside the parent object (User) despite the parent object's table not having a reference to the child object's table (since the User table doesn't have a PasswordResetToken in it) which allows for adding the cascade remove to the #OneToOne annotation which means that when a User gets deleted, all children will get deleted as well.
So, which way is the right way to model this relationship?
Thanks for your time
There are many ways to solve your problem. Some are less, some are more efficient.
Bidirectional with foreign key
#Entity
public class PasswordResetToken {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User User;
// other fields
}
#Entity
public class User {
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL,
fetch = FetchType.LAZY, optional = false)
private PasswordResetToken passwordResetToken;
// other fields
}
Bidirectional with principal/parent's primary key as foreign key
Since it's 1-1 relationship, you could use User's ID as a primary key for PasswordResetToken table.
#Entity
public class PasswordResetToken {
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private User User;
// other fields
}
#Entity
public class User {
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL,
fetch = FetchType.LAZY, optional = false)
private PasswordResetToken passwordResetToken;
// other fields
}
Unidirectional
If you want to have unidirectional mapping, and to have PasswordResetToken entity as part of User entity, you'll have to move the foreign key to User table, since #JoinColumn has to be applied on entity owning the foreign key.
#Entity
public class User {
#OneToOne(cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
#JoinColumn("password_reset_token_id") // FK in User table
private PasswordResetToken passwordResetToken;
// other fields
}
As for performance, the most efficient is bidirectional with #MapsId. Bidirectional with #JoinColumn is less efficient, and I'm not sure about unidirectional mapping. One to one mappings are not that common in practice, and I'm not sure how often people use unidirectional mapping. Probably not at all, since the foreign key is usually on dependent side.
I don't know how big the token is, but what is wrong with storing the token in the User entity as simple column? You can abstract some parts by using an #Embeddable but really this should IMO be in the same table. If you are concerned with the amount of data fetched, you should be using DTOs to reduce the amount of data.
I know for a fact that with clause on fetch join are not allowed by hibernate
I am using spring data jpa and postgres.
Here is how my entity is designed
public class Organisation {
#Id
private Long id;
#OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL)
#LazyCollection(LazyCollectionOption.EXTRA)
private Set<Assignment> assignments = new HashSet<>();
#OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL)
private List<Event> events;
}
public class Event {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "organisations_id", nullable = false)
private Organisation organisation;
#OneToMany(mappedBy = "event", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<EventValue> eventValues = new HashSet<>();
}
public class EventValue {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "event_id")
private Event Event;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "assignment_id")
private Assignment assignment;
}
public class Assignment {
#Id
private Long id;
#OneToMany(mappedBy = "assignment", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<EventValue> eventValues = new HashSet<>();
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation;
}
Kind of a three way mapping. What the above entity design says is:
one organisation can have many events
one events can have many event values
one organisation can have many assignments
one assignment can be mapped to only one organisation and whithin the event of this organisation it is supposed to have only one event value (but as per entity design above it can have set of values which is not directly mapped to assignment)
So, I tried to query something like this.
#Query("select assignment from Assignment left join fetch assignment.organisation org
left join fetch org.event event left join fetch event.eventValues eventValue
with eventValue.assignment.id=?1 where assignment.id=?1)
Assignment getByAssignmentId(Long id);
What am I trying to achive with the query ?
To get assignment with given (id) -> organisation -> list of activities with HashSet containing only ONE activity value mapped to assignment.
The query is obviously going to fail because of using with clause on fetch join. I somehow feel the entity has 3 way dependency so it might be wrong.
I do not want to generic jdbcTemplate solution or SqlResultMapping solution where we need to do some kind of projection and set values manually. Is there a ORM solution to solve this problem ?
The reason why a WITH or ON clause is disallowed for join fetches is pretty simple. Hibernate works on managed entities, which means, once the entities are managed by the current persistence context, changes done to these objects will be flushed back to the database at the end of the transaction.
Now, if you were allowed to use the WITH or ON clause in a join fetch, the querying itself could alter the managed state of a collection, which would lead to UPDATE/DELETE statements to flush the collection changes back. Since this is completely unexpected, but a necessary side effect, it is disallowed.
Having said that, this is a perfect use case for Blaze-Persistence Entity Views.
Blaze-Persistence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. I created Entity Views on top of it to allow easy mapping between JPA models and custom interface defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
A DTO mapping for your model could look as simple as the following
#EntityView(Assignment.class)
interface AssignmentDto {
Long getId();
OrganisationDto getOrganisation();
}
#EntityView(Organisation.class)
interface OrganisationDto {
Long getId();
List<EventDto> getEvents();
}
#EntityView(Event.class)
interface EventDto {
Long getId();
#Mapping("eventValues[assignment.id = VIEW_ROOT(id)]")
EventValueDto getEventValue();
}
#EntityView(EventValue.class)
interface EventValueDto {
Long getId();
// Other stuff
}
The JOIN condition is modeled in the mapping expression eventValues[assignment.id = VIEW_ROOT(id)] which translates to what you would expect.
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
AssignmentDto dto = entityViewManager.find(entityManager, AssignmentDto.class, id);
But the Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
It will only fetch the mappings that you tell it to fetch.
I have an entity that has a #ManyToMany collection marked as lazy. When I list those entities in a JSP I call a method in each entity to decide if I should show a button or not, that method was developed to count the number of elements in that collection, so if the number of elements is too high it takes forever to show the JSP because Hibernate loads the entire collection with all of their data.
I was wondering if there's a way to call a NamedQuery from that method in the entity, for example:
#NamedNativeQuery(
name="showButton",
query="SELECT count(distinct(id)) FROM USER where GROUP_ID = :groupId")
#Entity
class Group {
...
#ManyToMany(mappedBy = "groups", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Set<User> users = new HashSet<User>();
}
...
public boolean showButton() {
// Can I call the named query here??
}
}
#NamedQuery is not possible, but #Formula can be used to calculate a property.
I am simply trying to perform an update of an entity. However hibernate attempts 2 SQL statements, one to perform the correct update and an unwanted second to update the ID alone to null, which causes my application to fail.
I am using Spring Data alongside Hibernate and when performing an update of an Entity, I see the expected update SQL is performed, however when running the application with SQL Server, a subsequent update is attempted which does the following:
update my_table set id=null where id=?
This fails obviously.
Cannot update identity column 'ID'.
Running the same code with H2 I do not see this second update triggered.
Any idea what might be the cause of this behaviour?
I am extending JpaRepository and using the default save().
Here is a snippet of my entity:
#Table(name = "MY_TABLE")
#Entity
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String anotherValue;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name="id")
private List<ChildEntity> children = new ArrayList<>();
// getters, builder, private default constructor ...
Snippet building my entity:
MyEntity.newBuilder()
.withId(id)
.withAnotherValue(valueUpdate)
.build();
Repository:
public interface MyRepository extends JpaRepository<MyEntity, Long>
Saving:
myRepository.save(myUpdatedEntity);
As i think of probable cause for this is if you associate two entities with their IDs as foreign keys then hibernate may try to update ID of parent as foreign key of other entity. Its not correct way to associate.
In a one-to-many relation add a foreign key in the many side entity, that have to reference the primary key of the one side entity class.
#Entity
public class MyEntity {
..
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name="id", referencedColumnName = "MYENTITY_ID")
private List<ChildEntity> children = new ArrayList<>();
}
I have a m:n relation beetwen objects (Meeting, Person) as many persons can be participant of many meetings.
I've set it like this
Meeting
#ManyToMany(mappedBy = "meetings")
protected Set<Person> participants = new HashSet<Person>();
Person
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name = "person_meeting",
joinColumns = {#JoinColumn(name = "person_id")},
inverseJoinColumns = {#JoinColumn(name = "meeting_id")}
)
protected Set<Meeting> meetings = new HashSet<Meeting>();
Id DB hibernate created me table meeting_participants with two fields: meeting_id, person_id. Cool, just as I wanted.
Now problematic case. I've create Meeting object and I saved it to DB. Than I create set of users, I add it to meeting
this.saveMeeting.setParticipants(set);
Hibernate displays:
Hibernate: update Meeting set duration=?, meetingDate=?, room=? where meeting_id=?
Nothing added to association. What do I need to change ?
// EDIT
I've changed in Meeting definition of the field
#ManyToMany(mappedBy = "meetings", cascade = {CascadeType.ALL})
protected Set<Person> participants = new HashSet<Person>();
Now I get error
org.hibernate.HibernateException: Illegal attempt to associate a collection with two open sessions
It is in this method
public static Long add(Meeting meeting) {
SessionFactory sf = null;
Session session = null;
sf = HibernateUtil.getSessionFactory();
session = sf.openSession();
session.beginTransaction();
try{
session.save(meeting);
session.getTransaction().commit();
session.flush();
} catch(HibernateException e){
session.getTransaction().rollback();
e.printStackTrace();
return new Long(-1);
}
session.close();
return meeting.getId();
}
The line that is causing the problem is:
session.save(meeting);
EDIT
Ok I've closed session properly. Everything works find BUT only when I'm creating new objects. When I want to update association it doesn not work. So the question is. How to update association ??
This question is asked every two days. You just initialized one side of the association, and you chose the one that Hibernate ignores. A bidirectional association has an owner side, and an inverse side. Hibernate only considers the owner side. And the owner side is the one which doesn't have the mappedBy attribute.
So you need to initialize the other side of the association:
for (Participant p : set) {
p.getMeetings().add(saveMeeting);
}
I've solved the problem. I didn't think about that so I didn not wrote it. Person was a top class and it was extended by two others. I've changed update action so that it took an instance of Object class. That way I'm able to update association