Hibernate中Criteria.scroll的Result Transformer失效问题求助
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_ONLYto minimize memory usage (Hibernate won’t keep a buffer of past rows). - If processing large datasets, avoid storing all
ObjAinstances in a singleList— process them in batches and clear the session cache periodically withsession.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

