关于hibernate的批处理

很多系统中大数据批量处理在所难免,网上也有很多关于hibernate的批处理建议,基本上都是一样的,但如果我们的系统设计的比较纯面向对象,这种方案是非常失败的。下面我来讲述下我使用这种方法碰到的问题,然后分析问题的原因,给出我们目前能接受的方案。

传统方案

利用hibernate.jdbc.batch_size参数,session.flush();session.clear();来释放内存


<hibernate-configuration>
<session-factory>
.........
<property name=” hibernate.jdbc.batch_size”>50</property>
.........
<session-factory>
<hibernate-configuration>

 

Session session=HibernateUtil.currentSession();
Transatcion tx=session.beginTransaction();
for(int i=0;i<100000;i++)
...{
Student s=new Student();
s.setName(“Paul”);
session.save(s);
if(i%50==0) //
以每50个数据作为一个处理单元

...{
session.flush(); //
保持与数据库数据的同步

session.clear(); //
清除内部缓存的全部数据,及时释放出占用的内存

}
}
tx.commit();

问题

1)  如果Student的某个属性是别的对象,这样.flush()时会去find每一个关联的对象,这样速度是大打折扣了

2)  session.clear()是个相当耗时的方法,特别是当缓存中数据量大的时候。Hibernate从来不建议程序调用该方法,由于session是基于线程的,所以当一个请求结束后session占用的内存自然会释放,这种速度可以忽略不计。

上面的问题主要出在问题1

新的方案

以空间换时间

针对传统方案一一解决

1)  预先批量查出Student关联的属性对象,这样.flush()时就不需要去find了。

一般我们的对象关联都设置为延迟加载,所以循环时才去find,这样性能也是很慢的

有两种办法来解决

1-1】        select h,m 查询多个对象,用List <Object[]>来接收,这样一次就把需要的数据全查到内存中了

1-2】        join fetch,具体参加jpa语言中的join

2)  不调用.clear(),用做够的内存来跑

 

List <Object[]> addlist=em.createQuery("select h,m from OweTmpVO h,Meter m join fetch m.contract where h.meterid=m.id and h.je<0 and h.cbdate<:date")

       .setParameter("date",date)

       .getResultList();

       System.out.println("--addlist--size:"+addlist.size());

       for(Object[] obs:addlist){

           Meter meter=(Meter)obs[1];

           OweTmpVO oweTmp=(OweTmpVO)obs[0];

          

           Owe newowe=new Owe();

           newowe.setId(oweTmp.getOweid());

           newowe.setQfdate(oweTmp.getCbdate());

           newowe.setQfje(oweTmp.getJe());

           newowe.setMeter(meter);

           newowe.setXzqh(meter.getXzqh());

           OweCustomerInfo oci=new OweCustomerInfo();

           oci.setAddress(meter.getAddress());

           oci.setId(newowe.getId());

           oci.setLxr(meter.getLinkMan().getLxr());

           oci.setName(meter.getUsername());

           oci.setTel(meter.getLinkMan().getTel());

           if (meter.getContract()!=null){

              oci.setHbh(meter.getContract().getHbh());

              oci.setLxr(meter.getContract().getLinkMan().getLxr());

              oci.setName(meter.getContract().getUsername());

              oci.setTel(meter.getContract().getLinkMan().getTel());

           }

           newowe.setCustomerInfo(oci);

           em.persist(newowe);

           em.remove(oweTmp);

       }

需要解决的问题

新的方案需要大量的内存,而虚拟机只能提供1G多的内存,1百万的记录足够让内存溢出。

我们需要将数据分批来处理,比如根据某些条件来限制查出的记录数,或者用分页(一次50万条),还有就是不能让这些分批在同一线程中,在同一线程中就没有分批的意义了(问题同样存在)。我们可以有两种选择

1)  通过页面来手动操作,这样就把每批放到不同的线程中了。但这需要用户参与,一般来说客户不会采取的。

2)  采用异步调用(定时任务),每个任务相当与一个独立的线程,这样就不用考虑session内存释放的问题了。

把每批用一个任务来完成,间隔执行每个任务即可,注意设置任务间隔,尽量保证同一时间只有一个任务在执行,要不内存还是会溢出的。

3)利用ejb来分布式处理,由于ejb可以独立部署,这样就相当于扩大了内存,可以在web层调用多个ejb来不一个大的任务分成多个小的任务来处理。

下面是我们基于seam的异步调用

public void createReportByYear(){

//临时设置session为足够长,与本方案无关

       javax.servlet.http.HttpServletRequest request=(HttpServletRequest)javax.faces.context.FacesContext.getCurrentInstance().getExternalContext().getRequest();

       int timeout=request.getSession().getMaxInactiveInterval();

       request.getSession().setMaxInactiveInterval(60000);

 

       em.createQuery("delete OweYearReport").executeUpdate();

       XzqhVO DCQ=getXzqh();

//处理一个区的数据

       createReportByYear1(DCQ);

//为每个区的任务设置个开始时间

       TimerSchedule timerSchedule0=new TimerSchedule(10l);//马上

       TimerSchedule timerSchedule1=new TimerSchedule(1000*60*1l);//一分钟后

       TimerSchedule timerSchedule2=new TimerSchedule(1000*60*2l);

       TimerSchedule timerSchedule3=new TimerSchedule(1000*60*3l);

       TimerSchedule timerSchedule4=new TimerSchedule(1000*60*4l);

       TimerSchedule timerSchedule5=new TimerSchedule(1000*60*4l);

       TimerSchedule timerSchedule6=new TimerSchedule(1000*60*5l);

//seam中的事件机制,用来启动任务,

       events.raiseTimedEvent("createReportByYear_xzqh1", timerSchedule0);

       events.raiseTimedEvent("createReportByYear_xzqh2", timerSchedule1);

       events.raiseTimedEvent("createReportByYear_xzqh3", timerSchedule2);

       events.raiseTimedEvent("createReportByYear_xzqh4", timerSchedule3);

        events.raiseTimedEvent("createReportByYear_xzqh5", timerSchedule4);

       events.raiseTimedEvent("createReportByYear_xzqh6", timerSchedule5);

       events.raiseTimedEvent("createReportByYear_xzqh7", timerSchedule6);

    }

//具体任务

    @Observer("createReportByYear_xzqh1")

    public void createReportByYearXzqh1(){

       em.getTransaction().begin();

       createReportByYear1(XzqhVO.XCQ);

       em.getTransaction().commit();

    }

//真正的业务方法,按区来处理

    public void createReportByYear1(XzqhVO xzqh){

       xzqh=(XzqhVO)DataDictionaryAction.instance().getDataItemById(XzqhVO.class, xzqh.getId());

      

       List<Owe> oweList=em.createQuery("select h from Owe h left join fetch h.meter left join fetch h.customerInfo where h.xzqh=:xzqh order by h.meter")

       .setParameter("xzqh", xzqh)

       .getResultList();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值