Skip to content

Automatic Data Type Conversions

Holger Thurow edited this page Jan 19, 2020 · 5 revisions

Writing

When writing data to JDBC, q2o relies on the driver to perform most conversions. q2o only calls Statement.setObject() internally, and expects that the driver will properly perform conversions. For example, convert an int or java.lang.Integer into an INTEGER column type.

If the @Convert annotation is present on the field in question, the appropriate user-specified javax.persistence.AttributeConverter will be called.

For fields where the @Enumerated annotation is present, q2o will obtain the value to persist by calling ordinal() on the enum instance in the case of EnumType.ORDINAL, and name() on the enum instance in the case of EnumType.STRING.

Reading

When reading data from JDBC, q2o relies on the driver to perform most conversions. q2o only calls ResultSet.getObject() internally, and expects that the driver will properly perform conversions to Java types. For example , for an INTEGER column type, return a java.lang.Integer from ResultSet.getObject().

However, if the Java object type returned by the driver does not match the type of the mapped member field, q2o permits the following automatic conversions:

Driver getObject() Java Type Mapped Member Java type
java.lang.Integer boolean (0 == false, everything else true)
java.math.BigDecimal java.math.BigInteger
java.math.BigDecimal int or java.lang.Integer (via cast)
java.math.BigDecimal long or java.lang.Long (via cast)
java.util.UUID String
java.sql.Clob String
... Many more in 3.16

If the @Convert annotation is present on the field in question, the appropriate user-specified javax.persistence.AttributeConverter will be called.

For fields where the @Enumerated annotation is present, q2o will map java.lang.Integer values from the driver to the correct Enum value in the case of EnumType.ORDINAL, and will map java.lang.String values from the driver to the correct Enum value in the case of EnumType.STRING.

Finally, q2o has specific support for the PostgreSQL PGobject and CITEXT data types. CITEXT column values are converted to java.lang.String. PGobject "unknown type" column values have their getValue() method called, and the result is attempted to be set via reflection onto the mapped member field.

Customized Conversion

In some cases the default conversion from JDBC type to Java type might not cut it. An enum, for example, can be saved to a Types.VARCHAR column, but would not automatically be marshaled back to an enum when retrieved.

In other cases, there may not be a way to set the field from the JDBC type. For example, JSR-310 java.time.* classes cannot be automagically set from the JDBC types. Enums also cannot be set from the JDBC types.

In these cases, you need to help provide conversion hints.

This is accomplished by adding the @Convert annotation to the field and identifying an AttributeConverter class that can handle the conversion.

Let's tackle using a java.time.LocalDate field that will be stored as a Types.DATE column.

First we need an AttributeConverter implementation:

import javax.persistence.AttributeConverter;
import java.sql.Date;
import java.time.LocalDate;

/**
 * class LocalDateAttributeConverter: An attribute converter for LocalDate <-> Date conversion.
 */
public class LocalDateAttributeConverter implements AttributeConverter<LocalDate, Date> {
    @Override
    public Date convertToDatabaseColumn(LocalDate localDate) {
        return localDate == null ? null : Date.valueOf(localDate);
    }

    @Override
    public LocalDate convertToEntityAttribute(Date date) {
        return date == null ? null : date.toLocalDate();
    }
}

This class implements the JPA'a AttributeConverter interface and covers marshaling to/from LocalDate and Date objects. The implementation is pretty simple as the conversion is handled by Java classes themselves.

For our field, we will just need to decorate with the @Convert annotation. Using the Order class, we must add:

import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Id;
import javax.persistence.Table;
...
@Entity
@Table(name = "order")
class Order {
   ...
   @Column(name = "order_dt")
   @Convert(converter = LocalDateAttributeConverter.class)
   LocalDate ordered;
   ...
}

Now when the Order class is persisted or retrieved, q2o will be able to translate the Types.DATE column to/from the LocalDate field.

You can apply a similar process to Enums that cannot be handled with integer @Enumerated handling (i.e. persisted as String representations rather than ints), conversion between other types (i.e. a string column parsed to a javax.money.MonetaryAmount, etc.).