Hibernate對象管理入門?看這一篇就夠了
Hibernate的實現(xiàn)原理
Hibernate是一個用于對象關(guān)系映射(ORM)的Java框架,它允許開發(fā)者將Java對象映射到關(guān)系數(shù)據(jù)庫表,并提供數(shù)據(jù)持久化功能。以下是Hibernate的實現(xiàn)原理及其如何維護幾個關(guān)鍵狀態(tài)的詳細解釋:
實現(xiàn)原理
ORM(對象關(guān)系映射)
- Hibernate通過將Java類映射到數(shù)據(jù)庫表,將Java類的屬性映射到表的列。
- 使用Hibernate映射文件(XML或注解)定義這種映射關(guān)系。
Session管理
- Hibernate的核心是Session接口,它是與數(shù)據(jù)庫交互的接口。
- Session對象負責保存、更新、刪除和加載對象。
查詢語言
- Hibernate提供HQL(Hibernate Query Language)和Criteria API,允許開發(fā)者以面向?qū)ο蟮姆绞讲樵償?shù)據(jù)庫。
緩存機制
- Hibernate支持一級緩存(Session級別緩存)和二級緩存(SessionFactory級別緩存),以提高數(shù)據(jù)訪問性能。
狀態(tài)維護
Hibernate通過Session對象維護實體對象的生命周期狀態(tài)。主要有以下幾個狀態(tài):
Transient(瞬態(tài))
- 實體對象被創(chuàng)建,但沒有與數(shù)據(jù)庫的任何Session關(guān)聯(lián)。
- 對象沒有被持久化,沒有與數(shù)據(jù)庫記錄對應。
- 例如:Person person = new Person();
Persistent(持久化)
- 實體對象被Session管理,與數(shù)據(jù)庫記錄對應。
- 對象的任何變更都會同步到數(shù)據(jù)庫中。
- 例如:session.save(person);
Detached(游離)
- 實體對象曾經(jīng)是持久化狀態(tài),但當前Session已經(jīng)關(guān)閉。
- 對象仍然存在,但不再與數(shù)據(jù)庫同步。
- 例如:session.close();之后的對象。
Removed(刪除)
- 實體對象在Session中標記為刪除。
- 在事務提交時,對應的數(shù)據(jù)庫記錄會被刪除。
- 例如:session.delete(person);
Hibernate狀態(tài)維護的具體實現(xiàn)
Transient狀態(tài)
- 當對象被new出來但沒有被Session保存時,它是Transient狀態(tài)。
- 這些對象沒有數(shù)據(jù)庫標識符,沒有與數(shù)據(jù)庫記錄關(guān)聯(lián)。
Persistent狀態(tài)
- 當對象通過session.save()、session.persist()、session.update()或查詢方法從Session獲取時,它變?yōu)镻ersistent狀態(tài)。
- Hibernate為這些對象分配數(shù)據(jù)庫標識符,并將其狀態(tài)與數(shù)據(jù)庫同步。
- 事務提交時,Hibernate會將所有持久化對象的變更同步到數(shù)據(jù)庫中。
Detached狀態(tài)
- 當Session關(guān)閉后,持久化對象變?yōu)镈etached狀態(tài)。
- 可以通過session.evict(object)將對象從Session中移除,使其變?yōu)镈etached狀態(tài)。
- Detached對象可以通過session.update(object)、session.merge(object)重新關(guān)聯(lián)到Session,變回Persistent狀態(tài)。
Removed狀態(tài)
- 當對象通過session.delete(object)標記為刪除時,它變?yōu)镽emoved狀態(tài)。
- 事務提交時,Hibernate會刪除這些對象對應的數(shù)據(jù)庫記錄。
事務管理和緩存機制
事務管理
- Hibernate通常與JDBC或JTA事務管理器一起使用。
- 事務管理確保了一組數(shù)據(jù)庫操作要么全部成功,要么全部回滾。
一級緩存
- Hibernate的Session級別緩存,即一級緩存。
- 每個Session對象都有一個一級緩存,緩存當前事務中的所有持久化對象。
- 當Session加載對象時,它首先在一級緩存中查找,如果找不到才從數(shù)據(jù)庫加載。
二級緩存
- SessionFactory級別的緩存,跨Session共享。
- 用于緩存被頻繁讀取但不經(jīng)常更改的數(shù)據(jù)。
- Hibernate支持多種二級緩存實現(xiàn),如EHCache、OSCache、Infinispan等。
示例代碼
// Transient狀態(tài)
Person person = new Person();
person.setName("John");
// Persistent狀態(tài)
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
session.save(person); // person對象現(xiàn)在是Persistent狀態(tài)
tx.commit();
session.close(); // person對象現(xiàn)在是Detached狀態(tài)
// Removed狀態(tài)
Session newSession = sessionFactory.openSession();
Transaction newTx = newSession.beginTransaction();
newSession.delete(person); // person對象現(xiàn)在是Removed狀態(tài)
newTx.commit();
newSession.close();
通過這種機制,Hibernate能夠高效地管理對象與數(shù)據(jù)庫記錄之間的映射,并自動處理數(shù)據(jù)的持久化和同步工作。
Hibernate的示例代碼
例子構(gòu)建
我們有一個配置管理的功能,現(xiàn)在定義了一個系統(tǒng)配置的表:biz_conf
建表語句
-- auto-generated definition
drop sequence if exists seq_seed;
create sequence seq_seed
maxvalue 65535
cycle;
alter sequence seq_seed owner to postgres;
drop function if exists snowflake_id;
create function snowflake_id(seed integer) returns bigint
language plpgsql
as
$$
DECLARE
start_epoch INT8 := 1546300800000;
seq_id INT8 := 0;
now_millis INT8 := 0;
unique_id INT8 := 0;
region_val int := seed % 1024;
BEGIN
SELECT nextval('seq_seed') % 4095 INTO seq_id;
SELECT floor(extract(EPOCH FROM clock_timestamp()) * 1000) INTO now_millis;
unique_id := (now_millis - start_epoch) << 22;
unique_id := unique_id | (region_val << 10);
unique_id := unique_id | seq_id;
RETURN unique_id;
END;
$$;
alter function snowflake_id(integer) owner to postgres;
drop function if exists random_id;
create function random_id() returns bigint
language plpgsql
as
$$
BEGIN
RETURN snowflake_id(0);
END;
$$;
alter function random_id() owner to postgres;
drop table if exists biz_conf;
create table biz_conf
(
id bigint default random_id() not null
constraint biz_conf_pkey
primary key,
key_id varchar(255) not null,
val varchar(255) default ''::character varying not null,
created_time timestamp default now() not null
);
-- 添加新增的幾個配置 sftp的
INSERT INTO biz_conf (key_id, val) VALUES
('sftp服務器地址', ''),
('sftp服務器端口', '22'),
('sftp服務器賬號', ''),
('sftp服務器密碼', ''),
('sftp上傳路徑', './'),
('sm2公鑰',''),
('sm2私鑰','');
關(guān)系實體映射
我們在目錄entity下面存放關(guān)系實體映射
import jakarta.persistence.*;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import java.sql.Timestamp;
@Entity
@Table(name = "biz_conf")
@DynamicInsert
@DynamicUpdate
public class BizConf {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, unique = true, columnDefinition = "INT8")
public Long id;
@Column(name = "key_id", length = 256)
public String key_id;
@Column(name = "val",length = 256)
public String val;
@Column(name = "created_time", updatable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
public Timestamp created_time;
}
經(jīng)過我們的實踐中,在關(guān)系實體映射中,可以將字段直接置為public,同時將字段名直接與數(shù)據(jù)庫字段名保持一致。便于后續(xù)映射。(這里可以采用private+駝峰的模式,再使用Lombok,但是代碼直觀性不夠,如果沒有接觸過lombok,將幾乎看不出來原理,同時我們對各個反序列化框架對lombok的支持度存有一定的擔心,故這里我們是建議使用public + 原始字段名)。
構(gòu)建領(lǐng)域?qū)ο髮嶓w
import com.cqcdi.siem.log_audit_admin.entity.biz.BizConf;
import com.cqcdi.siem.log_audit_admin.maindao.BaseEntityDomain;
/**
* 類描述: BizConfModel
*
**/
public class BizConfModel extends BaseEntityDomain<BizConf> {
public BizConfModel() {}
public BizConfModel(Long id){
super(id);
}
@Override
protected Class<BizConf> getEntityClass() {
return BizConf.class;
}
@Override
protected String getEntityClassName() {
return "BizConf";
}
}
領(lǐng)域?qū)ο髮嶓w構(gòu)建完畢之后,我們就可以直接使用。這里的領(lǐng)域?qū)嶓w與我們的dao是有區(qū)別的,dao層一般是一個spring bean,也就是說基本上是全局單例,不會有對象狀態(tài)。但是我們可以發(fā)現(xiàn),領(lǐng)域?qū)嶓w實際上是沒有被標記未一個bean的,所以領(lǐng)域?qū)嶓w實際上是可以管理對象的狀態(tài)的。
正因為如此,hibernate和領(lǐng)域?qū)ο蟮母拍罱Y(jié)合,我們可以把對象的各種狀態(tài)管理起來。例如,我們可以先查出一個對象,修改了一些值之后,直接調(diào)用刷新機制。(這個與瞬時態(tài)的寫法其實是類似的,但是理念不太一樣)
例子
使用領(lǐng)域模型查詢和更新
/**
* 使用領(lǐng)域模型
*/
@Test
public void demo1() {
BizConfModel bizConfModel = new BizConfModel();
List<BizConf> bizConfs = bizConfModel.getAll();
bizConfs.stream().forEach(bizConf -> {
System.out.println(bizConf.key_id + " " + bizConf.val);
bizConf.val = "add_"+ DatetimeUtil.getViewStrOfDatetime(LocalDateTime.now()) + "_"+ bizConf.val; // 對象處于被管理的狀態(tài),如果修改了對象,直接調(diào)用flush即可入庫
bizConfModel.flush();
});
}
使用瞬時態(tài)查詢和更新(更新不會生效)
/**
* 使用工具類:所有操作都是瞬時態(tài)
*/
@Test
public void demo2(){
List<BizConf> bizConfs = BaseEntityUtils.getAll(BizConf.class);
bizConfs.stream().forEach(bizConf -> {
System.out.println(bizConf.key_id + " " + bizConf.val);
bizConf.val = "add_"+ DatetimeUtil.getViewStrOfDatetime(LocalDateTime.now()) + "_" + bizConf.val; // 對象處于瞬時態(tài)態(tài),如果修改了對象,直接調(diào)用flush,沒有入庫
BaseEntityUtils.flush(); // 不生效
});
}
使用領(lǐng)域模型、瞬時工具類、原生sql和jpql進行分頁查詢
/**
* 分頁查詢
*/
@Test
public void demo3(){
// 使用領(lǐng)域模型進行分頁查詢
BizConfModel domain = new BizConfModel();
domain.setDomainEntity(new BizConf());
domain.getDomainEntity().key_id = "sm2私鑰";
Page<BizConf> ans = domain.defaultPage(1,10);
ans.getContent().stream().forEach(bizConf1 -> {
System.out.println(bizConf1.key_id + " " + bizConf1.val);
});
System.out.println(" ------------------ ");
// 使用工具類進行分頁查詢
BizConf bizConf = new BizConf();
bizConf.key_id = "sm2私鑰";
Page<BizConf> bizConfs2 = BaseEntityUtils.defaultPage(bizConf,1,10,BizConf.class);
bizConfs2.getContent().stream().forEach(bizConf1 -> {
System.out.println(bizConf1.key_id + " " + bizConf1.val);
});
System.out.println(" ------------------ ");
// 使用sql分頁查詢
// 使用原生sql查詢
List<BizConf> bizConfs1 = BaseEntityUtils.executeNativeQuery("select * from biz_conf offset 1 limit 10",BizConf.class);
bizConfs1.stream().forEach(bizConf1 -> {
System.out.println(bizConf1.key_id + " " + bizConf1.val);
});
// 使用Jpql查詢:把原生sql中的表名替換成對象
System.out.println(" ------------------ ");
List<BizConf> bizConfList = BaseEntityUtils.findByConditions("from BizConf",new HashMap<>(),1,10,BizConf.class);
bizConfList.stream().forEach(bizConf1 -> {
System.out.println(bizConf1.key_id + " " + bizConf1.val);
});
}
Hibernate中的級聯(lián)操作
Hibernate提供了哪些方法來實現(xiàn)對象關(guān)系映射(ORM)
@OneToMany
@OneToMany 注解用于描述一對多(One-to-Many)的關(guān)聯(lián)關(guān)系,即一個實體對象關(guān)聯(lián)多個相關(guān)實體對象。這種關(guān)系通常在數(shù)據(jù)庫中以外鍵來表示。以下是 @OneToMany 注解的基本使用方式
在一的一方(One)
- 在擁有多個相關(guān)實體的一方,使用 @OneToMany 注解。
- 使用 mappedBy 屬性指定多的一方中關(guān)聯(lián)的屬性名。
@Entity
public class ParentEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// other fields
@OneToMany(mappedBy = "parentEntity")
private List<ChildEntity> children;
// getters and setters
}
在多的一方(Many)
- 在擁有單個關(guān)聯(lián)實體的一方,使用 @ManyToOne 注解。
- 使用 @JoinColumn 注解指定關(guān)聯(lián)的數(shù)據(jù)庫表列。
@Entity
public class ChildEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// other fields
@ManyToOne
@JoinColumn(name = "parent_entity_id")
private ParentEntity parentEntity;
// getters and setters
}
在上述示例中,ParentEntity 擁有多個 ChildEntity,因此在 ParentEntity 類上使用了 @OneToMany 注解,并通過 mappedBy 屬性指定了關(guān)聯(lián)關(guān)系的屬性名,這里是 "parentEntity"。在 ChildEntity 類中,使用 @ManyToOne 注解指定了多的一方的類型,并通過 @JoinColumn 注解指定了關(guān)聯(lián)的數(shù)據(jù)庫表列。 請注意,使用 @OneToMany 注解時,通常需要考慮懶加載、級聯(lián)操作、集合類型等配置。例如,可以使用 fetch 屬性來指定加載策略,使用 cascade 屬性來指定級聯(lián)操作的行為。配置的詳細信息會根據(jù)具體的業(yè)務需求而變化。
@OneToOne
- 用于描述一對一關(guān)系。
- 在關(guān)聯(lián)的兩個實體類的其中一個實體類的屬性上使用 @OneToOne 注解,表示該屬性與另一個實體類建立一對一關(guān)系。
- 使用 mappedBy 屬性指定另一個實體類中關(guān)聯(lián)的屬性名。
```java
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// other fields
@OneToOne(mappedBy = "employee")
private Address address;
// getters and setters
}
@Entity
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// other fields
@OneToOne
@JoinColumn(name = "employee_id")
private Employee employee;
// getters and setters
}
```
@ManyToOne
- 用于描述多對一關(guān)系。
- 在關(guān)聯(lián)的兩個實體類的其中一個實體類的屬性上使用 @ManyToOne 注解,表示該屬性與另一個實體類建立多對一關(guān)系。
- 使用 @JoinColumn 注解指定關(guān)聯(lián)的數(shù)據(jù)庫表列,可以使用 name 屬性指定列名。
```java
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// other fields
@ManyToOne
@JoinColumn(name = "customer_id")
private Customer customer;
// getters and setters
}
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// other fields
@OneToMany(mappedBy = "customer")
private List<Order> orders;
// getters and setters
}
```
@ManyToMany
- 用于描述多對多關(guān)系。
- 在關(guān)聯(lián)的兩個實體類的其中一個實體類的屬性上使用 @ManyToMany 注解,表示該屬性與另一個實體類建立多對多關(guān)系。
- 使用 @JoinTable 注解指定中間表的信息,包括表名、關(guān)聯(lián)列、被關(guān)聯(lián)列等。
```java
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// other fields
@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private List<Course> courses;
// getters and setters
}
@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// other fields
@ManyToMany(mappedBy = "courses")
private List<Student> students;
// getters and setters
}
```
這些注解提供了豐富的選項,以滿足不同場景下的實體關(guān)系映射需求。在使用這些注解時,根據(jù)具體的業(yè)務需求和數(shù)據(jù)庫表結(jié)構(gòu),適當?shù)嘏渲藐P(guān)聯(lián)關(guān)系、關(guān)聯(lián)表等信息,以實現(xiàn)正確而高效的數(shù)據(jù)映射。
級聯(lián)操作的例子
在Hibernate中,級聯(lián)操作可以通過在實體類的映射文件或注解中設(shè)置相應的級聯(lián)關(guān)系來實現(xiàn)。以下是級聯(lián)操作的例子:
級聯(lián)查詢
假設(shè)有兩個實體類:ParentEntity 和 ChildEntity,它們之間存在一對多的關(guān)系??梢酝ㄟ^配置級聯(lián)關(guān)系來在查詢時同時獲取關(guān)聯(lián)的子實體。
@Entity
public class ParentEntity {
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<ChildEntity> children;
// other fields and methods
}
@Entity
public class ChildEntity {
@ManyToOne
@JoinColumn(name = "parent_id")
private ParentEntity parent;
// other fields and methods
}
在上述例子中,cascade = CascadeType.ALL 表示在父實體上的所有操作都將級聯(lián)到子實體上,包括查詢。
級聯(lián)插入
ParentEntity parent = new ParentEntity();
ChildEntity child1 = new ChildEntity();
ChildEntity child2 = new ChildEntity();
// 設(shè)置關(guān)聯(lián)關(guān)系
child1.setParent(parent);
child2.setParent(parent);
// 將子實體添加到父實體的集合中
parent.getChildren().add(child1);
parent.getChildren().add(child2);
// 保存父實體,級聯(lián)保存子實體
entityManager.persist(parent);
在上述例子中,當保存父實體時,由于設(shè)置了級聯(lián)保存 (cascade = CascadeType.ALL),子實體也會被保存。
級聯(lián)更新
ParentEntity parent = entityManager.find(ParentEntity.class, parentId);
parent.setName("Updated Parent");
// 子實體的相關(guān)屬性也被更新
parent.getChildren().forEach(child -> child.setDescription("Updated Child"));
// 保存父實體,級聯(lián)更新子實體
entityManager.merge(parent);
在上述例子中,由于設(shè)置了級聯(lián)更新 (cascade = CascadeType.ALL),當更新父實體時,子實體的相關(guān)屬性也會被更新。
級聯(lián)刪除
ParentEntity parent = entityManager.find(ParentEntity.class, parentId);
// 刪除父實體,級聯(lián)刪除子實體
entityManager.remove(parent);
在上述例子中,由于設(shè)置了級聯(lián)刪除 (cascade = CascadeType.ALL),當刪除父實體時,子實體也會被級聯(lián)刪除。
請注意,級聯(lián)操作應該謹慎使用,確保了解它們的影響,以避免意外的數(shù)據(jù)變更。
級聯(lián)操作中的cascade
cascade 是 Hibernate 中用于配置級聯(lián)操作的重要屬性之一。該屬性用于指定在對父實體進行特定操作時,是否要同時應用相同的操作到關(guān)聯(lián)的子實體上。cascade 屬性通常在關(guān)聯(lián)關(guān)系的注解或映射文件中進行配置。 以下是一些常用的 cascade 屬性值
CascadeType.PERSIST (級聯(lián)保存)
當對父實體調(diào)用 persist() 方法時,子實體也會被保存。
CascadeType.MERGE (級聯(lián)更新)
當對父實體調(diào)用 merge() 方法時,子實體也會被更新。
CascadeType.REMOVE (級聯(lián)刪除)
當對父實體調(diào)用 remove() 方法時,子實體也會被刪除。
CascadeType.REFRESH (級聯(lián)刷新)
當對父實體調(diào)用 refresh() 方法時,子實體也會被刷新。
CascadeType.DETACH (級聯(lián)分離)
當對父實體調(diào)用 detach() 方法時,子實體也會被分離。
CascadeType.ALL (全部)
包含所有的級聯(lián)操作,即包括上述的 PERSIST、MERGE、REMOVE、REFRESH、DETACH。 在實體類的映射文件或使用注解進行配置時,cascade 屬性可以直接指定一個或多個級聯(lián)操作,如下所示
@Entity
public class ParentEntity {
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<ChildEntity> children;
// other fields and methods
}
在上述例子中,cascade = CascadeType.ALL 表示對 ParentEntity 上的所有操作都會級聯(lián)到關(guān)聯(lián)的 ChildEntity 上。我們可以根據(jù)實際需求選擇適當?shù)募壜?lián)操作。然而,要注意過度使用級聯(lián)操作可能導致不必要的性能開銷和意外的數(shù)據(jù)變更。
級聯(lián)操作的好處
級聯(lián)更新(CascadeType.MERGE)和級聯(lián)插入(CascadeType.PERSIST)在Hibernate中確實帶來了一些好處,但同時也需要謹慎使用以避免潛在的問題。以下是它們的一些優(yōu)勢
級聯(lián)更新(CascadeType.MERGE)的好處
簡化操作
級聯(lián)更新允許我們在對父實體進行 merge() 操作時,同時更新關(guān)聯(lián)的子實體。這樣,我們不需要手動處理每個關(guān)聯(lián)實體的更新。
維護關(guān)聯(lián)關(guān)系
級聯(lián)更新確保了關(guān)聯(lián)實體與父實體的狀態(tài)同步,無需手動調(diào)用 merge() 方法來更新關(guān)聯(lián)實體。
減少代碼冗余
避免了手動維護關(guān)聯(lián)實體狀態(tài)的繁瑣操作,簡化了代碼邏輯。
級聯(lián)插入(CascadeType.PERSIST)的好處
原子性
當我們持久化一個新的父實體時,級聯(lián)插入可以確保相關(guān)的子實體也被保存。這確保了相關(guān)實體的一致性,不需要單獨保存每個關(guān)聯(lián)實體。
簡化操作
通過級聯(lián)插入,我們不需要為每個關(guān)聯(lián)實體顯式調(diào)用 persist() 方法。Hibernate會自動保存相關(guān)的關(guān)聯(lián)實體。
減少錯誤
避免了遺漏保存關(guān)聯(lián)實體的錯誤,提高了代碼的可維護性。 然而,盡管級聯(lián)更新和插入帶來了便利,但需要注意以下幾點
性能開銷
過度使用級聯(lián)操作可能導致性能開銷,尤其是在涉及大量關(guān)聯(lián)實體的情況下。謹慎選擇使用級聯(lián),以避免不必要的數(shù)據(jù)庫操作。
數(shù)據(jù)一致性
級聯(lián)操作可能導致數(shù)據(jù)一致性的問題。例如,級聯(lián)插入可能會導致關(guān)聯(lián)實體被保存,即使它們不是父實體的直接子元素。
潛在的循環(huán)
避免配置導致循環(huán)級聯(lián)操作,可能導致無限遞歸或棧溢出的問題。 總體而言,級聯(lián)操作是強大的工具,但在使用時需要仔細考慮,并根據(jù)具體的業(yè)務需求和性能考慮來選擇適當?shù)募壜?lián)策略。
使用級聯(lián)操作的策略
使用級聯(lián)操作時,需要考慮不同的策略、場景和注意事項,以確保數(shù)據(jù)的一致性、性能和可維護性。以下是一些常見的策略、使用場景和注意事項
策略
CascadeType.MERGE(級聯(lián)更新)
適用場景
當我們希望在更新父實體時同時更新關(guān)聯(lián)的子實體時。
注意事項
避免循環(huán)級聯(lián),確保不會形成無限遞歸的級聯(lián)更新。
CascadeType.PERSIST(級聯(lián)插入)
適用場景
當我們希望在插入父實體時同時插入關(guān)聯(lián)的子實體時。
注意事項
小心處理關(guān)聯(lián)實體之間的引用,以避免不必要的插入操作。
CascadeType.REMOVE(級聯(lián)刪除)
適用場景
當我們希望在刪除父實體時同時刪除關(guān)聯(lián)的子實體時。
注意事項
謹慎使用級聯(lián)刪除,確保了解刪除操作的影響,避免誤刪關(guān)聯(lián)實體。
CascadeType.ALL(全部)
適用場景
當我們希望對父實體的所有操作都級聯(lián)到關(guān)聯(lián)的子實體時。
注意事項
盡量避免使用過多的級聯(lián)操作,只選擇實際需要的操作,以提高性能和可控性。
使用場景
一對一關(guān)系
當兩個實體之間是一對一關(guān)系時,可以使用級聯(lián)來確保它們的狀態(tài)同步。
一對多關(guān)系
在一對多關(guān)系中,級聯(lián)操作可用于確保在更新或刪除父實體時,相關(guān)的子實體也會被同步更新或刪除。
父子關(guān)系
在父子關(guān)系中,級聯(lián)插入和更新可簡化操作,確保整個對象圖的一致性。
注意事項
性能開銷
過度使用級聯(lián)操作可能導致性能問題。在選擇級聯(lián)操作時,需要考慮數(shù)據(jù)庫的負擔和查詢效率。
循環(huán)級聯(lián)
避免配置導致循環(huán)級聯(lián)操作,可能導致無限遞歸或棧溢出的問題。
數(shù)據(jù)一致性
注意級聯(lián)操作可能導致的數(shù)據(jù)一致性問題,特別是在刪除操作中。
關(guān)聯(lián)實體引用
確保處理關(guān)聯(lián)實體之間的引用,以避免不必要的插入和更新操作。
深度級聯(lián)
謹慎使用深度級聯(lián),盡量避免將級聯(lián)操作應用到整個對象圖。
總體而言,級聯(lián)操作是一個強大的特性,但需要謹慎使用。根據(jù)具體的業(yè)務需求和性能要求,選擇適當?shù)募壜?lián)策略,并確保了解每個級聯(lián)操作的影響。測試和審查數(shù)據(jù)變更的結(jié)果也是使用級聯(lián)操作時的關(guān)鍵步驟。
避免循環(huán)級聯(lián)的方法
循環(huán)級聯(lián)
什么是循環(huán)級聯(lián)
循環(huán)級聯(lián)(Cascading Cycle)是指在對象關(guān)系映射(ORM)中,特別是在Hibernate等ORM框架中,通過級聯(lián)操作導致的循環(huán)引用或無限遞歸的情況。 在ORM中,級聯(lián)操作是指在對一個實體進行操作時,自動對其關(guān)聯(lián)的其他實體進行相應的操作,比如保存、更新或刪除。循環(huán)級聯(lián)則是指這種級聯(lián)操作形成了一個循環(huán)鏈,其中實體之間的級聯(lián)操作形成一個循環(huán),可能導致無限遞歸或棧溢出等問題。 例如,考慮兩個實體Parent和Child,它們之間是雙向關(guān)聯(lián)的
// Parent Entity
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> children;
// Child Entity
@ManyToOne
private Parent parent;
在上述例子中,當我們嘗試保存或更新一個Parent實體時,由于配置了CascadeType.ALL,將觸發(fā)對關(guān)聯(lián)的Child實體進行級聯(lián)操作。如果在Child實體中也配置了類似的級聯(lián)操作,就可能形成一個循環(huán)鏈。 循環(huán)級聯(lián)可能導致以下問題
無限遞歸
級聯(lián)操作不斷觸發(fā),導致無限遞歸,最終可能導致棧溢出或應用程序無法正常執(zhí)行。
性能問題
無限遞歸會導致性能下降,因為系統(tǒng)不斷地執(zhí)行相同的級聯(lián)操作。 為了避免循環(huán)級聯(lián),需要謹慎配置級聯(lián)操作,避免形成循環(huán)鏈,或者使用一些技術(shù)手段(如懶加載、手動維護關(guān)聯(lián)關(guān)系等)來規(guī)避潛在的問題。在使用ORM框架時,了解和審查關(guān)聯(lián)關(guān)系、級聯(lián)配置以及避免雙向關(guān)聯(lián)等是很重要的。
循環(huán)級聯(lián)的例子
以下是一個簡單的循環(huán)級聯(lián)的例子,使用Java中的Hibernate注解??紤]兩個實體類Person和Address,它們之間是雙向關(guān)聯(lián)的,并且都配置了級聯(lián)保存操作。
import javax.persistence.*;
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "address_id")
private Address address;
// Getter and Setter methods
}
@Entity
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String street;
@OneToMany(mappedBy = "address", cascade = CascadeType.ALL)
private List<Person> residents;
// Getter and Setter methods
}
在上述例子中,Person和Address之間存在雙向關(guān)聯(lián)。一個Person實體擁有對應的Address實體,而一個Address實體擁有對應的List<Person>。此外,配置了級聯(lián)操作,使得對Person實體的保存操作會級聯(lián)保存關(guān)聯(lián)的Address實體,而對Address實體的保存操作也會級聯(lián)保存關(guān)聯(lián)的Person實體。 這種配置可能導致循環(huán)級聯(lián)的問題。例如,當我們嘗試保存一個Person實體時,Hibernate會自動保存關(guān)聯(lián)的Address實體,而在保存Address實體時,又會級聯(lián)保存關(guān)聯(lián)的Person實體,形成了一個循環(huán)鏈。 為了避免循環(huán)級聯(lián),可以通過適當配置級聯(lián)操作、使用mappedBy來指定關(guān)系的擁有方、懶加載等方式來規(guī)避潛在的問題。在實際應用中,需要根據(jù)具體場景來合理設(shè)計實體之間的關(guān)系以及級聯(lián)操作。
如何檢測實體級聯(lián)中是否有級聯(lián)循環(huán)
檢測實體級聯(lián)中是否存在級聯(lián)循環(huán)是一項重要的任務,因為級聯(lián)循環(huán)可能導致無限遞歸或棧溢出等問題。以下是一些方法,我們可以使用它們來檢測實體級聯(lián)中是否存在潛在的級聯(lián)循環(huán)
使用工具或插件:
有一些工具和插件可以幫助檢測級聯(lián)循環(huán)。例如,Hibernate Envers(Hibernate的審計插件)可以用于檢測潛在的級聯(lián)問題。在使用這些工具時,我們需要按照它們的文檔進行配置和使用。 也可以生成對象圖或者類圖進行分析。
手動分析映射關(guān)系:
仔細分析實體之間的映射關(guān)系,特別是雙向關(guān)聯(lián)的情況。確保在雙向關(guān)聯(lián)時,通過使用mappedBy等屬性正確指定關(guān)系的擁有方,以避免循環(huán)級聯(lián)。
測試場景:
創(chuàng)建一些簡單的測試場景,執(zhí)行級聯(lián)操作,觀察是否會發(fā)生無限遞歸或棧溢出。這種方法雖然相對簡單,但可能不適用于復雜的數(shù)據(jù)模型。
審查日志:
在Hibernate的日志中啟用詳細的日志記錄級別,觀察是否有關(guān)于級聯(lián)操作的異?;蚓?。有時,日志中會提供有關(guān)級聯(lián)操作的有用信息,以便我們能夠識別潛在的循環(huán)問題。
使用斷點調(diào)試:
使用調(diào)試工具在執(zhí)行級聯(lián)操作時設(shè)置斷點。觀察在實體關(guān)系中是否存在循環(huán),以及在何處可能觸發(fā)無限遞歸。
自定義檢測邏輯:
編寫自定義的檢測邏輯,檢查實體之間的關(guān)聯(lián)關(guān)系。這可能涉及遞歸地檢查實體的關(guān)聯(lián)關(guān)系,并確保沒有循環(huán)。這種方法可能相對復雜,但可以提供更精細的控制。 我們應該注意到的是,由于每個應用程序和數(shù)據(jù)模型都是獨特的,沒有一種通用的方法適用于所有情況。建議使用上述方法的組合,根據(jù)我們的具體場景選擇最適合的方式。在檢測到潛在的級聯(lián)循環(huán)問題時,及時調(diào)整映射配置,以避免可能的運行時異常。
解決方案
在Hibernate中,實體級聯(lián)操作可能導致循環(huán)級聯(lián)操作,為了避免這種問題,我們可以采取以下一些方案
使用CascadeType中的subset進行限制:
在@OneToMany或@ManyToMany注解中,通過cascade屬性指定級聯(lián)操作的類型,可以選擇性地配置級聯(lián),而不是使用CascadeType.ALL。使用CascadeType.PERSIST、CascadeType.MERGE等來限制級聯(lián)的范圍,以避免不必要的級聯(lián)操作。
@OneToMany(mappedBy = "parent", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<ChildEntity> children;
手動維護關(guān)聯(lián)關(guān)系:
不使用級聯(lián),手動管理關(guān)聯(lián)關(guān)系。在進行操作時,手動添加或刪除關(guān)聯(lián)關(guān)系,而不依賴于級聯(lián)操作。這樣可以更精確地控制實體之間的關(guān)系。
使用orphanRemoval屬性:
當orphanRemoval設(shè)置為true時,如果從父實體中移除了某個子實體,該子實體將被刪除。這可以避免在刪除父實體時引起子實體的級聯(lián)刪除,從而防止循環(huán)級聯(lián)。
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ChildEntity> children;
懶加載:
將關(guān)聯(lián)關(guān)系設(shè)置為懶加載,這樣只有在實際使用關(guān)聯(lián)實體時才會加載。通過延遲加載,可以減少不必要的級聯(lián)操作。
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<ChildEntity> children;
避免雙向關(guān)聯(lián):
雙向關(guān)聯(lián)時,容易出現(xiàn)循環(huán)級聯(lián)問題??紤]是否真的需要雙向關(guān)聯(lián),如果可以單向關(guān)聯(lián)就單向關(guān)聯(lián),從而避免循環(huán)級聯(lián)的發(fā)生。
// 避免雙向關(guān)聯(lián)
// ParentEntity
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<ChildEntity> children;
// ChildEntity
@ManyToOne
private ParentEntity parent;
使用@JsonManagedReference和@JsonBackReference (僅在使用Jackson等庫進行JSON序列化時有效):
如果我們使用JSON序列化工具,如Jackson,我們可以使用@JsonManagedReference和@JsonBackReference來解決循環(huán)引用問題。
// ParentEntity
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
@JsonManagedReference
private List<ChildEntity> children;
// ChildEntity
@ManyToOne
@JsonBackReference
private ParentEntity parent;
選擇適合我們場景的方案,根據(jù)需求進行靈活配置,以避免循環(huán)級聯(lián)操作帶來的問題。
Hibernate中關(guān)鍵類的實現(xiàn)原理
SessionFactory
SessionFactory 是 Hibernate 中的一個關(guān)鍵接口,它負責創(chuàng)建 Session 對象。SessionFactory 的實現(xiàn)原理涉及到 Hibernate 的啟動、配置、元數(shù)據(jù)加載、緩存管理等方面。 以下是 SessionFactory 的主要實現(xiàn)原理
Hibernate的啟動過程
當應用程序啟動時,Hibernate 會初始化并構(gòu)建 SessionFactory。這通常發(fā)生在應用程序啟動時的某個初始化階段。SessionFactory 的創(chuàng)建是一個開銷較大的操作,因為它需要加載元數(shù)據(jù)、配置信息以及構(gòu)建內(nèi)部數(shù)據(jù)結(jié)構(gòu)。
配置信息加載
Hibernate 通過讀取配置文件(如 hibernate.cfg.xml)或者通過編程方式配置,獲取數(shù)據(jù)庫連接信息、Hibernate屬性、映射文件的位置等配置信息。這些配置信息被用于創(chuàng)建數(shù)據(jù)庫連接池、指定方言、配置緩存等。
元數(shù)據(jù)加載
Hibernate需要加載應用程序中的實體類和映射文件,以建立 Java 對象與數(shù)據(jù)庫表之間的映射關(guān)系。元數(shù)據(jù)加載的過程包括解析注解或 XML 映射文件,構(gòu)建實體類的元數(shù)據(jù),然后將這些元數(shù)據(jù)注冊到 SessionFactory 中。
緩存管理
SessionFactory 負責管理 Hibernate 的緩存機制,包括一級緩存(Session 級別的緩存)和二級緩存(SessionFactory 級別的緩存)。這些緩存用于提高數(shù)據(jù)訪問性能,減少對數(shù)據(jù)庫的頻繁訪問。
SessionFactory 在 Hibernate 中負責管理緩存,主要包括一級緩存(Session 級別的緩存)和二級緩存(SessionFactory 級別的緩存)。緩存的使用旨在提高性能,減少對數(shù)據(jù)庫的頻繁訪問。
一級緩存(Session 級別的緩存)
一級緩存介紹
- 一級緩存是與 Session 實例相關(guān)聯(lián)的緩存,也稱為“Session 緩存”或“對象緩存”。
- 在 Session 的生命周期內(nèi),通過 get、load、save、update 等方法加載的實體對象都存儲在一級緩存中。
緩存的生命周期
- 一級緩存的生命周期與 Session 的生命周期相同。當 Session 關(guān)閉時,一級緩存中的對象也會失效。
緩存的管理
- 一級緩存是由 Session 實例負責管理的。在同一個 Session 中,如果多次加載相同的實體對象,第一次加載后的對象會被存儲在一級緩存中,后續(xù)加載相同的對象時,直接從緩存中獲取。
二級緩存(SessionFactory 級別的緩存)
二級緩存介紹
- 二級緩存是跨 Session 的緩存,也稱為“SessionFactory 緩存”。
- 二級緩存存儲的是從數(shù)據(jù)庫中加載的實體對象,以及查詢的結(jié)果集。
配置二級緩存
- 通過配置文件(如hibernate.cfg.xml)啟用二級緩存
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
支持的緩存提供者
- Hibernate 支持多種二級緩存提供者,如 Ehcache、Infinispan、Redis 等。在上述配置中,使用了 Ehcache 作為緩存提供者。
使用緩存
- 二級緩存中的對象可以被不同的 Session 共享。當某個 Session 加載一個實體對象后,該對象會被存儲在二級緩存中。當另一個 Session 加載相同的實體對象時,可以直接從二級緩存中獲取,而不必再次訪問數(shù)據(jù)庫。
緩存策略
- 通過配置可以設(shè)置緩存的策略,例如緩存的過期時間、是否讀寫緩存等。 需要注意的是,緩存的使用需要慎重考慮,因為緩存可能導致數(shù)據(jù)的不一致性。在某些情況下,需要手動清理緩存或者使用合適的緩存策略。此外,緩存的性能影響取決于應用程序的訪問模式和數(shù)據(jù)更新頻率。
線程安全性
SessionFactory 是線程安全的,多個線程可以共享一個 SessionFactory 實例。SessionFactory 通過多個線程共享可以提高性能,因為它們可以共享一些元數(shù)據(jù)和緩存,減少資源開銷。
SessionFactory 在 Hibernate 中是線程安全的。它的線程安全性是通過以下幾個機制來保證的
不可變性
在 Hibernate 中,SessionFactory 是一個不可變的對象。一旦創(chuàng)建,其狀態(tài)不可修改。這意味著在多線程環(huán)境中,多個線程可以同時訪問 SessionFactory 實例,而不會導致數(shù)據(jù)不一致或競態(tài)條件。
線程本地實例
Hibernate 通過將 SessionFactory 與線程進行綁定,確保每個線程都擁有獨立的 Session 實例。這是通過使用 ThreadLocal 來實現(xiàn)的。當應用程序中的一個線程需要創(chuàng)建或獲取 Session 時,SessionFactory 會為該線程創(chuàng)建一個獨立的 Session 實例,并將其與線程關(guān)聯(lián)起來。這樣,每個線程都有自己的 Session 實例,而不會互相干擾。
public class HibernateUtil {
private static final SessionFactory sessionFactory;
static {
sessionFactory = new Configuration().configure().buildSessionFactory();
}
private static final ThreadLocal<Session> threadLocalSession = new ThreadLocal<>();
public static Session getSession() {
Session session = threadLocalSession.get();
if (session == null) {
session = sessionFactory.openSession();
threadLocalSession.set(session);
}
return session;
}
}
上述示例中,HibernateUtil 類的 getSession 方法用于獲取與當前線程關(guān)聯(lián)的 Session 實例。
單例模式和懶加載
通常情況下,SessionFactory 是以單例模式創(chuàng)建的,確保在整個應用程序生命周期內(nèi)只有一個 SessionFactory 實例。這減少了資源開銷并提高了性能。此外,SessionFactory 的創(chuàng)建通常是在應用程序啟動時進行的,而不是在每次需要 Session 時創(chuàng)建。這種懶加載的策略也有助于提高性能。 通過這些機制,Hibernate 的 SessionFactory 在多線程環(huán)境中可以安全地被多個線程共享,而不會引起競爭條件或線程安全問題。注意,盡管 SessionFactory 是線程安全的,但 Session 對象本身并不是線程安全的,因此在多線程環(huán)境中使用 Session 時需要謹慎處理。
創(chuàng)建 Session
SessionFactory 的主要責任之一是創(chuàng)建 Session 實例。Session 是 Hibernate 中用于進行數(shù)據(jù)庫操作的主要接口。通過 SessionFactory 創(chuàng)建的 Session 具有與數(shù)據(jù)庫的物理連接,并負責管理事務、執(zhí)行查詢、保存、更新和刪除操作等。 總體而言,SessionFactory 的實現(xiàn)原理涉及到配置加載、元數(shù)據(jù)加載、緩存管理等多個方面,其設(shè)計旨在提高性能、簡化開發(fā)過程,并提供對數(shù)據(jù)庫操作的封裝。 Hibernate 的 SessionFactory 通常是一個長期存在的對象,負責整個應用程序的數(shù)據(jù)庫訪問。
SessionFactory提供了哪些方法
SessionFactory 接口是 Hibernate 中用于創(chuàng)建 Session 實例的工廠,它定義了一系列用于創(chuàng)建和配置 Session 的方法。以下是 SessionFactory 接口的一些主要方法
openSession()
Session openSession()
該方法用于創(chuàng)建一個新的 Session 實例。每個 Session 實例都表示一個與數(shù)據(jù)庫的物理連接。
openStatelessSession()
StatelessSession openStatelessSession()
該方法用于創(chuàng)建一個新的 StatelessSession 實例。與普通的 Session 不同,StatelessSession 不維護持久化上下文,對應用程序更加透明。
withOptions()
Session openSession(Interceptor interceptor)
該方法用于創(chuàng)建一個新的 Session 實例,并允許通過 Interceptor 對象來自定義對數(shù)據(jù)庫操作的攔截。Interceptor 接口允許我們在執(zhí)行 SQL 語句之前和之后執(zhí)行一些額外的邏輯。
getCurrentSession()
Session getCurrentSession() throws HibernateException
該方法用于獲取當前線程關(guān)聯(lián)的 Session 實例。通常與 Hibernate 的事務管理機制一起使用。需要在配置文件中啟用事務管理,并在事務管理的上下文中才能使用。
openTemporarySession()
Session openTemporarySession()
該方法用于創(chuàng)建一個新的臨時 Session 實例。臨時 Session 在處理臨時數(shù)據(jù)時可能會更加高效,但通常情況下,使用 openSession() 是更常見的選擇。
close()
void close()
該方法用于關(guān)閉 SessionFactory,釋放資源。在應用程序關(guān)閉時調(diào)用,或者在不再需要 SessionFactory 時調(diào)用。通常由應用程序的生命周期管理來調(diào)用。 這些方法代表了 SessionFactory 提供的主要功能,其中最常用的是 openSession() 用于創(chuàng)建 Session 實例。在使用 SessionFactory 時,還需要注意線程安全性和資源管理,以便正確地管理 Session 的生命周期。
EntityManager
在 Hibernate 中,EntityManager 對象是與持久性上下文(Persistence Context)交互的主要接口。Hibernate 提供了兩種主要的持久性上下文管理方式,一種是使用 JPA 規(guī)范定義的 EntityManager,另一種是使用 Hibernate 提供的 Session。EntityManager 主要用于以下作用
實體管理
EntityManager 負責實體的生命周期管理,包括實體的持久化、合并、移除以及查詢等操作。
事務管理
在 JPA 中,EntityManager 通常與事務一起使用。它可以參與容器管理的事務,或者通過 EntityTransaction 來手動管理事務。
查詢
EntityManager 提供了強大的查詢功能,支持 JPQL(Java Persistence Query Language)和 Criteria 查詢。通過這些查詢方式,可以方便地檢索實體對象。
緩存管理
EntityManager 負責緩存實體對象,以提高性能。緩存可以在一定程度上避免數(shù)據(jù)庫頻繁的讀寫操作。
在 JPA(Java Persistence API)中,EntityManager 接口提供了一些用于管理緩存的方法。這些方法主要用于實體對象的加載、存儲以及刷新等操作。以下是一些與緩存管理相關(guān)的 EntityManager 方法
find(Class<T> entityClass, Object primaryKey)
- 用于通過主鍵查找實體對象。
- 在調(diào)用該方法時,EntityManager 會首先檢查一級緩存(即持久性上下文)中是否已經(jīng)存在相應的實體對象,如果存在,則直接返回。
- 如果一級緩存中不存在該實體對象,則會查詢數(shù)據(jù)庫,并將查詢結(jié)果存入一級緩存,然后返回該實體對象。
MyEntity entity = entityManager.find(MyEntity.class, primaryKey);
getReference(Class<T> entityClass, Object primaryKey)
- 類似于 find 方法,但是返回的是實體的代理對象,不會立即加載實體的所有屬性。
- 也會首先檢查一級緩存,如果緩存中存在,則返回代理對象。
- 如果緩存中不存在,則查詢數(shù)據(jù)庫,返回代理對象。
MyEntity entity = entityManager.getReference(MyEntity.class, primaryKey);
persist(Object entity)
- 用于將新創(chuàng)建的實體對象添加到持久性上下文中。
- 這個操作會觸發(fā)實體的 INSERT 操作,并將實體對象存入一級緩存。
entityManager.persist(newEntity);
merge(T entity)
- 將一個脫管狀態(tài)(detached)的實體對象合并到持久性上下文中。
- 如果合并的實體對象在一級緩存中不存在,會先進行數(shù)據(jù)庫查詢,然后將查詢結(jié)果合并到一級緩存中。
MyEntity managedEntity = entityManager.merge(detachedEntity);
refresh(Object entity)
- 用于刷新實體對象的狀態(tài),即重新加載實體對象的屬性值。
- 會先檢查一級緩存,如果緩存中存在,則重新加載緩存中的值。
- 如果緩存中不存在,則查詢數(shù)據(jù)庫,然后刷新緩存。
entityManager.refresh(entity);
這些方法都涉及到一級緩存的管理,一級緩存是持久性上下文中的緩存,用于存儲已加載的實體對象。這些方法的使用可以有效地管理緩存,提高應用程序的性能。
對象狀態(tài)變更
EntityManager 監(jiān)聽實體對象的狀態(tài)變更,并在適當?shù)臅r候同步這些變更到數(shù)據(jù)庫。這包括臟檢查(dirty checking)和自動更新。 在 Hibernate 中,如果我們使用 JPA 規(guī)范,可以通過 EntityManagerFactory 創(chuàng)建 EntityManager 實例。如果我們使用 Hibernate 的本地 API,可以使用 SessionFactory 創(chuàng)建 Session。這兩者在實際使用中有一些細微的區(qū)別,但本質(zhì)上都是用來管理持久化操作和對象狀態(tài)的工具。
在 Hibernate 中,對象狀態(tài)變更的實現(xiàn)主要依賴于以下兩個機制
臟檢查(Dirty Checking)
Hibernate 使用臟檢查來跟蹤對象狀態(tài)的變化。當一個對象從數(shù)據(jù)庫加載到內(nèi)存時,Hibernate 會在內(nèi)存中保留一個快照(snapshot)以記錄對象在加載時的狀態(tài)。在事務提交之前,Hibernate 會再次檢查對象的當前狀態(tài)與快照的狀態(tài)是否有變化,如果有變化,則認為對象是“臟的”(dirty)。Hibernate 將檢測到的變化同步到數(shù)據(jù)庫中,以確保與數(shù)據(jù)庫的數(shù)據(jù)一致性。
Write-Behind機制
Hibernate 使用 Write-Behind 機制來最小化數(shù)據(jù)庫寫操作的次數(shù)。在事務提交時,Hibernate 不會立即將所有的變更寫入數(shù)據(jù)庫,而是將變更先記錄到事務的緩存中。在事務提交完成后,Hibernate 將所有的變更一次性地批量寫入數(shù)據(jù)庫,以減少與數(shù)據(jù)庫的通信次數(shù)。
Write-Behind 機制是一種數(shù)據(jù)庫寫入優(yōu)化技術(shù),用于減少對數(shù)據(jù)庫的頻繁寫操作,提高性能。這一機制的核心思想是延遲(或批量)寫入數(shù)據(jù)庫,而不是在每次數(shù)據(jù)變更時立即寫入。 在 Hibernate 中,Write-Behind 機制通常與緩存一起使用,以最小化數(shù)據(jù)庫寫操作的次數(shù)。當一個事務中的對象發(fā)生變更時,變更不會立即寫入數(shù)據(jù)庫,而是先被記錄到緩存中。這樣,在事務提交時,Hibernate 可以收集所有的變更,然后一次性地批量寫入數(shù)據(jù)庫。 Write-Behind 機制帶來了以下幾個優(yōu)勢
減少數(shù)據(jù)庫寫操作
通過延遲寫入,可以減少對數(shù)據(jù)庫的頻繁寫操作,提高性能。
批量寫入
將多個變更一次性批量寫入數(shù)據(jù)庫,減少了與數(shù)據(jù)庫的通信次數(shù),進而提高了吞吐量。
異步寫入
在一些情況下,寫入操作可以異步進行,不會阻塞當前事務的執(zhí)行。這有助于提高事務的響應性。
緩解鎖沖突
如果多個事務并發(fā)修改同一數(shù)據(jù),Write-Behind 機制可以減少對數(shù)據(jù)庫的鎖定需求,從而降低鎖沖突的可能性。 要啟用 Write-Behind 機制,通常需要配置相關(guān)的參數(shù)和策略。Hibernate 提供了一些配置選項,以便開發(fā)人員可以根據(jù)應用程序的需求進行調(diào)整。在使用 Hibernate 緩存和進行大規(guī)模數(shù)據(jù)操作時,考慮使用 Write-Behind 機制是一種常見的優(yōu)化手段。
這兩個機制的結(jié)合使得 Hibernate 能夠高效地管理對象狀態(tài)的變更。當我們修改了一個持久化對象的屬性時,Hibernate會通過臟檢查來檢測對象的狀態(tài)是否發(fā)生變化。如果變化,Hibernate 將更新緩存中的對象狀態(tài),并在事務提交時使用 Write-Behind 機制將變更同步到數(shù)據(jù)庫。 這種機制不僅減少了數(shù)據(jù)庫的訪問次數(shù),還提高了性能。但需要注意的是,對于大規(guī)模的數(shù)據(jù)操作,仍然需要慎重使用,以免引起性能問題。在一些情況下,我們可能需要手動調(diào)整 Hibernate 的配置,或者使用特定的注解和 API 來優(yōu)化數(shù)據(jù)操作的性能。
Session
在Hibernate中,Session 類是與數(shù)據(jù)庫交互的主要接口之一,它代表了一個與數(shù)據(jù)庫的一次會話。Session 提供了一系列的方法來執(zhí)行數(shù)據(jù)庫操作,包括保存、更新、刪除、查詢等。以下是Session 類的主要作用和一些常用的 API
主要作用
管理持久化對象
Session 負責管理持久化對象的生命周期,包括從數(shù)據(jù)庫中檢索對象、將對象持久化到數(shù)據(jù)庫、更新對象狀態(tài)以及刪除對象。
執(zhí)行數(shù)據(jù)庫操作
通過 Session,可以執(zhí)行各種數(shù)據(jù)庫操作,如保存、更新、刪除和查詢。這使得開發(fā)者能夠直接與數(shù)據(jù)庫交互而無需編寫原始的 SQL 語句。
實現(xiàn)緩存
Session 包含一個緩存,用于存儲已加載的對象,以提高性能。這樣,當對同一實體進行多次查詢時,Hibernate 可以避免重復的數(shù)據(jù)庫查詢,從而提升效率。
事務管理
Session 提供了事務管理的功能,可以通過 beginTransaction()、commit() 和 rollback() 方法來管理事務,確保數(shù)據(jù)庫操作的一致性。
常用的 API
save(Object obj)
保存一個對象到數(shù)據(jù)庫。如果對象已經(jīng)存在,會拋出異常。
update(Object obj)
更新一個對象到數(shù)據(jù)庫。如果對象不存在,會拋出異常。
saveOrUpdate(Object obj)
如果對象不存在,則保存它;如果對象已存在,則更新它。
delete(Object obj)
從數(shù)據(jù)庫中刪除一個對象。
get(Class<?> clazz, Serializable id)
通過主鍵獲取對象。
load(Class<?> clazz, Serializable id)
通過主鍵加載對象。與 get 類似,但 load 返回一個代理對象,只有在訪問其屬性時才會真正加載。
createQuery(String hql)
創(chuàng)建一個 HQL(Hibernate Query Language)查詢對象。
createSQLQuery(String sql)
創(chuàng)建一個原生 SQL 查詢對象。
beginTransaction()
開啟事務。
commit()
提交事務。
rollback()
回滾事務。
clear()
清空緩存中的所有對象。
flush()
強制將緩存中的數(shù)據(jù)同步到數(shù)據(jù)庫。
這些方法提供了對數(shù)據(jù)庫的常用操作,通過使用Session,開發(fā)者可以更方便地進行數(shù)據(jù)庫交互而無需處理底層的數(shù)據(jù)庫連接和SQL語句。需要注意的是,每個Session實例通常對應一個數(shù)據(jù)庫連接,因此在使用完畢后應該及時關(guān)閉,以釋放數(shù)據(jù)庫資源。
EntityManager和Session對比
EntityManager 和 Session 是分別來自 JPA(Java Persistence API)和 Hibernate 的兩個重要接口,用于進行持久化操作。以下是它們之間的一些對比:
來源和標準
- EntityManager: 是 Java Persistence API (JPA) 的一部分,它是在 Java EE 環(huán)境中進行對象持久化操作的標準接口。
- Session: 是 Hibernate 框架中的核心接口,用于在 Java SE 和 Java EE 環(huán)境中進行數(shù)據(jù)庫操作。
實現(xiàn)方
- EntityManager: 由 JPA 提供,可以由多個 JPA 提供商(如 Hibernate、EclipseLink 等)實現(xiàn)。
- Session: 是 Hibernate 框架的一部分,只能由 Hibernate 實現(xiàn)。
包含的功能
- EntityManager:
提供了 JPA 標準的 CRUD 操作。
具有查詢語言 JPQL(Java Persistence Query Language)。
支持事務和事務控制。
支持持久化上下文和一級緩存。
- Session:
提供了豐富的 Hibernate 特有的功能,如 Criteria 查詢、HQL(Hibernate Query Language)查詢等。
具有事務和事務控制。
提供了二級緩存(SessionFactory 級別的緩存)。
生命周期
- EntityManager: 生命周期通常與事務綁定,它可以在一個事務內(nèi)持續(xù)存在,也可以在每個事務中創(chuàng)建和關(guān)閉。
- Session: 通常由應用程序創(chuàng)建并管理,它可以在應用程序運行的整個生命周期內(nèi)存在,也可以在每個事務中創(chuàng)建和關(guān)閉。
查詢語言
- EntityManager: 使用 JPQL(Java Persistence Query Language),是與 JPA 標準相關(guān)的查詢語言。
- Session: 使用 HQL(Hibernate Query Language),是與 Hibernate 特有的查詢語言,也支持 SQL 原生查詢。
關(guān)聯(lián)
- EntityManager: 通常與 JPA 的注解一起使用,更關(guān)注 POJO(Plain Old Java Object)的映射。
- Session: 可以使用 Hibernate 的注解,也支持 XML 配置,提供了更多的靈活性。
適用范圍
- EntityManager: 更通用,適用于 JPA 提供商提供的多種實現(xiàn)。
- Session: 更專注于 Hibernate 框架,適用于需要直接使用 Hibernate 功能的場景。
總的來說,選擇使用 EntityManager 還是 Session 取決于我們的項目使用的持久化框架和標準。如果我們在使用 JPA,通常使用 EntityManager;如果我們在使用 Hibernate,可以使用 Session。在某些情況下,兩者可以混合使用,但要確保它們不會相互沖突。