一、1+N問題,也叫N+1問題
1.問題描述如testQueryByNoLazy方法所示:
import java.util.Date;
import java.util.List;
import org.hibernate.*;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class Modeltest {
private static SessionFactory sf=null;
@BeforeClass
public static void beforeClass(){
sf=new AnnotationConfiguration().configure().buildSessionFactory();
}
@Test
public void testSave(){
//先插入c再插入t保證每個t都關聯不同的c
Session session=sf.getCurrentSession();
session.beginTransaction();
for(int i=0;i<10;i++){
Category c=new Category();
c.setName("c"+i);
Topic t=new Topic();
t.setCategory(c);
t.setTitle("t"+i);
t.setCreateDate(new Date());
session.save(c);
session.save(t);
}
session.getTransaction().commit();
}
@Test
public void testQueryByNoLazy(){
//預設Topic中是Eager,這樣取資料的話會發出11條sql語句(1條取所有t,10條取c)由于是eager的原因
Session session=sf.getCurrentSession();
session.beginTransaction();
List<Topic> topics=(List<Topic>)session.createQuery("from Topic").list();
for(Topic t : topics){
System.out.println(t.getTitle()+":"+t.getId());
}
session.getTransaction().commit();
}
@Test
public void testQueryByLazy(){
//将預設的fetch設定為Lazy,這樣隻會發出1條sql語句(隻取t),但是如果用到其關聯的c的話,用到誰的c會發1條sql語句取c。
Session session=sf.getCurrentSession();
session.beginTransaction();
List<Topic> topics=(List<Topic>)session.createQuery("from Topic").list();
for(Topic t : topics){
System.out.println(t.getTitle()+":"+t.getId()+":"+t.getCategory().getName());
}
session.getTransaction().commit();
}
@Test
public void testQueryByCriteria(){
//Criteria預設使用left join取資料 不會發那麼多sql語句
Session session=sf.getCurrentSession();
session.beginTransaction();
List<Topic> topics=(List<Topic>)session.createCriteria(Topic.class).list();
for(Topic t : topics){
System.out.println(t.getTitle()+":"+t.getId()+":"+t.getCategory().getName());
}
session.getTransaction().commit();
}
@Test
public void testQueryByJoinfetch(){
//手動設定left join 也不會發出那麼多sql語句
Session session=sf.getCurrentSession();
session.beginTransaction();
List<Topic> topics=(List<Topic>)session.createQuery("from Topic t join fetch t.category c").list();
for(Topic t : topics){
System.out.println(t.getTitle()+":"+t.getId()+":"+t.getCategory().getName());
}
session.getTransaction().commit();
}
@Test
public void testQueryByBatchSize(){
//這個查詢和nolazy方法一樣 隻是在Category類上加了@BatchSize(size=5) 這樣的話會發出3條sql語句(1條取t,2條取c:每一條sql語句使用id in()的形式各取出5條c)
Session session=sf.getCurrentSession();
session.beginTransaction();
List<Topic> topics=(List<Topic>)session.createQuery("from Topic").list();
for(Topic t : topics){
System.out.println(t.getTitle()+":"+t.getId()+":"+t.getCategory().getName());
}
session.getTransaction().commit();
}
@Test
public void testSchemaExport(){
new SchemaExport(new AnnotationConfiguration().configure()).create(false, true);
}
@AfterClass
public static void afterClass(){
sf.close();
}
}
import org.hibernate.annotations.BatchSize;
@Entity
//@BatchSize(size=5)
public class Category {
private int id;
private String name;
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.*;
@Entity
public class Topic {
private int id;
private String title;
private Category category;
private Date createDate;
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}//解決方法:<span style="font-family: Arial, Helvetica, sans-serif;">@ManyToOne(fetch=FetchType.LAZY)</span>
@ManyToOne
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
}
二、list和iterate效率
1.list隻往緩存裡寫,不從緩存裡讀;iterate先去緩存裡讀,再去資料庫讀,會先讀出資料的ID,用到該資料的其他屬性後才發出sql語句查詢
2.load也是先查緩存;外連結不能和iterate一起使用,會報錯
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.hibernate.*;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class Modeltest {
private static SessionFactory sf=null;
@BeforeClass
public static void beforeClass(){
sf=new AnnotationConfiguration().configure().buildSessionFactory();
}
@Test
public void testSave(){
//先插入c再插入t保證每個t都關聯不同的c
Session session=sf.getCurrentSession();
session.beginTransaction();
for(int i=0;i<10;i++){
Category c=new Category();
c.setName("c"+i);
Topic t=new Topic();
t.setCategory(c);
t.setTitle("t"+i);
t.setCreateDate(new Date());
session.save(c);
session.save(t);
}
session.getTransaction().commit();
}
@Test
public void testQueryByLazy(){
//将預設的fetch設定為Lazy,這樣隻會發出1條sql語句(隻取t),但是如果用到其關聯的c的話,用到誰的c會發1條sql語句取c。
Session session=sf.getCurrentSession();
session.beginTransaction();
// List<Topic> topics=(List<Topic>)session.createCriteria(Topic.class).list();
List<Topic> topics=(List<Topic>)session.createQuery("from Topic").list();
for(Topic t : topics){
System.out.println(t.getTitle()+":"+t.getId()+":"+t.getCategory().getName());
}
session.getTransaction().commit();
}
@Test
public void testQueryByIterate(){
/*如果用left join+iterate的話會報錯:org.hibernate.QueryException: fetch may not be used with scroll() or iterate() [from com.wzy.model.Topic t join fetch t.category c]
at org.hibernate.hql.ast.tree.FromElement.setFetch(FromElement.java:429)
at org.hibernate.hql.ast.tree.FromElementFactory.createEntityJoin(FromElementFactory.java:263)
at org.hibernate.hql.ast.tree.DotNode.dereferenceEntityJoin(DotNode.java:454)
at org.hibernate.hql.ast.tree.DotNode.dereferenceEntity(DotNode.java:373)
at org.hibernate.hql.ast.tree.DotNode.resolve(DotNode.java:232)
at org.hibernate.hql.ast.tree.FromReferenceNode.resolve(FromReferenceNode.java:117)
at org.hibernate.hql.ast.HqlSqlWalker.createFromJoinElement(HqlSqlWalker.java:369)
at org.hibernate.hql.antlr.HqlSqlBaseWalker.joinElement(HqlSqlBaseWalker.java:3452)
at org.hibernate.hql.antlr.HqlSqlBaseWalker.fromElement(HqlSqlBaseWalker.java:3239)
at org.hibernate.hql.antlr.HqlSqlBaseWalker.fromElementList(HqlSqlBaseWalker.java:3112)
at org.hibernate.hql.antlr.HqlSqlBaseWalker.fromClause(HqlSqlBaseWalker.java:720)
at org.hibernate.hql.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:571)
at org.hibernate.hql.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:288)
at org.hibernate.hql.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:231)
at org.hibernate.hql.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:254)
at org.hibernate.hql.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:185)
at org.hibernate.hql.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:136)
at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:101)
at org.hibernate.engine.query.HQLQueryPlan.<init>(HQLQueryPlan.java:80)
at org.hibernate.engine.query.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:94)
at org.hibernate.impl.AbstractSessionImpl.getHQLQueryPlan(AbstractSessionImpl.java:156)
at org.hibernate.impl.SessionImpl.iterate(SessionImpl.java:1215)
at org.hibernate.impl.QueryImpl.iterate(QueryImpl.java:69)
at com.wzy.model.Modeltest.testQueryByIterate(Modeltest.java:59)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:73)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:46)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:180)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:41)
at org.junit.runners.ParentRunner$1.evaluate(ParentRunner.java:173)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
at org.junit.runners.ParentRunner.run(ParentRunner.java:220)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)*/
//iterate隻會取出資料的Id 程式中用到其他屬性值的時候會再發出sql語句取資料庫中取 ,因為iterate會利用緩存list則不會
//下面我取的是多的一方,由于1+N問題,會發出多條sql語句将1的一方也取出來
Session session=sf.getCurrentSession();
session.beginTransaction();
//Iterator<Topic> topics=(Iterator<Topic>)session.createQuery("from Topic t join fetch t.category c").iterate();
Iterator<Topic> topics=(Iterator<Topic>)session.createQuery("from Topic").iterate();
while(topics.hasNext()){
Topic t=topics.next();
System.out.println(t.getTitle()+":"+t.getId()+":"+t.getCategory().getName());
}
//下面我取的是一的一方,這樣就不考慮1+N問題了
/*Iterator<Category> category=(Iterator<Category>)session.createQuery("from Category").iterate();
while(category.hasNext()){
Category c=category.next();
System.out.println(c.getName());
}*/
session.getTransaction().commit();
}
@Test
public void testQueryByList(){
//聽說如果用left join+iterate的話會報錯 list實作iterater的接口
Session session=sf.getCurrentSession();
session.beginTransaction();
List<Category> categorys=(List<Category>)session.createQuery("from Category").list();
for(Category c : categorys){
System.out.println(c.getName());
}
session.getTransaction().commit();
}
@Test
public void testQueryByIterate2(){
Session session=sf.getCurrentSession();
session.beginTransaction();
//取兩遍Category sql隻發一遍,從緩存中讀取
Iterator<Category> category=(Iterator<Category>)session.createQuery("from Category").iterate();
while(category.hasNext()){
Category c=category.next();
System.out.println(c.getName());
}
Iterator<Category> category2=(Iterator<Category>)session.createQuery("from Category").iterate();
while(category2.hasNext()){
Category c2=category.next();
System.out.println(c2.getName());
}
session.getTransaction().commit();
}
@Test
public void testQueryByList2(){
//取兩遍Category sql發2遍 不讀緩存,重新整理緩存
Session session=sf.getCurrentSession();
session.beginTransaction();
List<Category> categorys=(List<Category>)session.createQuery("from Category").list();
for(Category c : categorys){
System.out.println(c.getName());
}
List<Category> categorys2=(List<Category>)session.createQuery("from Category").list();
for(Category c2 : categorys){
System.out.println(c2.getName());
}
session.getTransaction().commit();
}
@Test
public void testSchemaExport(){
new SchemaExport(new AnnotationConfiguration().configure()).create(false, true);
}
@AfterClass
public static void afterClass(){
sf.close();
}
}
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.*;
@Entity
public class Topic {
private int id;
private String title;
private Category category;
private Date createDate;
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@ManyToOne(fetch=FetchType.LAZY)
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
}
import javax.persistence.*;
@Entity
public class Category {
private int id;
private String name;
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
三、緩存
1.一級緩存:session級别緩存,跨session讀不到,不用更改配置(相當局部變量)
2.二級緩存:sessionFatory級别緩存,同一個sessionFatory裡誇session共享資料,誇sessionFatory讀不到(相當全局變量),需要在hibernate配置檔案裡加上打開二級緩存的代碼,并在需要緩存的實體entity上加@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)标簽:
(1)二級緩存是有很多類型的:
HashTable:隻是用來測試的,不能投入使用,隻能用在記憶體裡,不用于叢集環境,支援三級緩存,代碼為org.hibernate.cache.HashtableCacheProvider
EhCache:hibernate3.2預設Cache,記憶體不夠的時候可以存入硬碟,不用于叢集環境,支援三級緩存,代碼為org.hibernate.cache.EhCacheProvider
OSCache:記憶體不夠的時候可以存入硬碟,不用于叢集環境,支援三級緩存,代碼為org.hibernate.cache.OSCacheProvider
SwarmCahce:記憶體不夠的時候可以存入硬碟,支援叢集環境,不支援三級緩存,代碼為org.hibernate.cache.SwarmCacheProvider
JBossCahe1.x::記憶體不夠的時候可以存入硬碟,支援叢集環境,支援三級緩存,代碼為org.hibernate.cache.TreeCacheProvider
JBossCache2:記憶體不夠的時候可以存入硬碟,支援叢集環境,支援三級緩存,代碼為org.hibernate.cache.jbc2.JBossCacheRegionProvider
(2)二級緩存配置檔案代碼(使用EhCache): 先打開二級緩存,再設定緩存類型,再加入緩存配置檔案
<property name="cache.use_second_level_cache">true</property>
<property name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
在根目錄下加入ehcache.xml
3.查詢緩存(三級緩存):打開query緩存,使用.setCacheable(true)
hibernate配置檔案要在“二級緩存”代碼後加上:<property name="cache.use_query_cache">true</property>
[email protected]有三個參數usage一般都是READ_WRITE,region指定緩存(在ehcache配置檔案中命名的cache):
@Cache(
CacheConcurrencyStrategy usage(); (1)
String region() default ""; (2)
String include() default "all"; (3)
)
(1) | usage: 給定緩存的并發政策(NONE, READ_ONLY, NONSTRICT_READ_WRITE, READ_WRITE, TRANSACTIONAL) |
(2) | region (可選的):緩存範圍(預設為類的全限定類名或是集合的全限定角色名) |
(3) | include (可選的):值為all時包括了所有的屬性(proterty), 為non-lazy時僅含非延遲屬性(預設值為all) |
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.hibernate.*;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class Modeltest {
private static SessionFactory sf=null;
@BeforeClass
public static void beforeClass(){
sf=new AnnotationConfiguration().configure().buildSessionFactory();
}
@Test
public void testSave(){
//先插入c再插入t保證每個t都關聯不同的c
Session session=sf.getCurrentSession();
session.beginTransaction();
for(int i=0;i<10;i++){
Category c=new Category();
c.setName("c"+i);
Topic t=new Topic();
t.setCategory(c);
t.setTitle("t"+i);
t.setCreateDate(new Date());
session.save(c);
session.save(t);
}
session.getTransaction().commit();
}
@Test
public void testQueryCache(){
//load,iterate會使用2級緩存,list會存入2級緩存,但不會從2級緩存中取值
Session session=sf.getCurrentSession();
session.beginTransaction();
Category c=(Category)session.load(Category.class, 1);
System.out.println(c.getName());
session.getTransaction().commit();
Session session2=sf.getCurrentSession();
session2.beginTransaction();
Category c2=(Category)session2.load(Category.class, 1);
System.out.println(c2.getName());
session2.getTransaction().commit();
}
@Test
public void testQueryCacheByList(){
//load,iterate會使用2級緩存,list會存入2級緩存,但不會從2級緩存中取值
Session session=sf.getCurrentSession();
session.beginTransaction();
List<Category> categories=(List<Category>)session.createQuery("from Category").setCacheable(true).list();
for(Category c:categories){
System.out.println(c.getName());
}
session.getTransaction().commit();
Session session2=sf.getCurrentSession();
session2.beginTransaction();
List<Category> categories1=(List<Category>)session2.createQuery("from Category").setCacheable(true).list();
for(Category c:categories1){
System.out.println(c.getName());
}
session2.getTransaction().commit();
}
@Test
public void testSchemaExport(){
new SchemaExport(new AnnotationConfiguration().configure()).create(false, true);
}
@AfterClass
public static void afterClass(){
sf.close();
}
}
import javax.persistence.*;
import org.hibernate.annotations.BatchSize;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
@Entity
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class Category {
private int id;
private String name;
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.*;
@Entity
public class Topic {
private int id;
private String title;
private Category category;
private Date createDate;
@Id
@GeneratedValue
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@ManyToOne(fetch=FetchType.LAZY)
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
}