With spring-data-jpa 2.0.8.RELEASE
I have a #OneToMany related entity pair. Let's say King & Peasant. I wanted to have a logic where when a peasant is updated, this would also update the kings #LastModifiedDate value manually. I do something like this;
#PreUpdate
#PreRemove
#PrePersist
void updateParents() {
Date now = new Date();
BaseEntity container = getParent();
while (Objects.nonNull(container)) {
container.setUpdateDateTime(now);
container = container.getParent();
}
}
and this works well, meaning it does update all the parents up to the king (the table structure is really messy with 5 depth from king to the lowest serf), the problem I am having is, the modifications on parents are not persisted at all. I have a service like following;
#Transactional
public void update(String kingId, String peasantSeqNo) {
Peasant peasant = peasantRepository.getPeasant(kingId, peasantSeqNo);
peasant.setNobility(false);
peasantRepository.save(peasant);
}
In above code, the #PreUpdate annotated updateParents() method is triggered, and king's update timestamp is updated, still after the transactions end, this change is not persisted. I can trigger this persistence with an explicit kingRepository.flush() but I want it to be done automatically, just with the modification of the parent.
The linkage between King-Peasant is as follows;
#JsonManagedReference
#OneToMany(mappedBy = "parent", cascade = REMOVE)
private List<Peasant> peasantry;
and
#JsonBackReference
#MapsId("kingId")
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "ID_KING", referencedColumnName = "ID_KING", nullable = false)
private King parent;
This is somehow an issue with my utilization of JPA, but cannot find the exact reason & a solution, can you give me any input on this?
You are hitting a limitation of JPA (implementations).
When the event gets triggered the JPA implementation already decided which entities it is going to persist, thus your changes don't get picked up.
This behavior is actually defined as not being defined in the JPA specification:
In general, the lifecycle method of a portable application should not invoke EntityMan- ager or query operations, access other entity instances, or modify relationships within the same persistence context[46].[47] A lifecycle callback method may modify the non-relationship state of the entity on which it is invoked.
[46] Note that this caution applies also to the actions of objects that might be injected into an entity listener.
[47] The semantics of such operations may be standardized in a future release of this specification.
So you need to move this behavior away from the JPA Listeners into your business logic.
Related
I have a problem with indexing the boolean #field in Hibernate Search, the problem is when the object has changed the rest of the fields are changed as well only the boolean field keeps the old state of the object.
#JsonIgnore
#Field(name = "isWarning", index = Index.YES)
#SortableField(forField = "isWarning")
private boolean isWarning() {
//some logic
}
what is the right way to approach this problem?
I assume this "logic" you mention accesses other entities. You need to tell Hibernate Search that those entities are included in the entity with the isWarning method.
Let's say the isWarning method is defined in an entity called MainEntity, and it accesses data from another entity called SomeOtherEntity.
In SomeOtherEntity, you will have the reverse side of the association:
public class SomeOtherEntity {
#ManyToOne // Or #OneToOne, or whatever
private MainEntity mainEntity;
}
Just add #ContainedIn and you should be good:
public class SomeOtherEntity {
#ManyToOne // Or #OneToOne, or whatever
#ContainedIn
private MainEntity mainEntity;
}
Note that, unfortunately, this can have a significant impact in terms of performance if SomeOtherEntity is frequently updated: Hibernate Search will not be aware of exactly which part of SomeOtherEntity is used in MainEntity, and thus will reindex MainEntity each time SomeOtherEntity changes, even if the changes in SomeOtherEntity don't affect the result of isWarning. A ticket has been filed to address this issue, but it's still pending.
Most important structural description:
In my application is used Spring data JPA.
I have a model part in my application:
#Entity
public class Event implements Identifiable {
// ...
#OneToMany(cascade = CascadeType.ALL, mappedBy = "event", orphanRemoval = true)
#OrderColumn(name = "order_index")
private List<Attendee> attendees = new ArrayList<>();
// ...
}
#Entity
public class Attendee implements Identifiable {
// ...
#ManyToOne
#JoinColumn(columnDefinition = "event_id")
private Event event;
// ...
}
It should be clear for all who knows JPA.
I've implemented test to save an event, as result dependent attendees collection saves also (hibernate magic used because of correspond relation settings shown in very beginning.)
eventRepository.save(event);
provides save|update event and replace all old nested attendees with new ones.
Problem description:
Some times I need use additional handlers in same transaction with saving event. They use repositories also:
// transaction starts... some handlers are used
eventRepository.save(event);
// some additional handlers2 are used
// transaction ends
If handlers2 contains any repository operation with independent entity, for example:
profileRepository.findAll();
// or
profileRepository.findByEmail("anyEmail");
, it fails with exception org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.nextiva.calendar.entity.Attendee. It seems Attendee collection should be saved because it was not.
To fix the issue I've used workaround like:
// transaction starts... some handlers are used
attendeeRepository.save(event.getAttendees());
eventRepository.save(event);
// some additional handlers2 are used
// transaction ends
It works now, but I do not like this way.
Questions:
Is it my architectural issue?
Is it hibernate issue?
How should it be configured without redundant calling attendeeRepository.save(event.getAttendees());?
Try to switch to JpaRepository and use
eventRepository.saveAndFlush(event);
instead of eventRepository.save(event).
Or
eventRepository.save(event);
eventRepository.flush();
It will force the repo to flush all pending changes to the database.
Also check whether you set #Transactional(readOnly = true) on your repo interface that turn the flush mode to NEVER (as described in the reference, or MANUAL as described here) also for save methods. Perhaps this is the issue...
Hibernate persists modified entities at the of transactional methods, I can avoid by using session#evict(entity).
If I detach it from the persistence context, the entities whithin it will also be detached?
For instance, I have this classes:
#Entity
public class User extends BaseEntity{
#Column(name = "email")
private String email;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private List<Address> addresses;
// getters and setters
}
#Entity
public class Address extends BaseEntity{
#Column(name = "email")
private String email;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID")
private User user;
// getters and setters
}
If I detach a user object, but change the address object in it, will the address be persisted at the end of transaction? Like this:
User user = userDAO.getById(id);
session.evict(user);
Address address = user.getAddresses().get(0);
address.setNumber(number);
addressDAO.saveOrUpdate(address); //will this work?
Entities that are updated or deleted using a EntityManager.createQuery() are not loaded into the Persistence Context, this only happens for select queries, and when you use find()or merge().
After you do an update or delete query your persistence context may actually be out-of-sync with the database, because the query doesn't update the entities which has already been loaded into the persistence context (you need to call refresh() to see the changes).
If you load a number of user (into the persistence context), and later doUpdate User set status='active' where id IN (:ids), then you have not modified any of the users in the persistence context, you have only modified the database. To modify a user, you must modify the actually managed Entity by calling `aUser.setStatus('active'), when the transaction commits, JPA will check all managed entities against a copy created when it was loaded, and if anything has changed it will do an Update.
If you are loading 5000 objects into the Persistence it may take some time for JPA to run though the entity graph, and detect the changes when the transaction commits. If you didn't modify anything, and would like to speed up the change-detection, there are two ways to do this. Load your entities using a read-only query, this tells JPA that it does not need to keep a copy of the loaded entity. The other option is to call EntityManager.clear() to throw away all managed entities. However, if you are interested in performance, the best solution is probably to avoid loading the entities into the persistence context. As I understand you problem, you need to do a Update User set ... where id IN (:ids)and for that you only need the user's id so you don't need to load the user, you just need the ids, and therefore you can do List<Long> ids = em.createQuery("select u.id from User u where ...", Long.class).getResultList();
Hope this clarifies things for you :)
EDIT: this is written from a JPA perspective, but for hibernate EntityManager just forwards directly to SessionImpl, so the behavior is exactly as described, except for find() being called get()in native Hibernate.
Since JPA 2.0
given an EntityManager you can call detach with the entity you want to be detached as parameter
void detach(Object entity)
more here
if you use injection then you can inject an EntityManger in the service where you want to detach the required entity.
I've got this structure of project:
class UserServiceSettingsImpl {
...
#ManyToOne
private UserImpl user;
#ManyToOne
private ServiceImpl service;
...
}
class ServiceImpl {
....
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "service", orphanRemoval = true)
private Set<UserServiceSettingsImpl> userServiceSettings;
....
}
class UserImpl {
....
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user", orphanRemoval = true)
private Set<UserServiceSettingsImpl> serviceSettings;
....
}
I am trying to delete Service and everything that belongs to it (UserServiceSettingsImpl), but accidentally, this settings are not being removed (I suppose because they are not orphans since UserImpl has them too). So the thing is: is there a way to delete Settings, without deleting them from user manually (there could be a lot of users with a lot of settings, iterating through it could take a lot of time) ?
You are correct in why the UserServiceSettings are not being deleted when deleting a service if they are also referenced by a User. They are not orphans and will have to be deleted explicitly per your business logic.
Three ideas:
Use the ORM to batch delete entities.
It's not much different than iterating, but might be optimized while still using the ORM.
List settingsCopy = new ArrayList<>(service.getSettings());
service.getSettings().clear();
myDao.deleteAll(settingsCopy);
Use direct HSQL/SQL to batch delete.
This depends on what framework you are using, but generally would be something like this, probably in your repository/dao class:delete from UserServiceSettingsImpl o where o.service.id = ? However, hibernate does not support JOINs when deleting, afaik, so this doesn't work as written. It's generally necessary to rework the HSQL to use a "delete where id IN(...)" type format.
Setup CASCADE DELETEs and CASCADE UPDATEs in your database DDL, outside of the ORM framework. (Not recommended.)
However, the last two options have problems if there is chance that service's and user's UserServiceSettings can be modified at same time via multiple threads (even with correct transaction boundaries), or if those entities will be used within the orm context after the delete without a reload. In that case, you will likely run in to unexpected and sporadic errors with the last two approaches, and instead, should iterate the settings and delete via the ORM, even if it is inefficient.
Even with the first approach, it can be tricky to avoid errors in highly concurrent environments when deleting shared entities.
You're correct that you cannot delete them in any kind of automatic way - they will never be orphans. I think the best you can do is just write yourself a helper method. e.g. if you have a ServiceDao class, you would just add a helper as:
public void deleteServiceAndSettings(Service service) {
for (UserServiceSettings setting : service.getUserServiceSettings()) {
session.delete(setting);
}
session.delete(service);
}
I have a weird problem with two entities with one-to-many relation in JPA. I am using Glassfish 3.1.2.2 with EclipseLink 2.3.2. This is the first entity:
#NamedQueries({
#NamedQuery(name="SampleQueryGroup.findAll", query="SELECT g FROM SampleQueryGroup g")
})
#Entity
public class SampleQueryGroup implements Serializable {
// Simple properties, including id (primary key)
#OneToMany(
mappedBy = "group",
fetch = FetchType.EAGER,
cascade = {CascadeType.REMOVE, CascadeType.MERGE}
)
private List<SampleQuery> sampleQueries;
// Gettes/setters, hashcode/equals
}
And this is the second one:
#Entity
public class SampleQuery implements Serializable {
// Simple properties, including id (primary key)
#ManyToOne(cascade = {CascadeType.PERSIST})
private SampleQueryGroup group;
// Gettes/setters, hashcode/equals
}
I have a stateless session bean which uses an injected EntityManager to run SampleQueryGroup.findAll named query. I also have a CDI managed bean which calls the SSB method and iterates through SampleQueryGroup.getSampleQueries() for each SampleQueryGroup returned by the method. I didn't paste the code as it is pretty straightforward and somehow standard for any Java EE application.
The problem is the eager fetch does not work and getSampleQueries() returns an empty list. However, when I change the fetch type back to FetchType.LAZY, everything works and I get the list correctly populated. I don't understand why this happens. Does it have anything to do with internal caching mechanisms?
My guess is that when you add a new SampleQuery you are not adding it to the SampleQueryGroup sampleQueries, so when you access it, it is not their. When it is LAZY you do not trigger it until you have inserted the SampleQuery, so then it is there.
You need to maintain both sides of your relationships. (you could also disable caching, or refesh the object, but your code would still be broken).
See,
http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Object_corruption.2C_one_side_of_the_relationship_is_not_updated_after_updating_the_other_side