2015-09-24 8 views
5

ho qualcosa di simile il seguente modello di Hibernate:Memorizzazione di un elenco <SomeClass> come JSON in un campo di testo con Hibernate

class Person { 
    String name; 
    List<Address> addresses; 
} 

class Address { 
    String street; 
    String city; 
} 

Ora voglio persistere persona ad un tavolo dove tutti gli indirizzi della persona vengono serializzati ad un Stringa JSON e memorizzata in una colonna nella tabella Persona. Un record Persona nel database sarà simile a questo:

name: 'Benjamin Franklin', addresses: '[{"street"="...","city"="..."}, {...}]' 

C'è un modo per ottenere questo utilizzando Hibernate?

Se gli indirizzi non erano un elenco, potrei registrare un UserType per eseguire la serializzazione.

Non posso nemmeno utilizzare @Converter di JPA, poiché l'implementazione di Hibernate non rileva le modifiche, vedere HHH-10111.

+0

Quale db usi? Postgres per esempio ha un tipo di colonna json. Anche se non l'ho mai provato, usarlo con hibenate dovrebbe essere possibile ... http: //www.vivekpatidar.com/? P = 13 –

+1

Non voglio dipendere da un DB specifico, quindi voglio usare un VARCHAR o colonna CLOB/TEXT – Jochen

+0

In realtà è possibile utilizzare UserType. Aggiunta una risposta – NikolaB

risposta

3

È possibile creare tipo personalizzato:

import com.fasterxml.jackson.core.type.TypeReference; 
import com.fasterxml.jackson.databind.JavaType; 
import com.fasterxml.jackson.databind.ObjectMapper; 
import org.hibernate.usertype.ParameterizedType; 
import org.hibernate.usertype.UserType; 

public class JsonListType implements UserType, ParameterizedType { 

    public static final String LIST_TYPE = "LIST"; 
    private static final int[] SQL_TYPES = new int[]{Types.LONGVARCHAR}; 
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 
    private static final TypeReference LIST_TYPE_REF = new TypeReference<List<?>>() {}; 

    private JavaType valueType = null; 
    private Class<?> classType = null; 

    @Override 
    public int[] sqlTypes() { 
     return SQL_TYPES; 
    } 

    @Override 
    public Class<?> returnedClass() { 
     return classType; 
    } 

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

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

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

    @Override 
    public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException { 
     nullSafeSet(st, value, index); 
    } 

    public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException { 
     String value = rs.getString(names[0]); 
     Object result = null; 
     if (valueType == null) { 
      throw new HibernateException("Value type not set."); 
     } 
     if (value != null && !value.equals("")) { 
      try { 
       result = OBJECT_MAPPER.readValue(value, valueType); 
      } catch (IOException e) { 
       throw new HibernateException("Exception deserializing value " + value, e); 
      } 
     } 
     return result; 
    } 

    public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException { 
     StringWriter sw = new StringWriter(); 
     if (value == null) { 
      st.setNull(index, Types.VARCHAR); 
     } else { 
      try { 
       OBJECT_MAPPER.writeValue(sw, value); 
       st.setString(index, sw.toString()); 
      } catch (IOException e) { 
       throw new HibernateException("Exception serializing value " + value, e); 
      } 
     } 
    } 

    @Override 
    public Object deepCopy(Object value) throws HibernateException { 
     if (value == null) { 
      return null; 
     } else if (valueType.isCollectionLikeType()) { 
      try { 
       Object newValue = value.getClass().newInstance(); 
       Collection newValueCollection = (Collection) newValue; 
       newValueCollection.addAll((Collection) value); 
       return newValueCollection; 
      } catch (InstantiationException e) { 
       throw new HibernateException("Failed to deep copy the collection-like value object.", e); 
      } catch (IllegalAccessException e) { 
       throw new HibernateException("Failed to deep copy the collection-like value object.", e); 
      } 
     } 
    } 

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

    @Override 
    public Serializable disassemble(Object value) throws HibernateException { 
     return (Serializable) deepCopy(value); 
    } 

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

    @Override 
    public Object replace(Object original, Object target, Object owner) throws HibernateException { 
     return deepCopy(original); 
    } 

    @Override 
    public void setParameterValues(Properties parameters) { 
     String type = parameters.getProperty("type"); 
     if (type.equals(LIST_TYPE)) { 
      if (parameters.getProperty("element") != null) { 
       try { 
        valueType = OBJECT_MAPPER.getTypeFactory().constructCollectionType(ArrayList.class, Class.forName(parameters.getProperty("element"))); 
       } catch (ClassNotFoundException e) { 
        throw new IllegalArgumentException("Type " + type + " is not a valid type."); 
       } 
      } else { 
       valueType = OBJECT_MAPPER.getTypeFactory().constructType(LIST_TYPE_REF); 
      } 
      classType = List.class; 
     } 
    } 

e usarlo come:

@Type(type = "com.company.util.JsonListType", parameters = {@org.hibernate.annotations.Parameter(name = "type", value = "LIST"), @org.hibernate.annotations.Parameter(name = "element", value = "com.company.model.MyCustomClass")}) 
private List<MyCustomClass> myCustomClasses; 

Questa soluzione non è specifica per DB e puoi facilmente estenderla per supportare Maps e entità clonabili personalizzate.

+0

Si prega di aggiungere sempre le importazioni. Sono seduto qui speculando su quale versione di ObjectMapper e TypeReference devo usare. – Sonny

+1

@Sdon, mi dispiace, ho aggiunto le importazioni, spero che aiuti. – NikolaB

+0

@NikolaB L'annotazione sopra crea una nuova colonna nella stessa tabella. C'è un'annotazione per creare una nuova tabella e avere un riferimento di chiave esterna alla tabella effettiva? – Aditya

0

non mi sono provato, ma ecco un blog che vale la pena di riferimento a ... https://dzone.com/articles/annotating-custom-types

Che si propone in sostanza di aggiungere un'annotazione personalizzata. La classe annotata, per esempio, lo chiama "AddressJSONParser" dovrebbe convertire il tuo oggetto Address in JSON (usando parser) e ritorna come String. Si dovrebbe anche pensare di avere un parser, che fa il contrario, da stringa JSON torna a oggetto Indirizzo ...

Problemi correlati