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.


sexta-feira, 27 de outubro de 2017

Cuidado com Try/Catch no EJB e Spring!

Ao criarmos um método geralmente colocamos blocos de código dentro de Try/Catch para podermos tratar os possíveis erros e o sistema continuar seu trabalho. Este é talvez o principal problema de integridade e consistência de dados quando as transações são usadas. Veja o exemplo a seguir:

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public TradeData placeTrade(TradeData trade) throws Exception {
    try {
        insertTrade(trade);
        updateAcct(trade);
        return trade;
    } catch (Exception up) {
       //log the error
       throw up;
    }
}

Suponha que a conta não tenha fundos suficientes para comprar o estoque em questão ou não está configurado para comprar ou vender estoque ainda e lança uma exceção verificada (por exemplo, FundsNotAvailableException). A ordem comercial – insertTrade(trade) - persistiu no banco de dados ou a unidade lógica inteira do trabalho foi revertida? A resposta, surpreendentemente, é que, após uma exceção verificada (no Spring Framework ou EJB), a transação compromete qualquer trabalho que ainda não tenha sido comitado. Isso significa que se uma exceção verificada ocorrer durante o método updateAcct() , a ordem comercial foi persistida, mas a conta não foi atualizada para refletir a transação financeira.

As exceções de tempo de execução (ou seja, exceções não verificadas) forçam automaticamente a transação EJB/Spring a reverter completamente, mas as exceções verificadas não. Isso não é de todo ruim ou errado, pois muitas vezes precisamos tratar os erros e deixar o sistema prosseguir. Por exemplo, se em um processo, precisamos enviar um e-mail com as operações salvas no banco, mas o servidor de e-mail estiver fora, qual o mais importante? Salvar os dados ou enviar o e-mail? Então tratamos o envio do e-mail e deixamos os dados serem salvos.

Mas e quando for o caso em que precisarmos abortar tudo?

A anotação @TransactionAttribute encontrada na especificação EJB não inclui diretivas para especificar o comportamento de reversão. Em vez disso, você deve usar o método SessionContext.setRollbackOnly() para marcar a transação para rollback em um local específico, ou, o mais aconselhavel é criar uma exceção que force esse rollback com a anotação @ApplicationException setando  a propriedade rollback = true. Vamos ver primeiro ver como fazer a exceção funcionar apenas para um método:

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public TradeData placeTrade(TradeData trade) throws Exception {
    try {
       insertTrade(trade);
       updateAcct(trade);
       return trade;
    } catch (Exception up) {
        //log the error
        sessionCtx.setRollbackOnly();
        throw up;
    }
}

Uma vez que o método setRollbackOnly() é invocado, você não pode mudar de idéia; O único resultado possível é reverter a transação após a conclusão do método que iniciou a transação.

           Para pegar a sessão em um EJB faz-se daseguinte maneira:

@Resource
SessionContext context;
 

           A segunda opção que é criar uma Exceção e anotá-la segue abaixo:

@ApplicationException(rollback=true) 
public class RegraNegocioException extends Exception
{
   ...
}
 
Já no Spring Framework você especifica esse comportamento do rollback através do parâmetro rollbackFor na anotação @Transactional, conforme mostrado a seguir:

@Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
public TradeData placeTrade(TradeData trade) throws Exception {
    try {
           insertTrade(trade);
updateAcct(trade);
           return trade;
    } catch (Exception up) {
           //log the error
           throw up;
    }
}

Esses são problemas difíceis de serem detectados no momento do desenvolvimento e de testes, pois geralmente ocorrem quando o processamento da aplicação e o acesso ao banco de dados estão muito altos.

Mas atenção! Essa solução funciona se você tomar cuidado com as armadilhas do REQUIRED_NEW, como mostro nesse outro post:

http://olamundo-java.blogspot.com.br/2017/10/armadilhas-do-requirednew-transaction.html

        Espero que esse post ajude. A fonte pela qual me baseei para escrever esse post segue abaixo:



quarta-feira, 25 de outubro de 2017

Armadilhas do REQUIRED_NEW transaction no EJB e Spring

Pesquisando sobre um erro de duplicação de dados e problemas com transações no EJB encontrei essa explicação muito interessante. O atributo REQUIRED_NEW tanto no EJB quanto no Spring podem ter resultados inesperados e levar a dados corrompidos e inconsistentes. Isso pode ocorrer devido o fato de ele sempre iniciar uma nova transação quando o método é chamado, mesmo já existindo uma transação ou não.

Veja o Exemplo:

@Transactional(propagation=Propagation.REQUIRES_NEW)
public long insertTrade(TradeData trade) throws Exception
{
          ...
}

@Transactional(propagation=Propagation.REQUIRES_NEW)
public void updateAcct(TradeData trade) throws Exception
{
   ...
}

Nesse caso os 2 métodos são públicos e estão anotados com REQUIRED_NEW. Quando chamados de forma separada, não há problema. Porém, se um for usado os dois métodos dentro de uma mesma unidade de trabalho transacionada, como por exemplo um outro método, isso pode causar inconsistências. Por exemplo, digamos que dentro do método insertTrade() você chame o método updadeAcct(), se ocorrer um rolled back depois do chamada do updateAcct(), ele já vai ter efetuado o commit no banco de dados, sendo desfeito apenas o que pertence ao insertTrade().

@Transactional(propagation=Propagation.REQUIRES_NEW)
public long insertTrade(TradeData trade) throws Exception {
       em.persist(trade);
       updateAcct(trade);
      //exception occurs here! Trade rolled back mas updateAcct(trade) não!
      ...
}



Isso acontece porque uma nova transação é iniciada no método updateAcct() criando uma árvore de transações, e houve um commit quando ele terminou sua execução, independentemente do que ocorra em outra transação da árvore. Devido a esse comportamento, o atributo de transação REQUIRES_NEW deve ser usado somente se a ação do banco de dados no método que está sendo invocado precisa ser salva no banco de dados independentemente do resultado da transação do topo da árvore (como uma funcionalidade de auditoria por exemplo).

O ponto principal é sempre usar o atributo MANDATORY ou REQUIRED vez de REQUIRES_NEW menos que você tenha um motivo para usá-lo.

         Esses são problemas difíceis de serem detectados no momento do desenvolvimento e de testes, pois geralmente ocorrem quando o processamento da aplicação e o acesso ao banco de dados estão muito altos.

          Aconselho uma passada por esse outro post que deixo o link logo abaixo também, pois traz um outro problema que pode acarretar em erros semelhantes de inconsistência de dados na aplicação.


terça-feira, 17 de outubro de 2017

Removendo um elemento de uma Collection durante um laço "for" no Java

Erros simples as vezes nos fazem perder tempo. As vezes temos uma coleção de objetos e em determinado momento precisamos percorrer essa coleção e remover alguns objetos de dentro dela, porém somos surpreendidos com uma mensagem de erro. O foreach não permite que façamos isso, pois ele acaba se perdendo na iteração.

Para resolver o problema, use o Iterator do java.

private void preencheCarroComCabecalho(
            final List<BtpCarro> lstBtpCarros, BtpCabecalho btpCabecalho)
{
        for (final Iterator<BtpCarros> iterator = lstBtpCarros
                .iterator(); iterator.hasNext();)
        {
            final BtpCarro btpCarro = iterator
                    .next();
            if ( btpCarros.getCarSeqCarro() != null )
            {
                btpCarros.setMarca(cabecalho.getMarca());
                btpCarros.setAno(cabecalho.getAno());
                btpCarros.setCor(cabecalho.getCor());
            }
            else
            {
      iterator.remove();
            }
        }
}