`

HibernateTemplate类回调HibernateCallback--针对大批量数据

阅读更多

hibernate回调防止内存溢出,我是这样用的(事务交给spring管理,手动操作session,50个清空下session):

@Override
 public void saveTCommission(final List<TProductType> productTypeList, final String typeNum,
   final String employeeId) {
  
  hibernateTemplate.execute( new HibernateCallback() {
   @Override
   public Object doInHibernate(Session session) throws HibernateException,
     SQLException {
    long start=System.currentTimeMillis();
    for( int i=0; i<productTypeList.size(); i++ ) {

    TCommission commission = new TCommission();
    TCommissionId TCommissionId = new TCommissionId();//创建TCommissionId类
     TCommissionId.setProductNum(productTypeList.get(i).getProductNum());
     TCommissionId.setTypeNum(typeNum);
     
     commission.setId(TCommissionId);
     commission.setFastCommission(productTypeList.get(i).getMoney());
     commission.setCreateDate(new Date());//设置创建时间
     commission.setEmployeeId(employeeId);//设置处理工号
     commission.setMoney(productTypeList.get(i).getMoney());
     
     hibernateTemplate.save(commission);
     
     if( i % 50 == 0  ) {
      session.flush();
      session.clear();
     }
    }
    long end=System.currentTimeMillis();
    System.out.println("耗时:"+(end-start));
    return null;
   }
  });
 }

##注意:1.设置batch_size抓取个数、second_level_cache二级缓存

               <prop key="hibernate.jdbc.batch_size">50</prop>
           
<prop key="hibernate.cache.use_second_level_cache">false</prop>

               <!-- spring管理hibernate这样设置 -->

               <property name="hibernateProperties">
                     <props>
                            <prop key="hibernate.dialect">
                                     org.hibernate.dialect.OracleDialect
                            </prop>
                            <prop key="hibernate.show_sql">true</prop>
                            <prop key="hibernate.format_sql">true</prop>
                            <prop key="hibernate.default_batch_fetch_size">50</prop>
                     </props>
              </property>

        2.new对象必须放在里面save,否则只创建部分的,因为始终是一个对象。

        3.session每50个就flush()、clear()一次。
        4.上面的代码保证seesion不会内存溢出,但是Jboss服务器的内存大小也要设置(默认128M,改大一些),否则一样出现内存溢出问题。
        -Xms512m -Xmx1024m -XX:MaxNewSize=256m -XX:MaxPermSize=256m(将这段代码复制到myeclipse--servers--jboss4--jdk中,修改jboss默认内存大小)
##1万条大概10秒
##hibernate的回调HibernateCallback,防止内存溢出,50个清空下session,针对大数据量1万、10万条记录。

 

 

目的:使用HibernateTemplate执行execute(newHibernateCallback())方法,从HibernateCallback中得到session,在此session中做多个操作,并希望这些操作位于同一个事务中。

 

  如果你这样写(1):

  public static void main(String ss)

  CtxUtil.getBaseManager().getHibernateTemplate().execute(newHibernateCallback()

  public t doInHibernate(Session session) throwsHibernateException,SQLException

  // 保存stu1

  Student stu1 = new Student();

  stu1.setName("aaaa");// 在数据库中,name字段不允许为null

  session.save(stu1);

  session.flush();//实际上,如果不是程序员"手痒"来调用这个flush(),HibernateTemplate中session的事务处理还是很方便的

  Student stu2 = new Student();

  session.save(stu2);// 没有设置name字段,预期会报出例外

  session.flush();

  return null;

  );

  你期望spring在执行完execute回调后,在关闭session的时候提交事务,想法是很好的,但spring并不会这么做。让我们来看看在Hibernate的源代码中,session.beginTransation()做了什么事。看如下代码(2):

  public Transaction beginTransaction()throwsHibernateException

  errorIfClosed();

  if ( rootSession != null )

  // todo : should seriously consider not allowing a txn tobeginfrom a child session

  // can always route the request to the root session

  log.warn( "Transaction started on non-root session" );

  Transaction result = getTransaction();

  result.begin();

  return result;

  这个方法中的result是一个org.hibernate.transaction.JDBCTransaction实例,而方法中的getTransaction()方法源代码为(3):

  public Transaction getTransaction() throws HibernateException

  if (hibernateTransactionnull)

  log.error(owner.getFactory().getSettings()

  .getTransactionFactory().getClass());

  hibernateTransaction = owner.getFactory().getSettings()

  .getTransactionFactory()

  .createTransaction( this, owner );

  return hibernateTransaction;

  再次追踪,owner.getFactory()。getSettings().getTransactionFactory()的createTransaction()方法源代码如下(4):

  public Transaction createTransaction(JDBCContextjdbcContext,Context transactionContext)

  throws HibernateException

  return new JDBCTransaction( jdbcContext, transactionContext);

  它返回了一个JDBCTransaction,没什么特别的。

  在代码2中,执行了result.begin(),其实也就是JDBCTransaction实例的begin()方法,来看看(5):

  public void begin() throws HibernateException

  if (begun)

  return;

  if (commitFailed)

  throw new TransactionException("cannot re-start transactionafterfailed commit");

  log.debug("begin");

  try

  toggleAutoCommit = jdbcContext.connection().getAutoCommit();

  if (log.isDebugEnabled())

  log.debug("current autocommit status: " + toggleAutoCommit);

  if (toggleAutoCommit)

  log.debug("disabling autocommit");

  jdbcContext.connection().setAutoCommit(false);//把自动提交设为了false

  catch (SQLException e)

  log.error("JDBC begin failed", e);

  throw new TransactionException("JDBC begin failed: ", e);

  callback = jdbcContext.registerCallbackIfNecessary();

  begun = true;

  committed = false;

  rolledBack = false;

  if (timeout > 0)

  jdbcContext.getConnectionManager().getBatcher().setTransactionTimeout(timeout);

  jdbcContext.afterTransactionBegin(this);

  在直接使用Hibernate时,要在事务结束的时候,写上一句:tx.commit(),这个commit()的源码为:

  public void commit() throws HibernateException

  if (!begun)

  throw new TransactionException("Transaction notsuccessfullystarted");

  log.debug("commit");

  if (!transactionContext.isFlushModeNever()&&callback)

  transactionContext.managedFlush(); // if an exceptionoccursduring

  // flush, user must call

  // rollback()

  notifyLocalSynchsBeforeTransactionCompletion();

  if (callback)

  jdbcContext.beforeTransactionCompletion(this);

  try

  commitAndResetAutoCommit();//重点代码,它的作用是提交事务,并把connection的autocommit属性恢复为true

  log.debug("committed JDBC Connection");

  committed = true;

  if (callback)

  jdbcContext.afterTransactionCompletion(true, this);

  notifyLocalSynchsAfterTransactionCompletion(Status.STATUS_COMMITTED);

  catch (SQLException e)

  log.error("JDBC commit failed", e);

  commitFailed = true;

  if (callback)

  jdbcContext.afterTransactionCompletion(false, this);

  notifyLocalSynchsAfterTransactionCompletion(Status.STATUS_UNKNOWN);

  throw new TransactionException("JDBC commit failed", e);

  finally

  closeIfRequired();

  上面代码中,commitAndResetAutoCommit()方法的源码如下:

  private void commitAndResetAutoCommit() throws SQLException

  try

  jdbcContext.connection().commit();//这段不用说也能理解了

  finally

  toggleAutoCommit();//这段的作用是恢复connection的autocommit属性为true

  上述代码的toggleAutoCommit()源代码如下:

  private void toggleAutoCommit()

  try

  if (toggleAutoCommit)

  log.debug("re-enabling autocommit");

  jdbcContext.connection().setAutoCommit(true);//这行代码的意义很明白了吧

  catch (Exception sqle)

  log.error("Could not toggle autocommit", sqle);

  因此,如果你是直接使用hibernate,并手动管理它的session,并手动开启事务关闭事务的话,完全可以保证你的事务.

  public static void main(String ss)

  CtxUtil.getBaseManager().getHibernateTemplate().execute(newHibernateCallback()

  public t doInHibernate(Session session) throwsHibernateException,SQLException

  log.info(session.connection().getAutoCommit());//打印一下事务提交方式

  // 保存stu1

  Student stu1 = new Student();

  stu1.setName("aaaa");// 在数据库中,name字段不允许为null

  session.save(stu1);

  session.flush();

  Student stu2 = new Student();

  session.save(stu2);// 没有设置name字段,预期会报出例外

  session.flush();

  return null;

  );

  运行后,它打出的结果是true,也就是说,虽然保存stu2时会报出例外,但如果commit属性为true,则每一个到达数据库的sql语句会立即被提交。换句话说,在调用完session.save(stu1)后,调用session.flush(),会发送sql语句到数据库,再根据commit属性为true,则保存stu1的操作已经被持久到数据库了,尽管后面的一条insert语句出了问题。

  因此,如果你想在HibernateCallback中使用session的事务,需要如下写:

  public static void main(String ss)

  CtxUtil.getBaseManager().getHibernateTemplate().execute(newHibernateCallback()

  public t doInHibernate(Session session) throwsHibernateException,SQLException

  session.connection().setAutoCommit(false);

  //保存stu1

  Student stu1=new Student();

  stu1.setName("aaaa");//在数据库中,name字段不允许为null

  session.save(stu1);

  session.flush();

  Student stu2 = new Student();

  session.save(stu2);//没有设置name字段,预期会报出例外

  session.flush();

  session.connection().commit();

  //至于session的关闭就不用我们操心了

  return null;

  );

  运行上述代码,没问题了。至此,可能有些读者早就对代码1不满意了:为什么每次save()以后要调用flush()?这是有原因的。下面我们来看看把session.flush()去掉后会出什么问题。改掉后的代码如下:

  public static void main(String ss)

  CtxUtil.getBaseManager().getHibernateTemplate().execute(newHibernateCallback()

  public t doInHibernate(Session session) throwsHibernateException,SQLException

  session.connection().setAutoCommit(false);

  // 保存stu1

  Student stu1 = new Student();

  stu1.setName("aaaa");// 在数据库中,name字段不允许为null

  session.save(stu1);

  // session.flush();

  Student stu2 = new Student();

  session.save(stu2);// 没有设置name字段,预期会报出例外

  // session.flush();

  session.connection().commit();

  return null;

  );

  运行上述代码,后台报数据库的notnull错误,这个是合理的,打开数据库,没有发现新增记录,这个也是合理的。你可能会说:由于事务失败,数据库当然不可能会有任何新增记录。好吧,我们再把代码改一下,去除notnull的错误,以确保它能正常运行。代码如下:

  public static void main(String ss)

  CtxUtil.getBaseManager().getHibernateTemplate().execute(newHibernateCallback()

  public t doInHibernate(Session session) throwsHibernateException,SQLException

  session.connection().setAutoCommit(false);

  // 保存stu1

  Student stu1 = new Student();

  stu1.setName("aaaa");// 在数据库中,name字段不允许为null

  session.save(stu1);

  // session.flush();

  Student stu2 = new Student();

  stu2.setName("asdfasdf");//好了,这个字段设过值,不会再报not null错误了

  session.save(stu2);

  // session.flush();

  session.connection().commit();

  return null;

  );

  至此再运行上述代码,出现了一个奇怪的问题:虽然控制台把insert语句打出来了,但是:数据库没有出现任何新的记录。

  究其原因,有二:

  一、session.connection()。commit()确实导致数据库事务提交了,但是此刻session并没有向数据库发送任何语句。

  二、在spring后继的flushIfNecessary()和closeSessionOrRegisterDeferredClose()方法中,第一个方法向数据库发送sql语句,第二个方法关闭session,同时关闭connection,然后问题在于:connection已经在程序中被手动设置为auttocommit=false了,因此在关闭数据库时,也不会提交事务。

  解决这个问题很容易,在程序中手动调用session.flush()就可以了。如下代码:

  public static void main(String ss)

  CtxUtil.getBaseManager().getHibernateTemplate().execute(newHibernateCallback()

  public t doInHibernate(Session session) throwsHibernateException,SQLException

  session.connection().setAutoCommit(false);

  //保存stu1

  Student stu1=new Student();

  stu1.setName("aaaa");//在数据库中,name字段不允许为null

  session.save(stu1);

  Student stu2 = new Student();

  session.save(stu2);//没有设置name字段,预期会报出例外

  session.flush();//向数据库发送sql

  session.connection().commit();

  return null;

  );

  运行上述代码,打开数据库查看,没有新增任何记录。在代码中新加一行stu2.setName("aaa");再次运行代码,发现数据库表中多了两条记录。事务操作成功。

  至此,虽然操作成功,但事情还没有结束。这是因为spring在调用doInHibernate()的后继的步骤中,还要进行flushIfNecessary()操作,这个操作其实最后调用的还是session.flush()。因为在程序中已经手动调用过session.flush(),所以由spring调用的session.flush()并不会对数据库发送sql(因为脏数据比对的原因)。虽然不会对结果有什么影响,但是多调了一次flush(),还是会对性能多少有些影响。能不能控制让spring不调用session.flush()呢?可以的,只要加上一句代码,如下所示:

  public static void main(String ss)

  CtxUtil.getBaseManager().getHibernateTemplate().setFlushMode(0);//0也就是FLUSH_NEVER

  CtxUtil.getBaseManager().getHibernateTemplate().execute(newHibernateCallback()

  public t doInHibernate(Session session) throwsHibernateException,SQLException

  session.connection().setAutoCommit(false);

  //保存stu1

  Student stu1=new Student();

  stu1.setName("aaaa");//在数据库中,name字段不允许为null

  session.save(stu1);

  Student stu2 = new Student();

  stu2.setName("sdf");

  session.save(stu2);//没有设置name字段,预期会报出例外

  session.flush();

  session.connection().commit();

  return null;

  );

  通过设置HibernateTemplate的flushMode=FLUSH_NEVER来通知spring不进行session.flush()的调用,则spring的flushIfNecessary()将不进行任何操作,它的flushIfNecessary()源代码如下:

  protected void flushIfNecessary(Session session,booleanexistingTransaction) throws HibernateException

  if (getFlushMode() FLUSH_EAGER (!existingTransaction&&getFlushMode() != FLUSH_NEVER))

  logger.debug("Eagerly flushing Hibernate session");

  session.flush();

  至此,代码1中的main()终于修改完毕。但事实上,这样的操作无疑是比较麻烦的,因此如果在spring中想利用session进行事务操作时,最好还是用TransactionTemplate(编程式事务)或是声明式事务比较方便一些。

  本例通过这么一个虽然简单但又绕来绕去的例子,主要是说明hibernate事务的一些内在特性,以及HibernateTemplate中如何处理session和事务的开关,让读者对HibernateTemplate的源代码处理细节有一些了解,希望能给读者有抛砖引玉的作用。

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics