7.1 Uw eigen typeconverter maken

Soms doen zich situaties voor wanneer u een vrij complex gegevenstype in één kolom van een tabel wilt opslaan. Als Hibernate weet hoe het naar een string moet worden geconverteerd (en terug), dan is alles in orde. Als dat niet het geval is, moet u uw eigen dataconverter schrijven.

Stel dat iemand besluit om het geboortejaar van een gebruiker in de database op te slaan als YY.MM.DDbijvoorbeeld: 98.12.15. Je moet het ook converteren naar een gewone datum: 15/12/1998. Dan moet je je eigen converter schrijven.

Om dit te doen, moet u een interface implementeren 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]);
    }
}

En natuurlijk kan deze converter aan elk veld worden toegevoegd (mits de typen overeenkomen):


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

Converters moeten heel vaak worden gebruikt als u de database niet hebt ontworpen. De gegevens daar kunnen in "vreemde formaten" zijn. Datums kunnen worden opgeslagen als tekenreeksen, Booleans als TEKENs met Y- en N-waarden en dergelijke.

7.2 Ons eigen gegevenstype maken

Weet je nog de tabel met de lijst met typen die bekend zijn bij Hibernate? Ik heb het over typen die samen met de annotatie worden gespecificeerd @Type. U kunt uw eigen gegevenstype schrijven, dat op dezelfde manier kan worden gebruikt als andere ingebouwde typen in Hibernate.

We willen bijvoorbeeld het LocalTime-type hebben, dat in de database wordt opgeslagen, niet als TIME, maar als VARCHAR. En we hebben bijvoorbeeld toegang tot zo'n database en we mogen de gegevenstypen in de kolommen niet wijzigen. Dan kunnen we ons eigen Hibernate-type schrijven. Laten we het LocalTimeString noemen.

Eerst hebben we een kleine klasse nodig die ons nieuwe type zal beschrijven:

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

Het is iets van het type Enum dat uit één waarde bestaat. De set van dergelijke enkele enams is alle soorten die bekend zijn bij Hibernate.

We hebben ook een klasse nodig - een analoog van de converter, die twee methoden zal bevatten - wrap()en unwrap()voor het converteren van waarden van het LocalTime-type naar String.

Dit is hoe het eruit zal zien zonder de methoden te implementeren:

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

    }

}

Laten we nu de implementatie van de methoden schrijven:

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

En de tweede methode:

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

Klaar. U kunt deze klasse gebruiken om tijd op te slaan als een string:


@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 Uw type registreren

U kunt uw gegevenstype ook registreren tijdens de slaapstandconfiguratie. Dit is een beetje niet triviaal.


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

U moet eerst MetadataSources ophalen, MetadataBuilder ervan ophalen en gebruiken om uw klas toe te voegen. Het kan via hibernate.cfg.xml, maar ook een beetje omslachtig.

Maar na registratie kunt u als volgt schrijven:


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