新手入門hibernate框架

Hibernate

創(chuàng)建 hibernate 工程示例:

創(chuàng)建工程,引入 jar 包

創(chuàng)建配置文件 hibernate.cfg.xml, 配置數(shù)據(jù)庫(kù)連接

編寫實(shí)體類(entity), 標(biāo)明注解, 然后配置在 hibernate.cfg.xml 中

創(chuàng)建?SessionFactory, 獲取?Session, 通過操作實(shí)體類操作數(shù)據(jù)庫(kù)渴丸。

Session

對(duì)象的三種狀態(tài):

Transient, 瞬時(shí)狀態(tài),指的是對(duì)象已經(jīng)被初始化,但沒有跟 hibernate 的 session 建立過聯(lián)系把还,即數(shù)據(jù)庫(kù)里沒有數(shù)據(jù)對(duì)應(yīng)校焦。

Persistent, 持久化狀態(tài),指的是對(duì)象在數(shù)據(jù)中有對(duì)應(yīng)數(shù)據(jù)艘包,對(duì)象有 id 值的猛。它可能是通過 save 或 load 等方式得到的,并且在 session 緩存中有定義想虎。

Detached, 脫管狀態(tài)卦尊,曾經(jīng)被持久,在數(shù)據(jù)庫(kù)中有數(shù)據(jù)對(duì)應(yīng)舌厨。但是岂却,在 session 緩存里沒有記錄。也許是 session 關(guān)閉了裙椭,也許是清空了躏哩。

狀態(tài)之間可以進(jìn)行轉(zhuǎn)換,下面是大致的轉(zhuǎn)換流程:

get/load/query()

get/load 會(huì)優(yōu)先在 session 緩存里尋找對(duì)象揉燃,如果找不到扫尺,再去查詢數(shù)據(jù)庫(kù)

query 會(huì)直接查詢數(shù)據(jù)庫(kù)

get 不懶,會(huì)立刻查詢炊汤。如果沒有找到器联,那么返回 null

load 延遲加載,立刻返回一個(gè)代理對(duì)象婿崭。如果沒有找到拨拓,那么拋出異常

LazyInitializationException !!!

flush/refresh()

flush 將 session 緩存里的數(shù)據(jù)同步到數(shù)據(jù)庫(kù),觸發(fā)相應(yīng)的 sql 語(yǔ)句氓栈。

以下情況渣磷,會(huì)觸發(fā) flush 操作:

調(diào)用 commit 的時(shí)候,會(huì)觸發(fā) session.flush() 操作授瘦。

執(zhí)行 session.createQuery() 查詢的時(shí)候醋界,也會(huì)觸發(fā) flush 操作。

手動(dòng)執(zhí)行 flush 操作提完。

refresh 是將數(shù)據(jù)庫(kù)里的信息形纺,同步到 session 緩存。

clear/evict()

從 session 緩存中清理數(shù)據(jù)

save/persist()

都是用來將瞬時(shí)對(duì)象變?yōu)槌志没瘜?duì)象徒欣,即將數(shù)據(jù)插入數(shù)據(jù)庫(kù)逐样,對(duì)應(yīng) insert 語(yǔ)句。

save 是 hibernate 原生的語(yǔ)法,persist 是 jpa 的語(yǔ)法脂新。

在執(zhí)行的時(shí)候挪捕,不會(huì)立刻插入數(shù)據(jù),只有執(zhí)行了 flush 操作争便,才真正觸發(fā)數(shù)據(jù)庫(kù)操作级零。

save/persist 方法會(huì)立刻為實(shí)體類對(duì)象生成主鍵。

他們的區(qū)別是, 如果在保存之前滞乙,重新手動(dòng)賦予了主鍵:

save 會(huì)忽視你的賦值

persist 會(huì)拋異常

update/merge()

他們主要用來完成實(shí)體類對(duì)象的修改奏纪,對(duì)應(yīng)的是 update 語(yǔ)句。

若更新一個(gè)持久化對(duì)象斩启,可以不顯式調(diào)用 update, 因?yàn)?flush 操作會(huì)觸發(fā) update

可以將一個(gè)脫管對(duì)象轉(zhuǎn)換為持久化對(duì)象

merge 是 jpa 中的語(yǔ)法

doWork

可以將 jdbc 的 connection 對(duì)象暴露出來序调,用于插入一些 jdbc 操作語(yǔ)法。

Identifier

-- JPA 默認(rèn)

@GeneratedValue(strategy = GenerationType.AUTO/IDENTITY/SEQUENCE/TABLE)

-- JPA 定制序列/Table

@GeneratedValue(generator = "xxx")

@SequenceGenerator(name = "xxx", sequenceName = "seq_xxx", associateSize = 1)

@TableGenerator(name = "xxx", table = "tb_xxx")

-- Hibernate 格式的 generator:

@GeneratedValue(generator = "yyy")

@GenericGenerator(name = "yyy", strategy = "native")

@GenericGenerator(name = "yyy", strategy = "uuid2")

@GenericGenerator(name = "yyy", strategy = "table")

Association

1-N

一對(duì)多的關(guān)系浇垦,在數(shù)據(jù)庫(kù)的角度炕置,需要使用外鍵維護(hù)這種關(guān)系。

一般情況下男韧,在多的一邊的表上朴摊,建立一個(gè)外鍵映射到另一個(gè)表。

比如此虑,有兩個(gè)表 author, book 一般而言甚纲,book 的定義類似是這樣的:

create table book {

? ? bookid int primary key,

? ? name varchar2(20) not null,

? ? price float,

? ? publish_date date default sysdate,

? ? -- 下面字段用來維護(hù)跟作者的關(guān)系

? ? -- 它是一個(gè)外鍵約束

? ? authorid references author

}

book/author 分別對(duì)應(yīng)實(shí)體類 Book/Author,我們可以在其中任意一個(gè)實(shí)體類中朦前,設(shè)置他們的關(guān)系介杆。

如果只是在其中一個(gè)中設(shè)置關(guān)系,那么叫“單邊關(guān)系”韭寸、“單向關(guān)聯(lián)”春哨,否則是“雙向關(guān)聯(lián)”。

其中最常用的是 多對(duì)一的單向關(guān)聯(lián) 和 *多對(duì)一的雙向關(guān)聯(lián)*恩伺。

多對(duì)一的單向:

public class Author {

? ? @Id @GeneratedValue private long id;

? ? private String name;

}

public class Book {

? ? @Id @GeneratedValue private long id;

? ? private String name;

? ? private FLoat price;

? ? // 只是在多的一段設(shè)置關(guān)系赴背。這是非常常用的一種方式。

? ? // 用 @JoinColumn 定制外鍵字段的名字

? ? @ManyToOne @JoinColumn

? ? private Author author;

}

多對(duì)一的雙向關(guān)系:

// 多的一端晶渠,即主端凰荚,需要負(fù)責(zé)維護(hù)關(guān)系

public class Book {

? ? @Id @GeneratedValue private long id;

? ? private String name;

? ? private FLoat price;

? ? // 只是在多的一端設(shè)置關(guān)系。這是非常常用的一種方式褒脯。

? ? // 用 @JoinColumn 定制外鍵字段的名字

? ? @ManyToOne @JoinColumn

? ? private Author author;

}

// 一的一端便瑟,即從端,需要當(dāng)甩手掌柜

public class Author {

? ? @Id @GeneratedValue private long id;

? ? private String name;

? ? // 不要讓雙方都去維護(hù)關(guān)系番川,不然會(huì)有沖突或重復(fù)到涂。

? ? // 一般情況下脊框,需要讓多的一端維護(hù)關(guān)系即可。這里用 mappedBy 表名养盗,自己當(dāng)甩手掌柜缚陷。

? ? @OneToMany(mappedBy = "author")

? ? private Set<Books> books = new HashSet<>();

}

在數(shù)據(jù)插入的時(shí)候适篙,要先保存一的一端往核,再保存多的一端,否則嚷节,會(huì)有冗余的 SQL 語(yǔ)句聂儒。

M-N

多對(duì)多的關(guān)系,需要使用中間表維護(hù)雙方關(guān)系硫痰。對(duì)應(yīng)的注解為 @ManyToMany

必須為雙方制定從屬關(guān)系衩婚,也就是將維護(hù)關(guān)系的責(zé)任交給其中一個(gè)實(shí)體類(mappedBy),從而避免重復(fù)或沖突效斑。

可以使用 @JoinTable 對(duì)中間表進(jìn)行定制

例子:

@Entity

public class Emp {

? ? @ManyToMany? // 負(fù)責(zé)關(guān)系的維護(hù)

? ? @JoinTable(...)

? ? private Set<Project> projects = new HashSet<>();

}

@Entity

public class Project {

? ? @ManyToMany(mappedBy = "projects")? // 甩手掌柜

? ? private Set<Emp> emps = new HashSet<>();

}

1-1

兩種方式:

在其中一個(gè)表上創(chuàng)建一個(gè)列非春,保存另一個(gè)表的主鍵。即外鍵關(guān)聯(lián)缓屠。

兩個(gè)表奇昙,有關(guān)聯(lián)的數(shù)據(jù),使用相同的主鍵敌完。即主鍵關(guān)聯(lián)储耐。

外鍵關(guān)聯(lián):

@Entity

public class Person {

? ? @Id @GeneratedValue? // 主鍵自動(dòng)生成

? ? private long id;


? ? @OneToOne @JoinColumn? // 負(fù)責(zé)維護(hù)外鍵

? ? private IdCard idcard;

}

@Entity

public class IdCard {

? ? @Id @GeneratedValue // 主鍵自動(dòng)生成

? ? private long id;


? ? @OneToOne(mappedBy="idcard")? // 甩手掌柜

? ? private Person person;

}

主鍵關(guān)聯(lián):

@Entity

public class Person {

? ? @Id? ? ? // 主鍵*不要*自動(dòng)生成!!

? ? private long id;


? ? @OneToOne // 負(fù)責(zé)維護(hù)外鍵,將外鍵映射到主鍵滨溉。即將另一張表的外鍵映射到本表的主鍵什湘。

? ? @MapsId @JoinColumn(name = "id")

? ? private IdCard idcard;

}

@Entity

public class IdCard {

? ? @Id @GeneratedValue // 主鍵自動(dòng)生成

? ? private long id;


? ? @OneToOne(mappedBy="idcard")? // 甩手掌柜

? ? private Person person;

}

Embed

這不屬于關(guān)聯(lián)關(guān)系,只是一種包含:

@Entity

class Person {

? ? @Embedded

? ? private Name name;

}

@Embeddable

class Name {

? ? String firstName;

? ? String lastName;

}

Inheritance

SINGLE_TABLE

將所有的東西塞進(jìn) 一張表 中晦攒,即所有的子類跟父類使用一張表闽撤, 在這張表中使用“區(qū)別列”(DiscriminatorColumn)來區(qū)分各個(gè)類。

這是默認(rèn)的繼承策略脯颜。

@Entity

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)

@DiscriminatorColumn(name = "xxx") // 可以定制分割列的名字

public class Animal {}

@Entity

@DiscriminatorValue("狗") // 可以定制

public class Dog extend Animal {}

它并不符合范式哟旗,但也有自己的優(yōu)點(diǎn):

使用了區(qū)別的列

只使用了一張表,所以查詢速度快

缺點(diǎn):子類的獨(dú)有列伐脖,不能添加唯一/非空約束

缺點(diǎn):太多冗余字段

JOINED

是一種完全“符合范式”的設(shè)計(jì):

將所有共有的屬性提取到父表中

僅將子類特有的屬性保存到子表中

父表跟子表通過外鍵的方式建立關(guān)系

如果查詢子表的詳細(xì)數(shù)據(jù)热幔,通過關(guān)聯(lián)查詢關(guān)聯(lián)相關(guān)表即可

@Entity

@Inheritance(strategy = InheritanceType.JOINED)

public class Animal { }

@PrimaryKeyJoinColumn(name = "xxxxid")? // 可以定制關(guān)聯(lián)主鍵

public class Dog extend Animal { }

總結(jié):

優(yōu)點(diǎn):沒有任何冗余

缺點(diǎn):查詢的效率低,因?yàn)樾枰P(guān)聯(lián)各張表

TABLE_PER_CLASS(union)

每個(gè)類對(duì)應(yīng)一張表讼庇,大家互相隔離绎巨,各自為政!

@Entity

@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)

public class Animal { }

@Entity

public class Dog extend Animal { }

總結(jié):

優(yōu)點(diǎn):獨(dú)立,自由蠕啄,查詢快

如果只查詢子類场勤,那么不需要任何關(guān)聯(lián)戈锻;但如果查詢父類的話,需要使用 Union 關(guān)聯(lián)各表

缺點(diǎn):存在冗余字段

缺點(diǎn):如果要更新父類中的字段和媳,每個(gè)子表都需要去更新

MappedSuperclass

如果父類不是 Entity格遭,只是為子類提供公共屬性,那么留瞳,將其注解為 @MappedSuperclass 即可拒迅。

@MappedSuperclass

abstract public class Person {

? ? @Id private long id;

? ? @Column private String name;

}

@Entity

public class Girl extend Person {

? ? private String wechat;

}

@Entity

public class Boy extend Person {

? ? private String address;

}

**

級(jí)聯(lián)(Cascade)

比如說,一個(gè)部門有很多員工她倘,它們是多對(duì)一的關(guān)系璧微。如果我們要?jiǎng)h除1號(hào)部門:

Dept d = session.load(Dept.class, 1L);

session.delete(d);

我們會(huì)刪除失敗并得到一個(gè)異常,因?yàn)椴块T被員工數(shù)據(jù)引用硬梁,所以要?jiǎng)h除部門前前硫,需要先將引用到部門的所有員工刪掉。

如果我們不想手動(dòng)刪除部門內(nèi)部員工荧止,那么可以采取 *級(jí)聯(lián)操作*屹电,即對(duì) Dept 實(shí)體類中的 emps 屬性這樣設(shè)置:

@OneToMany(mappedBy="dept", cascade=CascadeType.REMOVE)

private List<Emp> emps = new ArrayList<>();

那么,再去執(zhí)行刪除操作的時(shí)候跃巡,部門危号、連帶它所有的員工,都會(huì)被刪除瓷炮。一步到位序目,快速絕倫挺份。

除了刪除操作任斋,級(jí)聯(lián)的類型還有:

CascadeType.PERSIST

CascadeType.MERGE

CascadeType.REFRESH

CascadeType.ALL (快捷方式荤傲,代指所有)

雖然 cascade 會(huì)讓我們的代碼更簡(jiǎn)介,使用更方便烘绽。但是淋昭,在工業(yè)環(huán)境中,*不建議使用 cascade 設(shè)置*安接。

刪除數(shù)據(jù)的方式

第一種方法:

// 優(yōu)點(diǎn):快速簡(jiǎn)潔

// 缺點(diǎn):不能關(guān)聯(lián)刪除

Product product = new Product();

product.setId(44L);

session.delete(product);

第二種方法:

// 優(yōu)點(diǎn)翔忽,能關(guān)聯(lián)刪除

// 缺點(diǎn),不直接

Product product = session.load(Product.class, id); // load, not get

session.delete(product);

第三種方法:

// 優(yōu)點(diǎn)盏檐,更靈活

// 缺點(diǎn)歇式,跟第一種方式一樣,不能刪除關(guān)聯(lián)

int result = session

? ? .createQuery("delete Product where id = :id")

? ? .setParameter("id", 44L)

? ? .executeUpdate();

查詢

get/load

根據(jù)主鍵進(jìn)行查詢胡野。這是最基本材失,最高效的一種查詢手段。

Query

//// 基本語(yǔ)法

String hql = "from xxx where yyy";

Query query = session.createQuery(hql);

query.setParameter("aaa", "bbb");

query.uniqueResult();

// 可以用鏈?zhǔn)秸Z(yǔ)法簡(jiǎn)化語(yǔ)句

session.createQuery("from xxx where yyy").setParameter("aaa", "bbb").uniqueResult();

//// select 語(yǔ)句 和 返回值

from Emp e where e.name = 'x';? ? ? ? ? // 默認(rèn)不需要寫 select, 那么會(huì)將結(jié)果封裝到 Emp 對(duì)象中

select e from Emp e where e.name = 'x';? // 上面的語(yǔ)句硫豆,跟此句是一致的

select name from Emp;? ? ? ? ? ? ? ? ? ? // 返回值:Object

select name, salary from Emp;? ? ? ? ? ? // 返回值:Object[]

select new list(name) from Emp;? ? ? ? ? // 返回值:ArrayList

select new map(name, salary) from Emp;? // 返回值:HashMap

select new map(name as name, salary as sal) from Emp; // 定制 key 值

select new Boy(name, salary) from Emp;? // 返回值:Boy 對(duì)象

//// 得到返回結(jié)果

session.createQuery("from Book", Book.class).uniqueResult();

session.createQuery("from Book", Book.class).list();

session.createQuery("from Book", Book.class).iterate().next();

// 過濾操作

session.createFilter(customer.getOrders(), "where price > 5000").list();

//// 聚合函數(shù)及其他運(yùn)算符的使用

// 返回值:Object[]

select max(salary), avg(salary), sum(salary) from Emp;

// group by

select max(salary), avg(salary), sum(salary) from Emp e group by e.department;

// 將結(jié)果封裝到 map 中

select new map(max(salary) as maxsal, avg(salary) as avgsal, sum(salary) as sumsal) from Emp e group by e.department;

// 運(yùn)算符和函數(shù)

select sum(salary + nvl(commission, 0)) as res from Emp;

//// join

// Query 不能使用 JOIN 抓取策略龙巨。Query 默認(rèn)使用 select 語(yǔ)句進(jìn)行關(guān)聯(lián)數(shù)據(jù)的加載笼呆。

// 如果想強(qiáng)制使用 join 語(yǔ)句,需要通過 hql 語(yǔ)句指定:

/// 1. 隱式設(shè)置

from Emp e where e.department.location = 'NEW YORK';

/// 2. 顯式調(diào)用

// fetch 決定最后結(jié)果的形式:

//? - 有 fetch: [Emp, Emp, ...]

//? - 無 fetch: [[Emp, Dept], [Emp, Dept]]

from Emp e join e.dept where e.name = 'xxx';

from Emp e left join e.dept where e.name = 'xxx';

from Emp e left join fetch e.dept where e.name = 'xxx';

//// 分頁(yè)旨别、總行數(shù)

long count = session.createQuery("select count(*) from Emp", Long.class).uniqueResult();

long count = session.createQuery("select count(*) from Emp", Long.class).iterate().next();

// oracle: rownum/row_number()

// sqlserver: top/row_number()

// mysql/sqlite: limit x offset y

// hibernate 通過下面語(yǔ)句屏蔽了底層細(xì)節(jié):

/// 從 80 行開始诗赌,取 5 行記錄

session.createQuery("from xxx").setFirstResult(80).setMaxResults(5).list();

//// delete & update

delete Emp where name = :oldName;

update Emp set name = :newName where id = :id;

// 級(jí)聯(lián)操作的設(shè)置,對(duì) Query 也是無效的秸弛,比如铭若,想刪除一個(gè)部門,需要先刪除員工胆屿,再刪部門:

delete Emp e where e.department.deptno = '#DN';

delete Dept where deptno = '#DN';

Criteria

Criteria奥喻,標(biāo)準(zhǔn)偶宫、規(guī)范非迹,它是 Criterion 的復(fù)數(shù)形式。

優(yōu)勢(shì):

面向?qū)ο?/p>

不用拼接sql纯趋,方便擴(kuò)展

統(tǒng)一性憎兽,跨數(shù)據(jù)庫(kù)

Criteria 接口: 表示特定類的一個(gè)查詢

Criterion 接口: 表示一個(gè)限定條件

示例:

// Session 是 Criteria 的工廠

// Criterion 的主要實(shí)現(xiàn)由 Example、Junction 和 SimpleExpression

// Criterion 一般通過 Restrictions 提供的工廠方法獲得

List<Emp> emps = session.createCriteria(Emp.class) // 創(chuàng)建

? ? .add( Restrictions.like("name", "K%") )? ? ? ? // 模糊

? ? .add( Restrictions.gt( "salary", 2000F ) )? ? // 大于

? ? .addOrder( Order.desc("salary") )? ? ? ? ? ? ? // 排序-1

? ? .addOrder( Order.desc("commission") )? ? ? ? ? // 排序-2

? ? .list();

// 約束可以按邏輯分組

List<Emp> emps = sess.createCriteria(Emp.class)

? ? .add( Restrictions.like("name", "K%") )

? ? .add( Restrictions.or( Restrictions.ge( "salary", 3000F ),

? ? ? ? ? ? ? ? ? ? ? ? ? Restrictions.isNotNull("commission") ) )

? ? .list();

// Property~Example 是添加約束的另兩種方法

List<Emp> emps = session.createCriteria(Emp.class)

? ? .add(Property.forName("name").eq("KING")) // Property

? ? .add(Example.create(king))? ? // 將 king 上的數(shù)據(jù)封裝成條件

? ? .list();

//// 關(guān)聯(lián)查詢

List<Emp> emps = session.createCriteria(Emp.class)

? ? .createCriteria("depts")? ? ? // vs. createAlias

? ? .add( Restrictions.eq("location", "NEW YORK") )

? ? .list();

//// Projections 提供投影查詢吵冒,并能分組聚合

// 投影條件

ProjectionList projectionList = Projections.projectionList()

? ? .add( Projections.property("dept") )

? ? .add( Projections.rowCount() )

? ? .add( Projections.max("salary") )

? ? .add( Projections.sum("salary", "sum" ) )

? ? .add( Projections.groupProperty("dept") );

// 查詢結(jié)果

List<Object[]> rs = session.createCriteria(Emp.class)

? ? .setProjection( projectionList )

? ? .addOrder( Order.asc("sum") )

? ? .list();

NativeSQL

基本語(yǔ)法纯命,默認(rèn)的返回的結(jié)果為 Object[]:

session.createNativeQuery("select ename, sal from emp").list();

session.createNativeQuery("select * from emp").list();

session.createNativeQuery("select * from emp e, dept d where e.deptno=d.deptno and d.loc=:loc")

? ? .setParameter("loc", "NEW YORK")

? ? .list();

可以通過 addScalar() 設(shè)置返回類型,并限定結(jié)果:

// 下面的查詢痹栖,得到的結(jié)果為 Object[], 包含兩個(gè)元素:0:id / 1:name

session.createNativeQuery("select * from emp where id=9999")

? ? .addScalar("empno", StandardBasicType.INTEGER)

? ? .addScalar("ename", StandardBasicType.STRING)

? ? .list();

也可以將結(jié)果封裝到 Entity(實(shí)體類) 中:

// simplest

session.createNativeQuery("select * from emp where sal > 2000")

? ? .addEntity(Emp.class).list();

// with alias

session.createNativeQuery("select e.* from emp e where sal > 2000")

? ? .addEntity("e", Emp.class)

? ? .list();

// multiple

session.createNativeQuery("select e.*, d.* from emp e join dept d using (deptno) where e.sal > 2000")

? ? .addEntity("e", Emp.class)

? ? .addEntity("d", Dept.class)

? ? .list();

將結(jié)果封裝到普通對(duì)象(非實(shí)體類)亿汞。注意,必須要使用 addScalar() 設(shè)置字段:

List<Person> persons = session.createSQLQuery("select * from emp")

? .addScalar("ename", StandardBasicType.INTEGER)

? .addScalar("salary", StandardBasicType.FLOAT)

? .setResultTransformer(Transforms.aliasToBean(Person.class))

? .list();

NameQuery

Query Strategy

一個(gè)實(shí)體類對(duì)象揪阿,里面有各個(gè)屬性疗我,這些屬性的值可能不是在同一張表中。

為了效率南捂,需要有一定加載策略吴裤,主要兩個(gè)方面:

when,屬性數(shù)據(jù)的加載時(shí)機(jī)溺健,是否在加載這個(gè)實(shí)體類的時(shí)候就立刻加載麦牺。

how,通過什么樣的語(yǔ)句加載鞭缭,select/join/其他剖膳。

比如,有一個(gè)實(shí)體類岭辣,叫 Girl:

@Entity

public class Girl {

? ? // 基本數(shù)據(jù)吱晒,保存在 girl 表中的數(shù)據(jù):

? ? //? select id, name from girl;

? ? // 這種數(shù)據(jù)的默認(rèn)加載機(jī)制是:

? ? //? 1. when: 立刻加載(EAGER)

? ? //? 2. how:? SELECT 語(yǔ)句

? ? @Id private long id;

? ? private String name;

? ? // 關(guān)聯(lián)數(shù)據(jù),單結(jié)果易结,保存在 boy 表中的:

? ? //? select * from boy where id='我的老父親枕荞,您的編號(hào)';

? ? // 這種方式的默認(rèn)加載機(jī)制是:

? ? //? 1. when: 立刻加載(EAGER)

? ? //? 2. how:? LEFT JOIN 連接

? ? @ManyToOne

? ? private Boy father;

? ? // 關(guān)聯(lián)數(shù)據(jù)柜候,結(jié)果集,保存在 bag 表中的

? ? //? select * from bag where big_owner='女孩的編號(hào)';

? ? // 這種屬性數(shù)據(jù)的默認(rèn)加載機(jī)制是:

? ? //? 1. when: 延遲加載(LAZY)

? ? //? 2. how:? SELECT 語(yǔ)句

? ? @OneToMany(mappedBy = "girl")

? ? private Set<Bag> bags = new HashSet();

}

如果我們調(diào)用 session.load(Girl.class, 1L), 會(huì)加載編號(hào)為 1 的女孩的數(shù)據(jù)躏精。

她的數(shù)據(jù)分為三種:

基本數(shù)據(jù)渣刷,包含在 girl 表中的,比如 =id/name=矗烛。

關(guān)聯(lián)數(shù)據(jù)/XtoOne辅柴,比如 father 屬性。

關(guān)聯(lián)數(shù)據(jù)/XtoMany瞭吃,比如 bags 屬性碌嘀。

可以通過 fetch 屬性/@Fetch 注解 定制加載策略,分別對(duì)應(yīng) when/how, 例:

@ManyToOne(fetch = FetchType.EAGER)// 定義加載的時(shí)機(jī)(when)

@Fetch(FetchMode.SELECT)? ? ? ? ? // 定義加載語(yǔ)句的樣式(how)

private Boy boyfriend;

如果 when 為 EAGER=歪架,默認(rèn)的 how 為 =FetchMode.JOIN

如果 when 為 EAGER=股冗,可以定制使 how 為 =FetchMode.SELECT/SUBSELECT

如果 how 為 JOIN, 那么 when 只能是 EAGER

如果設(shè)置了 hibernate.default_batch_fetch_size 或在實(shí)體類/集合上標(biāo)注了 @BatchSize, 會(huì)對(duì) LAZY 屬性加載采取批量?jī)?yōu)化。

@Fetch(FetchMode.SUBSELECT) 可以優(yōu)化 HQL 返回的列表的關(guān)聯(lián)數(shù)據(jù)查詢語(yǔ)句

*JOIN 策略對(duì) Query 查詢無效*和蚪,如需關(guān)聯(lián)查詢止状,在語(yǔ)句中顯式調(diào)用 join 語(yǔ)句!

N+1 問題

比如,如果:

打印出編號(hào)大于10的部門中的所有員工姓名攒霹。

那么怯疤,語(yǔ)句大致如此:

String hql = "from Dept where depto > :dn";

List<Dept> depts = session.createQuery(hql, Dept.class)

? ? .setParameter("dn", 10)

? ? .list();

for(Dept dept: depts) {

? ? for(Emp emp : dept.getEmps()) {

? ? ? ? System.out.printf("部門: %s, 姓名: %s\n",

? ? ? ? ? ? ? ? ? ? ? ? ? dept.getName(),

? ? ? ? ? ? ? ? ? ? ? ? ? emp.getName());

? ? }

}

因?yàn)?@OneToMany 默認(rèn)是 Lazy + SELECT 策略,所以催束,每個(gè)部門的員工只有使用的時(shí)候才去查詢集峦。

這就導(dǎo)致了上面的語(yǔ)句發(fā)送很多條 select 語(yǔ)句(N+1),嚴(yán)重影響效率抠刺。

*這就是 N+1 問題*塔淤。

解決方案有主要有下面幾種:

在 hql 語(yǔ)句中,使用 join 語(yǔ)句進(jìn)行關(guān)聯(lián)查詢矫付。

將 Dept#emps 的策略設(shè)置為 SUBSELECT 方式凯沪。

采取批量抓取的優(yōu)化方式(BatchSize),即在 Dept#emps 上面加上注解: =@BatchSize(size=n)=买优。

使用二級(jí)緩存妨马。

緩存(Cache)

緩存分為三種:事務(wù)范圍;應(yīng)用范圍杀赢;集群范圍烘跺。

二級(jí)緩存是應(yīng)用范圍的緩存機(jī)制。適合放入二級(jí)緩存的數(shù)據(jù):

很少修改脂崔,不會(huì)修改滤淳,或不允許被更改的數(shù)據(jù)(常量數(shù)據(jù))

不是很重要,允許偶爾出錯(cuò)的數(shù)據(jù)

而一些重要的數(shù)據(jù)或者修改頻繁的數(shù)據(jù)砌左,是不適合放到緩存里的脖咐。

配置使用二級(jí)緩存過程:

加入 JAR 包支持:

"org.hibernate:hibernate-ehcache:5.2.11.Final"


配置 /ehcache.xml [可選]

在 hibernate.cfg.xml 中啟用:

<prop key="hibernate.cache.use_second_level_cache">true</prop>

<prop key="hibernate.cache.use_query_cache">true</prop>

<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</prop>


配置要被緩存的類或集合

@Cachable

@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)


使用示例

session.createQuery("from Employee where id=7782").setCacheable(true).list();

session.createQuery("from Employee where id=7782").setCacheable(true).list();

session.createQuery("from Employee where id=7782").setCacheable(true).list();


鎖(Lock)

Hibernate 中铺敌,設(shè)置鎖定有下面三種方式:

session.load(Male.class, 1L, LockMode.WRITE)

session.lock(m, LockModeType.WRITE);

session.createQuery(hql).setLockMode(LockModeType.PESSIMISTIC_WRITE);

Hibernate 中鎖的類型,分為兩種:

悲觀鎖屁擅。使用數(shù)據(jù)庫(kù)底層的 for update 語(yǔ)句偿凭。數(shù)據(jù)會(huì)被鎖定,直到事務(wù)結(jié)束派歌。

樂觀鎖弯囊。使用實(shí)體類中的額外字段( @Version )。它不會(huì)真正在數(shù)據(jù)上加鎖胶果,而是用版本號(hào)區(qū)別記錄的不同匾嘱。

-- 它會(huì)在初次讀取數(shù)據(jù)時(shí)將 version 一起讀出,得到【版本號(hào)】早抠,比如 10

-- 等到提交數(shù)據(jù)的時(shí)候霎烙,發(fā)送下面語(yǔ)句:

update xxx set version = 10 + 1, ... where id = 2 and version = 10;

-- 如果數(shù)據(jù)被別人修改過,那么 version 已經(jīng)不是 10贝或,所以上面語(yǔ)句不會(huì)更新到任何數(shù)據(jù)吼过。

-- 同樣,hibernate 會(huì)拋出下面異常:

---- javax.persistence.OptimisticLockException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1

-- 從而防止了數(shù)據(jù)的修改沖突咪奖。


悲觀鎖更適用于修改頻率大,讀取不多的數(shù)據(jù)酱床。樂觀鎖適用于修改非常少羊赵,但讀取特別多的數(shù)據(jù)。悲觀鎖需要耗費(fèi)更多資源扇谣。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末昧捷,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子罐寨,更是在濱河造成了極大的恐慌靡挥,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸯绿,死亡現(xiàn)場(chǎng)離奇詭異跋破,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)瓶蝴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門毒返,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舷手,你說我怎么就攤上這事拧簸。” “怎么了男窟?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵盆赤,是天一觀的道長(zhǎng)贾富。 經(jīng)常有香客問我,道長(zhǎng)牺六,這世上最難降的妖魔是什么祷安? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮兔乞,結(jié)果婚禮上汇鞭,老公的妹妹穿的比我還像新娘。我一直安慰自己庸追,他們只是感情好霍骄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著淡溯,像睡著了一般读整。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咱娶,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天米间,我揣著相機(jī)與錄音,去河邊找鬼膘侮。 笑死屈糊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的琼了。 我是一名探鬼主播逻锐,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼雕薪!你這毒婦竟也來了昧诱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤所袁,失蹤者是張志新(化名)和其女友劉穎盏档,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體燥爷,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜈亩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了局劲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勺拣。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鱼填,靈堂內(nèi)的尸體忽然破棺而出药有,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布愤惰,位于F島的核電站苇经,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宦言。R本人自食惡果不足惜扇单,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望奠旺。 院中可真熱鬧蜘澜,春花似錦、人聲如沸响疚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)忿晕。三九已至装诡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間践盼,已是汗流浹背鸦采。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咕幻,地道東北人渔伯。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像谅河,于是被迫代替她去往敵國(guó)和親咱旱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容