Hibernate与Java8 Stream 并行(多线程)处理思路及实现

在处理大量表数据的时候大家经常会碰到这样的场景:
假如存在一个表且行数为30万行,目前需要把这30万条数据查询出来处理后生成一份新的报表,显然把整个表读在内存中再处理是不现实的,因为30万条数据会占用大量的内存,这时候思路会进一步通过分页查询,处理一批数据就释放资源。可是因为这是单线程查询,为何把30万条数据先分成若干份,然后再使用多线程并行处理,思路是有了,现在通过HIbernate和Java8 Stream API如何实现的问题了,下面展示例子

	NavigableMap<Month, Optional<List<FgOutItem>>> result = new ConcurrentSkipListMap<>((i1, i2) -> i1.compareTo(i2));
		IntStream.range(1, 13).parallel().forEach(month -> {
			List<FgOutItem> fgOutItems = HibernateUtils.doInNewSession(new CurrentSessionCallback<List<FgOutItem>>() {
				@Override
				public List<FgOutItem> work(Session currentSession) {
					FgOutItemQueryBuilder fgOutItemQueryBuilder = new FgOutItem.FgOutItemQueryBuilder();
					fgOutItemQueryBuilder.queryComment("获取货明细每个月份的数据");
					LocalDate start = LocalDate.of(currentDate.getYear(), month, 1);
					fgOutItemQueryBuilder.fObDate(TimeDateUtils.toDate(start), TimeDateUtils.lastDayOfMonth(start));
					List<FgOutItem> fgOutItems = fgOutItemQueryBuilder.list();
					return fgOutItems;
				}
			});

			result.put(Month.of(month), Optional.ofNullable(fgOutItems));
		});

上面的思想是这样,在一张出货表中把数据按月份分成12个月,这里调用Stream#parallel()方法是使用原生的Stream API 来创建和启动流程(结合Java Stream就为从线程管理中解脱)Java Stream parallel默认是使用Java 环境中的CommonPool作为线程池而使用的线程数可以依据需求设置(方法请在网上找),那线程管理问题算是解决了,现在面临新的问题就是Hibernate Session 线程安全的问题(Session对象是非线程安全的对象),假如了解Spring整合Hibernate逻辑你会知道Spring中有一个org.springframework.orm.hibernate5.SpringSessionContext ,这个类简单来说是保证在每一个线程中都绑定唯一个Hibernate Session实例(不明白可以提评论),通过这种方式达到Hibernate线程安全的目的,如果在Spring受控的线程中SpringSessionContext会帮我们做这件事程,但是现在并行处理的线程是CommonPool中获取的而是Spring管理的线程,所以接下来要做的事情就是在Stream循环结构体中把Session实例绑定到当前线程中,请看以下代码:

@FunctionalInterface
public interface CurrentSessionCallback<R> {
	R work(Session currentSession);
}

定义一个回调接口,接口传递当前绑定后的Session实例,而HibernateUtils#doInNewSession处理管理Hibernate Session实例的逻辑

public static <R> R doInNewSession(CurrentSessionCallback<R> callback) {
		SessionFactory sessionFactory = AppContext.getService(SessionFactory.class);
		boolean hasResource = TransactionSynchronizationManager.hasResource(sessionFactory);
		if (!hasResource) {
			TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(sessionFactory.openSession()));
			invokeCount.incrementAndGet();
		}
		try {
			SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
			R result = callback.work(sessionHolder.getSession());
			if (!Hibernate.isInitialized(result)) {
				Hibernate.initialize(result);
			}
			return result;
		} catch (Exception e) {
			logger.error("并行执行Hibernate Session错误", e);
			throw new RuntimeException(e.getMessage());
		} finally {
			if (!TransactionSynchronizationManager.isActualTransactionActive()) {//有可能是主经程也会被invoker
				SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
				sessionHolder.getSession().close();
				invokeCount.decrementAndGet();
			}
		}

	}

**总结:**通过上述方法就可以很轻易地做到把一个大表分成若干份再并行处理,最后当然需要reduce的操作(把各个线程处理完的结果再合并输出),其实这种设计思想和MapReduce是一样,同样也是分而治之的方式,只是MapReduce把任务拆分后分给节点而这里分配给线程,这样操作大大简化管理线程的逻辑,我个人是觉得很方便,现在只要遇到这种需要大数据的报表的时候就会采用这种多线程并行处理方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

victorkevin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值