Dúvida sobre persistência de objetos de uma classe Moeda

26 respostas
David

Pessoal, estou com uma dúvida aqui que deve ser idiota, mas eu não consegui encontrar informações suficientes no Google para resolvê-la. A dúvida surgiu após ler o artigo do Phillip na Mundo Java 17.

Imaginem que eu tenha uma classe Moeda, por exemplo, que tem dois atributos: um do tipo long e seria o número de centavos de uma determinada quantia em dinheiro (para evitar problemas de operações com ponto flutuante), o outro seria um objeto java.util.Currency que serveria para dizer em qual moeda aquela quantia está (Real, Dólar, Euro, etc.). Imaginem ainda uma classe Pedido que tem como um de seus atributos o valorTotal do pedido, do tipo Moeda.

A dúvida é: como se daria a persistência dessa moeda em um banco de dados? Na tabela pedido eu deveria ter uma coluna para a quantidade de centavos e outra para a moeda? Se sim e eu estiver usando o hibernate, como seria feito o mapeamento de Pedido? Se não, como eu deveria fazer? Desculpem se não fui claro.

Para finalizar, alguém já viu ou usou isso aqui? http://www.jvalue.org/

Editado: Não gostei do título, mas não consegui imaginar nada muito bom. Se alguém tiver alguma sugestão…

26 Respostas

rodrigoy

David,

Para não ter problemas de ponto flutuante você pode usar o BigDecimal. Sobre o mapeamento no hibernate:

http://www.hibernate.org/hib_docs/v3/reference/en/html/components.html

[]s

pcalcado

Oi,

Depende bastante. Como sua empresa(ou cliente) quer guardar valores monetários no banco de dados? Centavos e inteiro separados? Só centavos? um double?

Seja como for, acredito que um mapeamento de componente (linkado pelo Rodrigo) seja o que você procura. Value objects como moedas ou datas dificilmente vão ter uma tabela própria ou coisa do tipo, eles geralmente ficam na mesma tupla da Entity que o referencia.

David

No momento eu não estou fazendo nada no momento que precise disso, foi apenas uma dúvida que surgiu após a leitura do artigo. E eu acredito que mapeamento de componentes faça o que eu quero mesmo! Muito obrigado! :slight_smile:

O

Uma dúvida.
Se você trabalha com apenas duas casas é necessário usar o BigDecimal;
Já tive problemas com o double em operações de comparação.
Mas resolvi isto.
É recomendável usar double ?

pcalcado

Double não deve ser usado para matemática exata, não foi feito para isso. Até hoje 99% dos sistemas que trabalhei (excluindo apenas um fortemente baseado em estatística) não deveriam usar double. Provavelmente é o seu caso.

Use Bigdecimal ou uma classe de Moeda, como sugerido no artigo em questão.

David

quote=pcalcado não deveriam usar double. (…)[/quote]Não deveriam mas foi usado? Ou você realmente não usou? Se usou, o que fez para resolver os problemas?

Eu estou trabalhando num sistema em que o double não deveria ter sido usado, mas por falta de experiência nossa no início (o projeto tem mais de dois anos), ele foi. Estou tendo uns problemas chatos agora, pois uns relatórios financeiros ficam dando diferenças de 1 centavo para mais ou para menos, mas infelizmente estamos sem tempo para fazer um refactoring.

pcalcado

Foram usados. Se você olhar meu curriculum vai ver que nos últimos anos eu tenho praticamente refatorado sistemas de todos os tamanhos.

Refatorar de double pra Bigdecimal/Moeda não é fácil. É normal quando se falta experiência mas depois que se aprende uma vez é apra nunca mais usar mesmo. Lembro de um sistema de gerência de empréstimos que fiz (nad aa ver com o exemplo da revista, por acaso) que foi onde descobri sobre doubles. Simplesmente a cotna nunca batia com a do sistema que eu estava substituindo, eram poucos mas imperdoáveis centavos de diferença.

No meu projeto atual, além de refatorar dois sistemas eu estou criando um novo, que lida basicamente com vendas, e este não usa double, apenas Moeda.

O

Uma solução adotada em um dos projetos da minha empresa foi fazer instrumentação do código (bytecode enhancement). Desta forma foram utilizadas operações com double em código-fonte, mas na execução, as contas eram feitas com BigDecimal. Esta solução foi escolhida para não poluir o código, pois pode-se usar os operadores matemáticos em expressões complexas.

pcalcado

i.e. bytecode enhancement porque Java não tem sobrecarga de operador.

Bom, que tal especificar fórmulas complexas em Groovy? Na verdade a idéia pode evoluir para uma DSL.

David

Como isso seria feito, Phillip? Poderia dar um exemplo?

O

Essa classe Moeda seria uma classe personalizada ?
Alguém tem um exemplo de como fazer um mapeamento no hibernate para
uma classe Moeda personalizada ?

Obs: Sei qque posso encontrar no Google, mas gostaria de ouvir a opinião pessoal e a experiência de vocês.

Que ferramenta você usou para isto ?
Fiquei muito interessado.

F

Em um dos clientes que atendo o modulo de calculo foi todo feito com Jython e provavelmente iremos migrar para Groovy.

]['s

drix

Pessoal!
Qual o número da revista com o Artigo?

Poderiam postar o código fonte da classe Moeda?
Algum exemplo com bigDecimal? :wink:

David

Sim, ver abaixo.

Veja o post acima do rodrigoy sobre componentes no hibernate.

Não estou com a revista aqui mas foi essa última, cuja matéria de capa é a de TV Digital. A classe moeda seria algo assim:

public class Moeda {
    private long centavos;

    public Moeda(long centavos) {
        this.centavos = centavos;
    }

    public Moeda(long inteiro, int centavos) {
        this.centavos = inteiro * 100 + centavos;
    }

    public Moeda somarCom(Moeda m) {
        return new Moeda(this.centavos + m.centavos);
    }    

    // ... e por ai vai.

}

Eu pensei ainda em colocar um atributo java.util.Currency para dizer em qual moeda aquele valor estava, pois as vezes eu preciso trabalhar com moedas diferentes.

E sobre o BigDecimal: http://java.sun.com/j2se/1.5.0/docs/api/java/math/BigDecimal.html

rodrigoy

Este é um ótimo exemplo para demonstrar que um value object possui comportamentos (operações de negócio). Este pattern é confundido com o DTO.

O

Neste caso é iteressante armazenar o simbolo monetário u alguma informação dizendo qual a moeda utilizada ?
Como vocês fazem isto ?
Nunca trabalhei em um projeto que necessitasse disto.

David

Se você for trabalhar com moedas diferentes é necessário sim você dizer qual a moeda que vai usar. Eu pensei em um atributo do tipo java.util.Currency, como falei antes, mas não sei se seria viável ou como ele seria armazenado no banco.

sudeval

Galera vcs que trabalham com BigDecimal, um colega meu disse que o ejb nao dava “mapeamento” para BigDecimal e sim apenas para Double, ainda é assim ou ja tem suporte para BigDecimal tbem ? senão so da pra usar Double mesmo é isso ?

Ironlynx
Se você for trabalhar com moedas diferentes é necessário sim você dizer qual a moeda que vai usar. Eu pensei em um atributo do tipo java.util.Currency, como falei antes, mas não sei se seria viável ou como ele seria armazenado no banco.
Geralmente eu uso um código + ou - assim(pego em koders.com):
import java.math.BigDecimal;

/**
 * Object to store and manipulate money.
 */
public class Currency extends BigDecimal {
    /**
     * Construct a Currency object of value zero.
     */
    public Currency() {
        super(0);
    }

    /**
     * Construct a Currency object from a BigDecimal or Currency object.
     * A null value result in an object containing zero.
     */
    public Currency(BigDecimal value) {
        super((value == null) ? 0.0 : value.doubleValue());
    }
 
    /**
     * Construct a Currency object from a double.
     */
    public Currency(double value) {
        super(value);
    }
 
    /**
     * Construct a Currency object from a float.
     */
    public Currency(float value) {
        super((double)value);
    }
 
    /**
     * Construct a Currency object from a String.
     */
    public Currency(String value) {
        this(Double.valueOf(value).doubleValue());
    }

    /**
     * Check if equal to a float value.
     */
    public boolean equals(float value) {
        return compareTo(new Currency(value)) == 0;
    }

    /**
     * Check if equal to a double value.
     */
    public boolean equals(double value) {
        return compareTo(new Currency(value)) == 0;
    }

    /**
     * Returns a Currency whose value is (this + val).
     */
    public Currency add(Currency val) {
        return new Currency(super.add(val));
    }

    /**
     * Returns a Currency whose value is (this - val).
     */
    public Currency subtract(Currency val) {
        return new Currency(super.subtract(val));
    }

    /**
     * Returns a Currency whose value is (this * val)
     */
    public Currency multiply(Currency val){
        return new Currency(super.multiply(val));
    }
            
    /**
     * Returns a Currency whose value is (this * val)
     */
    public Currency multiply(int val){
        return multiply(new Currency(val));
    }
            
    /**
     * Returns a Currency whose value is (this / val).
     */
    public Currency divide(Currency val)
        throws ArithmeticException, IllegalArgumentException {
        return new Currency(super.divide(val, 2, ROUND_HALF_UP));
    }

    /**
     * Returns a Currency whose value is (this / val).
     */
    public Currency divide(int val)
        throws ArithmeticException, IllegalArgumentException {
        return divide(new Currency((double)val));
    }

    /**
     * Returns a Currency whose value is the absolute value of this
     * number.
     */
    public Currency absCurrency() {
        return (signum() < 0 ? negateCurrency() : this);
    }

    /**
     * Returns a Currency whose value is -1 * this.
     */
    public Currency negateCurrency() {
        return new Currency(negate());
    }

    /**
     * Convert to string with two decimals.
     */
    public String toString() {
        if (scale() == 2) {
            return super.toString();
        } else {
            return setScale(2, ROUND_HALF_UP).toString();
        }
    }
}
Mas não tem muito jeito se vc precisar de muuitas casas decimais e/ou mexer com double para alguma coisa...Uso sempre BigDecimal, mas custa em velocidade... Eu também tenho problemas quando eu penso no que(que tipo Monetário ou Numérico) inserir no banco quando o assunto é dinheiro.
BiraBoy

No caso da classe moeda blz. Todas as respostas dadas ao David se encaixam.

Mas andei relendo a matéria da Mundo Java 17 com o artigo do Phillip e pensei sobre a classe Categoria do exemplo. Ela é um Value Object (DDD, Evans), blz.

Mas pensando no banco, como persistir? Porque:

  • Pensando que são categorias repetitivas (uma categoria chamada “normal” com teto “1000” e máximo de parcelas “10” como no exemplo da revista)
  • E se categoria for desmembrada apenas em campos do banco da mesma tabela que possui essa categoria seguindo essa metodologia de mapeamento OR.

Então teremos um banco sem normalização.

É isso mesmo? Ou Categoria não é na verdade um Value Object?

Ou tem outro jeito de ter uma tabela para categoria e categoria não se repetir no banco e ao mesmo tempo ser Value Object?

pcalcado

Antes de mais nada não poste outra mensagem apenas para fazer up no seu post. O GUJ nãot em SLA e este tipo de atitude é desaconselhado pela moderação.

Você está confundindo conceitos sobre o que é um Domain Model e o que é persistência em um banco relacional. Ser um Value Object não diz como as instâncias serão persistidas (ou se serão persistidas) então a forma como você persiste os dados não influencia em algo ser um Value Object ou não.

Pelo que entendi do seu texto você pergunta se teríamos dois registros iguais na tabela. A resposta, como quase sempre, é depende do que você quer fazer, mas -novamente- não é porque você tem um value object que precisa ser assim. Nada impede que você tenha vários Value Objects absolutamente iguais em memória mas que sejam persistidos em apenas um registro no banco de dados.

A forma com que objetos são transformados em tabelas e vice-versa não influencia (teoricamente) em Domain-Driven Design.

Emerson_Macedo

@BiraBoy
Existe uma thread recente sobre esse assunto da persistência dos Value Objects. (Que inclusive você chegou a postar nele)

http://www.guj.com.br/posts/list/68206.java

@Phillip
Só para esclarecer, eu concordo com você que Persistência nada tem a ver com Domain Driven Design. Porém, quando estamos aprendendo DDD e vamos implementar algum projeto pra valer, chega na hora de persistir as classes geradas com os conceitos dessa abordagem e é normal que haja uma dúvida/confusão/falta de conhecimento. Aconteceu comigo recentemente e eu aprendi o que fazer. Então, acho que apesar de serem coisas distintas, quando em um projeto real pra quem ta começando no DDD é sempre bom esclarecer.

BiraBoy

Primeiramente me perdoem os moderadores pela avidez :oops: (ps: o que é SLA?)

Minha dúvida é bem específica. Voltando ao exemplo da classe Categoria do seu exemplo, Phillip.

Pensando que considero Categoria, na modelagem de domínio, um Value Object (DDD, Evans). E considerando que na minha solução específica quero que na persistência em banco ele fique numa tabela própria para que não se repita e fique assim normalizado.

Qual estratégia de mapeamento OR com hibernate se usa neste caso, nesta decisão?

Minha dúvida específica é essa.

Mais uma vez perdão :roll:

Ah emerleite é que eu quis aproveitar o tópico do David porque ele citou o exemplo da revista Mundo Java na qual surgiu minha dúvida :slight_smile:

pcalcado

Você pode utilizar um id artificial que só serve como índice da tabela e para fazer relacionamentos. Ser um Value Object não indica que a classe não vá ter uma chave primária quando persistida num banco de dados relacional.

BiraBoy

Então nesse caso, no hibernate eu deixo de mapear como component (que seria o caso da classe Moeda) e e mapeio como entidade normal, né?

Emerson_Macedo

No caso vai ser sua escolha.
Da uma lida no post que eu te passei que lá o Sergio explicou direitinho.

Criado 27 de junho de 2006
Ultima resposta 31 de out. de 2007
Respostas 26
Participantes 11