1定义全局抓取计划
你的目标是为应用程序中的每个用例找到最好的获取方法方法和抓取策略;同时也要最小化SQL查询次数,以获得最好的性能。
HQL常用于对象获取,而不是更新、插入或者删除数据。对象状态同步是持久化管理的工作,而不是开发人员的工作。但是,正如前一章
中说的,HQL都支持大批量的操作。
按条件查询(Criteria的用法):
(criteria是hibernate固有的)
按示例查询:
作为Criteria工具的一部分,hibernate支持按示例查询(Query By Example,QBE)
延迟的默认抓取技术:
Hibernate给所有的实体和集合默认一个延迟的抓取策略。这意味着Hibernate在默认情况下只加载你正在查询的对象。
load()操作之后,内存中并没有持久化的item对象。甚至加载Item的SQL也没有被执行。Hibernate创建了一个看起来跟真的一样的代理(proxy)
理解代理:
代理是在运行时生成的占位符。每当Hibernate返回实体类的实例时,它就检查是否可以返回一个代理来代替,并避免数据库命中。
JPA中:find()必须命中,getReference(),可能返回一个代理。
新的延迟策略:
<set lazy="extra" ...>
如果调用size()、contains()或者isEmpty(),集合不再被初始化--查询数据库来获取必要的信息。
禁用代理生成:
即使是被关联,也会变成立即加载。
<class lazy="false" ...>
@org.hibernate.annotations.Proxy(lazy=false)
public class User{...}
关联和集合的即时加载:
(比如当你想从一个脱管状态的实体获取关联对象)
但是,当你想象一下,当很过千丝万偶的地方都设置了立即加载,那么获取一个对象,会关联获取出很多层关联对象!
不过你可以尝试使用视图对象,或者JOIN等关联获取方式。
通过拦截延迟加载:
Hibernate提供的运行时代理生成,是透明的延迟加载的一种极好的选择。(但是,通过instanceof测试多态的关联不可用,因为代理
是运行时生成的子类的一个实例。)
其它持久化工具没有代理的方式,都是使用的拦截。
通过lazy="no-proxy"告知对这个关联应用拦截。
(但是没给例子,不知道怎么玩)
2选择抓取策略
你的目标是把SQL语句的数量减少到最少,简化SQL语句,以便尽可能地提高查询效率。
批量预抓(batch fetching)取数据:
批量抓取经常被称为瞎猜优化(blind-guess optimization)
<class name="User" batch-size="10" ..>
也可用于集合
通过子查询预抓取集合:
<set fetch="subselect" ..>
@org.hibernate.annotations.Fetch(org.hibernate.annotations.FetchMode.SUBSELECT)
配置文件:
测试代码:
通过联结即时抓取:
<set fetch="join" ..>
@org.hibernate.annotations.Fetch(org.hibernate.annotations.FetchMode.JOIN)
配置文件:
测试代码:
很奇怪,不解,难道我这个版本的hibernate有bug?把session.close();放在获取之前就会报no session的错误,那么肯定就是采用的懒加载的方式
换个版本(hibernate-distribution-3.6.4.Final):
还是这样。
突然明白过来,他在你指定的HQL中,如何生效呢?呵呵
JPA:
调用:
离职执行了查询集合数据
上面这样就生效了。看来fetch方式如此不堪。
总结:
join:
1、无论使用hibernate和jpa使用HQL或者QL语句,是不生效的。
2、区别在于JPA:
@OneToMany(mappedBy="item",cascade=CascadeType.ALL,fetch=FetchType.LAZY)
@Fetch(value=FetchMode.JOIN)
后台打印:
Hibernate: select item0_.ITEM_ID as ITEM1_0_, item0_.ITEM_NAME as ITEM2_0_ from Item item0_
Hibernate: select bids0_.ITEM_ID as ITEM3_0_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_1_0_, bids0_.BID_MONEY as BID2_1_0_, bids0_.ITEM_ID as ITEM3_1_0_ from Bid bids0_ where bids0_.ITEM_ID=?
Hibernate: select bids0_.ITEM_ID as ITEM3_0_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_1_0_, bids0_.BID_MONEY as BID2_1_0_, bids0_.ITEM_ID as ITEM3_1_0_ from Bid bids0_ where bids0_.ITEM_ID=?
Hibernate: select bids0_.ITEM_ID as ITEM3_0_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_1_0_, bids0_.BID_MONEY as BID2_1_0_, bids0_.ITEM_ID as ITEM3_1_0_ from Bid bids0_ where bids0_.ITEM_ID=?
像这样,无论,JPA中你是否指明了懒加载,只要生命了fetch=join方式,就会变成立即加载(我认为是这个版本的bug)。
Hibernate不会,hibernate始终就是默认就是把返回代理放在最优先考虑的方式。
3、如果使用非HQL或QL的查询方式,如get之类的,JOIN就会出现问题,因为使用的left outer join方式,就只会查询出有关联数据的Item。
SUBSELECT:
1、JPA和hibernate是相同的:
@OneToMany(mappedBy="item",cascade=CascadeType.ALL,fetch=FetchType.LAZY)
@Fetch(value=FetchMode.SUBSELECT)
就是,默认是懒加载模式
后台打印:
Hibernate: select item0_.ITEM_ID as ITEM1_1_, item0_.ITEM_NAME as ITEM2_1_ from Item item0_
2、但是,如果修改为:
@OneToMany(mappedBy="item",cascade=CascadeType.ALL,fetch=FetchType.EAGER)
@Fetch(value=FetchMode.SUBSELECT)
后台就会打印:
Hibernate: select item0_.ITEM_ID as ITEM1_1_, item0_.ITEM_NAME as ITEM2_1_ from Item item0_
Hibernate: select bids0_.ITEM_ID as ITEM3_1_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_0_0_, bids0_.BID_MONEY as BID2_0_0_, bids0_.ITEM_ID as ITEM3_0_0_ from Bid bids0_ where bids0_.ITEM_ID in (select item0_.ITEM_ID from Item item0_)
就是JPA中QL语句也会生效SUBSELECT设置。Hibernate也会生效。
所以,综上所述,当时使用HQL或QL查询的时候,而且需要获取脱管对象的关联对象时,建议使用SUBSELECT方式,但是,还需要额外指明即时加载。且,JPA的JOIN方式的QL查询还存在bug。
但是,我们如果想在第一个select的HQL方式中也用join该如何呢?
3高速缓存基本原理
事务范围高速缓存--添加到当前的工作单元,它可能是一个数据库事务,甚至是一个对话。他只有在工作单元运行时才是有效的,才能被使用。每个工作单元都有自己的高速缓存。这个高速缓存中的数据不会被并发访问。
过程范围高速缓存--在许多(可能并发的)工作单元或者事务之间共享。这意味着过程范围高速缓存中的数据被并发运行的线程访问,显然隐含和事务隔离性。
集群范围高速缓存--在同一台机器的多个线程之间或者一个集群中的多台机器上共享。这里,网络通信是一个值得考虑的关键点。
1、高速缓存和对象同一性
2、高速缓存和并发
3、高速缓存和事务隔离性
完全的ORM解决方案让你单独给每个类配置二级高速缓存。高速缓存的好的备选类是表示一下数据的类:
很少改变的数据;不重要的数据(如内容管理数据);应用程序固有的而非共享的数据。
对于二级高速缓存来说,不好的备选类:
经常被更新的数据;财务数据;通过遗留应用程序共享的数据。
许多应用程序都包含大量下面类型的数据:
少量的实例;被另一个类或者几个类的许多实例引用的每一个实例;很少更新的实例。
这种数据有时称作引用数据(reference data)。
Hibernate高速缓存架构:
1、Hibernate二级高速缓存
范围:从特定的SessionFactory或者EntityManager所有持久化上下文共享同一个二级高速缓存。
二级高速缓存是否被启用;Hibernate并发策略;高速缓存过期策略;高速缓存的物理格式。
(如果你有更新比读取更经常数据,就不要启用二级高速缓存)
2、内建的并发策略
事务隔离级别:
事务--只可用于托管环境,如有必要,他还保证完全的事务隔离知道可重复读取。给主要用于读取的数据使用这种策略,因为在这种数据中,防止并发事务中的废弃数据最为关键,极少数情况下用于更新。
读/写--这种策略利用时间戳机制,维护读取提交隔离,并且只在非集群环境中可用。还是给主要用于读取数据使用这种策略,因为在这种数据中防止并发事务中废弃数据最为关键,极少数情况下用于更新。
非严格读/写--不提供高速缓存和数据库之间的一致性保证。如果有可能并发访问相同的实体,你应该配置一个足够短的超时期限。否则,则可能从高速缓存中读取废弃的数据。如果数据几乎不变,并且废弃的数据不可能是关键的关注点,那就使用这种策略。
只读--并发策略适合于从不改变的数据。他只用于引用数据。
3、选择高速缓存提供程序
见图:高速缓存并发策略支持.jpg
4高速缓存实战
首先,考虑每个实体类和集合,并找出哪种高速缓存并发策略可能适合。在给本地的和集群的高速缓存选择好高速缓存提供程序之后,讲编写他们的配置文件。
步骤一:选择并发控制策略:
Hibernate一把设置:
JPA配置:
步骤二:设置hibernate.cfg.xml
选择EHCache作为二级高速缓存
步骤三:配置ehcache.xml
eternal="false",不会永恒,配置超时时间。(以妙为单位)
控制二级高速缓存:
配置开启:
<property name="cache.use_second_level_cache">true</property>
移除单个元素:
SessionFactory.evict(Category.class, new Long(1));
移除对象:
SessionFactory.evict("auction.model.Category");
(二级高速缓存的清除是非事物的)
交互方式:
CacheMode.NORMAL--默认的行为。
CacheMode.IGNORE--Hibernate从来不与二级高速缓存交互,除了更新发生时被高速缓存的项目失效之外。
CacheMode.PUT--Hibernate从来不从二级高速缓存中读取项目,但是当它从数据库中读取项目时,会把项目添加到高速缓存。
CacheMode.REFRESH--Hibernate从来不从二级高速缓存中读取项目,但是当他从数据库中读取项目时,会把项目添加到高速缓存。在这种模式下,hibernate.cache.use_minimal_puts的作用被忽略,一遍在一个复制的集群高速缓存中强制高速缓存的刷新。
测试调用:
与test1()方法对应,曾经有人给过一个demo:
你的目标是为应用程序中的每个用例找到最好的获取方法方法和抓取策略;同时也要最小化SQL查询次数,以获得最好的性能。
HQL常用于对象获取,而不是更新、插入或者删除数据。对象状态同步是持久化管理的工作,而不是开发人员的工作。但是,正如前一章
中说的,HQL都支持大批量的操作。
按条件查询(Criteria的用法):
- public Item quertItem(String itemName) {
- Session session = getSessionFactory().openSession();
- Criteria criteria = session.createCriteria(Item.class);
- criteria.add(Restrictions.like("itemName", itemName));
- Item item = (Item) criteria.uniqueResult();
- return item;
- }
(criteria是hibernate固有的)
按示例查询:
作为Criteria工具的一部分,hibernate支持按示例查询(Query By Example,QBE)
- public Item quertItem2(String itemName) {
- Session session = getSessionFactory().openSession();
- Criteria criteria = session.createCriteria(Item.class);
- Item itemExample = new Item(itemName);
- criteria.add(Example.create(itemExample));
- Item item = (Item) criteria.uniqueResult();
- return item;
- }
延迟的默认抓取技术:
Hibernate给所有的实体和集合默认一个延迟的抓取策略。这意味着Hibernate在默认情况下只加载你正在查询的对象。
load()操作之后,内存中并没有持久化的item对象。甚至加载Item的SQL也没有被执行。Hibernate创建了一个看起来跟真的一样的代理(proxy)
理解代理:
代理是在运行时生成的占位符。每当Hibernate返回实体类的实例时,它就检查是否可以返回一个代理来代替,并避免数据库命中。
JPA中:find()必须命中,getReference(),可能返回一个代理。
新的延迟策略:
<set lazy="extra" ...>
如果调用size()、contains()或者isEmpty(),集合不再被初始化--查询数据库来获取必要的信息。
禁用代理生成:
即使是被关联,也会变成立即加载。
<class lazy="false" ...>
@org.hibernate.annotations.Proxy(lazy=false)
public class User{...}
关联和集合的即时加载:
(比如当你想从一个脱管状态的实体获取关联对象)
但是,当你想象一下,当很过千丝万偶的地方都设置了立即加载,那么获取一个对象,会关联获取出很多层关联对象!
不过你可以尝试使用视图对象,或者JOIN等关联获取方式。
通过拦截延迟加载:
Hibernate提供的运行时代理生成,是透明的延迟加载的一种极好的选择。(但是,通过instanceof测试多态的关联不可用,因为代理
是运行时生成的子类的一个实例。)
其它持久化工具没有代理的方式,都是使用的拦截。
通过lazy="no-proxy"告知对这个关联应用拦截。
(但是没给例子,不知道怎么玩)
2选择抓取策略
你的目标是把SQL语句的数量减少到最少,简化SQL语句,以便尽可能地提高查询效率。
批量预抓(batch fetching)取数据:
批量抓取经常被称为瞎猜优化(blind-guess optimization)
<class name="User" batch-size="10" ..>
也可用于集合
通过子查询预抓取集合:
<set fetch="subselect" ..>
@org.hibernate.annotations.Fetch(org.hibernate.annotations.FetchMode.SUBSELECT)
配置文件:
- <class name="Item" table="ITEM">
- <id name="itemId" column="ITEM_ID" type="integer">
- <generator class="native"/>
- </id>
- <property name="itemName" type="string" column="ITEM_NAME"/>
- <bag name="bids" inverse="true" cascade="save-update" fetch="subselect">
- <key column="ITEM_ID_M"></key>
- <one-to-many class="Bid"/>
- </bag>
- </class>
测试代码:
- private static void select() {
- Configuration configuration = new Configuration().configure();
- SessionFactory sessionFactory = configuration.buildSessionFactory();
- Session session = sessionFactory.openSession();
- Item item = (Item) session.get(Item.class, 1);
- System.out.println("-----");
- Collection<Bid> bids = item.getBids();
- System.out.println("+++++");
- for(Iterator it = bids.iterator();it.hasNext();){
- Bid bid = (Bid) it.next();
- System.out.println(bid);
- }
- // hibernate打印:
- // Hibernate: select item0_.ITEM_ID as ITEM1_1_0_, item0_.ITEM_NAME as ITEM2_1_0_ from ITEM item0_ where item0_.ITEM_ID=?
- // -----
- // +++++
- // Hibernate: select bids0_.ITEM_ID_M as ITEM3_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_0_0_, bids0_.bidMoeny as bidMoeny0_0_, bids0_.ITEM_ID_M as ITEM3_0_0_ from BID bids0_ where bids0_.ITEM_ID_M=?
- // Bid [bidId=1, bidMoeny=12.0]
- // Bid [bidId=2, bidMoeny=13.0]
- //如果是多条就会变成:
- //Hibernate: select bids0_.ITEM_ID_M as ITEM3_1_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_0_0_, bids0_.bidMoeny as bidMoeny0_0_, bids0_.ITEM_ID_M as ITEM3_0_0_ from BID bids0_ where bids0_.ITEM_ID_M in (select item0_.ITEM_ID from ITEM item0_)
- session.close();
- sessionFactory.close();
- }
通过联结即时抓取:
<set fetch="join" ..>
@org.hibernate.annotations.Fetch(org.hibernate.annotations.FetchMode.JOIN)
配置文件:
- <class name="Item" table="ITEM">
- <id name="itemId" column="ITEM_ID" type="integer">
- <generator class="native"/>
- </id>
- <property name="itemName" type="string" column="ITEM_NAME"/>
- <set name="bids" inverse="true" cascade="save-update" fetch="join">
- <key column="ITEM_ID_M"></key>
- <one-to-many class="Bid"/>
- </set>
- </class>
测试代码:
很奇怪,不解,难道我这个版本的hibernate有bug?把session.close();放在获取之前就会报no session的错误,那么肯定就是采用的懒加载的方式
- /**
- * 前提是我Item有三条数据,两条没有Bid
- */
- private static void select() {
- Configuration configuration = new Configuration().configure();
- SessionFactory sessionFactory = configuration.buildSessionFactory();
- Session session = sessionFactory.openSession();
- Query query = session.createQuery("select o from Item o");
- List<Item> items = query.list();
- System.out.println(items.size());
- for(int i=0;i<items.size();i++){
- Item item = items.get(i);
- System.out.println(item.getBids());
- }
- // 后台打印:
- // Hibernate: select item0_.ITEM_ID as ITEM1_1_, item0_.ITEM_NAME as ITEM2_1_ from ITEM item0_
- // 3
- // Hibernate: select bids0_.ITEM_ID_M as ITEM3_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_0_0_, bids0_.bidMoeny as bidMoeny0_0_, bids0_.ITEM_ID_M as ITEM3_0_0_ from BID bids0_ where bids0_.ITEM_ID_M=?
- // [Bid [bidId=2, bidMoeny=12.0], Bid [bidId=1, bidMoeny=13.0]]
- // Hibernate: select bids0_.ITEM_ID_M as ITEM3_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_0_0_, bids0_.bidMoeny as bidMoeny0_0_, bids0_.ITEM_ID_M as ITEM3_0_0_ from BID bids0_ where bids0_.ITEM_ID_M=?
- // []
- // Hibernate: select bids0_.ITEM_ID_M as ITEM3_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_0_0_, bids0_.bidMoeny as bidMoeny0_0_, bids0_.ITEM_ID_M as ITEM3_0_0_ from BID bids0_ where bids0_.ITEM_ID_M=?
- // []
- // 很奇怪,不解,难道我这个版本的hibernate有bug?把session.close();放在获取之前就会报no session的错误,那么肯定就是采用的懒加载的方式
- session.close();
- sessionFactory.close();
- }
换个版本(hibernate-distribution-3.6.4.Final):
还是这样。
突然明白过来,他在你指定的HQL中,如何生效呢?呵呵
- /**
- * 前提是我Item有三条数据,两条没有Bid
- */
- private static void select2() {
- Configuration configuration = new Configuration().configure();
- SessionFactory sessionFactory = configuration.buildSessionFactory();
- Session session = sessionFactory.openSession();
- Item item = (Item) session.get(Item.class, 1);
- // 打印:
- // Hibernate: select item0_.ITEM_ID as ITEM1_1_1_, item0_.ITEM_NAME as ITEM2_1_1_, bids1_.ITEM_ID_M as ITEM3_1_3_, bids1_.BID_ID as BID1_3_, bids1_.BID_ID as BID1_0_0_, bids1_.bidMoeny as bidMoeny0_0_, bids1_.ITEM_ID_M as ITEM3_0_0_ from ITEM item0_ left outer join BID bids1_ on item0_.ITEM_ID=bids1_.ITEM_ID_M where item0_.ITEM_ID=?
- session.close();
- sessionFactory.close();
- }
JPA:
- @Entity
- public class Item implements Serializable {
- @Id
- @GeneratedValue
- @Column(name="ITEM_ID")
- private Integer itemId;
- @Column(name="ITEM_NAME")
- private String itemName;
- @OneToMany(mappedBy="item",cascade=CascadeType.ALL)
- @Fetch(value=FetchMode.JOIN)
- private Set<Bid> bids = new HashSet<Bid>();
- .....
调用:
离职执行了查询集合数据
- private static void select() {
- EntityManagerFactory factory = Persistence.createEntityManagerFactory("partner4java");
- EntityManager em = factory.createEntityManager();
- Query query = em.createQuery("select o from Item o ");
- List<Item> items = query.getResultList();
- // 后台打印:
- // Hibernate: select item0_.ITEM_ID as ITEM1_1_, item0_.ITEM_NAME as ITEM2_1_ from Item item0_
- // Hibernate: select bids0_.ITEM_ID as ITEM3_1_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_0_0_, bids0_.BID_MONEY as BID2_0_0_, bids0_.ITEM_ID as ITEM3_0_0_ from Bid bids0_ where bids0_.ITEM_ID=?
- // Hibernate: select bids0_.ITEM_ID as ITEM3_1_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_0_0_, bids0_.BID_MONEY as BID2_0_0_, bids0_.ITEM_ID as ITEM3_0_0_ from Bid bids0_ where bids0_.ITEM_ID=?
- em.close();
- factory.close();
- }
- private static void select2() {
- EntityManagerFactory factory = Persistence.createEntityManagerFactory("partner4java");
- EntityManager em = factory.createEntityManager();
- Item item = em.find(Item.class, 1);
- System.out.println("----");
- System.out.println(item.getBids());
- // 后台打印:
- // Hibernate: select item0_.ITEM_ID as ITEM1_0_1_, item0_.ITEM_NAME as ITEM2_0_1_, bids1_.ITEM_ID as ITEM3_0_3_, bids1_.BID_ID as BID1_3_, bids1_.BID_ID as BID1_1_0_, bids1_.BID_MONEY as BID2_1_0_, bids1_.ITEM_ID as ITEM3_1_0_ from Item item0_ left outer join Bid bids1_ on item0_.ITEM_ID=bids1_.ITEM_ID where item0_.ITEM_ID=?
- // ----
- // [Bid [bidId=1, bidMoeny=12.0], Bid [bidId=2, bidMoeny=13.0]]
- //但是,我们是三条数据,一条Item没有Bid,所以出问题了
- em.close();
- factory.close();
- }
上面这样就生效了。看来fetch方式如此不堪。
总结:
- JPA使用下面代码测试(前提是有三条Item,一条没有Bid数据):
- private static void select() {
- EntityManagerFactory factory = Persistence.createEntityManagerFactory("partner4java");
- EntityManager em = factory.createEntityManager();
- Query query = em.createQuery("select o from Item o ");
- List<Item> items = query.getResultList();
- em.close();
- factory.close();
- }
join:
1、无论使用hibernate和jpa使用HQL或者QL语句,是不生效的。
2、区别在于JPA:
@OneToMany(mappedBy="item",cascade=CascadeType.ALL,fetch=FetchType.LAZY)
@Fetch(value=FetchMode.JOIN)
后台打印:
Hibernate: select item0_.ITEM_ID as ITEM1_0_, item0_.ITEM_NAME as ITEM2_0_ from Item item0_
Hibernate: select bids0_.ITEM_ID as ITEM3_0_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_1_0_, bids0_.BID_MONEY as BID2_1_0_, bids0_.ITEM_ID as ITEM3_1_0_ from Bid bids0_ where bids0_.ITEM_ID=?
Hibernate: select bids0_.ITEM_ID as ITEM3_0_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_1_0_, bids0_.BID_MONEY as BID2_1_0_, bids0_.ITEM_ID as ITEM3_1_0_ from Bid bids0_ where bids0_.ITEM_ID=?
Hibernate: select bids0_.ITEM_ID as ITEM3_0_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_1_0_, bids0_.BID_MONEY as BID2_1_0_, bids0_.ITEM_ID as ITEM3_1_0_ from Bid bids0_ where bids0_.ITEM_ID=?
像这样,无论,JPA中你是否指明了懒加载,只要生命了fetch=join方式,就会变成立即加载(我认为是这个版本的bug)。
Hibernate不会,hibernate始终就是默认就是把返回代理放在最优先考虑的方式。
3、如果使用非HQL或QL的查询方式,如get之类的,JOIN就会出现问题,因为使用的left outer join方式,就只会查询出有关联数据的Item。
SUBSELECT:
1、JPA和hibernate是相同的:
@OneToMany(mappedBy="item",cascade=CascadeType.ALL,fetch=FetchType.LAZY)
@Fetch(value=FetchMode.SUBSELECT)
就是,默认是懒加载模式
后台打印:
Hibernate: select item0_.ITEM_ID as ITEM1_1_, item0_.ITEM_NAME as ITEM2_1_ from Item item0_
2、但是,如果修改为:
@OneToMany(mappedBy="item",cascade=CascadeType.ALL,fetch=FetchType.EAGER)
@Fetch(value=FetchMode.SUBSELECT)
后台就会打印:
Hibernate: select item0_.ITEM_ID as ITEM1_1_, item0_.ITEM_NAME as ITEM2_1_ from Item item0_
Hibernate: select bids0_.ITEM_ID as ITEM3_1_1_, bids0_.BID_ID as BID1_1_, bids0_.BID_ID as BID1_0_0_, bids0_.BID_MONEY as BID2_0_0_, bids0_.ITEM_ID as ITEM3_0_0_ from Bid bids0_ where bids0_.ITEM_ID in (select item0_.ITEM_ID from Item item0_)
就是JPA中QL语句也会生效SUBSELECT设置。Hibernate也会生效。
所以,综上所述,当时使用HQL或QL查询的时候,而且需要获取脱管对象的关联对象时,建议使用SUBSELECT方式,但是,还需要额外指明即时加载。且,JPA的JOIN方式的QL查询还存在bug。
但是,我们如果想在第一个select的HQL方式中也用join该如何呢?
3高速缓存基本原理
事务范围高速缓存--添加到当前的工作单元,它可能是一个数据库事务,甚至是一个对话。他只有在工作单元运行时才是有效的,才能被使用。每个工作单元都有自己的高速缓存。这个高速缓存中的数据不会被并发访问。
过程范围高速缓存--在许多(可能并发的)工作单元或者事务之间共享。这意味着过程范围高速缓存中的数据被并发运行的线程访问,显然隐含和事务隔离性。
集群范围高速缓存--在同一台机器的多个线程之间或者一个集群中的多台机器上共享。这里,网络通信是一个值得考虑的关键点。
1、高速缓存和对象同一性
2、高速缓存和并发
3、高速缓存和事务隔离性
完全的ORM解决方案让你单独给每个类配置二级高速缓存。高速缓存的好的备选类是表示一下数据的类:
很少改变的数据;不重要的数据(如内容管理数据);应用程序固有的而非共享的数据。
对于二级高速缓存来说,不好的备选类:
经常被更新的数据;财务数据;通过遗留应用程序共享的数据。
许多应用程序都包含大量下面类型的数据:
少量的实例;被另一个类或者几个类的许多实例引用的每一个实例;很少更新的实例。
这种数据有时称作引用数据(reference data)。
Hibernate高速缓存架构:
1、Hibernate二级高速缓存
范围:从特定的SessionFactory或者EntityManager所有持久化上下文共享同一个二级高速缓存。
二级高速缓存是否被启用;Hibernate并发策略;高速缓存过期策略;高速缓存的物理格式。
(如果你有更新比读取更经常数据,就不要启用二级高速缓存)
2、内建的并发策略
事务隔离级别:
事务--只可用于托管环境,如有必要,他还保证完全的事务隔离知道可重复读取。给主要用于读取的数据使用这种策略,因为在这种数据中,防止并发事务中的废弃数据最为关键,极少数情况下用于更新。
读/写--这种策略利用时间戳机制,维护读取提交隔离,并且只在非集群环境中可用。还是给主要用于读取数据使用这种策略,因为在这种数据中防止并发事务中废弃数据最为关键,极少数情况下用于更新。
非严格读/写--不提供高速缓存和数据库之间的一致性保证。如果有可能并发访问相同的实体,你应该配置一个足够短的超时期限。否则,则可能从高速缓存中读取废弃的数据。如果数据几乎不变,并且废弃的数据不可能是关键的关注点,那就使用这种策略。
只读--并发策略适合于从不改变的数据。他只用于引用数据。
3、选择高速缓存提供程序
见图:高速缓存并发策略支持.jpg
4高速缓存实战
首先,考虑每个实体类和集合,并找出哪种高速缓存并发策略可能适合。在给本地的和集群的高速缓存选择好高速缓存提供程序之后,讲编写他们的配置文件。
步骤一:选择并发控制策略:
Hibernate一把设置:
- <class name="Category" table="CATEGORY">
- <!-- 缓存配置;读/写并发策略 -->
- <cache usage="read-write"/>
- <id name="categoryId" column="CATEGORY_ID" type="long">
- <generator class="native"/>
- </id>
- <property name="categoryName" type="string" column="CATEGORY_NAME"/>
- </class>
JPA配置:
- /**
- * 分类
- * @author partner4java
- *
- */
- @Entity
- @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
- public class Category implements Serializable {
- @Id
- @GeneratedValue
- @Column(name="CATEGORY_ID")
- private Long categoryId;
- @Column(name="CATEGORY_NAME")
- private String categoryName;
- ...
步骤二:设置hibernate.cfg.xml
选择EHCache作为二级高速缓存
- <!-- Enable the second-level cache -->
- <property name="cache.use_second_level_cache">true</property>
- <!-- 老版本
- <property name="cache.provider_class">
- net.sf.ehcache.hibernate.EhCacheProvider
- </property>
- -->
- <property name="cache.region.factory_class">
- net.sf.ehcache.hibernate.EhCacheRegionFactory</property>
- <property name="net.sf.ehcache.configurationResourceName">
- ehcache.xml
- </property>
步骤三:配置ehcache.xml
- <ehcache>
- <defaultCache
- maxElementsInMemory="10"
- eternal="false"
- timeToIdleSeconds="120"
- timeToLiveSeconds="120"
- overflowToDisk="false">
- </defaultCache>
- <cache
- name="quick.update"
- maxElementsInMemory="10"
- eternal="false"
- timeToIdleSeconds="10"
- timeToLiveSeconds="10"
- overflowToDisk="false"
- />
- <!-- <terracottaConfig url="localhost:9510"/> -->
- </ehcache>
eternal="false",不会永恒,配置超时时间。(以妙为单位)
控制二级高速缓存:
配置开启:
<property name="cache.use_second_level_cache">true</property>
移除单个元素:
SessionFactory.evict(Category.class, new Long(1));
移除对象:
SessionFactory.evict("auction.model.Category");
(二级高速缓存的清除是非事物的)
交互方式:
CacheMode.NORMAL--默认的行为。
CacheMode.IGNORE--Hibernate从来不与二级高速缓存交互,除了更新发生时被高速缓存的项目失效之外。
CacheMode.PUT--Hibernate从来不从二级高速缓存中读取项目,但是当它从数据库中读取项目时,会把项目添加到高速缓存。
CacheMode.REFRESH--Hibernate从来不从二级高速缓存中读取项目,但是当他从数据库中读取项目时,会把项目添加到高速缓存。在这种模式下,hibernate.cache.use_minimal_puts的作用被忽略,一遍在一个复制的集群高速缓存中强制高速缓存的刷新。
测试调用:
- private static void test1() {
- Configuration configuration = new Configuration().configure();
- SessionFactory sessionFactory = configuration.buildSessionFactory();
- Session session = sessionFactory.openSession();
- Transaction tr = session.beginTransaction();
- Category category = new Category("hell");
- // session.save(category);
- session.persist(category);
- System.out.println(category);
- tr.commit();
- session.close();
- try {
- //期间改变数据库的数据(删除此条数据)
- Thread.sleep(30*1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- Session session2 = sessionFactory.openSession();
- Category category2 = (Category) session2.get(Category.class, category.getCategoryId());
- System.out.println(category2);
- //打印null,证明,我即使配置了<cache usage="read-write"/>,通过session的save或者persist还是不会把新的实体保存到二级缓存,那么就搞不清到底是怎么回事了?
- session2.close();
- sessionFactory.close();
- }
- private static void test2() {
- Configuration configuration = new Configuration().configure();
- SessionFactory sessionFactory = configuration.buildSessionFactory();
- Session session = sessionFactory.openSession();
- Transaction tr = session.beginTransaction();
- Category category = (Category) session.get(Category.class, 2L);
- System.out.println(category);
- tr.commit();
- session.close();
- try {
- //期间改变数据库的数据(删除此条数据)
- Thread.sleep(30*1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- Session session2 = sessionFactory.openSession();
- Category category2 = (Category) session2.get(Category.class, 2L);
- System.out.println(category2);
- //正常打印,且没执行sql,获取二级缓存数据,但是如果配置<cache usage="read-write" region="quick.update"/>,10秒清楚缓存,那么就会超时,导致,打印sql和执行sql
- session2.close();
- sessionFactory.close();
- }
- private static void test3() {
- Configuration configuration = new Configuration().configure();
- SessionFactory sessionFactory = configuration.buildSessionFactory();
- Session session = sessionFactory.openSession();
- Transaction tr = session.beginTransaction();
- Category category = (Category) session.get(Category.class, 5L);
- System.out.println(category);
- category.setCategoryName("hello world22");
- tr.commit();
- session.close();
- Session session2 = sessionFactory.openSession();
- Category category2 = (Category) session2.get(Category.class, 5L);
- System.out.println(category2);
- //ok,没有执行查询,且更新生效。但如如果改成,<cache usage="read-only"/>,后台报错: Can't write to a readonly object
- session2.close();
- sessionFactory.close();
- }
- // 总结:
- // 17.read-only
- // Caches data that is never updated.
- // 17.nonstrict-read-write
- // Caches data that is sometimes updated without ever locking the cache. If concurrent access to an item
- // is possible, this concurrency strategy makes no guarantee that the item returned from the cache is the
- // latest version available in the database. Configure your cache timeout accordingly!
- // 17.read-write
- // Caches data that is sometimes updated while maintaining the semantics of "read committed" isolation
- // level. If the database is set to "repeatable read", this concurrency strategy almost maintains the
- // semantics. Repeatable read isolation is compromised in the case of concurrent writes.
与test1()方法对应,曾经有人给过一个demo:
- //save() 方法保存一个对象到数据库中, 同时也会缓存该对象
- private static void test2() {
- Session session = HibernateUtil.getSessionFactory().openSession();
- Transaction tx = session.beginTransaction();
- User user = new User();
- user.setAge(21);
- user.setName("name_21");
- Serializable userId = session.save(user);
- session.get(User.class, userId);
- tx.commit();
- session.close();
- }
- //那么你看看那这个demo是否正确呢?我以为这个demo是不对的,因为是同一个session当然,不会执行第二条查询sql,不然你把缓存去掉试试。