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.
Related
I need some advice on how to properly configure a unidirectional many-to-one relationship with with JPA.
I have an entity called ScheduleEntry. Schedule entries need to know their parent schedule entry, but a parent schedule entry doesn't need to know its child entries. For that reason ScheduleEntry looks like this:
#Entity
#Data
public class ScheduleEntry {
[...]
#ManyToOne
#JoinColumn(name = "parent_id", nullable = true)
private ScheduleEntry parent;
#ElementCollection
#CollectionTable(name = "schedule_entry", joinColumns = #JoinColumn(name = "parent_id"))
#Column(name = "recurrenceNumber")
#EqualsAndHashCode.Exclude
private Set<Integer> recurrences;
}
There is no OneToMany side of this relationship.
This works fine when creating entries, setting their parent entry and fetching entries with parent entries.
However, whenever I update a parent ScheduleEntry Hibernate executes a DELETE statement and delete all child entries of the updated ScheduleEntry:
org.hibernate.SQL: update schedule_entry set active=?, cancelled=?, capacity=?, description=?, end_time=?, parent_id=?, recurrence_number=?, recurs_until_time=?, start_time=?, title=? where id=?
org.hibernate.SQL: delete from schedule_entry where parent_id=?
And that's not what I want. I want that child entries keep their reference to the updated parent entry and don't get deleted. I.e. I want to prevent HIbernate from executing the DELETE statement. Any ideas how to achieve that?
PS: The code that executes the update:
public void update(DTO dto) {
jpaRepository.save(mapper.dtoToModel(dto));
}
called from within a REST controller that manages the transaction scope with Spring's #Transactional:
#Transactional
#PutMapping("/{id}")
public ResponseEntity<?> update(#PathVariable("id") Long id, #RequestBody ScheduleEntryDTO dto) {
return super.update(id, dto);
}
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.
How do you make a select statement or filter a List that is nested within an entity in spring? I have an object that looks like this...
#Entity
#Table(name = "employee")
public class Employee {
...
#OneToMany(mappedBy = "_employee", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
#JsonManagedReference
Set<Deal> _deals;
#OneToMany(mappedBy = "_employee", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
#JsonManagedReference //This is simply to avoid a stackoverflow error according to this link http://stackoverflow.com/questions/3325387/infinite-recursion-with-jackson-json-and-hibernate-jpa-issue
Set<Recommendation> _recommendations;
#OneToMany(mappedBy = "_employee", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
#JsonManagedReference //This is simply to avoid a stackoverflow error according to this link http://stackoverflow.com/questions/3325387/infinite-recursion-with-jackson-json-and-hibernate-jpa-issue
Set<Event> _events;
public Employee() {
}
//getters and setters
....
I get employees with a repository that is accessed by a service class.
The repository looks like this.
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
public Employee getEmployeeById(Long _id);
public Employee getEmployeeBy_username(String username);
}
So bascially when I get an employee by its id, it returns the above lists. When an employee is retrieved I need to do a select statement or filter in some way _deals, _recommendations and _events. So that only those who have the boolean attribute _active=true returned. As it is now, all deals recommendations and events are returned whether they are active or not. How do I filter or select from these lists only active objects?
You almost always select a single Entity type per query, and preferably you would do the filtering in the database. If you want the Deals, Recommendations and Events belonging to a specific Employee, I would normally put these methods in the Repository belonging to entity type I'm trying to load, it could look like this:
#Repository
public interface DealRepository extends JpaRepository<Deal, Long> {
#Query("select d from Deal d where d.active= true and d.employee.id = :employeeId")
List<Deal> findActiveDeals(#Param("employeeId") long employeeId);
}
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?
I am trying to implement my model using hibernate annotations. I have 3 classes, image, person, and tags. Tags is a a table consisting of 4 fields, an id, personId, imageId, and a createdDate. Person has the fields name, id, birthdate, etc. My image class is defined as follows:
#Entity
#Table(name="Image")
public class Image {
private Integer imageId;
private Set<Person> persons = new HashSet<Person>();
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
public Integer getImageId() {
return imageId;
}
public void setImageId(Integer imageId) {
this.imageId = imageId;
}
#ManyToMany
#JoinTable(name="Tags",
joinColumns = {#JoinColumn(name="imageId", nullable=false)},
inverseJoinColumns = {#JoinColumn(name="personId", nullable=false)})
public Set<Person> getPersons() {
return persons;
}
public void setPersons(Set<Person> persons) {
this.persons = persons;
}
If I remove the annotations on the getPersons() method I can use the classes and add and remove records. I want to fetch all the tags with the image and I am trying to use a set. I keep getting the following error:
org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: com.exmaple.persons, no session or session was closed
Can someone please help me and let me know what I am doing wrong?
Thank you
This error message - which actually has nothing to do with your association mapping strategy or annotations - means that you have attempted to access a lazy-loaded collection on one of your domain objects after the Session was closed.
The solution is to either disable lazy-loading for this collection, explicitly load the collection before the Session is closed (for example, by calling foo.getBars().size()), or making sure that the Session stays open until it is no longer needed.
If you are not sure what lazy-loading is, here is the section in the Hibernate manual.
Thanks for the response matt. I am confused now. My query to retrieve the image looks like this:
public Image findByImageId(Integer imageId) {
#SuppressWarnings("unchecked")
List<Image> images = hibernateTemplate.find(
"from Image where imageId=?", imageId);
return (Image)images.get(0);
}
I thought that I can call the single hql query and if my mappings are correct it will bring back the associated data.
I was looking at this example at this link hibernate mappings:
2.2.5.3.1.3. Unidirectional with join table
A unidirectional one to many with join table is much preferred. This association is described through an #JoinTable.
#Entity
public class Trainer {
#OneToMany
#JoinTable(
name="TrainedMonkeys",
joinColumns = #JoinColumn( name="trainer_id"),
inverseJoinColumns = #JoinColumn( name="monkey_id")
)
public Set<Monkey> getTrainedMonkeys() {
...
}
#Entity
public class Monkey {
... //no bidir
} Trainer describes a unidirectional relationship with Monkey using the join table TrainedMonkeys, with a foreign key trainer_id to Trainer (joinColumns) and a foreign key monkey_id to Monkey (inversejoinColumns).