Giriş
Bu yazı JPA sağlayıcısı olarak Hibernate kullanıldığında olan şeylerle ilgili. Özet olarak şöyle yapmak
lazım. Neden böyle gerektiği ise yazının devamında
@Entity
public class Exam {
@Lob
private String description; //Bu işe yaramıyor
}
@Entity
public class Exam {
@Column(columnDefinition="TEXT")
private String text; //Böyle yapmak lazım
}
Detaylar
Blob tipini Java kodu içinde saklamak için genelde üye alan olarak iki tip kullanılıyor.
1. byte []
2. String
Eğer üye alana byte[] ise PostgreSQL BLOB veriyi saklamak için iki farklı sütun sağlar. Bunlar
şöyle.
bytea - data stored in table
oid - table holds just identifier to data stored elsewhere
Eğer üye alana String ise PostgreSQL CLOB veriyi saklamak için iki farklı sütun sağlar. Bunlar şöyle.
OID Nedir?
PostgreSQL provides two distinct ways to store binary data. Binary data can be stored in a table using the data type BYTEA or by using the Large Object feature, which stores the binary data in a separate table in a special format and refers to that table by storing a value of type OID in your table.
OID için bir başka açıklama
şöyle.
A column of type Oid is just a reference to the binary contents which are actually stored in the system's pg_largeobject table. In terms of storage, an Oid a 4 byte integer. On the other hand, a column of type bytea is the actual contents.
Problem Nedir?
Burada problem Hibernate'in varsayılan davranış olarak hep
OID kullanması ve veriyi
PG_LARGEOBJECT tablosunda saklaması
Bu davranışı
istemiyoruz çünkü kendi tablomuzdaki satır silinse bile PG_LARGEOBJECT tablosundaki satır
silinmez. Açıklaması
şöyle.
LOBs are NOT deleted from the database when the rows of your application table (containing the OIDs) get deleted.
This also means that space will NOT be reclaimed by the VACUUM process.
In order to get rid of unused LOBs, you have run VACUUMLO on the databases. Vacuumlo will delete all of the unreferenced LOBs from a database.
Bu orphan satırları silmek için
VACUUMLO komutu kullanılır.
1. Üye Alan Tipi byte [] İse
Eğer değişken tipi byte [] ise JDBC olarak
java.sql.Types.BLOB seçilir. Hibernate şöyle
yapar. Yani varsayılan sütun tipi oid tipidir.
public PostgreSQLDialect() {
super();
...
registerColumnType(Types.VARBINARY, "bytea");
/**
* Notice it maps java.sql.Types.BLOB as oid
*/
registerColumnType(Types.BLOB, "oid");
}
bytea Sütun Tipi İle Çalışmak - İstenilen Davranış
bytea binary string anlamına gelir.
Örnek
Eğer sütun tipi
bytea ise sütunu yaratmak için şöyle
yaparız.
public class Template {
...
@Lob
@Column(columnDefinition="bytea")
private byte[] data;
}
Örnek
Eğer sütun tipi
bytea ise okuyup yazmak için şöyle
yaparız.
@Lob
@Column(name="image")
@Type(type="org.hibernate.type.BinaryType")
private byte[] image;
2. Üye Alan Tipi String İse Elimizde şöyle bir kod
olsun@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
}
Burada veri tabanındaki sütunun varsayılan uzunluğu 255. Açıklaması
şöyle
In Java, the string datatype can hold about 2Gb of text data, but the table column size will be limited to 255 characters by default for the model above.
Dolayısıyla eğer uzun bir String tipi kaydederken org.postgresql.util.PSQLException: ERROR: value too long for type character varying(255) hatası alabiliriz.
2.1 @Lob Olarak İşaretlemek - İstemiyoruz
Biz de sütunumuzu @Lob ile işaretlemeye karar veririz. Bu durumda üye alan için ise JDBC tiplerinden
java.sql.Types.CLOB seçilir. PostgreSQL CLOB sütunu olarak
text tipini kullanır. Ancak Hibernate String'i de aynı oid gibi başka bir alanda
saklar!
Örnek
Elimizde şöyle bir kod
olsun.
@Column(name = "doc_txt")
@Lob
private String docText;
Hibernate tarafından üretilen SQL şöyledir
CREATE TABLE document (
id INT8 GENERATED BY DEFAULT AS IDENTTITY,
date_created TIMESTAMPT NOT NULL,
doc_txt OID,
PRIMARY KEY (id)
);
SELECT * FROM document
id date_created doc_txt
1 2020-01-01 10:10:00 76388
Yani veri başka tabloda. Veriyi görmek için şöyle yaparız
SELECT * FROM pg_largeobject WHERE loid=76338
Örnek
Elimizde şöyle bir kod
olsun.
@Column(name = "xml")
@Lob
private String xml;
Bu tabloya bakarsak xml sütunu text
tipindendir.
Column | Type | Modifiers
----------------+-----------------------------+-----------
xml | text |
xml sütunundaki veriye bakarsak oid değer
görürüz.
# select * from tablename
xml
+------------
242781
(1 row)
2.2 Text Sütun Tipi İle Çalışmak - İstenilen Davranış
Örnek - Kolay yol
String'i tablo içinde saklamak için şöyle
yaparız
@Column(columnDefinition="text")
private String column;
Açıklaması
şöyle. Yani 1 GB civarında metin saklanabilir.
.. , the longest possible character string that can be stored is about 1 GB
...
It is smaller than 4Gb, allowed by LOB storage, but still long enough for most use cases.
Örnek - Hibernate 6
@Column(name = "doc_txt", length = Length.LOB_DEFAULT)
private String docText;
Açıklaması
şöyle. Yani sütun TEXT tipinden olmuyor
In Hibernate 6, the org.hibernate.type.TextType class was removed. To define a column to store a long text, we can define the attribute in the following way:
This will give us the following column definition in the database: doc_txt varchar(1048576). It is not the TEXT datatype, but it can still store about 1Gb of text in the table. It is the largest possible character string in PostgreSQL.
Yani sütun TEXT tipinden olsun istersek şöyle
yaparız@JdbcTypeCode(SqlTypes.LONG32VARCHAR)
@Column(name = "doc_txt")
private String docText;
Örnek - Hibernate 5 Anotasyonu
Açıklaması
şöyle. Bu sınıfın eski ismi
org.hibernate.type.StringClobType şeklindeydi. Sonrada isim değiştirdi.
The Hibernate @Type value that maps to PG's Text data type is org.hibernate.type.TextType. This is what you should use.
String'i tablo içinde saklamak için bir tane daha Hibernate anotasyonu ekleriz. Şöyle yaparız.
@Type(type="org.hibernate.type.TextType")
Örnek
@Type(type = "org.hibernate.type.TextType")
@Column(name = "doc_txt")
private String docText;