日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
如何在PostgreSQL中存儲(chǔ)文本

?譯者 | 趙青窕

創(chuàng)新互聯(lián)建站是一家集網(wǎng)站建設(shè),賓川企業(yè)網(wǎng)站建設(shè),賓川品牌網(wǎng)站建設(shè),網(wǎng)站定制,賓川網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,賓川網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。

審校 | 孫淑娟

基于JPA實(shí)體定義的DDL生成器是許多開發(fā)人員的日常任務(wù)。在大多數(shù)情況下,我們使用Hibernate內(nèi)置生成器或JPA Buddy插件等工具,這些工具會(huì)簡化我們的工作,但也有例外,當(dāng)涉及到在數(shù)據(jù)庫中存儲(chǔ)大量數(shù)據(jù)時(shí),情況會(huì)變得有點(diǎn)復(fù)雜。

用例:存儲(chǔ)文檔

假設(shè)需要在PostgreSQL數(shù)據(jù)庫中存儲(chǔ)一個(gè)非空的文檔對(duì)象。JPA實(shí)體代碼如下所示:

Java:
@Entity
@Table(name = "document")
public class Document {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;

@Column(name = "date_created", nullable = false)
private LocalDateTime dateCreated;

@Column(name = "doc_txt")
private String docText;

//Getters and setters omitted for brevity
}  

問題是:如果我們需要存儲(chǔ)非常長的文檔文本怎么辦?在Java中,字符串?dāng)?shù)據(jù)類型可以保存大約2Gb的文本數(shù)據(jù),但是對(duì)于上面的模型,表的列(table column)大小將被限制為255個(gè)字符。那么,我們應(yīng)該改變什么呢?

方法1:使用LOB存儲(chǔ)

在關(guān)系數(shù)據(jù)庫中,存在一種用于存儲(chǔ)大量數(shù)據(jù)的特定數(shù)據(jù)類型:LOB(Large OBject)。一旦需要在數(shù)據(jù)庫中存儲(chǔ)大型文本,我們就可以開始定義LOB列。我們需要做的就是用@Lob注釋標(biāo)記docText屬性。

Java:
@Lob
@Column(name = "doc_txt")
private String docText;  

讓我們使用Hibernate為表生成DDL,以映射“Document”實(shí)體。SQL將是:

SQL:
create table document (
id int8 generated by default as identity,
date_created timestamp not null,
doc_txt oid,
primary key (id)
);

可以看到,doc_text列的數(shù)據(jù)類型是oid。它是什么?在文檔中有如下說明:

PostgreSQL提供了兩種不同的方式來存儲(chǔ)二進(jìn)制數(shù)據(jù)。二進(jìn)制數(shù)據(jù)可以使用數(shù)據(jù)類型BYTEA或使用Large Object特性存儲(chǔ)在表中,該特性以特殊格式將二進(jìn)制數(shù)據(jù)存儲(chǔ)在單獨(dú)的表中,并通過在表中存儲(chǔ)OID類型的值來引用該表。

在我們的例子中,第二種方式是有效的。這個(gè)單獨(dú)的表名為pg_largeobject,它存儲(chǔ)分成“頁”的數(shù)據(jù),通常每個(gè)頁2kb,如文檔中所述。

因此,Hibernate將大型文本作為二進(jìn)制數(shù)據(jù)存儲(chǔ)在單獨(dú)的表中。這是否意味著我們應(yīng)該在選擇數(shù)據(jù)時(shí)進(jìn)行額外的連接或在保存數(shù)據(jù)時(shí)進(jìn)行額外的插入操作?讓我們啟用SQL日志記錄,創(chuàng)建Document實(shí)體并使用Spring Data JPA將其保存到數(shù)據(jù)庫中。

Java:
Document doc = new Document();
doc.setDateCreated(LocalDateTime.of(2020, 1, 1, 10, 10));
doc.setDocText("This is the doc text");
Document saved = documentRepository.save(doc);

Hibernate會(huì)在控制臺(tái)中顯示一個(gè)普通的SQL插入:

SQL:
insert
into
document
(date_created, doc_txt)
values
(?, ?)

現(xiàn)在,我們可以通過在控制臺(tái)中執(zhí)行以下SQL語句來檢查數(shù)據(jù)是否被正確存儲(chǔ):

SQL:
select * from document

我們將看到上述命令的結(jié)果應(yīng)該與下表類似:

id

data_created

doc_txt

1

2020-01-01 10:10:00

76388

我們?cè)谶@個(gè)表中看不到文檔文本,只是對(duì)大對(duì)象存儲(chǔ)中的對(duì)象的一個(gè)引用。讓我們檢查pg_largeobject表:

SQL
select * from pg_largeobject where loid=76338

此時(shí),就可以看到文檔文本了。

loid

pageno

data

76388

0

This is the doc text

因此,Hibernate在幕后自動(dòng)將數(shù)據(jù)保存到兩個(gè)表中?,F(xiàn)在,我們可以嘗試使用Spring data JPA獲取文檔數(shù)據(jù):

Java
documentRepository.findById(1L).ifPresent(d -> System.out.println(d.getDocText()));   

我們可以在控制臺(tái)中看到以下SQL語句:

SQL
select
document0_.id as id1_0_0_,
document0_.date_created as date_cre2_0_0_,
document0_.doc_txt as doc_txt3_0_0_
from
document document0_
where
document0_.id=?

輸出應(yīng)符合下面的預(yù)期:

Plain Text
This is the doc text

Hibernate從pg_largeobject表中選擇數(shù)據(jù)。讓我們嘗試使用JPQL來執(zhí)行相同的查詢。為此,我們創(chuàng)建了一個(gè)附加的Spring Data JPA存儲(chǔ)庫方法并調(diào)用它:

Java
//repository
@Query("select d from Document d where d.id = ?1")
Optional findByIdIs(Long id);
//...
//invocation
documentRepository.findByIdIs(1L).ifPresent(d -> System.out.println(d.getDocText()));

這種方式將無法完成我們的預(yù)期工作:

Plain text
org.springframework.orm.jpa.JpaSystemException: Unable to access lob stream

Caused by: org.hibernate.HibernateException: Unable to access lob stream

Caused by: org.postgresql.util.PSQLException: Large Objects may not be used in auto-commit mode.

Hibernate執(zhí)行額外的數(shù)據(jù)庫讀取來獲取LOB數(shù)據(jù)。在auto-commit模式下,此讀取將在單獨(dú)的事務(wù)中執(zhí)行。PostgreSQL驅(qū)動(dòng)程序顯式地禁止它,如上面的錯(cuò)誤消息所示。為了解決這個(gè)問題,我們需要在一個(gè)事務(wù)中執(zhí)行這樣的查詢,或者禁用auto-commit模式。

“CrudRepository”中的Spring Data JPA方法,如findById()和findAll(),默認(rèn)情況下在一個(gè)事務(wù)中執(zhí)行。這就是為什么在第一個(gè)例子中一切正常的原因。當(dāng)我們使用Spring Data JPA查詢方法或JPQL查詢時(shí),我們必須像下面的示例那樣顯式地使用@Transactional。

Java
@Transactional
@Query("select d from Document d where d.id = ?1")
Optional findByIdIs(Long id);

@Transactional
List findByDateCreatedIsBefore(LocalDateTime dateCreated);

但是禁用auto-commit模式似乎比使用@Transactional注釋更可取。例如,為了在Spring Boot中實(shí)現(xiàn)默認(rèn)的連接池(HikariCP),我們需要設(shè)置spring.datasource.hikari.auto-commit屬性為false。

將文本存儲(chǔ)在單獨(dú)的表中可能會(huì)導(dǎo)致其他問題。讓我們添加一個(gè)存儲(chǔ)庫方法,使用LIKE子句為docText字段選擇文檔:

Java
@Transactional
List findByDocTextLike(String text);

該方法將生成以下查詢:

SQL
select
document0_.id as id1_0_,
document0_.date_created as date_cre2_0_,
document0_.doc_txt as doc_txt3_0_
from
document document0_
where
   document0_.doc_txt like ? escape ?

這個(gè)查詢將會(huì)失敗,錯(cuò)誤如下:

Plain Text
org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a];

Caused by: org.postgresql.util.PSQLException: ERROR: function pg_catalog.like_escape(bigint, character varying) does not exist
Hint: No function matches the given name and argument types. You might need to add explicit type casts.

Hibernate無法生成正確的SQL來處理LOB文本列的LIKE子句。對(duì)于這種情況,我們可以使用nativequery。在這個(gè)查詢中,我們必須從LOB存儲(chǔ)中獲取文本數(shù)據(jù),并將其轉(zhuǎn)換為字符串格式。之后,我們可以在LIKE子句中使用它(不要忘記' @Transactional '):

Java
@Query(value = "select * from document d " +
"where convert_from(lo_get(doc_txt), 'UTF-8') like '%'||?1||'%'", nativeQuery = true)
@Transactional
List findByDocTextLike(String text);

現(xiàn)在一切都工作正常。請(qǐng)記住,nativequery可能與其他RDBMS不兼容,并且不會(huì)在運(yùn)行時(shí)進(jìn)行驗(yàn)證。只有在絕對(duì)必要的時(shí)候才使用。

總結(jié):采用LOB來存儲(chǔ)

在PostgreSQL中將大型文本存儲(chǔ)為LOB對(duì)象的優(yōu)點(diǎn)和缺點(diǎn)是什么呢?

優(yōu)點(diǎn):

  • PostgreSQL為LOB對(duì)象使用了優(yōu)化的存儲(chǔ)方式;
  • 可以存儲(chǔ)多達(dá)4Gb的文本。

缺點(diǎn):

  • WHERE子句中的一些函數(shù)(LIKE、SUBSTRING等)在Hibernate中不能用于LOB文本列。對(duì)此,我們需要使用nativeQuery;
  • 要使用JPQL或Spring Data JPA存儲(chǔ)庫查詢方法獲取文本,我們必須對(duì)存儲(chǔ)庫方法使用@Transactional注釋或禁用auto-commit模式。

這里有一個(gè)問題:為什么不直接將文本數(shù)據(jù)存儲(chǔ)在表中?我們也來討論一下這種方式。

方式2:Column Re-Definition

PostgreSQL允許我們將長文本數(shù)據(jù)存儲(chǔ)在特定數(shù)據(jù)類型(TEXT)的列中。我們可以在注釋中指定列定義。

Java
@Column(name = "doc_txt", columnDefinition = "text")
private String docText;

這使我們能夠以“通常”的方式處理長文本。沒有事務(wù),native query和JPQL按照預(yù)期工作。與LOB類型相比,有一個(gè)限制是可存儲(chǔ)的最長字符串約為1GB。

當(dāng)長度小于4GB時(shí),可以使用LOB,但對(duì)于大多數(shù)用例來說已經(jīng)足夠長了。

這里唯一的問題是硬編碼的列定義(hardcoded column definition)。為了克服這個(gè)問題,我們可以在Hibernate 5中使用注釋@Type和轉(zhuǎn)換器org.hibernate.type.TextType。它比前面的列定義有一個(gè)優(yōu)點(diǎn):它不是特定于供應(yīng)商的(vendor-specific)。

Java
@Type(type = "org.hibernate.type.TextType")
@Column(name = "doc_txt")
private String docText;

在Hibernate 6中,org.hibernate.type.TextType類被刪除了。為了定義存儲(chǔ)長文本的列,我們可以這樣定義屬性:

Java
@Column(name = "doc_txt", length = Length.LOB_DEFAULT)
private String docText;

這將在數(shù)據(jù)庫中給出以下列定義:doc_txt varchar(1048576)。它不是TEXT類型,但仍然可以在表中存儲(chǔ)大約1Gb的文本。它是PostgreSQL中最大的字符串。

在Hibernate 6中,我們可以按照下面的方式定義docText屬性來生成一個(gè)包含TEXT數(shù)據(jù)類型的列:

Java
@JdbcTypeCode(SqlTypes.LONG32VARCHAR)
@Column(name = "doc_txt")
private String docText;

不幸的是,自2022年6月開始,Hibernate 6無法從表中獲取數(shù)據(jù)。它生成TEXT類型的正確表和列定義。將數(shù)據(jù)從doc_txt列提取到實(shí)體屬性將會(huì)失敗。錯(cuò)誤文本如下所示:

Plain Text
Unknown wrap conversion requested: [B to java.lang.String : `org.hibernate.type.descriptor.java.StringJavaType` (java.lang.String)

因此,在text /VARCHAR列中存儲(chǔ)長文本帶來的問題較少。事務(wù)、LIKE條件等沒有問題。唯一的缺點(diǎn)就是存儲(chǔ)大小最多是1Gb。還有其他注意事項(xiàng)嗎?

如果我們?cè)跀?shù)據(jù)庫中使用TEXT列類型和@Lob注釋,可能會(huì)出現(xiàn)問題。讓我們看看它是如何工作的。首先,讓我們創(chuàng)建一個(gè)表文檔,并向其中插入一些數(shù)據(jù):

SQL
create table document (
id int8 generated by default as identity,
date_created timestamp not null,
doc_txt text,
primary key (id)
);

insert into document (id, date_created, doc_txt) values (1, '2021-10-10', 'This is the document text number 1');

我們將使用帶有@Lob列的文檔實(shí)體定義:

Java
@Entity
@Table(name = "document")
public class Document {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;

@Column(name = "date_created", nullable = false)
private LocalDateTime dateCreated;

@Lob
@Column(name = "doc_txt")
private String docText;

//Getters and setters omitted for brevity
}

文檔獲取的代碼將是相同的:

Java
documentRepository.findById(1L).ifPresent(d -> System.out.println(d.getDocText()));

如果我們嘗試執(zhí)行這個(gè)repository方法,將會(huì)看到下面的結(jié)果:

Plain Text
java.lang.IllegalStateException: Failed to execute Application

Caused by: org.hibernate.exception.DataException: could not execute query

Caused by: org.postgresql.util.PSQLException: Bad value for type long: This is the document text number 1

我們可以看到,Hibernate處理@Lob屬性值作為對(duì)LOB對(duì)象數(shù)據(jù)的引用。數(shù)據(jù)庫表中的TEXT列類型不影響此行為。

那保存數(shù)據(jù)呢?讓我們先清空表,嘗試保存帶有@Lob字段的文檔實(shí)體,并使用Spring Data JPA獲取它。下面是對(duì)應(yīng)的代碼:

Java
//Saving
Document doc = new Document();
doc.setDateCreated(LocalDateTime.now());
doc.setDocText("This is another text document");
documentRepository.save(doc);
...
//Fetching
documentRepository.findAll().forEach(d -> System.out.println(d.getDocText));
...
//Result
This is another text document

因此,看起來帶有@Lob屬性的實(shí)體似乎可以處理TEXT列。在數(shù)據(jù)庫表中,我們會(huì)看到熟悉的畫面:

id

data_created

doc_txt

1

2022-06-16 15:28:26.751041

76388

loid

pageno

data

76388

0

This is another text document

如果我們使用SQL將文檔數(shù)據(jù)插入到表中,然后選擇數(shù)據(jù),我們將得到以下結(jié)果:

SQL
insert into document (date_created, doc_txt) values ('2021-10-10', 'This is the document text');

select * from document;

id

data_created

doc_txt

1

2022-06-16 15:28:26.751041

76388

2

2021-10-10 00:00:00

This is the document text

現(xiàn)在我們不能使用Spring data JPA從數(shù)據(jù)庫中選擇數(shù)據(jù)。當(dāng)選擇第二行時(shí),應(yīng)用程序?qū)⒁蝾愋娃D(zhuǎn)換錯(cuò)誤而崩潰。

讓我們將@Type注釋添加到屬性中…

Java
@Lob
@Type(type = "org.hibernate.type.TextType")
@Column(name = "doc_txt")
private String docText;

并嘗試將文檔的文本數(shù)據(jù)打印到應(yīng)用程序控制臺(tái)。

Java
documentRepository.findAll().forEach(d -> System.out.println(d.getDocText)); 

我們將會(huì)看到下面的輸出信息:

Hibernate: select document0_.id as id1_0_, document0_.date_created as date_cre2_0_, document0_.text as text3_0_ from document document0_   

76388
This is the document text

使用@Type注釋,我們可以選擇數(shù)據(jù),但是OID引用被轉(zhuǎn)換為文本,因此我們“丟失”了存儲(chǔ)在LOB存儲(chǔ)中的文本。

總結(jié):在表中存儲(chǔ)長文本

那么,在數(shù)據(jù)庫中將長文本存儲(chǔ)為文本列的優(yōu)點(diǎn)和缺點(diǎn)是什么呢?

優(yōu)點(diǎn):

  • 查詢將會(huì)按照預(yù)期工作;不需要單獨(dú)的事務(wù)或nativequery。

缺點(diǎn):

  • 存儲(chǔ)大小限制為1Gb
  • 混合使用@Lob屬性定義和TEXT列數(shù)據(jù)類型可能會(huì)導(dǎo)致意外結(jié)果。

總結(jié):如何在PostgreSQL中存儲(chǔ)長文本

1.在大多數(shù)情況下,將長文本數(shù)據(jù)與其他實(shí)體數(shù)據(jù)一起存儲(chǔ)在同一個(gè)表中應(yīng)該沒問題。它將允許您使用Hibernate和SQL來操作數(shù)據(jù)。

  • 在Hibernate 5中,使用@Type(type = "org.hibernate.type.TextType")注釋JPA實(shí)體屬性。
  • 如果你使用Hibernate 6,使用@Column(name =…, length = length . lob_default)注釋用于列定義。
  • 注意,使用這種方法時(shí),存儲(chǔ)的文本不能超過1Gb。

2.如果您計(jì)劃存儲(chǔ)大量的字符數(shù)據(jù)(超過1Gb),那么對(duì)JPA實(shí)體屬性使用@Lob注釋。Hibernate

將使用PostgreSQL來對(duì)大數(shù)據(jù)存儲(chǔ)進(jìn)行優(yōu)化。在使用lob時(shí),有幾件事需要考慮。

  • 我們必須在一個(gè)事務(wù)中執(zhí)行JPQL查詢和Spring Data JPA查詢方法,或者顯式禁用auto-commit模式。
  • 要在WHERE條件中使用LOB列,我們可能需要使用nativequery。

3.Hibernate文檔中有一個(gè)很好的建議:請(qǐng)不要僅僅因?yàn)槟阆胍粋€(gè)TEXT列就使用JPA的@Lob注釋。@Lob注釋的目的不是控制DDL的生成!因此,不要將@Lob實(shí)體屬性定義與TEXT列數(shù)據(jù)類型一起使用。

希望這些簡單的實(shí)用方法可以幫助你使用Hibernate在PostgreSQL中存儲(chǔ)文本數(shù)據(jù)時(shí),避免一些不必要的問題。

譯者介紹

趙青窕,社區(qū)編輯,從事驅(qū)動(dòng)開發(fā)工作。

原文標(biāo)題:??How to Store Text in PostgreSQL: Tips, Tricks, and Traps???,作者:Andrey Belyaev?


當(dāng)前名稱:如何在PostgreSQL中存儲(chǔ)文本
鏈接地址:http://m.5511xx.com/article/cogdspd.html