问题背景
项目中使用了hibernate来做ORM,当项目启动的时候需要把所有的数据加载到内存当中作为缓存和索引。日常开发中一切正常,导入正式数据测试时,由于数据量骤增,启动速度变得很慢,启动一次需要1个多时。其中大部分时间是hiberate在跟数据库交互,于是准备跟hibernate要效率。
备选方案
经过研究,初步定义以下两种方案:
1.优化hibernate的SQL查询:日志中出现了大量的hibernate 打的SQL,而且很多都是在进行关联查询的时候打出来的SQL,如果是自己写SQL查询的话可以用一条SQL查出来再封装就可以,而hibernate进行了多次查询,降低了效率,可以从优化SQL的方向入手解决。
2.使用hibernate二级缓存:目前二级缓存较为成熟,可以作为数据的一个懒加载实现,不需要一启动就把所有数据都放到内存里。
鉴于方案2代码改造量较大,而且改造后需要牺牲目前的查询索引,最后定下方案1.
开始优化
原代码的写法(简化改写过后):
List<Order> orders = session.createQuery("from Order").list();
在此处得到 所有需要缓存的对象后开始构建索引,操作类似以下:
StringBuilder display = new StringBuilder(); for (Order order : orders) { display.append(order.getId()).append(": "); Customer customer = order.getCustomer(); display.append(customer.getName()); if (customer instanceof VipCustomer) { display.append("(vip): "); List<Category> categories =((VipCustomer)customer).getCategories(); for (Category category : categories) { display.append(category.getItems().size()).append(";"); } } display.append(" ["); Set<Item> items = order.getItems(); for (Item item : items) { display.append(item.getProduct()+", "); item.getCategories().size(); } display.append("]\n"); } System.out.println(display);
于是打出来SQL如下:
select order0_.id as id3_, order0_.CUSTOMER_ID as CUSTOMER2_3_ from T_ORDER order0_ select customer0_.id as id2_0_, customer0_.name as name2_0_, customer0_.interest as interest2_0_, customer0_.DTYPE as DTYPE2_0_ from Customer customer0_ where customer0_.id=? select categories0_.VIP_ID as VIP3_2_1_, categories0_.id as id1_, categories0_.id as id1_0_, categories0_.name as name1_0_, categories0_.VIP_ID as VIP3_1_0_ from Category categories0_ where categories0_.VIP_ID=? select categories0_.VIP_ID as VIP3_2_1_, categories0_.id as id1_, categories0_.id as id1_0_, categories0_.name as name1_0_, categories0_.VIP_ID as VIP3_1_0_ from Category categories0_ where categories0_.VIP_ID=? 以上重复N行。。。 select items0_.ORDER_ID as ORDER1_3_1_, items0_.ITEM_ID as ITEM2_1_, item1_.id as id0_0_, item1_.product as product0_0_, item1_.packageType as packageT4_0_0_, item1_.greeting as greeting0_0_, item1_.DTYPE as DTYPE0_0_ from Order_TO_ITEM items0_ inner join Item item1_ on items0_.ITEM_ID=item1_.id where items0_.ORDER_ID=? select categories0_.ITEM_ID as ITEM2_0_2_, categories0_.CATEGORY_ID as CATEGORY1_2_, category1_.id as id1_0_, category1_.name as name1_0_, category1_.VIP_ID as VIP3_1_0_, vipcustome2_.id as id2_1_, vipcustome2_.name as name2_1_, vipcustome2_.interest as interest2_1_ from CATEGORY_TO_ITEM categories0_ inner join Category category1_ on categories0_.CATEGORY_ID=category1_.id left outer join Customer vipcustome2_ on category1_.VIP_ID=vipcustome2_.id where categories0_.ITEM_ID=? 以上重复N行
主要都是通过get order的关联对象以及关联对象的关联对象打出来的log,大概都关联了3,4级。
实现SQL优化 ,引入了 HQL的 inner join fetch 和 left join fetch。需要区别于一般的inner join,和 left join。中文名好像叫,迫切内连接和迫切左外连接(名称来源不详)。
List<Order> orders = session.createQuery("from Order newOrder inner join newOrder.customer").list();
这样出来的list里面有两种对象,一种是order,一种是inner join的customer,left join也一样。
List<Order> orders = session.createQuery("from Order newOrder inner join fetch newOrder.customer").list();
这么写出来的就是一个对象。对比一下更改前后的SQL变化:
--更改之前 select order0_.id as id3_, order0_.CUSTOMER_ID as CUSTOMER2_3_ from T_ORDER order0_ select customer0_.id as id2_0_, customer0_.name as name2_0_, customer0_.interest as interest2_0_, customer0_.DTYPE as DTYPE2_0_ from Customer customer0_ where customer0_.id=? --更改之后 select order0_.id as id3_0_, customer1_.id as id1_1_, order0_.CUSTOMER_ID as CUSTOMER2_3_0_, customer1_.name as name1_1_, customer1_.interest as interest1_1_, customer1_.DTYPE as DTYPE1_1_ from T_ORDER order0_ inner join Customer customer1_ on order0_.CUSTOMER_ID=customer1_.id
原来分两次查询的SQL合成一次了。于是继续优化:
List<Order> orders = session.createQuery("from Order newOrder " + "inner join fetch newOrder.customer customer" + "left join fetch newOrder.items").list();
这两条SQL消失了:
select items0_.ORDER_ID as ORDER1_3_1_, items0_.ITEM_ID as ITEM2_1_, item1_.id as id0_0_, item1_.product as product0_0_, item1_.packageType as packageT4_0_0_, item1_.greeting as greeting0_0_, item1_.DTYPE as DTYPE0_0_ from Order_TO_ITEM items0_ inner join Item item1_ on items0_.ITEM_ID=item1_.id where items0_.ORDER_ID=? select categories0_.ITEM_ID as ITEM2_0_2_, categories0_.CATEGORY_ID as CATEGORY1_2_, category1_.id as id1_0_, category1_.name as name1_0_, category1_.VIP_ID as VIP3_1_0_, vipcustome2_.id as id2_1_, vipcustome2_.name as name2_1_, vipcustome2_.interest as interest2_1_ from CATEGORY_TO_ITEM categories0_ inner join Category category1_ on categories0_.CATEGORY_ID=category1_.id left outer join Customer vipcustome2_ on category1_.VIP_ID=vipcustome2_.id where categories0_.ITEM_ID=?
但是list里却出来多条order记录,于是修改代码:
List<Order> orders = new ArrayList<Order>(new HashSet<Order>(session.createQuery("from Order newOrder " + "inner join fetch newOrder.customer customer" + "left join fetch newOrder.items").list()));
有点繁琐,但是很有效,代码最小改动。
下面要处理这两批SQL
select items0_.CATEGORY_ID as CATEGORY2_0_1_, items0_.ITEM_ID as ITEM1_1_, item1_.id as id2_0_, item1_.product as product2_0_ from CATEGORY_TO_ITEM items0_ inner join Item item1_ on items0_.ITEM_ID=item1_.id where items0_.CATEGORY_ID=? select categories0_.ITEM_ID as ITEM1_2_2_, categories0_.CATEGORY_ID as CATEGORY2_2_, category1_.id as id0_0_, category1_.name as name0_0_, category1_.VIP_ID as VIP3_0_0_, vipcustome2_.id as id1_1_, vipcustome2_.name as name1_1_, vipcustome2_.interest as interest1_1_ from CATEGORY_TO_ITEM categories0_ inner join Category category1_ on categories0_.CATEGORY_ID=category1_.id left outer join Customer vipcustome2_ on category1_.VIP_ID=vipcustome2_.id where categories0_.ITEM_ID=?
这两条SQL是第三级和第四级的关联,由于categories是VipCustomer类的属性而并非全部的order里的Customer里面都是VipCustomer(类图及代码后文附上),所以不能直接使用inner left fetch。
使用 @Fetch annotation来控制get查询的SQL语句,对于Category类的items属性这样改:
@ManyToMany(cascade=CascadeType.ALL) @JoinTable(name="CATEGORY_TO_ITEM",joinColumns=@JoinColumn(name="CATEGORY_ID"),inverseJoinColumns=@JoinColumn(name="ITEM_ID")) @Fetch(FetchMode.SUBSELECT) private List<Item> items;
对于Item类的categories属性则这样改:
@ManyToMany(cascade=CascadeType.REFRESH) @JoinTable(name="CATEGORY_TO_ITEM",joinColumns=@JoinColumn(name="ITEM_ID"),inverseJoinColumns=@JoinColumn(name="CATEGORY_ID")) @Fetch(FetchMode.SUBSELECT) private List<Category> categories;
于是大量SQL 简化为两条:
select categories0_.ITEM_ID as ITEM1_2_2_, categories0_.CATEGORY_ID as CATEGORY2_2_, category1_.id as id0_0_, category1_.name as name0_0_, category1_.VIP_ID as VIP3_0_0_, vipcustome2_.id as id1_1_, vipcustome2_.name as name1_1_, vipcustome2_.interest as interest1_1_ from CATEGORY_TO_ITEM categories0_ inner join Category category1_ on categories0_.CATEGORY_ID=category1_.id left outer join Customer vipcustome2_ on category1_.VIP_ID=vipcustome2_.id where categories0_.ITEM_ID in (select item1_.id from CATEGORY_TO_ITEM items0_ inner join Item item1_ on items0_.ITEM_ID=item1_.id where items0_.CATEGORY_ID in (select categories0_.id from Category categories0_ where categories0_.VIP_ID=?)) select categories0_.ITEM_ID as ITEM1_2_2_, categories0_.CATEGORY_ID as CATEGORY2_2_, category1_.id as id0_0_, category1_.name as name0_0_, category1_.VIP_ID as VIP3_0_0_, vipcustome2_.id as id1_1_, vipcustome2_.name as name1_1_, vipcustome2_.interest as interest1_1_ from CATEGORY_TO_ITEM categories0_ inner join Category category1_ on categories0_.CATEGORY_ID=category1_.id left outer join Customer vipcustome2_ on category1_.VIP_ID=vipcustome2_.id where categories0_.ITEM_ID in (select item3_.id from T_ORDER order0_ inner join Customer customer1_ on order0_.CUSTOMER_ID=customer1_.id inner join Order_TO_ITEM items2_ on order0_.id=items2_.ORDER_ID inner join Item item3_ on items2_.ITEM_ID=item3_.id)
关于FetchMode,有三种枚举:
@Fetch(FetchMode.JOIN) 使用left join查询,但是在这次项目中设置成这个的话SQL数量反而更多而且似乎懒加载方式全部都不管用了。
@Fetch(FetchMode.SELECT) 没有变化和原来一样(N+1条SQL)
@Fetch(FetchMode.SUBSELECT) 使用in (.....)查询。
问题解决
启动数分钟之内完成。
附图:
源码不需要数据库即可运行
相关推荐
2. 同一次会话中,再次尝试加载该对象时,Hibernate会首先检查一级缓存,如果找到就直接返回,避免了数据库查询。 3. 一级缓存是本地缓存,因此只对当前会话可见,当会话关闭时,一级缓存中的所有对象也会随之清除。...
使用`setFirstResult()`和`setMaxResults()`方法,或者在HQL/Criteria查询中直接指定`LIMIT`和`OFFSET`,可以防止一次性加载过多数据,节省内存资源。 5. **延迟加载(Lazy Loading)**: 默认情况下,Hibernate的...
5. **预加载与子查询**:预加载(Eager Fetching)可一次性加载所有关联数据,避免多次数据库交互。然而,过度的预加载可能导致内存消耗过大。子查询在某些复杂关联查询中可以提升效率,但也可能带来性能问题,需...
级联操作允许一次操作就影响到相关的一系列对象。 6. **缓存机制**: Hibernate支持一级缓存(Session缓存)和二级缓存,提高数据读取速度。 通过“Hibernate Reference官方文档实践日记一”,我们可以期待作者详细...
本文将深入探讨Hibernate优化策略,帮助开发者理解和实践高效的数据访问。 #### 一、理解并合理使用HQL与SQL **1.1 HQL vs. SQL** - **HQL(Hibernate Query Language)**:一种面向对象的查询语言,类似于SQL,...
本章将深入探讨Hibernate 3的持久化技术实践与性能优化策略,帮助开发者提升应用效率和系统性能。 一、Hibernate 3简介 Hibernate 3是Hibernate的第三个主要版本,它在2.0的基础上进行了大量的改进和增强,包括对...
- 对于大数据量查询,避免一次性加载所有结果,使用`iterator()`分批加载,减少内存占用。 3. **持久化对象处理**: - 延迟加载机制:在一对多、多对一关系中,使用延迟加载可以减少不必要的数据库交互,节约内存...
3. **SessionFactory的创建**:根据配置文件初始化SessionFactory,它是线程安全的,建议在应用启动时创建一次。 4. **Session的使用**:SessionFactory用于创建Session实例,Session是操作数据库的单线程上下文。...
延迟加载(Lazy Loading)是Hibernate的一项重要特性,允许我们在真正需要时才加载关联对象,从而避免了“大数据量”的一次性加载。理解并合理运用懒加载,可以避免内存溢出。 10. **性能优化**: Hibernate 提供...
9. 第一次运行与优化 在实际使用中,可能会遇到缓存问题、性能瓶颈等。例如,第一加载实体时可能会进行全表扫描,可通过二级缓存、预加载、批处理等方式优化。此外,合理选择访问策略、避免N+1查询等问题也对性能有...
使用Hibernate进行持久层操作时,如何优化查询响应时间和提高性能成为了一个重要的课题。本文将根据给定的文件内容,详细阐述在持久层设计方面可以采取的关键优化措施。 #### 二、减少对数据库的访问 设计优秀的...
【Hibernate学习笔记第一次课】 在本课程中,我们将深入探讨Hibernate框架的基础知识,这是一个流行的Java对象关系映射(ORM)工具,它简化了数据库与Java应用程序之间的交互。我们的重点是Hibernate 3.1版本,虽然...
- **批量操作**:通过一次执行多个操作来减少与数据库的交互次数,比如批量插入、批量更新等。 ### 结论 通过学习《精通Hibernate3.0—Java数据库持久层开发实践》,开发者不仅可以深入了解Hibernate的核心原理和...
代表一次数据库会话,提供了与数据库交互的方法,如保存、更新、删除对象以及执行查询。 5. Transaction 处理数据库事务,确保数据的一致性。 6. 查询相关操作 Hibernate支持HQL和SQL查询,包括分页查询、带...
这在处理大数据量时非常有用,避免了一次性加载过多数据导致的内存压力。 8. **事件监听器和拦截器**:Hibernate允许用户自定义事件监听器或拦截器,以便在特定的生命周期事件(如对象的保存、更新、删除等)发生时...
Iterator**:大数据量查询时,避免使用`list()`,因为它会一次性加载所有结果;而`iterator()`则按需加载,适合大数据量但只需要部分数据的情况。 5. **延迟加载机制**: - **一对多、多对一关系**:启用延迟加载...
6. **性能优化**:为了提高性能,可以考虑使用`fetch="join"`来实现查询时的连接加载,避免多次数据库交互。同时,合理使用缓存策略也能提升系统性能。 在实际开发中,理解并熟练运用Hibernate的一对多关联映射是...