terça-feira, 12 de dezembro de 2017

Converters do JPA

O JPA 2.1 trouxe conversores que nos permitem especificar como mapear tipos de dados. Um conversor deve implementar a interface javax.persistence.AttributeConverter<X,Y>, passando a classe da entidade em X e classe do banco de dados em Y. O conversor também precisa ser anotado com javax.persistence.Converter.

Ao implementar essa interface, dois métodos são requeridos: o convertToDatabaseCollumn que é usado pelo JPA para persistir o atributo no banco de dados e o convertToEntityAtribute que é usado para trazer os dados do banco para o Java.

Os conversores funcionam basicamente como os conversores do JSF, e podem ser usados de 2 maneiras, com o atributo autoApplay=true na anotação @Converter, fazendo com que o conversor seja aplicado automaticamente em todos os casos, ou aplicando o conversor diretamente na propriedade nas entidades - @Convert(Converter = NomeDaClasse.class) – conforme necessidade.

          Podem ser usados para mapear novos tipo de dados que o JPA ainda não mapeia sozinho como é o caso do LocalDate e LocalDateTime, mapear Enums, quando se quer por exemplo, guardar um atributo do enum ao invés da sua representação ORDINAL ou STRING e até para criptografia, como vou mostrar em um outro post.

          Como exemplo vou me deter ao converter para o LocalDate e um converter para guardar uma propriedade de um Enum. Vamos ao primeiro exemplo:

@Entity
public class Pedido {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id", updatable = false, nullable = false)
        private Long id;
       
        @Column
        private LocalDate dataPrevisaoEntrega;
                
        ...
       
}

          Segue o conversor:

@Converter(autoApply = true)
public class LocalDateConverter implements AttributeConverter<LocalDate, Date> {
       
    @Override
    public Date convertToDatabaseColumn(LocalDate localDate) {
        return (localDate == null ? null : Date.valueOf(localDate));
    }

    @Override
    public LocalDate convertToEntityAttribute(Date sqlDate) {
        return (sqlDate == null ? null : sqlDate.toLocalDate());
    }
}

         
          Perceba que no exemplo, na anotação @Converter foi passado o atributo autoApplay=true. Isso faz com que o conversor seja aplicado automaticamente em todos os casos. Quando esse atributo é falso, você fica responsável por informar ao JPA sempre que desejar usá-lo, anotando a propriedade com @Convert e passando a classe do conversor como segue:

@Column
@Convert(Converter = LocalDateConverter.class)
private LocalDate dataPrevisaoEntrega;

          Vamos para o segundo exemplo, onde temos o Enum, o Converter e como aplicá-lo:      

public enum Veiculo {
   
    BUS("B"), CAR("C"), TRAIN("T"), PLANE("P");

    private String codigo;

    private Veiculo(String codigo) {
        this.codigo = codigo;
    }

    public String getCodigo() {
        return codigo;
    }

    public static Veiculo fromCodigo(String codigo) {
        switch (codigo) {
        case "B":
            return Veiculo.BUS;

        case "C":
            return Veiculo.CAR;

        case "T":
            return Veiculo.TRAIN;

        case "P":
            return Veiculo.PLANE;

        default:
            throw new IllegalArgumentException("codigo [" + codigo
                    + "] não suportado.");
        }
    }
}
         
          O converter para o Enum segue abaixo:

@Converter(autoApply = true)
public class VeiculoConverter implements AttributeConverter<Veiculo, String> {

    @Override
    public String convertToDatabaseColumn(Veiculo Veiculo) {
        return Veiculo.getCodigo();
    }

    @Override
    public Veiculo convertToEntityAttribute(String dbData) {
        return Veiculo.fromCodigo(dbData);
    }

}
         
          E então basta usar o enum:

@Entity
public class Viagem {
    
    private Veiculo veiculo;

    ...
}

          Pronto, sempre que precisarmos especificar como uma classe deve ser salva no banco podemos usar os conversores. Vou compartilhar um site muito bom para quem quer se aprofundar no JPA e no qual me baseei para este artigo:


segunda-feira, 11 de dezembro de 2017

Salvando LocalDate e LocalDateTime com JPA 2.1

Quando o JPA 2.1 foi lançado o Java 8 ainda não tinha saído, então algumas features ficaram prejudicadas. Porém para nossa felicidade, o JPA 2.1 tem conversores que nos permitem dizer ao JPA como mapear tipos de dados. A seguir vou mostrar como mapear então o LocalDate e o LocalDateTime:

@Entity
public class Pedido {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "id", updatable = false, nullable = false)
        private Long id;
       
        @Column
        private LocalDate dataPrevisaoEntrega;
       
        @Column
        private LocalDateTime dataPedido;
       
        ...
       
}

          Um conversor deve implementar a interface javax.persistence.AttributeConverter<X,Y>, passando a classe da entidade em X e classe do banco de dados em Y. O conversor também precisa ser anotado com javax.persistence.Converter. Seguem os conversores:

@Converter(autoApply = true)
public class LocalDateConverter implements AttributeConverter<LocalDate, Date> {
       
    @Override
    public Date convertToDatabaseColumn(LocalDate localDate) {
        return (localDate == null ? null : Date.valueOf(localDate));
    }

    @Override
    public LocalDate convertToEntityAttribute(Date sqlDate) {
        return (sqlDate == null ? null : sqlDate.toLocalDate());
    }
}

@Converter(autoApply = true)
public class LocalDateTimeConverter implements AttributeConverter<LocalDateTime, Timestamp> {
       
    @Override
    public Timestamp convertToDatabaseColumn(LocalDateTime localDateTime) {
        return (localDateTime == null ? null : Timestamp.valueOf(localDateTime));
    }

    @Override
    public LocalDateTime convertToEntityAttribute(Timestamp sqlTimestamp) {
        return (sqlTimestamp == null ? null : sqlTimestamp.toLocalDateTime());
    }
}
         
          Perceba que na anotação @Converter foi passado o atributo autoApplay=true. Isso faz com que o conversor seja aplicado automaticamente em todos os casos. Quando esse atributo é falso, você fica responsável por informar ao JPA sempre que desejar usá-lo, anotando a propriedade com @Convert e passando a classe do conversor como segue:

@Column
@Convert(Converter = LocalDateConverter.class)
private LocalDate dataPrevisaoEntrega;

          Pronto, podemos trabalhar com as novas classes do Java 8 de forma simples e prática. Vou compartilhar um site muito bom para quem quer se aprofundar no JPA e no qual me baseei para este artigo:

terça-feira, 5 de dezembro de 2017

OneToMany - JPA

Temos o relacionamento OneToMany quando uma entidade aponta para uma lista de elementos de determinado tipo. Esse relacionamento pode ser implementado de forma unidirecional ou bidirecional dependendo da situação, e cada uma dessas formas tem suas vantagens e desvantagens.

No relacionamento unidirecional a implementação é mais simples, porém o acesso se dá por padrão e para efeitos de CRUD pela entidade que contém a lista. Abaixo temos a representação de um relacionamento OneToMany unidirecional:



Para implementarmos esse exemplo temos:

@Entity
public class Carro
{
          // outras propriedades da classe Carro
          ...

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "acessorio_id")
private List<Acessorio> lstAcessorio;

          // outras propriedades e métodos da classe Carro
          ...
}

          Não há uma propriedade do tipo Carro na classe Acessorio. Todas as operações do CRUD são feitas a partir da classe Carro. O JPA lida com isso muito bem e não há nada que precisemos fazer.

          Porém existem casos que o acesso precisa ser bidirecional, e isso torna as coisas um pouco mais trabalhosas. Veja o diagrama abaixo:




          Nesse relacionamento, eu tenho uma lista de itens dentro do pedido e cada item do pedido aponta para o pedido o qual faz parte. Como abaixo:

@Entity
public class Pedido
{
          // outras propriedades da classe Pedido
          ...

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "pedido")
@JoinColumn(name = "item_pedido_id")
private List<ItemPedido> lstItemPedido;
          // outras propriedades e métodos da classe Pedido
          ...
}
@Entity
public class ItemPedido
{
          // outras propriedades da classe ItemPedido
          ...

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "pedido_id")
private Pedido pedido;
          // outras propriedades e métodos da classe ItemPedido
          ...
}

Note que foi adicionado a propriedade mappedBy = "pedido" na anotação da lista de itens na classe Pedido. Quando ocorre o relacionamento bidirecional, o JPA começa a ter alguns problemas para garantir a integridade dos dados na hora de criar e remover os objetos da lista, pois ele não consegue sozinho adicionar em cada elemento da lista de itens o pedido a qual ele pertence. É preciso que nós cuidemos dessa parte. Devemos fazer com que toda vez que formos adicionar um novo item a lista de itens do pedido, o pedido seja também adicionado ao item da lista. Existe basicamente dois locais para fazermos isso, no Front Controller ou na entidade que contém a lista. Vou mostrar o exemplo dos métodos que podem ser adicionados a entidade Pedido.

public ItemPedido addItemPedido ( ItemPedido itemPedido ) {
     getItemPedido().add( itemPedido );
     itemPedido.setPedido( this );

     return itemPedido;
}

public ItemPedido removeItemPedido ( ItemPedido itemPedido ) {
     getItemPedido().remove( itemPedido );
     itemPedido.setPedido( null );

     return itemPedido;
}
          
        Sempre que formos adicionar ou remover um item da lista, devemos fazer isso usando a instancia do pedido, e claro com a lista já devidamente instanciada.

Pronto, dessa forma temos um relacionamento OneToMany bidirecional.