7.1 Tạo trình chuyển đổi kiểu của riêng bạn

Đôi khi các tình huống phát sinh khi bạn muốn lưu trữ một loại dữ liệu khá phức tạp trong một cột của bảng. Nếu Hibernate biết cách chuyển đổi nó thành chuỗi (và ngược lại) thì mọi thứ đều ổn. Nếu không, thì bạn sẽ phải viết trình chuyển đổi dữ liệu của riêng mình.

Giả sử ai đó quyết định lưu trữ năm sinh của người dùng trong cơ sở dữ liệu dưới dạng YY.MM.DD, ví dụ: 98.12.15. Bạn cũng cần chuyển đổi nó thành một ngày thông thường: 15/12/1998. Sau đó, bạn phải viết trình chuyển đổi của riêng bạn.

Để làm điều này, bạn cần triển khai một giao diện AttributeConverter<EntityType, DbType>.


@Converter(autoApply = true)
public class DateConverter implements AttributeConverter<java.time.LocalDate, String> {
 
    public String convertToDatabaseColumn(java.time.LocalDate date) {
    	return date.format("YY.MM.DD");
    }
 
    public java.time.LocalDate convertToEntityAttribute(String dbData) {
    	String[] data = dbData.split(".");
    	return LocalDate.of(data[2], data[1], "19"+data[0]);
    }
}

Và, tất nhiên, trình chuyển đổi này có thể được thêm vào bất kỳ trường nào (miễn là các loại khớp):


@Entity
@Table(name="user")
class User {
   @Id
   @Column(name="id")
   public Integer id;
 
   @Column(name="join_date")
   @Convert(converter = DateConverter.class)
   public java.time.LocalDate date;
}

Bộ chuyển đổi thường phải được sử dụng nếu bạn không thiết kế cơ sở dữ liệu. Dữ liệu có thể ở "định dạng lạ". Ngày có thể được lưu trữ dưới dạng chuỗi, Booleans dưới dạng CHAR với giá trị Y và N, v.v.

7.2 Tạo kiểu dữ liệu của riêng chúng tôi

Ghi nhớ bảng với danh sách các loại được biết đến với Hibernate? Tôi đang nói về các loại được chỉ định cùng với chú thích @Type. Bạn có thể viết kiểu dữ liệu của riêng mình, kiểu dữ liệu này có thể được sử dụng giống như các kiểu dựng sẵn khác trong Hibernate.

Ví dụ: chúng tôi muốn có loại Thời gian cục bộ, loại này sẽ được lưu trữ trong cơ sở dữ liệu không phải là THỜI GIAN, mà là VARCHAR. Và, ví dụ, chúng tôi có quyền truy cập vào cơ sở dữ liệu như vậy và chúng tôi không được phép thay đổi loại dữ liệu trong các cột của nó. Sau đó, chúng ta có thể viết kiểu Hibernate của riêng mình. Hãy gọi nó là LocalTimeString.

Trước tiên, chúng ta cần một lớp nhỏ sẽ mô tả kiểu mới của chúng ta:

public class LocalTimeStringType extends AbstractSingleColumnStandardBasicType<<LocalTime> {

    public static final LocalTimeStringType  INSTANCE = new LocalTimeStringType ();

    public LocalTimeStringType () {
    	super(VarcharTypeDescriptor.INSTANCE, LocalTimeStringJavaDescriptor.INSTANCE);
    }

    @Override
    public String getName() {
    	return "LocalTimeString";
    }
}

Nó là một cái gì đó thuộc loại Enum bao gồm một giá trị. Tập hợp các enams đơn như vậy là tất cả các loại được Hibernate biết đến.

Chúng ta cũng cần một lớp - một dạng tương tự của trình chuyển đổi, lớp này sẽ chứa hai phương thức - wrap()unwrap()để chuyển đổi các giá trị của loại Thời gian cục bộ thành Chuỗi.

Đây là giao diện của nó mà không cần triển khai các phương thức:

public class LocalTimeStringJavaDescriptor extends AbstractTypeDescriptor<LocalTime> {

    public static final LocalTimeStringJavaDescriptor INSTANCE =  new  LocalTimeStringJavaDescriptor();

    public LocalTimeStringJavaDescriptor() {
    	super(LocalTime.class, ImmutableMutabilityPlan.INSTANCE);
    }

    public <X> X unwrap(LocalTime value, Class<X> type, WrapperOptions options) {

    }

    public <X> LocalTime wrap(X value, WrapperOptions options) {

    }

}

Bây giờ hãy viết cách thực hiện các phương thức:

public <X> X unwrap(LocalTime value, Class<X> type, WrapperOptions options) {

    if (value == null)
    	return null;

    if (String.class.isAssignableFrom(type))
    	return (X) LocalTimeType.FORMATTER.format(value);

    throw unknownUnwrap(type);
}

Và phương pháp thứ hai:

@Override
public <X> LocalTime wrap(X value, WrapperOptions options) {
    if (value == null)
    	return null;

    if(String.class.isInstance(value))
    	return LocalTime.from(LocalTimeType.FORMATTER.parse((CharSequence) value));

    throw unknownWrap(value.getClass());
}

Sẵn sàng. Bạn có thể sử dụng lớp này để lưu trữ thời gian dưới dạng chuỗi:


@Entity
@Table(name="user")
class User
{
   @Id
   @Column(name="id")
   public Integer id;
 
   @Column(name="join_time")
   @Type(type = "com.codegym.hibernate.customtypes.LocalTimeStringType")  
   public java.time.LocalTime time;
}

7.3 Đăng ký loại của bạn

Bạn cũng có thể đăng ký loại dữ liệu của mình trong quá trình cấu hình Hibernate. Đây là một chút không tầm thường.


ServiceRegistry serviceRegistry = StandardServiceRegistryBuilder()
    .applySettings(getProperties()).build();
                                                                                                                                                              	                                        	 
    MetadataSources metadataSources = new MetadataSources(serviceRegistry);
    Metadata metadata = metadataSources
  	.addAnnotatedClass(User.class)
  	.getMetadataBuilder()
  	.applyBasicType(LocalTimeStringType.INSTANCE)
  	.build();
                                                                                                                                                              	                                        	 
    SessionFactory factory =  metadata.buildSessionFactory();

Trước tiên, bạn sẽ cần lấy MetadataSources, lấy MetadataBuilder từ nó và sử dụng nó để thêm lớp của bạn. Có thể thông qua hibernate.cfg.xml, nhưng cũng hơi rườm rà.

Nhưng sau khi đăng ký, bạn có thể viết như thế này:


@Entity
@Table(name="user")
class User
{
   @Id
   @Column(name="id")
   public Integer id;
 
   @Column(name="join_time")
   @Type(type = "LocalTimeString")  
   public java.time.LocalTime time;
}