7.1 Skapa din egen typomvandlare

Ibland uppstår situationer när man vill lagra en ganska komplex datatyp i en kolumn i en tabell. Om Hibernate vet hur man konverterar det till en sträng (och tillbaka), så är allt bra. Om inte, måste du skriva din egen datakonverterare.

Låt oss säga att någon bestämmer sig för att lagra en användares födelseår i databasen som , YY.MM.DDtill exempel: 98.12.15. Du måste också konvertera det till ett vanligt datum: 15/12/1998. Sedan måste du skriva din egen omvandlare.

För att göra detta måste du implementera ett gränssnitt 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]);
    }
}

Och naturligtvis kan denna omvandlare läggas till i vilket fält som helst (förutsatt att typerna matchar):


@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;
}

Omvandlare måste mycket ofta användas om du inte har designat databasen. Uppgifterna där kan vara i "konstiga format". Datum kan lagras som strängar, Booleans som CHAR med Y- och N-värden och liknande.

7.2 Skapa vår egen datatyp

Kommer du ihåg tabellen med listan över typer som är kända för Hibernate? Jag talar om typer som anges tillsammans med anteckningen @Type. Du kan skriva din egen datatyp, som kan användas på samma sätt som andra inbyggda typer i Hibernate.

Till exempel vill vi ha typen LocalTime, som kommer att lagras i databasen inte som TIME, utan som VARCHAR. Och vi har till exempel tillgång till en sådan databas, och vi får inte ändra datatyperna i dess kolumner. Sedan kan vi skriva vår egen Hibernate-typ. Låt oss kalla det LocalTimeString.

Först behöver vi en liten klass som kommer att beskriva vår nya typ:

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";
    }
}

Det är något av typen Enum som består av ett värde. Uppsättningen av sådana enstaka enams är alla typer som är kända för Hibernate.

Vi behöver också en klass - en analog till omvandlaren, som kommer att innehålla två metoder - wrap()och unwrap()för att konvertera värden av typen LocalTime till String.

Så här kommer det att se ut utan att implementera metoderna:

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) {

    }

}

Låt oss nu skriva implementeringen av metoderna:

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);
}

Och den andra metoden:

@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());
}

Redo. Du kan använda den här klassen för att lagra tid som en sträng:


@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 Registrera din typ

Du kan också registrera din datatyp under Hibernate-konfigurationen. Detta är lite icke-trivialt.


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();

Du måste först skaffa MetadataSources, hämta MetadataBuilder från den och använda den för att lägga till din klass. Det är möjligt genom hibernate.cfg.xml, men också lite krångligt.

Men efter registrering kan du skriva så här:


@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;
}