How to implement a custom basic type using Hibernate UserType
Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Hypersistence Optimizer is that tool!
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.
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 define 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!
You don't have to create all these types manually. You can simply get them via Maven Central using the following dependency:
<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.
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!
Online Workshops
If you enjoyed this article, I bet you are going to love my upcoming Online Workshops.
- Transactions and Concurrency Control Patterns (3 hours) on the 10th of February
- High-Performance Java Persistence (16 hours) starting on the 1st of March in collaboration with Bouvet
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.
