2016-09-21 34 views
6

Naszym problemem jest to, że nie możemy uzyskać dane (w tym pustych ciągi o długości 0) z bazy danych starszego typu ze względu na StringIndexOutOfBoundsExceptiion pochodzących z hibernacji na CharacterTypeDescriptor. Chcielibyśmy zmienić zachowanie Hibernate, aby poprawnie rozwiązać puste ciągi.Jak zmienić hibernacji CharacterTypeDescriptor obsługiwać pustą kolumnę wartości

Przykład Dane:

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

wykorzystujemy hibernacji z javax.persistence.Query.

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

ten prowadzi do StringIndexOutOfBoundsException z jego korzeni z nich to kod z hibernacji:

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 
} 

Zostało to potwierdzone przez a post on the hibernate forums.

Nie mamy możliwości aktualizacji hibernacji z tej wersji buggy i szukania sposobu na zmianę mapowania Hibernate.

Nie możemy używać PreparedStatements lub zwykłych połączeń JDBC ani JPA-Entities.

Zmiana starszej bazy danych również nie jest możliwa. Instrukcja SQL działa bezbłędnie za pomocą DBVisualizer.

Czy istnieje sposób, aby zmienić sposób mapowania według Hibernate?

+0

Po prostu muszę zapytać, dlaczego, do cholery, nie możesz użyć żadnego z gadżetów, które zapewnia Hibernate? – rorschach

+0

Ponieważ hibernacja nie jest "obsługiwana". Dlatego musimy używać jako języka SQL niskiego poziomu, jak to tylko możliwe. – michaelbahr

Odpowiedz

5

East peasy!

W rzeczywistości, ponieważ jest to bardzo dobre pytanie, postanowiłem napisać an article to demonstrate how easily you can achieve this goal using a custom Hibernate Type.

Najpierw trzeba zdefiniować 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; 
    } 
} 

Teraz możemy przejść do określenia rzeczywistej 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)); 
     } 
    } 
} 

Mapowanie podmiot wygląda następująco:

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

Załóżmy, że mamy te wiersze tabeli:

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'); 

Po przeczytaniu wszystkich podmiotów:

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

Dostaniesz oczekiwany wynik:

Event type: a 
Event type: 
Event type: b 

Zapoznaj się z kodu źródłowego na GitHub.

1

Biorąc pod uwagę wszystkie twoje ograniczenia, myślę, że najłatwiejszym (ale hackiest) rozwiązaniem jest ponowne napisanie zapytania SELECT, aby sprawdzić, czy wartość kolumny jest pusta, i w takim przypadku zwróć trochę tekstu zastępczego. lub null, jeśli zamiast tego Hibernate może obsłużyć to.

+0

Zrobiłem to i wydaje się, że działa. Podpisze kod później. Dzięki! – michaelbahr