How to change Hibernate CharacterTypeDescriptor to handle empty column values

Our problem is that we cannot retrieve data (which include empty strings of length 0) from an outdated database due to StringIndexOutOfBoundsExceptiion originating from the Hibernate CharacterTypeDescriptor . We would like to change the behavior of Hibernate to properly resolve blank lines.

Sample data:

 1, 'Berlin', 17277, '', 'aUser' 2, 'London', 17277, '', 'anotherUser' 

We use sleep mode with javax.persistence.Query .

 String sql = "SELECT * FROM table"; Query query = entityManager.createNativeQuery(sql); List resultList = query.getResultList(); 

This results in a StringIndexOutOfBoundsException , the root of which is the following code from Hibernate:

 if ( String.class.isInstance( value ) ) { final String str = (String) value; return Character.valueOf( str.charAt(0) ); // this fails, as there is no char at position 0 } 

This was confirmed by a post on sleeping forums .

We have no way to update hibernate from this version with errors, and we are looking for a way to change the display of Hibernate.

We cannot use PreparedStatements, regular JDBC connections, or JPA entities.

Changing an obsolete database is also not possible. The SQL statement works flawlessly with DBVisualizer.

Is there a way to change the way strings are displayed in Hibernate?

+9
source share
2 answers

CharacterType presented in this answer is available through the hibernate-types project, so there is no need to write it yourself.

For more details about using the CharacterType directly, check out this article .

First you need to define ImmutableType :

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

Now we can move on 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)); } } } 

An entity mapping is as follows:

 @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 let's say we have these table rows:

 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 reading all entities:

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

You will get the expected result:

 Event type: a Event type: Event type: b 

Check out the source code on GitHub .

+5
source

Given all the limitations you have, I think the simplest (but hardest) solution is to simply rewrite the SELECT query to check if the column value is empty and in this case return some placeholder text .. or null if Hibernate can handle it.

+1
source

All Articles