🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # Hibernate优化方案 ## HQL优化 1. 使用参数绑定 使用绑定参数的原因是让数据库一次解析SQL,对后续的重复请求可以使用用生成好的执行计划,这样做节省CPU时间和内存 2. 避免SQL注入 尽量少使用NOT 如果where子句中包含not关键字,那么执行时该字段的索引失效。 3. 尽量使用where来替换having Having在检索出所有记录后才对结果集进行过滤,这个处理需要一定的开销,而where子句限制记录的数目,能减少这方面的开销 4. 减少对表的查询 在含有子查询的HQL中,尽量减少对表的查询,降低开销 5. 使用表的别名 当在HQL语句中连接多个表时,使用别名,提高程序阅读性,并把别名前缀与每个列上,这样一来,可以减少解析时间并减少列歧义引起的语法错误。 6. 实体的更新与删除 在hibernate3以后支持hql的update与delete操作 ~~~ String hql = "update User user set user age = 20 where user age = 18"; Query queryupdate = session.createQuery(hql); int ret = queryupdate.executeUpdate(); ~~~ ### 一级缓存优化 一级缓存也叫做session缓存,在一个hibernate session有效,这级缓存的可干预性不强,大多于hibernate自动管理,但它提供清除缓存的方法,这在大批量增加(更新)操作是有效果的,例如,同时增加十万条记录,按常规进行,很可能会出现异常,这时可能需要手动清除一级缓存,session.evict以及session.clear. ## 检索策略(抓取策略) ### 延迟加载 延迟加载 是hibernate为提高程序执行的效率而提供的一种机制,即只有真正使用该对象的数据时才会创建。 load方法采用的策略延迟加载. get方法采用的策略立即加载。 检索策略分为两种: 1. 类级别检索 2. 关联级别检索 ### 类级别检索 类级别检索是通过session直接检索某一类对应的数据,例如 ~~~ Customer c=session.load(Customer.class, 1); Session.createQuery("from Order"); ~~~ 类级别检索策略分为立即检索与延迟检索,默认是延迟检索,类级别的检索策略可以通过元素的lazy属性来设置 ,默认值是true 在hbm配置文件中设置 ~~~ <class name="Customer" table="t_customer" catelog="hibernateTest" lazy="true"> ~~~ 在类中使用注解 ~~~ @Proxy(lazy=true) public class Customer { ~~~ 如果将lazy设置为false,代表类级别检索也使用立即检索. 这时候load与get就一样,都是立即检索 如果对一个延迟代理对象进行初始化? ~~~ Customer c1 = session.load(Customer.class, 1); Hibernate.inintialize(c1); ~~~ ### 关联级别检索 查询到某个对象,获得其关联的对象或属性,这种称为关联级别检索,例如 ~~~ c.getOrders().size(); c.getName(); ~~~ 对于关联级别检索我们就要研究其检索策略(抓取策略) ### 抓取策略介绍 指的是查找到某个对象后,通过这个对象去查询关联对象的信息时的一种策略。 一对一`<one-to-one>` 一对多(多对一)`<set>`下有`<one-to-many> <many-to-one>` 多对多`<set>`下有`<many-to- many>` 我们主要是在`<set>`与`<many-to-one>`或`<one-to-one>`上设置fetch lazy 例如:查询一个客户,要关联查询它的订单 客户是一的一方,在客户中有set集合来描述其订单,在配置中我们是使用 ~~~ <set> <one-to-many> </set>.. ~~~ 可以在set标签上设置两个属性 fetch lazy Fetch主要描述的是SQL语句的格式(例如是多条,子查询,多表联查 Lazy 控制SQL语句何时发送 例如:在查询一个订单时,要查询客户信息 `<many-to-one> 或<one-to-one>` 也可以设置fetch lazy Fetch主要描述的是SQL语句的格式(例如是多条,子查询,多表联查 Lazy 控制SQL语句何时发送 总结: 讲解抓取策略 在两方面设置 ~~~ <set fetch="" lazy=""> <many-to-one fetch="" lazy=""> <one-to-one> ~~~ ### 注解配置抓取策略 问题:如何使用注解来设置 在`<setse>`设置的fetch与lazy可以使用下面注解来描述 ~~~ @Fetch(FetchMode.SUBSELECT) @LazyCollection(LazyCollectionOption.EXTRA) private Set<Order> orders = new HashSet<Order>(); ~~~ ~~~ <set name="orders" inverse="true" cascade="save-update" lazy="true" fetch="join"> ~~~ 在`<many-to-one>`或`<one-to-one>`上设置fetch与lazy ~~~ @Fetch(FetchMode.SELECT) @LazyToOne(LazyToOneOption.FALSE) private Customer c; ~~~ ~~~ <!-- 多对多 --> <many-to-one fetch="join" lazy="false"></many-to-one> ~~~ ## set上的fetch与lazy set上的fetch与lazy它主要是用于设置关联的集合信息的抓取策略。 Fetch可取值有: 1. SELECT 多条简单的sql (默认值) 2. JOIN 采用迫切左外连接 3. SUBSELECT 将生成子查询的SQL lazy可取值有: 4. TURE 延迟检索 (默认值) 5. FALSE 立即检索 6. EXTRA 加强延迟检索(及其懒惰) ~~~ //得到id=1的Customer Customer c = session.get(Customer.class ,1); //得到Customer关联的order信息 int size = c.getOrders().size(); System.out.println(size); ~~~ ### 第一种组合 会首先查询客户信息,当需要订单信息时,才会关联查询订单信息 ~~~ @Fetch(FetchMode.SELECT) @LazyCollection(LazyCollectionOption.TRUE) private Set<Order> orders = new HashSet<Order>(); ~~~ ### 第二种组合 当查询客户信息时,就会将订单信息也查询,也就是说订单信息没有进行延迟 ~~~ @Fetch(FetchMode.SELECT) @LazyCollection(LazyCollectionOption.FALSE) private Set<Order> orders = new HashSet<Order>(); ~~~ ### 第三种组合 当查询客户信息时,不会查询订单信息,当需要订单的个数时,也不会查询订单信息, 只会通过count来统计订单个数。 当我们使用size(),contains()或isEmpty()方法时不会查询订单信息。 ~~~ @Fetch(FetchMode.SELECT) @LazyCollection(LazyCollectionOption.EXTRA) private Set<Order> orders = new HashSet<Order>(); ~~~ ### 第四种组合 如果fetch选择的是join方案,那么lazy它会失效。 生成SQL将采用的是迫切左外连接(left outer join fetch) 会立即查询。 ~~~ @Fetch(FetchMode.JOIN) @LazyCollection(LazyCollectionOption.FALSE) private Set<Order> orders = new HashSet<Order>(); ~~~ ### 第五种组合 会生成子查询,但是我们在查询订单时采用的是延迟加载。 ~~~ @Fetch(FetchMode.SUBSELECT) @LazyCollection(LazyCollectionOption.TRUE) private Set<Order> orders = new HashSet<Order>(); ~~~ ### 第六种组合 会生成子查询,在查询客户信息时,就会将订单信息也查询出来 ~~~ @Fetch(FetchMode.SUBSELECT) @LazyCollection(LazyCollectionOption.FALSE) private Set<Order> orders = new HashSet<Order>(); ~~~ ### 第七种组合 在查询订单时,只会根据情况来确定是否要订单信息,如果不需要,例如我们 程序中size操作,那么就会发出`select count(*) from Order where c_customer_id=?` ~~~ @Fetch(FetchMode.SUBSELECT) @LazyCollection(LazyCollectionOption.EXTRA) private Set<Order> orders = new HashSet<Order>(); ~~~ ### 结论 ~~~ <!-- lazy属性:决定是否延迟加载 true(默认值):延迟加载,懒加载 false:立即加载 extra:极其懒惰 fetch属性:决定加载策略,使用什么类型的sql语句加载集合数据 select(默认值):单表查询加载 join:使用多表查询加载集合 subselect:使用子查询加载集合 --> ~~~ 结论为了提高效率 fetch的选择上应选择 select.lazy的取值应选择true.全部使用默认值 no-session问题解决? 扩大session作用范围 ~~~ Filter //前处理,打开session事务 chain.doFilter(request, response); //放行 //后处理,关闭session,事务 ~~~ ## One的一言fetch与lazy `<set fetch lazy>`它主要是设置在获取到一的一方时,如果去查询多的一方。 在`<many-to-one>或<one-to-one>`如果去查询对方。 对于程序 就是在多的一方如何查询一的主方信息 例如:获取到一个订单对象,要查询客户信息。 Fetch可取值: select 默认值,代表发送一条或多条简单的select语句 join 发送一条迫切左外连接 lazy可取值 false 不采用延迟加载 proxy 默认值 是否采用延迟,需要另一方的类级别延迟策略来决定 no-proxy 不用研究 ~~~ //得到订单 Order order = session.get(Order.class, 1); //得到订单对应的客户 Customer c = order.getC(); System.out.println(c.getName()); ~~~ ### 第一种组合 ~~~ @Fetch(FetchMode.SELECT) @LazyToOne(LazyToOneOption.PROXY) private Customer c; ~~~ 注意: Customer的类级别延迟策略 ~~~ @Proxy(lazy=true) public class Customer { ~~~ 当我们执行时,会首先发送一条sql只查询订单信息,客户信息会延迟,只有真正需要客户信息时,才会发送sql来查询客户信息. ### 第二种组合 ~~~ @Fetch(FetchMode.SELECT) @LazyToOne(LazyToOneOption.PROXY) private Customer c; ~~~ 注意: Customer的类级别延迟策略 ~~~ @Proxy(lazy=false) public class Customer { ~~~ 当查询订单时,不会对客户信息进行延迟,立即查询客户信息 ### 第四种组合 ~~~ @Fetch(FetchMode.JOIN) @LazyToOne(LazyToOneOption.FALSE) private Customer c; ~~~ 如果fetch值为join,那么lazy失效 会发送一条迫切左外连接来查询,也就是立即查询 ## 批量抓取 我们在查询多个对象的关联对象时,可以采用批量抓取方式来对程序进行优化. 要想实现批量抓取: 可以在配置文件中 `batch-size`属性来设置 可以使用注解 `@BatchSize(size=4)` 可以采用批量抓取来解决N+1问题. 抓取客户的时候一次抓取几个联系人 查询客户,查询订单 ~~~ //得到所有客户 List<Customer> list = session.createQuery("from Customer").list(); //得到客户的订单信息 for (Customer c : list) { System.out.println(c.getOrders().size()); } ~~~ 可以在客户配置文件中配置`batch-size`,是在`<set>`标签上 ~~~ @BatchSize(size=3) private Set<Order> orders = new HashSet<Order>(); ~~~ 查询订单,查询客户 ~~~ //得到所有订单 List<Order> list = session.createQuery("from Order").list(); //得到客户的信息 for (Order order : list) { System.out.println(order.getC().getName()); } //订单一共有两种,在查询时会首先发送一条sql查询出所有订单,在根据订单查询客户一共3条语句完成 ~~~ 订单与客户,客户他是一个主表,订单是一个从表 在设置批量抓取时都是在主表中设置 在配置文件中在主表的`<calss>`标签上设置`batch-size` 在注解使用中 ~~~ @BatchSize(size=10) public class Customer { ~~~ 注意:无论是根据哪一方来查询别一方,在进行批量抓取时,都是在父方来设置 如果是要查询子信息,那么我们是在`<set>`上来设置 `batch-size`如果是从子方来查询父方, 也是在父方设置在`<class>`设置`batch-size` **父与子区分** 有外键的表是子,从关联方就是父(主)表