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ù)渴丸。
對(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)更多資源扇谣。