7

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

WBLord
  • 874
  • 6
  • 29
  • 1
    Have you tried `@Transactional(readOnly=true)`, however if you are reading only data the dirty checking shouldn't kick in, unless you are doing multiple queries and thus probably sub-optimal access in the first place. – M. Deinum Jun 07 '22 at 14:03
  • 1
    @M.Deinum The problem is that I can assemble a complex object. To do this, I can pull data from different repositories and change it. My task is not to make unnecessary checks – WBLord Jun 08 '22 at 10:26
  • 1
    Please add some code, also if you do a lot of queries you are probably doing the wrong thing and should write a dedicated query to retrieve what you want in 1 go instead of reaching out to different repositories, retrieve half the world and ditch 80% of that. – M. Deinum Jun 09 '22 at 05:35
  • 1
    @M.Deinum Update! – WBLord Jun 09 '22 at 10:13
  • Fixing `findAllByProfileAndConferenceWithinRange` is not an option? – dpr Jun 09 '22 at 14:32
  • @dpr Of course not, hibernate will constantly do dirty checks on the layers above – WBLord Jun 09 '22 at 17:06
  • @WBLord based on your comments in the code snippet I understood, that the large amount of entities read in `findAllByProfileAndConferenceWithinRange` is the root cause of the problem as this adds way too many entities to hibernate and hibernate takes ages for the dirty checks. Right? If that's correct you could change `findAllByProfileAndConferenceWithinRange` to not fetch managed entities but a custom class that is not managed by hibernate... – dpr Jun 10 '22 at 07:55
  • @dpr My task is to have a method that is able to transactionally pull out a large number of entities, while not constantly doing dirty checks. Then transfer these entities to a higher level (there should be one transaction) And there it is already possible to flash entities – WBLord Jun 10 '22 at 10:01

2 Answers2

4

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

Eskandar Abedini
  • 2,090
  • 2
  • 13
0

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);
}

}

Cody Tan
  • 1
  • 1