How to implement a custom basic type using Hibernate UserType

(Last Updated On: January 29, 2018)

Introduction

StackOverflow is a gold mine! Check out this question I bumped into this morning. Basically, our Hibernate user wants a resilient CharacterType which works with NULL or empty values. To make it even more interesting, we are going to make it work even if the underlying database column contains more than one character.

Custom type flavors

There are two ways to write a custom Hibernate type:

  • Using an SqlTypeDescriptor
  • Using the legacy UserTpe

While the former way is usually preferred, as demonstrated by this generic JSON type that works on both MySQL and PostgreSQL, I’m going to use the latter method to demonstrate how the UserTpe abstraction works.

You don't have to create all these types manually. You can simply get them via Maven Central using the following dependency:

1
2
3
4
5
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types.version}</version>
</dependency>

For more info, check out the hibernate-types open-source project.

ImmutableType

For the current use case, we are going to use an ImmutableType since Character is immutable anyway:

public abstract class ImmutableType<T> implements UserType {

    private final Class<T> clazz;

    protected ImmutableType(Class<T> clazz) {
        this.clazz = clazz;
    }

    @Override
    public Object nullSafeGet(
        ResultSet rs, 
        String[] names,
        SharedSessionContractImplementor session, 
        Object owner) 
        throws SQLException {
        return get(rs, names, session, owner);
    }

    @Override
    public void nullSafeSet(
        PreparedStatement st, 
        Object value, 
        int index,
        SharedSessionContractImplementor session) 
        throws SQLException {
        set(st, clazz.cast(value), index, session);
    }

    protected abstract T get(
        ResultSet rs, 
        String[] names,
        SharedSessionContractImplementor session, 
        Object owner) throws SQLException;

    protected abstract void set(
        PreparedStatement st, 
        T value, 
        int index,
        SharedSessionContractImplementor session) 
        throws SQLException;


    @Override
    public Class<T> returnedClass() {
        return clazz;
    }

    @Override
    public boolean equals(Object x, Object y) {
        return Objects.equals(x, y);
    }

    @Override
    public int hashCode(Object x) {
        return x.hashCode();
    }

    @Override
    public Object deepCopy(Object value) {
        return value;
    }

    @Override
    public boolean isMutable() {
        return false;
    }

    @Override
    public Serializable disassemble(Object o) {
        return (Serializable) o;
    }

    @Override
    public Object assemble(
        Serializable cached, 
        Object owner) {
        return cached;
    }

    @Override
    public Object replace(
        Object o, 
        Object target, 
        Object owner) {
        return o;
    }
}

CharacterType

Now, we can move to defining the actual CharacterType:

public class CharacterType 
    extends ImmutableType<Character> {

    public CharacterType() {
        super(Character.class);
    }

    @Override
    public int[] sqlTypes() { 
        return new int[]{Types.CHAR}; 
    }

    @Override
    public Character get(
        ResultSet rs, 
        String[] names,
        SharedSessionContractImplementor session, 
        Object owner) 
        throws SQLException {
        String value = rs.getString(names[0]);
        return (value != null && value.length() > 0) ? 
            value.charAt(0) : null;
    }

    @Override
    public void set(
        PreparedStatement st, 
        Character value, 
        int index,
        SharedSessionContractImplementor session) 
        throws SQLException {
        if (value == null) {
            st.setNull(index, Types.CHAR);
        } else {
            st.setString(index, String.valueOf(value));
        }
    }
}

That’s it!

Testing time

Assuming we have the following entity:

@Entity(name = "Event")
@Table(name = "event")
public class Event {

    @Id
    @GeneratedValue
    private Long id;

    @Type(type = "com.vladmihalcea.book.hpjp.hibernate.type.CharacterType")
    @Column(name = "event_type")
    private Character type;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Character getType() {
        return type;
    }

    public void setType(Character type) {
        this.type = type;
    }
}

And we have the following entries in the database:

INSERT INTO event (id, event_type) VALUES (1, 'abc');    
INSERT INTO event (id, event_type) VALUES (2, '');
INSERT INTO event (id, event_type) VALUES (3, 'b');

When selecting and logging all event entries:

doInJPA(entityManager -> {
    List<Event> events = entityManager.createQuery(
        "select e from Event e", Event.class)
    .getResultList();
    for(Event event : events) {
        LOGGER.info("Event type: {}", event.getType());
    }
});

The following output is obtained:

Event type: a
Event type:  
Event type: b

Great!

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.

Conclusion

Writing a custom type is very easy with Hibernate. Although the UserType does the trick, usually, the the SqlTypeDescriptor approach is to be preferred.

Subscribe to our Newsletter

* indicates required
10 000 readers have found this blog worth following!

If you subscribe to my newsletter, you'll get:
  • A free sample of my Video Course about running Integration tests at warp-speed using Docker and tmpfs
  • 3 chapters from my book, High-Performance Java Persistence, 
  • a 10% discount coupon for my book. 
Get the most out of your persistence layer!

Advertisements

2 thoughts on “How to implement a custom basic type using Hibernate UserType

  1. Hi,

    Is it correct to use it for enums to handle null and empty string (select from database) when it call Enum.valueOf to build enum object?

    thx

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.