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 4-day x 4 hours High-Performance Java Persistence Online Workshop
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.
Related