You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hibernate中Criteria.scroll的Result Transformer失效问题求助

Fixing ResultTransformer Issues with ScrollableResults in Hibernate

Ah, I’ve hit this exact problem before! The core issue here is that ScrollableResults doesn’t automatically apply your Criteria’s ResultTransformer the way criteria.list() does. When you use scroll(), Hibernate returns raw rows directly from the result set (which will have duplicate ObjA instances because of your one-to-many association), and skips the transformation logic that DISTINCT_ROOT_ENTITY handles for you in the batch list() call.

Here are a few practical solutions to fix this while keeping the memory benefits of ScrollableResults:

1. Manual Deduplication (Simplest Approach)

Since ScrollableResults iterates row-by-row, you can track which ObjA instances you’ve already processed to avoid duplicates. Use a set to store processed IDs, and only keep unique entities:

Session session = getSessionFactory().openSession();
Criteria criteria = session.createCriteria(ObjA);
// ... your other criteria filters here ...
criteria.setReadOnly(true);
criteria.setCacheable(false);

// Use FORWARD_ONLY mode for minimal memory footprint
ScrollableResults scroll = criteria.scroll(ScrollMode.FORWARD_ONLY);
Set<Long> processedObjAIds = new HashSet<>();

try {
    while (scroll.next()) {
        ObjA currentObjA = (ObjA) scroll.get(0); // Grab the root entity from the row
        
        // Only process if we haven't seen this ObjA before
        if (!processedObjAIds.contains(currentObjA.getId())) {
            processedObjAIds.add(currentObjA.getId());
            
            // Do your processing here - avoid storing all results in a List if possible!
            // For example: processSingleObjA(currentObjA);
            
            // If you need to batch process, add to a temporary list and flush periodically:
            // tempList.add(currentObjA);
            // if (tempList.size() == 1000) {
            //     batchProcess(tempList);
            //     tempList.clear();
            //     session.clear(); // Evict entities from first-level cache to save memory
            // }
        }
    }
    // Process any remaining items in the batch list
    // if (!tempList.isEmpty()) batchProcess(tempList);
} finally {
    scroll.close(); // Always close ScrollableResults!
    session.close();
}

Key notes here:

  • Use ScrollMode.FORWARD_ONLY to minimize memory usage (Hibernate won’t keep a buffer of past rows).
  • If processing large datasets, avoid storing all ObjA instances in a single List — process them in batches and clear the session cache periodically with session.clear() to free up memory.

2. Database-Level Deduplication (More Efficient)

Instead of deduplicating in memory, modify your Criteria to fetch distinct root entity IDs first, then load entities in batches. This reduces the number of rows Hibernate has to process:

Session session = getSessionFactory().openSession();

// Step 1: Fetch distinct ObjA IDs from the database
Criteria idCriteria = session.createCriteria(ObjA);
// ... apply your filters here ...
idCriteria.setProjection(Projections.distinct(Projections.id()));
List<Long> distinctObjAIds = idCriteria.list();

// Step 2: Load entities in batches to avoid OOM
int batchSize = 1000;
for (int i = 0; i < distinctObjAIds.size(); i += batchSize) {
    int endIndex = Math.min(i + batchSize, distinctObjAIds.size());
    List<Long> batchIds = distinctObjAIds.subList(i, endIndex);
    
    Criteria entityCriteria = session.createCriteria(ObjA);
    entityCriteria.add(Restrictions.in("id", batchIds));
    entityCriteria.setReadOnly(true);
    
    List<ObjA> batchResults = entityCriteria.list();
    // Process the batch
    // processBatch(batchResults);
    
    session.clear(); // Clear cache to free memory
}

session.close();

This approach is more efficient because the database handles deduplication, and you’re only loading manageable batches of entities at once.

3. Manually Apply the ResultTransformer

If you want to stick strictly to using ScrollableResults with the transformer, you can call the transformer’s transformTuple() method manually for each row. For DISTINCT_ROOT_ENTITY, this is essentially the same as manual deduplication, but here’s how it would look:

ResultTransformer transformer = CriteriaSpecification.DISTINCT_ROOT_ENTITY;
ScrollableResults scroll = criteria.scroll(ScrollMode.FORWARD_ONLY);
Set<ObjA> uniqueObjAs = new HashSet<>(); // Uses entity equality to avoid duplicates

try {
    while (scroll.next()) {
        Object[] tuple = scroll.get();
        String[] aliases = criteria.getProjection() == null ? new String[0] : criteria.getProjection().getAliases();
        ObjA transformed = (ObjA) transformer.transformTuple(tuple, aliases);
        
        uniqueObjAs.add(transformed);
        // Or process immediately instead of storing
    }
} finally {
    scroll.close();
    session.close();
}

This is less common for DISTINCT_ROOT_ENTITY since manual ID tracking is simpler, but it’s useful if you’re using custom transformers.


内容的提问来源于stack exchange,提问作者user2780757

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:26:48