2012-04-03 16 views
10

Tôi gặp sự cố khi viết và đọc lại các ký tự đặc biệt như ký hiệu Euro (€) thành thuộc tính Chuỗi LOB trong PostgreSQL 8.4 với Hibernate 3.6.10.Không thể lưu trữ dấu Euro vào thuộc tính Chuỗi LOB với Hibernate/PostgreSQL

Điều tôi biết là PostgreSQL cung cấp hai cách riêng biệt để lưu trữ các đối tượng ký tự lớn trong một cột của bảng. Chúng có thể được lưu trữ trực tiếp vào cột bảng đó hoặc gián tiếp trong một bảng riêng biệt (nó thực sự được gọi là pg_largeobject). Trong trường hợp sau, cột chứa một tham chiếu (OID) đến hàng trong pg_largeobject.

Hành vi mặc định trong Hibernate 3.6.10 là cách tiếp cận OID gián tiếp. Tuy nhiên, có thể thêm chú thích bổ sung @ org.hibernate.annotations.Type (type = "org.hibernate.type.TextType") vào thuộc tính Lob để có được hành vi lưu trữ trực tiếp.

Cả hai phương pháp đều hoạt động tốt, ngoại trừ thời điểm tôi muốn làm việc với các ký tự đặc biệt như ký hiệu Euro (€). Trong trường hợp đó, cơ chế lưu trữ trực tiếp tiếp tục hoạt động, nhưng cơ chế lưu trữ gián tiếp bị gián đoạn.

Tôi muốn chứng minh điều đó bằng ví dụ. Tôi đã tạo một thực thể thử nghiệm với 2 thuộc tính @Lob. Một tuân theo nguyên tắc lưu trữ trực tiếp, khác lưu trữ gián tiếp:

@Basic 
@Lob 
@Column(name = "CLOB_VALUE_INDIRECT_STORAGE", length = 2147483647) 
public String getClobValueIndirectStorage() 

@Basic 
@Lob 
@org.hibernate.annotations.Type(type="org.hibernate.type.TextType") 
@Column(name = "CLOB_VALUE_DIRECT_STORAGE", length = 2147483647) 
public String getClobValueDirectStorage() 

Nếu tôi tạo ra một tổ chức nào, cư cả khách sạn với dấu hiệu Euro và sau đó kéo dài nó đối với cơ sở dữ liệu tôi thấy sau khi tôi làm một SELECT tôi thấy

id | clob_value_direct_storage | clob_value_indirect_storage 
----+---------------------------+---------------------------- 
    6 | €       | 910579      

Nếu tôi sau đó truy vấn pg_largeobject bảng tôi thấy:

loid | pageno | data 
--------+--------+------ 
910579 |  0 | \254 

Cột 'dữ liệu' của pg_largeobject thuộc loại bytea, có nghĩa là thông tin được lưu trữ dưới dạng byte thô. Biểu thức '\ 254' đại diện cho một byte đơn và trong UTF-8 biểu diễn ký tự '¬'. Đây chính là giá trị mà tôi lấy lại khi tôi nạp thực thể trở lại từ cơ sở dữ liệu.

Dấu hiệu Euro trong UTF-8 gồm 3 byte, vì vậy tôi dự kiến ​​sẽ có cột 'dữ liệu' có 3 byte và không 1.

này không chỉ xảy ra đối với các dấu hiệu Euro, nhưng đối với nhiều nhân vật đặc biệt. Đây có phải là một vấn đề trong Hibernate? Hoặc trình điều khiển JDBC? Có cách nào tôi có thể tinh chỉnh hành vi này không?

Cảm ơn trước,
Trân trọng!
Franck de Bruijn

+1

Tại sao bạn sử dụng các đối tượng lớn ngay từ đầu? Chỉ cần sử dụng kiểu dữ liệu 'văn bản' cho cột đó. Không cần phải xáo trộn xung quanh với 'bytea' hoặc các đối tượng lớn nếu tất cả những gì bạn muốn lưu trữ là văn bản. –

+0

Có thể có nhiều lý do để làm như vậy. Tôi không biết. Tôi cung cấp một khuôn khổ cho những người dùng khác để sử dụng và tôi muốn hỗ trợ cả hai lựa chọn thay thế. Trong các phiên bản cũ của trình điều khiển JDBC (hoặc Hibernate, tôi không chắc chắn) hành vi mặc định là 'lưu trữ trực tiếp'. Sau đó thay đổi thành 'lưu trữ gián tiếp'. Có lẽ vì một số lý do chính đáng. –

+0

Tôi suy nghĩ về điều này nhiều hơn một chút và tôi thực sự bắt đầu đồng ý nhiều hơn và nhiều hơn nữa với a_horse_with_no_name. Trước tiên, cơ chế lưu trữ gián tiếp ngăn cản bạn sử dụng cột này trong truy vấn HQL, đây là một bất lợi lớn. Cơ chế lưu trữ gián tiếp tạo điều kiện cho tùy chọn phát trực tuyến, để bạn có thể truyền trực tiếp nội dung từ cơ sở dữ liệu đến ứng dụng khách (tiết kiệm dung lượng bộ nhớ). Để chắc chắn đây là một đối số hợp lệ cho BLOB, nhưng đối với các CLOB? Trong hầu hết các kịch bản, kích thước của CLOB thực tế sẽ không lớn, chắc chắn không nằm trong phạm vi từ 1 triệu trở lên. Điều này có thể được xử lý trong bộ nhớ. –

Trả lời

5

Sau rất nhiều đào xung quanh trong mã nguồn của Hibernate và người lái xe PostgreSQL JDBC Tôi cố gắng tìm ra nguyên nhân gốc rễ của vấn đề. Cuối cùng, phương thức write() của BlobOutputStream (do trình điều khiển JDBC cung cấp) được gọi để ghi nội dung của Clob vào cơ sở dữ liệu. Phương pháp này trông như thế này:

public void write(int b) throws java.io.IOException 
{ 
    checkClosed(); 
    try 
    { 
     if (bpos >= bsize) 
     { 
      lo.write(buf); 
      bpos = 0; 
     } 
     buf[bpos++] = (byte)b; 
    } 
    catch (SQLException se) 
    { 
     throw new IOException(se.toString()); 
    } 
} 

phương pháp này có một 'int' (32 bit/4 byte) như là đối số và chuyển đổi nó thành một 'byte' (8 bit/1 byte) mất hiệu quả 3 byte của thông tin . Biểu diễn chuỗi trong Java được mã hoá UTF-16, có nghĩa là mỗi ký tự được biểu diễn bằng 16 bit/2 byte. Ký hiệu Euro có giá trị int 8364. Sau khi chuyển đổi thành byte, giá trị 172 vẫn còn (trong biểu diễn octet 254).

Tôi không chắc bây giờ độ phân giải tốt nhất là vấn đề này. IMHO trình điều khiển JDBC phải chịu trách nhiệm mã hóa/giải mã các ký tự Java UTF-16 thành bất kỳ mã hóa nào mà nhu cầu cơ sở dữ liệu. Tuy nhiên, tôi không thấy bất kỳ khả năng tinh chỉnh nào trong mã trình điều khiển JDBC để thay đổi hành vi của nó (và tôi không muốn viết và duy trì mã trình điều khiển JDBC của riêng mình).

Vì vậy, tôi đã mở rộng Hibernate bằng ClobType tùy chỉnh và quản lý để chuyển đổi các ký tự UTF-16 thành UTF-8 trước khi ghi vào cơ sở dữ liệu và ngược lại khi truy xuất Clob.

Các giải pháp quá lớn để chỉ dán đơn giản trong câu trả lời này. Nếu bạn quan tâm, hãy thả cho tôi một dòng, và tôi gửi nó cho bạn.

Chúc mừng, Franck

+0

Franck Tôi đã nhận được xung quanh này bằng cách chỉ sử dụng đáng kinh ngạc lớn varchar (đó là cột văn bản postgres). Tôi biết nó không lý tưởng như cột varchar có lẽ đang được nạp vào bộ nhớ (thay vì clob có lẽ là đệm vào đĩa khi lớn) nhưng nó hoạt động. –

+0

Franck, hành vi của 'BlobOutputStream.write (int b)' là chính xác. Bất cứ điều gì được gọi là nó có khả năng được sử dụng nó không chính xác. Theo [OutputStream JavaDoc] (http://docs.oracle.com/javase/6/docs/api/java/io/OutputStream.html#write (int)) * "Hợp đồng chung cho' write' là một byte được ghi vào luồng đầu ra. byte được ghi là tám bit thứ tự thấp của đối số b. 24 bit thứ tự cao của b bị bỏ qua. "* Bạn có một trường hợp thử nghiệm chứng minh vấn đề này không? Nếu vậy, hãy gửi một lỗi chống lại Hibernate và liên kết đến nó ở đây. (Tôi giúp đỡ trên trình điều khiển JDBC) –

+0

Nếu trình điều khiển PostgreSQL đã thực hiện hỗ trợ cho NClob, bạn có thể thử dùng Hibernate sử dụng NClob thay cho Clob? Đó là kế hoạch hỗ trợ nhân vật được quốc hữu hóa của tôi trong Hibernate anyway: hỗ trợ các biến thể được quốc gia hóa được định nghĩa trong JDBC 4 (Types.NCLOB, Types.NCHAR, Types.NVARCHAR, Types.NLONGVARCHAR) –

Các vấn đề liên quan