web-dev-qa-db-fra.com

Comment mapper une colonne MySQL JSON à une propriété d'entité Java en utilisant JPA et Hibernate -

J'ai une colonne MySQL déclarée comme type JSON et j'ai des problèmes pour la mapper avec Jpa/Hibernate. J'utilise Spring Boot sur le back-end.

Voici une petite partie de mon code:

@Entity
@Table(name = "some_table_name")
public class MyCustomEntity implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@Column(name = "json_value")
private JSONArray jsonValue;

Le programme me renvoie une erreur et me dit que je ne peux pas mapper la colonne.

Dans la table mysql, la colonne est définie comme:

json_value JSON NOT NULL;

13
Heril Muratovic

Je préfère faire de cette façon:

  • Création d'un convertisseur (convertisseur d'attribut) de Map en String et vice versa.
  • Utilisation de Map pour mapper le type de colonne JSON mysql dans la classe de domaine (entité)

Le code est ci-dessous.

JsonToMapConverted.Java

@Converter
public class JsonToMapConverter 
                    implements AttributeConverter<String, Map<String, Object>> 
{
    private static final Logger LOGGER = LoggerFactory.getLogger(JsonToMapConverter.class);

    @Override
    @SuppressWarnings("unchecked")
    public Map<String, Object> convertToDatabaseColumn(String attribute)
    {
        if (attribute == null) {
           return new HashMap<>();
        }
        try
        {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.readValue(attribute, HashMap.class);
        }
        catch (IOException e) {
            LOGGER.error("Convert error while trying to convert string(JSON) to map data structure.");
        }
        return new HashMap<>();
    }

    @Override
    public String convertToEntityAttribute(Map<String, Object> dbData)
    {
        try
        {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(dbData);
        }
        catch (JsonProcessingException e)
        {
            LOGGER.error("Could not convert map to json string.");
            return null;
        }
    }
}

Partie de la classe de domaine (mappage d'entité)

...

@Column(name = "meta_data", columnDefinition = "json")
@Convert(attributeName = "data", converter = JsonToMapConverter.class)
private Map<String, Object> metaData = new HashMap<>();

...

Cette solution fonctionne parfaitement pour moi.

6
Heril Muratovic

Si les valeurs à l'intérieur de votre tableau json sont de simples chaînes, vous pouvez le faire:

@Type( type = "json" )
@Column( columnDefinition = "json" )
private String[] jsonValue;
3
J. Wang

Comme je l'ai expliqué dans cet article , il est très facile de conserver des objets JSON en utilisant Hibernate.

Vous n'avez pas besoin de créer tous ces types manuellement, vous pouvez simplement les obtenir via Maven Central en utilisant la dépendance suivante:

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-52</artifactId>
    <version>${hibernate-types.version}</version> 
</dependency> 

Pour plus d'informations, consultez le projet open-source hibernate-types .

Maintenant, pour expliquer comment tout cela fonctionne.

En supposant que vous ayez l'entité suivante:

@Entity(name = "Book")
@Table(name = "book")
@TypeDef(
    name = "json", 
    typeClass = JsonStringType.class
)
public class Book {

    @Id
    @GeneratedValue
    private Long id;

    @NaturalId
    private String isbn;

    @Type(type = "json")
    @Column(columnDefinition = "json")
    private String properties;

    //Getters and setters omitted for brevity
}

Remarquez deux choses dans l'extrait de code ci-dessus:

  • les @TypeDef est utilisé pour définir un nouveau type d'hibernation personnalisé, json qui est géré par le JsonStringType
  • l'attribut properties a un type de colonne json et il est mappé en tant que String

C'est tout!

Maintenant, si vous enregistrez une entité:

Book book = new Book();
book.setIsbn("978-9730228236");
book.setProperties(
    "{" +
    "   \"title\": \"High-Performance Java Persistence\"," +
    "   \"author\": \"Vlad Mihalcea\"," +
    "   \"publisher\": \"Amazon\"," +
    "   \"price\": 44.99" +
    "}"
);

entityManager.persist(book);

Hibernate va générer l'instruction SQL suivante:

INSERT INTO
    book 
(
    isbn, 
    properties, 
    id
) 
VALUES
(
    '978-9730228236', 
    '{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99}',  
    1
)

Et vous pouvez également le recharger et le modifier:

Book book = entityManager
    .unwrap(Session.class)
    .bySimpleNaturalId(Book.class)
    .load("978-9730228236");

book.setProperties(
    "{" +
    "   \"title\": \"High-Performance Java Persistence\"," +
    "   \"author\": \"Vlad Mihalcea\"," +
    "   \"publisher\": \"Amazon\"," +
    "   \"price\": 44.99," +
    "   \"url\": \"https://www.Amazon.com/High-Performance-Java-Persistence-Vlad-Mihalcea/dp/973022823X/\"" +
    "}"
);

Hibernate prenant caare de l'instruction UPDATE pour vous:

SELECT  b.id AS id1_0_
FROM    book b
WHERE   b.isbn = '978-9730228236'

SELECT  b.id AS id1_0_0_ ,
        b.isbn AS isbn2_0_0_ ,
        b.properties AS properti3_0_0_
FROM    book b
WHERE   b.id = 1    

UPDATE
    book 
SET
    properties = '{"title":"High-Performance Java Persistence","author":"Vlad Mihalcea","publisher":"Amazon","price":44.99,"url":"https://www.Amazon.com/High-Performance-Java-Persistence-Vlad-Mihalcea/dp/973022823X/"}'
WHERE
    id = 1

Tous les codes disponibles sur GitHub .

2
Vlad Mihalcea

Car personne ne peut faire @J. Wang répond au travail:

Essayez d'ajouter cette dépendance (c'est pour la mise en veille prolongée 5.1 et 5.0, vérification d'autres versions ici )

<dependency>
    <groupId>com.vladmihalcea</groupId>
    <artifactId>hibernate-types-5</artifactId>
    <version>1.2.0</version>
</dependency>

Et ajoutez cette ligne à l'entité

@TypeDef(name = "json", typeClass = JsonStringType.class)

Version complète de la classe d'entité:

@Entity
@Table(name = "some_table_name")
@TypeDef(name = "json", typeClass = JsonStringType.class)
public class MyCustomEntity implements Serializable {

   private static final long serialVersionUID = 1L;

   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   private Long id;

   @Type( type = "json" )
   @Column( columnDefinition = "json" )
   private List<String> jsonValue;
}

Je teste le code avec Spring Boot 1.5.9 et Hibernate-Types-5 1.2.0.

2
user1686407

réponse de Heril Muratovic est bon, mais je pense que le JsonToMapConverter devrait implémenter AttributeConverter<Map<String, Object>, String>, ne pas AttributeConverter<String, Map<String, Object>>. Voici le code qui fonctionne pour moi

@Slf4j
@Converter
public class JsonToMapConverter implements AttributeConverter<Map<String, Object>, String> {
    @Override
    @SuppressWarnings("unchecked")
    public Map<String, Object> convertToEntityAttribute(String attribute) {
        if (attribute == null) {
            return new HashMap<>();
        }
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.readValue(attribute, HashMap.class);
        } catch (IOException e) {
            log.error("Convert error while trying to convert string(JSON) to map data structure.", e);
        }
        return new HashMap<>();
    }

    @Override
    public String convertToDatabaseColumn(Map<String, Object> dbData) {
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(dbData);
        } catch (JsonProcessingException e) {
            log.error("Could not convert map to json string.", e);
            return null;
        }
    }
}
1
Songle Bao