Mapas persistentes com o Hibernate

Mapas persistentes com o Hibernate

1. Introdução

No Hibernate, podemos representarone-to-many relationships em nossos beans Java, tendo um dos nossos campos aList.

Neste tutorial rápido, vamos explorar várias maneiras de fazer isso com umMap.

2. Maps são diferentes deLists

Usar umMap para representar um relacionamento um-para-muitos é diferente de umList porque temos uma chave.

Essa chave transforma nosso relacionamento de entidade em uma associaçãoternary, onde cada chave se refere a um valor simples ou um objeto embutido ou uma entidade. Por causa disso, para usar umMap, sempre precisaremos dea join table to store the foreign key that references the parent entity – the key, and the value.

Mas esta tabela de junção será um pouco diferente de outras tabelas de junção emthe primary key won’t necessarily be foreign keys to the parent and the target.. Em vez disso, teremos a chave primária como uma composição de uma chave estrangeira para o pai e uma coluna que é a chave para nossoMap.

O par de valores-chave emMap pode ser de dois tipos:Value Type and Entity Type. Nas seções a seguir, veremos as maneiras de representar essas associações no Hibernate.

3. Usando@MapKeyColumn

Digamos que temos uma entidadeOrder e queremos acompanhar o nome e o preço de todos os itens em um pedido. Então,we want to introduce a Map<String, Double> to Order which will map the item’s name to its price:

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue
    @Column(name = "id")
    private int id;

    @ElementCollection
    @CollectionTable(name = "order_item_mapping",
      joinColumns = {@JoinColumn(name = "order_id", referencedColumnName = "id")})
    @MapKeyColumn(name = "item_name")
    @Column(name = "price")
    private Map itemPriceMap;

    // standard getters and setters
}

We need to indicate to Hibernate where to get the key and the value. Para a chave,we’ve used @MapKey*Column*, indicando que a chaveMap é a colunaitem_name de nossa tabela de junção,order_item_mapping. Da mesma forma,@Column  especifica que o valorMap’s  corresponde à colunaprice da tabela de junção.

Além disso,itemPriceMap object é um mapa de tipo de valor, portanto, devemos usar a anotação@ElementCollection .

Além de objetos de tipo de valor básico, os objetos@Embeddable também podem ser usados ​​como os valores deMap de maneira semelhante.

4. Usando@MapKey

Como todos sabemos, os requisitos mudam ao longo do tempo - então, agora, digamos que precisamos armazenar mais alguns atributos deItem along comitemName anditemPrice:

@Entity
@Table(name = "item")
public class Item {
    @Id
    @GeneratedValue
    @Column(name = "id")
    private int id;

    @Column(name = "name")
    private String itemName;

    @Column(name = "price")
    private double itemPrice;

    @Column(name = "item_type")
    @Enumerated(EnumType.STRING)
    private ItemType itemType;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created_on")
    private Date createdOn;

    // standard getters and setters
}

Assim, vamos mudarMap<String, Double> paraMap<String, Item> na classeOrder entity:

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue
    @Column(name = "id")
    private int id;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "order_item_mapping",
      joinColumns = {@JoinColumn(name = "order_id", referencedColumnName = "id")},
      inverseJoinColumns = {@JoinColumn(name = "item_id", referencedColumnName = "id")})
    @MapKey(name = "itemName")
    private Map itemMap;

}

Note que desta vez, usaremos a anotação@MapKey para que o Hibernate useItem#itemName como a coluna chave do mapa ao invés de introduzir uma coluna adicional na tabela de junção. Portanto, neste caso,the join table *order_item_mapping*  * não tem uma coluna-chave * - em vez disso, ele se refere ao nome deItem.

Isso está em contraste com@MapKeyColumn. Whenwe use @MapKeyColumn, the map key resides in the join table. Esta é a razão pela qualwe can’t define our entity mapping using both the annotations in conjunction.

Além disso,itemMap  é um mapa de tipo de entidade, portanto, temos que anotar o relacionamento usando@OneToMany ou@ManyToMany.

5. Usando@MapKeyEnumerated e@MapKeyTemporal

Sempre que especificamos um enum como a chaveMap, usamos@MapKeyEnumerated. Da mesma forma, para valores temporais,@MapKeyTemporal é usado. O comportamento é bastante semelhante às anotações padrão@Enumeratede@Temporal, respectivamente.

Por padrão, eles são semelhantes a@MapKeyColumn naquelea key column will be created in the join table. Se quisermos reutilizar o valor já armazenado na entidade persistente, devemos marcar o campo adicionalmente com@MapKey.

6. Usando@MapKeyJoinColumn

Em seguida, digamos que também precisamos rastrear o vendedor de cada item. Uma maneira de fazer isso é adicionar uma entidadeSeller e ligá-la à nossa entidadeItem:

@Entity
@Table(name = "seller")
public class Seller {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private int id;

    @Column(name = "name")
    private String sellerName;

    // standard getters and setters

}
@Entity
@Table(name = "item")
public class Item {
    @Id
    @GeneratedValue
    @Column(name = "id")
    private int id;

    @Column(name = "name")
    private String itemName;

    @Column(name = "price")
    private double itemPrice;

    @Column(name = "item_type")
    @Enumerated(EnumType.STRING)
    private ItemType itemType;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created_on")
    private Date createdOn;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "seller_id")
    private Seller seller;

    // standard getters and setters
}

Nesse caso, vamos assumir que nosso caso de uso é agrupar todos osOrderItems porSeller. Hence, vamos mudarMap<String, Item> paraMap<Seller, Item>:

@Entity
@Table(name = "orders")
public class Order {
    @Id
    @GeneratedValue
    @Column(name = "id")
    private int id;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "order_item_mapping",
      joinColumns = {@JoinColumn(name = "order_id", referencedColumnName = "id")},
      inverseJoinColumns = {@JoinColumn(name = "item_id", referencedColumnName = "id")})
    @MapKeyJoinColumn(name = "seller_id")
    private Map sellerItemMap;

    // standard getters and setters

}

Precisamos adicionar@MapKeyJoinColumn para conseguir isso, pois essa anotação permite que o Hibernate mantenha a colunaseller_id (a chave do mapa) na tabela de junçãoorder_item_mapping junto com a colunaitem_id . Então, na hora de ler os dados do banco de dados, podemos realizar uma operaçãoGROUP BY facilmente.

7. Conclusão

Neste artigo, aprendemos sobre as várias maneiras de persistirMap no Hibernate dependendo do mapeamento necessário.

Como sempre, o código-fonte deste tutorial pode ser encontradoover Github.