Hibernate checks the state of entities when committing transactions. This is useless and performance-critical when fetching large amounts of data to send to the client.
I found a solution with
entityManager.setFlushMode(FlushModeType.COMMIT);
I put back auto
after the query.
Are there any normal more compact solutions.
Now I'm thinking of doing something similar to aspectJ so I don't clutter up the code and I can annotate both the service and the repository. What do you think about this and how do you solve such a problem?
Example:
@Override
public Collection<ZoomCallInfoDTO> findZoomCalls(Collection<ContactId> invitedUsers, String name, Date startTime, Date endTime, int offset, int limit, boolean asc, callId callId)
{
// Why we did it?
// Due to bad logic of finding calls in time interval (ZoomCallRepository#findAllByProfileAndcallWithinRange)
// which loaded all calls for user to compute periodical calls there are TOO many entities in hibernate.
// So during converting calls to DTOs we also have N x M queries (N - number of call to return, M - number of queries for each call conversion).
// Each query makes hibernate checks all loaded entities for autoFlush policy. And we have bad performance ((
// Possible decisions:
// 1. Switch off autoFlush policy for GET methods:
// 1.1 - Use readOnly transaction (not good for us now because stating top transaction logic is not managed)
// 1.2 - Set flushMode manually (not good as persistence decision but the cheapest now) - CURRENTLY USED
// 2. Decrease number of loaded to hibernate entities - it need to redesign logic of computing periodical calls
// 3. Merge of 1 and 2 decisions - SHOULD BE IMPLEMENTED IN FUTURE
entityManager.setFlushMode(FlushModeType.COMMIT);
if (invitedUsers != null && !invitedUsers.isEmpty())
{
throw new RuntimeException("Search by invited users is not supported.");
}
UserProfile profile = callUtil.getCurrentUserProfile();
List<ZoomCallInfoDTO> callDTOs = new ArrayList<>();
Map<callId, callCacheItem> callCache = new HashMap<>();
for (ZoomCall call : ZoomCallRepository.findAllByProfileAndcallWithinRange(profile.getId(), name, startTime, endTime, offset, limit, asc, callId))
{
callDTOs.add(create(call, profile.getId(), callCache));
}
entityManager.setFlushMode(FlushModeType.AUTO);
return callDTOs;
}
I noticed that after such operations there is an "autoflush" there are too many of them
CodePudding user response:
One solution is implementing CustomEntityDirtinessStrategy
properties.setProperty("hibernate.entity_dirtiness_strategy", EntityDirtinessStrategy.class.getName());
Details in:
How to customize Hibernate dirty checking mechanism
More in:
Options for Entity Dirtness Checking
CodePudding user response:
The CustomEntityDirtinessStrategy is a recent Hibernate API addition, allowing us to provide an application-specific dirty checking mechanism. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class EntityDirtinessStrategy implements CustomEntityDirtinessStrategy {
@Override
public boolean canDirtyCheck(Object entity, EntityPersister persister, Session session) {
return entity instanceof DirtyAware;
}
@Override
public boolean isDirty(Object entity, EntityPersister persister, Session session) {
return !cast(entity).getDirtyProperties().isEmpty();
}
@Override
public void resetDirty(Object entity, EntityPersister persister, Session session) {
cast(entity).clearDirtyProperties();
}
@Override
public void findDirty(Object entity, EntityPersister persister, Session session, DirtyCheckContext dirtyCheckContext) {
final DirtyAware dirtyAware = cast(entity);
dirtyCheckContext.doDirtyChecking(
new AttributeChecker() {
@Override
public boolean isDirty(AttributeInformation attributeInformation) {
String propertyName = attributeInformation.getName();
boolean dirty = dirtyAware.getDirtyProperties().contains( propertyName );
if (dirty) {
LOGGER.info("The {} property is dirty", propertyName);
}
return dirty;
}
}
);
}
private DirtyAware cast(Object entity) {
return DirtyAware.class.cast(entity);
}
}