解决Hibernate效率低下问题

 

前几天,客户反馈说使用系统某一个模块十分缓慢,有时候还会崩溃;

客户机器数据量大概在1W5左右,加大请求超时时间后,虽然不会崩溃,但是依然慢的吓人,1W5的数据,居然需要30~40多秒钟;

系统是使用SSH框架开发,因为关联关系比较多,所以使用Hibernate进行管理数据库,是最合适的;

问题查找:

1.出现这个问题,还得说到以前的开发人员,当时在完成项目的阶段中,在设计表的时候,以及表之间的关联关系的时候,都将懒加载取消了

所以,这也是一个造成性能底下的问题,表格之间关联越多,在查询的时候,发出的select语句也就越多,有可能你只需要一条记录,而Hibernate因为

懒加载的原因,发出了几十上百条的查询语句;

 

2.某些模块对应的实体类,在设计时,有设计到分表;在进行打桩测试及添加懒加载后,发现在获取实体对象的关联对象时,如果这个关联对象的数据库

表没有做分表,查询很快,时间可以忽略不计;而有做分表的记录时,查询耗时,一条数据需要400~500ms;而且代码中也存在与一些这些分表的聚合函数,同样查询一次需要700ms左右;

由此可以发现问题存在与懒加载,分表, 以及聚合函数的合理使用

调整策略:

1.使用懒加载,当需要的时候才真正的发起select请求

将实体对象的Hibernate映射文件中,使用Lazy="false" 改为lazy="true",并且添加fetch="select"。具体意思,请百度;

在一对多的映射关系时,请合理使用fetch="join"; 具体看实际情况

 

部分映射配置文件:  
	<many-to-one name="allGoods" class="com.rhxy.bean_new.inventory.AllGoods" column="allGoods_id" fetch="select" />
        <many-to-one name="saleOrder" class="com.rhxy.bean_new.sale.SaleOrder" column="saleOrder_id" fetch="select"/>
        <set name="outStoreRecords"> //如果需要取消懒加载,可以lazy="flase"或者 fetch="join" 都是会在在查当前记录时发起多条select语句去查询集合的关联对象
            <key>
                <column name="saleOrderBill_id" />
            </key>
            <one-to-many class="com.rhxy.bean_new.inventory.OutStoreRecord" />
        </set>	<many-to-one name="allGoods" class="com.rhxy.bean_new.inventory.AllGoods" column="allGoods_id" fetch="select" />
        <many-to-one name="saleOrder" class="com.rhxy.bean_new.sale.SaleOrder" column="saleOrder_id" fetch="select"/>
        <set name="outStoreRecords"> //如果需要取消懒加载,可以lazy="flase"或者 fetch="join" 都是会在在查当前记录时发起多条select语句去查询集合的关联对象
            <key>
                <column name="saleOrderBill_id" />
            </key>
            <one-to-many class="com.rhxy.bean_new.inventory.OutStoreRecord" />
        </set>

这样的话,打印sql语句,可以看见,只有在我们使用到这个对象的属性时,才会真正的发起select语句,在数据量十分大的时候,可以很高的效率

 

 

2.针对获取分表记录时,重写DAO层函数,分表是已表名加年份;比如:

 

public <T> List<T> findAllBySubTable(String tableName,String hql, int start, int limit, String... properties) {
        int year = new Date().getYear() + 1900;
        Session session;
        if (year == 0) {
            session = getSessionFactory().openSession();//当前的session使用这个拦截器
        } else {
            HibernateInterceptor interceptor = new HibernateInterceptor();//我们的拦截器
            interceptor.setTargetTableName(tableName);//要拦截的目标表名
            interceptor.setTempTableName(tableName + "_" + year);  //要替换的子表名
            session = getSessionFactory().openSession(interceptor);//当前的session使用这个拦截器
        }
        List<T> entities = new ArrayList<T>();
        try {
            Query query = createQuery(session, hql, properties);
            query.setFirstResult(start);
            query.setMaxResults(limit);
            entities = query.list();
        } catch (Exception e) {
            e.printStackTrace();
            session.close();
        } finally {
            if (session.isOpen()) {
                session.close();
            }
        }
        return entities;
    }

原先我们是直接使用对象.get关联对象,或者使用

 

 

 public T get(Serializable id) {
        Session session = sessionFactory.openSession();
        Transaction tx = session.beginTransaction();
        T t = null;
        try {
            t = (T) session.get(entityClass, id);
            session.flush();
            tx.commit();
            session.close();
        } catch (Exception e) {
            tx.rollback();
            session.close();
        } finally {
            if (session.isOpen()) {
                session.close();
            }
        }
        return t;
    }

这样改变一下查询方法,就解决了分表查询效率底下的问题

 


3.在使用聚合函数的时候,不论这个实体类是否有设计分表,我们在任何时候,需要记住尽可能少的去访问数据库,拿尽量多的数据(注意懒加载,这里和上面说的并不冲突);

比如我现在有两条聚合函数的sql:

 

 public double getSumShouldOutQuantity(Stock stock) {
        double qty = 0;
        String sql = "select sum(shouldOutQuantity) from outStoreRecord where type='_1' and primaryRecord='T' and allGoods_id=" + stock.getAllGoods().getId();
        List<Object> list = this.findAllBySql(sql);
        if (list != null && list.size() > 0) {
            if (list.get(0) != null) {
                qty = Double.parseDouble(list.get(0).toString());
            }
        }
        return qty;
    }
 
 public double getSumActualQuantity(Stock stock) {
        double Aty = 0;
        String sql = "select sum(actualQuantity) from outStoreRecord as o where type='_1' and allGoods_id=" + stock.getAllGoods().getId();
        List<Object> list = this.findAllBySql(sql);
        if (list != null && list.size() > 0) {
            if (list.get(0) != null) {
                Aty = Double.parseDouble(list.get(0).toString());
            }
        }
        return Aty;
    }

查询应出库数量和实际出库数量,计算未出库数量,这样查询出来的两个数据,是不是还要再做一下减法?而且发出了两条查询语句,别不在意,请向以后考虑,可能现在并不会影响你什么,或者并不会耗时多少,但是当数据量达到几十万上百万千万时,又该如何?

 

修改查询语句:

 

 public double getSumNotOutStoreQty(Stock stock) {
        double Aty = 0;
        String sql = "select sum(shouldOutQuantity) -sum(actualQuantity) from outStoreRecord as o where type='_1' and allGoods_id=" + stock.getAllGoods().getId();
        List<Object> list = this.findAllBySqlOfSubTable("outStoreRecord", sql);
        if (list != null && list.size() > 0) {
            if (list.get(0) != null) {
                Aty = Double.parseDouble(list.get(0).toString());
            }
        }
        return Aty;
    }this.findAllBySqlOfSubTable("outStoreRecord", sql);
        if (list != null && list.size() > 0) {
            if (list.get(0) != null) {
                Aty = Double.parseDouble(list.get(0).toString());
            }
        }
        return Aty;
    }

这样,一条语句足以,注意红色地方因为这个表是一个分表,查询一条语句需要耗时很长,所以优化了sql语句和分表查询

 

至此,问题解决,原先的30~40s就不存在了,现在只需要不到1S中;

还没晚:

在使用懒加载的时候出现问题,相信你也会遇到;

在设置懒加载的时候,获取关联对象报错:no session or session was closed

这是因为,懒加载,用到数据的时候再去加载,这个时候session已经关闭;这个时候你要查看一下你的session建立方式:

1.sessionFactory.openSession();

2.sessionFactory.getCurrentSession();

第一种是需要手动关闭,不然会照成内存溢出,导致系统崩溃;第二种则不需要手动去关闭;所以,将程序中查询的session创建方式改为getCurrentSession(),问题解决;

getCurrentSession是必须提交事务的。所以在用到session.getCurrentSession()的这个方法一定是要配置声明式事务管理。

 

注意:如果是使用到分表,会使用到Hibernate的拦截器,去拦截查询的表,以替换使用的分表,仔细看上面的查询分表方法,这里如果使用getCurrentSession(),将不能使用拦截器,也就不能去查询分表;所以问题还是没解决;

在网上查询,使用OpenSessionInViewFilter,将session的生存周期延长到Action结束,也就是交给Spring管理,配置方法:

 

	<filter>
        <filter-name>hibernateFilter</filter-name>
        <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
        <init-param>
            <param-name>sessionFactoryBeanName</param-name>
            <param-value>sessionFactory</param-value>
        </init-param>
        <init-param>
            <param-name>singleSession</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>flushMode</param-name>
            <param-value>AUTO</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>hibernateFilter</filter-name>
        <url-pattern>*.action</url-pattern>
    </filter-mapping>  
  
    <filter>  
        <filter-name>struts</filter-name>  
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>  
        <init-param>...</init-param>  
    </filter>  <filter>
        <filter-name>hibernateFilter</filter-name>
        <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
        <init-param>
            <param-name>sessionFactoryBeanName</param-name>
            <param-value>sessionFactory</param-value>
        </init-param>
        <init-param>
            <param-name>singleSession</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>flushMode</param-name>
            <param-value>AUTO</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>hibernateFilter</filter-name>
        <url-pattern>*.action</url-pattern>
    </filter-mapping>  
  
    <filter>  
        <filter-name>struts</filter-name>  
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>  
        <init-param>...</init-param>  
    </filter>  

注意,OpenSessionInViewFilter一定要放在Strus配置的前面;如果还是无效,请将url-pattern 改为/*;

 

还有一个问题在获取session时,在重启服务器以后,会出现:

No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here

解决办法:

 

修改sessionFactory的配置:
<bean id="sessionFactory"
		  class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="hibernateProperties">
			<props>
				<prop key="hibernate.connection.autocommit">true</prop>
				<prop key="myeclipse.connection.profile">MySQL</prop>
				<!--<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>-->
				<prop key="hibernate.dialect">com.rhxy.dao_new.MySQLLocalDialect</prop>
				<!--<prop key="hibernate.hbm2ddl.auto">create</prop>-->
				<prop key="hibernate.hbm2ddl.auto">update</prop>
				<prop key="hibernate.myeclipse.connection.profile">true</prop>
				<prop key="hibernate.current_session_context_class">thread</prop>
				<prop key="hibernate.transaction.factory_class">
					org.hibernate.transaction.JDBCTransactionFactory
				</prop>
			</props>
		</property><prop key="hibernate.current_session_context_class">thread</prop>
				<prop key="hibernate.transaction.factory_class">
					org.hibernate.transaction.JDBCTransactionFactory
				</prop>
			</props>
		</property>


到此,所有问题以解决,比较详细,也比较繁琐;但是写程序不就是应该仔细不怕麻烦吗;

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值