Configurando valores padrão para colunas na JPA

245

É possível definir um valor padrão para colunas na JPA e se, como isso é feito usando anotações?

homaxto
fonte
está lá no Sepcification JPA onde eu posso colocar valor padrão para uma coluna como o valor para outra coluna, porque eu quero que ele seja escondido
shareef
3
Relacionado: O Hibernate possui @ org.hibernate.annotations.ColumnDefault ("foo")
Ondra Žižka

Respostas:

230

Na verdade, é possível na JPA, embora um pouco de hack usando a columnDefinitionpropriedade da @Columnanotação, por exemplo:

@Column(name="Price", columnDefinition="Decimal(10,2) default '100.00'")
Cameron Pope
fonte
3
10 é a parte inteira e 2 é a parte decimal do número, portanto: 1234567890.12 seria um número suportado.
18419 Nathan Feger
23
Observe também que essa colunaDefinition não é necessariamente independente do banco de dados e certamente não está automaticamente vinculada ao tipo de dados em Java.
18419 Nathan Feger
47
Depois de criar uma entidade com um campo nulo e persistir, você não terá o valor definido nele. Não é uma solução ...
Pascal Thivent
18
Não @NathanFeger, 10 é o comprimento e 2 a parte decimal. Portanto, 1234567890.12 não seria um número suportado, mas 12345678.90 é válido.
GPrimola
4
Você precisaria insertable=falsese a coluna fosse anulável (e para evitar o argumento desnecessário da coluna).
Eckes
314

Você pode fazer o seguinte:

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

Lá! Você acabou de usar zero como valor padrão.

Observe que isso será útil se você estiver acessando apenas o banco de dados a partir deste aplicativo. Se outros aplicativos também usarem o banco de dados, faça essa verificação no banco de dados usando o atributo de anotação columnDefinition de Cameron , ou de alguma outra maneira.

Pablo Venturino
fonte
38
Essa é a maneira correta de fazer isso, se você desejar que suas entidades tenham o valor correto após a inserção. +1
Pascal Thivent
16
Definir o valor padrão de um atributo anulável (um que tenha um tipo não primitivo) na classe de modelo interromperá as consultas de critérios que usam um Exampleobjeto como um protótipo para a pesquisa. Depois de definir um valor padrão, uma consulta de exemplo do Hibernate não ignorará mais a coluna associada, onde anteriormente a ignoraria porque era nula. Uma abordagem melhor é definir todos os valores padrão antes de chamar um Hibernate save()ou update(). Isso imita melhor o comportamento do banco de dados, que define valores padrão quando ele salva uma linha.
Derek Mahar
1
@ Harry: Esta é a maneira correta de definir valores padrão, exceto quando você estiver usando consultas de critérios que usam um exemplo como protótipo para a pesquisa.
Derek Mahar
12
Isso não garante que o padrão seja definido para tipos não primitivos (configuração, nullpor exemplo). Usando @PrePersiste @PreUpdateé uma opção melhor imho.
Jasper
3
Esta é a maneira correta de especificar o valor padrão para a coluna. columnDefinitionA propriedade não é independente do banco de dados e @PrePersistsubstitui sua configuração antes da inserção; "valor padrão" é outra coisa; o valor padrão é usado quando o valor não é definido explicitamente.
Utku Özdemir
110

outra abordagem é usar javax.persistence.PrePersist

@PrePersist
void preInsert() {
   if (this.createdTime == null)
       this.createdTime = new Date();
}
Husin Wijaya
fonte
1
Eu usei uma abordagem como esta para adicionar e atualizar registros de data e hora. Funcionou muito bem.
Spina
10
Isso não deveria ser if (createdt != null) createdt = new Date();ou algo assim? No momento, isso substituirá um valor especificado explicitamente, o que parece torná-lo realmente não padrão.
Max Nanasy
16
@MaxNanasyif (createdt == null) createdt = new Date();
Shane
Importa se o cliente e o banco de dados estiverem em fusos horários diferentes? Eu imagino que usar algo como "TIMESTAMP DEFAULT CURRENT_TIMESTAMP" obteria a hora atual no servidor de banco de dados e "createdt = new Date ()" obteria a hora atual no código java, mas essas duas vezes podem não ser as mesmas se o cliente está se conectando a um servidor em um fuso horário diferente.
FrustratedWithFormsDesigner
Adicionada a nullverificação.
Ondra Žižka
49

Em 2017, o JPA 2.1 ainda possui apenas @Column(columnDefinition='...')para o qual você coloca a definição SQL literal da coluna. O que é bastante flexível e obriga a declarar também outros aspectos, como tipo, causando um curto-circuito na visão da implementação da JPA sobre esse assunto.

Hibernate, porém, tem o seguinte:

@Column(length = 4096, nullable = false)
@org.hibernate.annotations.ColumnDefault("")
private String description;

Identifica o valor PADRÃO a ser aplicado à coluna associada via DDL.

Duas notas para isso:

1) Não tenha medo de ficar fora do padrão. Trabalhando como desenvolvedor do JBoss, já vi alguns processos de especificação. A especificação é basicamente a linha de base que os grandes players em determinado campo estão dispostos a comprometer-se a apoiar na próxima década. É verdade para segurança, para mensagens, ORM não faz diferença (embora o JPA cubra bastante). Minha experiência como desenvolvedor é que, em um aplicativo complexo, mais cedo ou mais tarde você precisará de uma API não padrão. E @ColumnDefaulté um exemplo quando supera os negativos do uso de uma solução não padrão.

2) É bom como todo mundo acena a inicialização do @PrePersist ou membro do construtor. Mas isso não é o mesmo. E as atualizações SQL em massa? E as instruções que não definem a coluna? DEFAULTtem sua função e isso não é substituível ao inicializar um membro da classe Java.

Ondra Žižka
fonte
Que versão das anotações de hibernação posso usar para o Wildfly 12, eu pego um erro com uma versão específica disso? Graças ind adiantado!
Fernando Pie
Obrigado Ondra. Existem mais soluções em 2019?
Alan
1
@ Alan não está em estoque JPA, infelizmente.
jwenting
13

O JPA não suporta isso e seria útil se o fizesse. O uso de columnDefinition é específico do banco de dados e não é aceitável em muitos casos. definir um padrão na classe não é suficiente quando você recupera um registro com valores nulos (o que normalmente acontece quando você executa novamente os testes antigos do DBUnit). O que eu faço é isso:

public class MyObject
{
    int attrib = 0;

    /** Default is 0 */
    @Column ( nullable = true )
    public int getAttrib()

    /** Falls to default = 0 when null */
    public void setAttrib ( Integer attrib ) {
       this.attrib = attrib == null ? 0 : attrib;
    }
}

O boxe automático em Java ajuda muito nisso.

Marco
fonte
Não abuse dos setters! Eles são para definir um parâmetro em um campo de objeto. Nada mais! Melhor usar o construtor padrão para valores padrão.
Roland
@Roland agradável em teoria, mas nem sempre funciona quando se lida com um banco de dados existente que já contém valores nulos nos quais seu aplicativo gostaria de não tê-los. Você precisaria fazer uma conversão de banco de dados primeiro em que a coluna não seja nula e defina um padrão sensato ao mesmo tempo e, em seguida, lide com a folga de outros aplicativos que assumem que ela é de fato anulável (ou seja, se você pode até modificar o banco de dados).
jwenting
Se se tratar de #JPA e setters / getters, eles sempre devem ser purificadores / getter, caso contrário, um dia seu aplicativo cresceu e cresceu e se torna um pesadelo de manutenção. O melhor conselho é o BEIJO aqui (eu não sou gay, LOL).
Roland
10

Vendo como eu me deparei com isso no Google enquanto tentava resolver o mesmo problema, vou apenas apresentar a solução que preparei para o caso de alguém achar útil.

Do meu ponto de vista, existem apenas 1 soluções para esse problema - @PrePersist. Se você fizer isso no @PrePersist, precisará verificar se o valor já foi definido.

TC1
fonte
4
+1 - Eu definitivamente optaria pelo @PrePersistcaso de uso do OP. @Column(columnDefinition=...)não parece muito elegante.
Piotr Nowicki
O @PrePersist não ajudará se já houver dados na loja com valores nulos. Magicamente não fará uma conversão de banco de dados para você.
Jwenting
10
@Column(columnDefinition="tinyint(1) default 1")

Acabei de testar o problema. Funciona muito bem. Obrigado pela dica.


Sobre os comentários:

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

Este não define o valor padrão da coluna no banco de dados (é claro).

asd
fonte
9
Ele não funciona bem no nível do objeto (você não obterá o valor padrão do banco de dados após uma inserção em sua entidade). Padrão no nível Java.
28810 Pascal Thivent
Funciona (especialmente se você especificar insertable = false no banco de dados, mas infelizmente a versão em cache não é atualizada (nem mesmo quando o JPA seleciona a tabela e as colunas) .Pelo menos não com o hibernate: - / ida e volta é ruim.
eckes 7/08/2015
9

você pode usar o java reflect api:

    @PrePersist
    void preInsert() {
       PrePersistUtil.pre(this);
    }

Isso é comum:

    public class PrePersistUtil {

        private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");


        public static void pre(Object object){
            try {
                Field[] fields = object.getClass().getDeclaredFields();
                for(Field field : fields){
                    field.setAccessible(true);
                    if (field.getType().getName().equals("java.lang.Long")
                            && field.get(object) == null){
                        field.set(object,0L);
                    }else if    (field.getType().getName().equals("java.lang.String")
                            && field.get(object) == null){
                        field.set(object,"");
                    }else if (field.getType().getName().equals("java.util.Date")
                            && field.get(object) == null){
                        field.set(object,sdf.parse("1900-01-01"));
                    }else if (field.getType().getName().equals("java.lang.Double")
                            && field.get(object) == null){
                        field.set(object,0.0d);
                    }else if (field.getType().getName().equals("java.lang.Integer")
                            && field.get(object) == null){
                        field.set(object,0);
                    }else if (field.getType().getName().equals("java.lang.Float")
                            && field.get(object) == null){
                        field.set(object,0.0f);
                    }
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
Thomas Zhang
fonte
2
Eu gosto dessa solução, mas tenho um ponto fraco, acho que seria importante verificar se a coluna é anulável ou não antes de definir o valor padrão.
Luis Carlos
Se preferir, colocar o código Field[] fields = object.getClass().getDeclaredFields();no também for()pode ser bom. E também adicione finalao seu parâmetro / exceções capturadas, pois você não deseja objectser modificado por acidente. Além disso, adicione um controlo sobre null: if (null == object) { throw new NullPointerException("Parameter 'object' is null"); }. Isso garante que object.getClass()é seguro invocar e não dispara a NPE. O motivo é evitar erros de programadores preguiçosos. ;-)
Roland
7

Eu uso columnDefinitione funciona muito bem

@Column(columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")

private Date createdDate;
língua
fonte
6
Parece que isso torna seu fornecedor de implementação jpa específico.
Udo Realizada
Torna apenas o fornecedor DDL específico. Mas você provavelmente tem hacks DDL específicos do fornecedor de qualquer maneira. No entanto, como mencionado acima, o valor (mesmo quando inserível = false) aparece no banco de dados, mas não no cache da entidade da sessão (pelo menos não no hibernate).
Eckes
7

Você não pode fazer isso com a anotação da coluna. Eu acho que a única maneira é definir o valor padrão quando um objeto é criado. Talvez o construtor padrão seja o lugar certo para fazer isso.

Timo
fonte
Boa ideia. Infelizmente, não há anotações ou atributos genéricos para os @Columnpróximos. E também sinto falta de comentários sendo definidos (tirados do Java doctag).
Roland
5

No meu caso, modifiquei o código-fonte do núcleo de hibernação, bem, para introduzir uma nova anotação @DefaultValue:

commit 34199cba96b6b1dc42d0d19c066bd4d119b553d5
Author: Lenik <xjl at 99jsj.com>
Date:   Wed Dec 21 13:28:33 2011 +0800

    Add default-value ddl support with annotation @DefaultValue.

diff --git a/hibernate-core/src/main/java/org/hibernate/annotations/DefaultValue.java b/hibernate-core/src/main/java/org/hibernate/annotations/DefaultValue.java
new file mode 100644
index 0000000..b3e605e
--- /dev/null
+++ b/hibernate-core/src/main/java/org/hibernate/annotations/DefaultValue.java
@@ -0,0 +1,35 @@
+package org.hibernate.annotations;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Specify a default value for the column.
+ *
+ * This is used to generate the auto DDL.
+ *
+ * WARNING: This is not part of JPA 2.0 specification.
+ *
+ * @author 谢继雷
+ */
+@java.lang.annotation.Target({ FIELD, METHOD })
+@Retention(RUNTIME)
+public @interface DefaultValue {
+
+    /**
+     * The default value sql fragment.
+     *
+     * For string values, you need to quote the value like 'foo'.
+     *
+     * Because different database implementation may use different 
+     * quoting format, so this is not portable. But for simple values
+     * like number and strings, this is generally enough for use.
+     */
+    String value();
+
+}
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java
index b289b1e..ac57f1a 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3Column.java
@@ -29,6 +29,7 @@ import org.hibernate.AnnotationException;
 import org.hibernate.AssertionFailure;
 import org.hibernate.annotations.ColumnTransformer;
 import org.hibernate.annotations.ColumnTransformers;
+import org.hibernate.annotations.DefaultValue;
 import org.hibernate.annotations.common.reflection.XProperty;
 import org.hibernate.cfg.annotations.Nullability;
 import org.hibernate.mapping.Column;
@@ -65,6 +66,7 @@ public class Ejb3Column {
    private String propertyName;
    private boolean unique;
    private boolean nullable = true;
+   private String defaultValue;
    private String formulaString;
    private Formula formula;
    private Table table;
@@ -175,7 +177,15 @@ public class Ejb3Column {
        return mappingColumn.isNullable();
    }

-   public Ejb3Column() {
+   public String getDefaultValue() {
+        return defaultValue;
+    }
+
+    public void setDefaultValue(String defaultValue) {
+        this.defaultValue = defaultValue;
+    }
+
+    public Ejb3Column() {
    }

    public void bind() {
@@ -186,7 +196,7 @@ public class Ejb3Column {
        }
        else {
            initMappingColumn(
-                   logicalColumnName, propertyName, length, precision, scale, nullable, sqlType, unique, true
+                   logicalColumnName, propertyName, length, precision, scale, nullable, sqlType, unique, defaultValue, true
            );
            log.debug( "Binding column: " + toString());
        }
@@ -201,6 +211,7 @@ public class Ejb3Column {
            boolean nullable,
            String sqlType,
            boolean unique,
+           String defaultValue,
            boolean applyNamingStrategy) {
        if ( StringHelper.isNotEmpty( formulaString ) ) {
            this.formula = new Formula();
@@ -217,6 +228,7 @@ public class Ejb3Column {
            this.mappingColumn.setNullable( nullable );
            this.mappingColumn.setSqlType( sqlType );
            this.mappingColumn.setUnique( unique );
+           this.mappingColumn.setDefaultValue(defaultValue);

            if(writeExpression != null && !writeExpression.matches("[^?]*\\?[^?]*")) {
                throw new AnnotationException(
@@ -454,6 +466,11 @@ public class Ejb3Column {
                    else {
                        column.setLogicalColumnName( columnName );
                    }
+                   DefaultValue _defaultValue = inferredData.getProperty().getAnnotation(DefaultValue.class);
+                   if (_defaultValue != null) {
+                       String defaultValue = _defaultValue.value();
+                       column.setDefaultValue(defaultValue);
+                   }

                    column.setPropertyName(
                            BinderHelper.getRelativePath( propertyHolder, inferredData.getPropertyName() )
diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java
index e57636a..3d871f7 100644
--- a/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java
+++ b/hibernate-core/src/main/java/org/hibernate/cfg/Ejb3JoinColumn.java
@@ -423,6 +424,7 @@ public class Ejb3JoinColumn extends Ejb3Column {
                getMappingColumn() != null ? getMappingColumn().isNullable() : false,
                referencedColumn.getSqlType(),
                getMappingColumn() != null ? getMappingColumn().isUnique() : false,
+               null, // default-value
                false
        );
        linkWithValue( value );
@@ -502,6 +504,7 @@ public class Ejb3JoinColumn extends Ejb3Column {
                getMappingColumn().isNullable(),
                column.getSqlType(),
                getMappingColumn().isUnique(),
+               null, // default-value
                false //We do copy no strategy here
        );
        linkWithValue( value );

Bem, esta é uma solução apenas para hibernação.

Xiè Jìléi
fonte
2
Embora eu aprecie o esforço das pessoas que contribuem ativamente para o projeto de código-fonte aberto, diminuí a votação desta resposta porque o JPA é uma especificação Java padrão sobre qualquer OR / M, o OP solicitou uma maneira do JPA para especificar valores padrão e seu patch funciona somente para o Hibernate. Se esse fosse o NHibernate, no qual não há especificação super partes para persistência (o NPA nem mesmo é suportado pelo MS EF), eu teria votado positivamente nesse tipo de patch. A verdade é que o JPA é bastante limitado comparado aos requisitos do ORM (um exemplo: sem índices secundários). De qualquer forma, parabéns pelo esforço
usr-local-
Isso não cria um problema de manutenção? Seria necessário refazer essas alterações sempre que hibernar atualizações ou em todas as novas instalações?
User1242321
Desde a pergunta é sobre JPA e Hibernate não é sequer mencionado, isso não responder à pergunta
Neil Stockton
5
  1. @Column(columnDefinition='...') não funciona quando você define a restrição padrão no banco de dados enquanto insere os dados.
  2. Você precisa fazer insertable = falsee remover columnDefinition='...'da anotação, para que o banco de dados insira automaticamente o valor padrão do banco de dados.
  3. Por exemplo, quando você define o sexo varchar é masculino por padrão no banco de dados.
  4. Você só precisa adicionar o insertable = falseHibernate / JPA, ele funcionará.
Appesh
fonte
Boa resposta. E aqui é explicar-sobre-inserível-falsa atualizável-falsa
Shihe Zhang
E não é uma boa opção, se você quiser definir seu valor algumas vezes.
Shihe Zhang
@ShiheZhang usando false não me permite definir o valor?
fvildoso 6/11/19
3
@PrePersist
void preInsert() {
    if (this.dateOfConsent == null)
        this.dateOfConsent = LocalDateTime.now();
    if(this.consentExpiry==null)
        this.consentExpiry = this.dateOfConsent.plusMonths(3);
}

No meu caso, devido ao campo ser LocalDateTime, usei isso, é recomendado devido à independência do fornecedor

Mohammed Rafeeq
fonte
2

As anotações JPA e Hibernate não suportam a noção de um valor padrão da coluna. Como solução alternativa para essa limitação, defina todos os valores padrão imediatamente antes de chamar um Hibernate save()ou update()na sessão. O mais próximo possível (menos que o Hibernate defina os valores padrão) imita o comportamento do banco de dados, que define valores padrão quando ele salva uma linha em uma tabela.

Ao contrário de definir os valores padrão na classe de modelo, como sugere esta resposta alternativa , essa abordagem também garante que as consultas de critérios que usam um Exampleobjeto como um protótipo para a pesquisa continuem funcionando como antes. Quando você define o valor padrão de um atributo anulável (um que tenha um tipo não primitivo) em uma classe de modelo, uma consulta por exemplo do Hibernate não ignorará mais a coluna associada, onde anteriormente o ignoraria porque era nulo.

Derek Mahar
fonte
Na solução anterior, o autor mencionou ColumnDefault ("")
nikolai.serdiuk
@ nikolai.serdiuk que a anotação ColumnDefault foi adicionada anos depois que esta resposta foi escrita. Em 2010, estava correto, não havia essa anotação (na verdade, não havia anotações, apenas configuração xml).
jwenting
1

Isso não é possível no JPA.

Aqui está o que você pode fazer com a anotação de coluna: http://java.sun.com/javaee/5/docs/api/javax/persistence/Column.html

PEELY
fonte
1
Não poder especificar um valor padrão parece uma falha grave das anotações da JPA.
Derek Mahar
2
O JDO permite isso em sua definição de ORM, portanto, o JPA deve incluir isso ... um dia
user383680
Você definitivamente pode fazê-lo, com o atributo columnDefinition, como Cameron Pope respondeu.
IntelliData 19/02
@DerekMahar não é o único déficit na especificação JPA. É uma boa especificação, mas não é perfeita
jwenting 04/04/19
1

Se você estiver usando um duplo, poderá usar o seguinte:

@Column(columnDefinition="double precision default '96'")

private Double grolsh;

Sim, é db específico.

Gal Bracha
fonte
0

Você pode definir o valor padrão no designer de banco de dados ou quando criar a tabela. Por exemplo, no SQL Server, você pode definir o cofre padrão de um campo Data para ( getDate()). Use insertable=falseconforme mencionado na sua definição de coluna. A JPA não especificará essa coluna nas inserções e o banco de dados gerará o valor para você.

Dave Anderson
fonte
-2

Você precisa de insertable=falsevocê@Column anotá- . A JPA ignorará a coluna ao inserir no banco de dados e o valor padrão será usado.

Consulte este link: http://mariemjabloun.blogspot.com/2014/03/resolved-set-database-default-value-in.html

MariemJab
fonte
2
Então como você irá inserir o valor dado @runtime ??
Ashish Ratan
Sim, é verdade. nullable=falsefalhará com um SqlException: Caused by: java.sql.SQLException: Column 'opening_times_created' cannot be null. Aqui eu esqueci de definir o timestamp "criado" com openingTime.setOpeningCreated(new Date()). É uma boa maneira de ter consistência, mas é isso que o questionador não estava perguntando.
Roland