29 Haziran 2020 Pazartesi

Hibernate'e Mahsus JPA @Lob Anotasyonu - PostgreSQL

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.
text
oid

OID Nedir?
Açıklaması şöyle
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) 
); 
Eğer tabloya bakarsak veri şöyledir
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
Şöyle yaparız
@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
Şöyle yaparız
@Type(type = "org.hibernate.type.TextType")   
@Column(name = "doc_txt")   
private String docText;

Hiç yorum yok:

Yorum Gönder