Définition des valeurs par défaut des colonnes dans JPA

245

Est-il possible de définir une valeur par défaut pour les colonnes dans JPA, et si, comment cela se fait-il à l'aide d'annotations?

homaxto
la source
existe-t-il dans la spécification JPA où puis-je mettre la valeur par défaut pour une colonne comme valeur pour une autre colonne, parce que je veux qu'elle soit cachée
shareef
3
Connexes: Hibernate a @ org.hibernate.annotations.ColumnDefault ("foo")
Ondra Žižka

Réponses:

230

En fait, c'est possible dans JPA, bien qu'un peu de piratage utilise la columnDefinitionpropriété de l' @Columnannotation, par exemple:

@Column(name="Price", columnDefinition="Decimal(10,2) default '100.00'")
Cameron Pope
la source
3
10 est la partie entière et 2 est la partie décimale du nombre, donc: 1234567890.12 serait un nombre pris en charge.
Nathan Feger
23
Notez également que cette définition de colonne n'est pas nécessairement indépendante de la base de données et n'est certainement pas liée automatiquement au type de données en Java.
Nathan Feger
47
Après avoir créé une entité avec un champ nul et l'avoir persisté, vous n'auriez pas défini la valeur. Pas une solution ...
Pascal Thivent
18
Non @NathanFeger, 10 est la longueur et 2 la partie décimale. Donc, 1234567890.12 ne serait pas un numéro pris en charge, mais 12345678.90 est valide.
GPrimola
4
Vous auriez besoin insertable=falsesi la colonne est nullable (et pour éviter l'argument de colonne inutile).
eckes
314

Vous pouvez effectuer les opérations suivantes:

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

Là! Vous venez d'utiliser zéro comme valeur par défaut.

Notez que cela vous servira si vous accédez uniquement à la base de données à partir de cette application. Si d'autres applications utilisent également la base de données, vous devez effectuer cette vérification à partir de la base de données à l'aide de l' attribut d' annotation columnDefinition de Cameron , ou d'une autre manière.

Pablo Venturino
la source
38
C'est la bonne façon de le faire si vous souhaitez que vos entités aient la valeur correcte après l'insertion. +1
Pascal Thivent
16
La définition de la valeur par défaut d'un attribut nullable (celui qui a un type non primitif) dans la classe de modèle interrompra les requêtes de critères qui utilisent un Exampleobjet comme prototype pour la recherche. Après avoir défini une valeur par défaut, un exemple de requête Hibernate n'ignorera plus la colonne associée là où auparavant elle l'ignorerait car elle était nulle. Une meilleure approche consiste à définir toutes les valeurs par défaut juste avant d'appeler un Hibernate save()ou update(). Cela imite mieux le comportement de la base de données qui définit les valeurs par défaut lors de l'enregistrement d'une ligne.
Derek Mahar
1
@Harry: c'est la bonne façon de définir les valeurs par défaut, sauf lorsque vous utilisez des requêtes de critères qui utilisent un exemple comme prototype pour la recherche.
Derek Mahar
12
Cela ne garantit pas que la valeur par défaut est définie pour les types non primitifs (paramètre nullpar exemple). Utiliser @PrePersistet @PreUpdateest une meilleure option à mon humble avis.
Jasper
3
C'est la bonne façon de spécifier la valeur par défaut de la colonne. columnDefinitionLa propriété n'est pas indépendante de la base de données et @PrePersistremplace votre paramètre avant l'insertion, "valeur par défaut" est autre chose, la valeur par défaut est utilisée lorsque la valeur n'est pas définie explicitement.
Utku Özdemir
110

une autre approche utilise javax.persistence.PrePersist

@PrePersist
void preInsert() {
   if (this.createdTime == null)
       this.createdTime = new Date();
}
Husin Wijaya
la source
1
J'ai utilisé une approche comme celle-ci pour ajouter et mettre à jour des horodatages. Cela a très bien fonctionné.
Spina
10
Cela ne devrait-il pas être if (createdt != null) createdt = new Date();quelque chose? À l'heure actuelle, cela remplacera une valeur explicitement spécifiée, ce qui semble ne pas être vraiment une valeur par défaut.
Max Nanasy
16
@MaxNanasyif (createdt == null) createdt = new Date();
Shane
Serait-il important que le client et la base de données soient dans des fuseaux horaires différents? J'imagine que l'utilisation de quelque chose comme "TIMESTAMP DEFAULT CURRENT_TIMESTAMP" obtiendrait l'heure actuelle sur le serveur de base de données, et "createdt = new Date ()" obtiendrait l'heure actuelle dans le code java, mais ces deux heures pourraient ne pas être les mêmes si le client se connecte à un serveur dans un fuseau horaire différent.
FrustratedWithFormsDesigner
Ajout du nullchèque.
Ondra Žižka
49

En 2017, JPA 2.1 n'a encore @Column(columnDefinition='...') de la définition littérale SQL de la colonne. Ce qui est assez peu flexible et vous oblige à déclarer également les autres aspects comme le type, court-circuitant le point de vue de l'implémentation JPA à ce sujet.

Hibernate cependant, a ceci:

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

Identifie la valeur DEFAULT à appliquer à la colonne associée via DDL.

Deux notes à cela:

1) N'ayez pas peur de devenir non standard. En tant que développeur JBoss, j'ai vu pas mal de processus de spécification. La spécification est fondamentalement la référence que les grands acteurs dans un domaine donné sont prêts à s'engager à soutenir pour la prochaine décennie. C'est vrai pour la sécurité, pour la messagerie, ORM ne fait aucune différence (bien que JPA couvre beaucoup). Mon expérience en tant que développeur est que dans une application complexe, vous aurez besoin de toute façon tôt ou tard d'une API non standard. Et @ColumnDefaultest un exemple lorsqu'il dépasse les inconvénients de l'utilisation d'une solution non standard.

2) C'est agréable de voir comment tout le monde agite l'initialisation de @PrePersist ou du membre constructeur. Mais ce n'est pas pareil. Qu'en est-il des mises à jour SQL groupées? Que diriez-vous des déclarations qui ne définissent pas la colonne? DEFAULTa son rôle et ce n'est pas substituable en initialisant un membre de classe Java.

Ondra Žižka
la source
Quelle version d'hibernate-annotations puis-je utiliser pour Wildfly 12, j'attrape une erreur avec une version spécifique de celui-ci? Merci d'avance!
Fernando Pie
Merci Ondra. Y a-t-il d'autres solutions en 2019?
Alan
1
@Alan n'est pas en stock JPA, malheureusement.
jwenting
13

JPA ne supporte pas cela et ce serait utile si c'était le cas. L'utilisation de columnDefinition est spécifique à la base de données et n'est pas acceptable dans de nombreux cas. la définition d'une valeur par défaut dans la classe n'est pas suffisante lorsque vous récupérez un enregistrement ayant des valeurs nulles (ce qui se produit généralement lorsque vous réexécutez d'anciens tests DBUnit). Ce que je fais c'est ceci:

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;
    }
}

La boxe automatique Java y contribue beaucoup.

Marco
la source
N'abusez pas des setters! Ils servent à définir un paramètre dans un champ d'objet. Rien de plus! Mieux utiliser le constructeur par défaut pour les valeurs par défaut.
Roland
@Roland est sympa en théorie, mais ne fonctionnera pas toujours lorsqu'il s'agit d'une base de données existante qui contient déjà des valeurs nulles où votre application ne souhaite pas les avoir. Vous devez d'abord effectuer une conversion de base de données où vous rendez la colonne non nulle et définissez une valeur par défaut sensible en même temps, puis traitez le jeu entre les autres applications qui supposent qu'il est en fait nullable (c'est-à-dire, si vous peut même modifier la base de données).
jwenting
S'il s'agit de la #JPA et des setters / getters, ils doivent toujours être purs setter / getter sinon un jour votre application a grandi et s'est développée et devient un cauchemar de maintenance. Le meilleur conseil est KISS ici (je ne suis pas gay, LOL).
Roland
10

Voyant que je suis tombé sur Google en essayant de résoudre le même problème, je vais juste jeter la solution que j'ai préparée au cas où quelqu'un la trouverait utile.

De mon point de vue, il n'y a vraiment qu'une seule solution à ce problème - @PrePersist. Si vous le faites dans @PrePersist, vous devez vérifier si la valeur a déjà été définie.

TC1
la source
4
+1 - @PrePersistJ'opterais certainement pour le cas d'utilisation d'OP. @Column(columnDefinition=...)ne semble pas très élégant.
Piotr Nowicki
@PrePersist ne vous aidera pas s'il existe déjà des données dans le magasin avec des valeurs nulles. Il ne fera pas comme par magie une conversion de base de données pour vous.
jwenting du
10
@Column(columnDefinition="tinyint(1) default 1")

Je viens de tester le problème. Cela fonctionne très bien. Merci pour l'astuce.


À propos des commentaires:

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

Celui-ci ne définit pas la valeur de colonne par défaut dans la base de données (bien sûr).

asd
la source
9
Cela ne fonctionne pas correctement au niveau de l'objet (vous n'obtiendrez pas la valeur par défaut de la base de données après une insertion dans votre entité). Par défaut au niveau Java.
Pascal Thivent
Cela fonctionne (surtout si vous spécifiez insertable = false dans la base de données, mais malheureusement la version mise en cache n'est pas actualisée (pas même lorsque JPA sélectionne la table et les colonnes). Du moins pas avec hibernation: - / mais même si cela fonctionnerait le supplémentaire aller-retour est mauvais
eckes
9

vous pouvez utiliser l'api java reflect:

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

Ceci est courant:

    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
la source
2
J'aime cette solution, mais j'ai un point faible, je pense que ce serait important de vérifier si la colonne est nullable ou non avant de définir la valeur par défaut.
Luis Carlos
Si vous préférez, mettre le code de Field[] fields = object.getClass().getDeclaredFields();dans for()pourrait également être correct. Et ajoutez également finalà vos exceptions de paramètre / prises car vous ne voulez pas objectêtre modifié par accident. Ajoutez également un contrôle sur null: if (null == object) { throw new NullPointerException("Parameter 'object' is null"); }. Cela garantit que c'est object.getClass()sûr à invoquer et ne déclenche pas un NPE. La raison est d'éviter les erreurs des programmeurs paresseux. ;-)
Roland
7

J'utilise columnDefinitionet ça marche très bien

@Column(columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")

private Date createdDate;
Tong
la source
6
Il semble que cela rend votre fournisseur d'implémentation JPA spécifique.
Udo tenue le
Il rend uniquement le fournisseur DDL spécifique. Mais vous avez de toute façon des piratages DDL spécifiques au fournisseur de toute façon .. Cependant, comme mentionné ci-dessus, la valeur (même lorsque insertable = false) apparaît dans la base de données mais pas dans le cache d'entité de la session (du moins pas en hibernation).
eckes
7

Vous ne pouvez pas faire cela avec l'annotation de colonne. Je pense que la seule façon est de définir la valeur par défaut lors de la création d'un objet. Peut-être que le constructeur par défaut serait le bon endroit pour le faire.

Timo
la source
Bonne idée. Malheureusement, il n'y a pas d'annotation ou d'attribut générique pour @Columnaround. Et je manque également de définir des commentaires (tirés de Java doctag).
Roland
5

Dans mon cas, j'ai modifié le code source hibernate-core, enfin, pour introduire une nouvelle annotation @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 );

Eh bien, c'est une solution d'hibernation uniquement.

Xiè Jìléi
la source
2
Bien que j'apprécie l'effort des personnes qui contribuent activement au projet open source, j'ai rétrogradé cette réponse parce que JPA est une spécification Java standard au-dessus de tout OR / M, l'OP a demandé une méthode JPA pour spécifier les valeurs par défaut et votre correctif fonctionne uniquement pour Hibernate. Si tel était NHibernate, où il n'y a pas de super-partes spécification pour persistence (NPA est même pas pris en charge par MS EF), j'aurais upvoted ce genre de patch. La vérité est que JPA est assez limité par rapport aux exigences de l'ORM (un exemple: pas d'index secondaire). Bref bravo à l'effort
usr-local-ΕΨΗΕΛΩΝ
Cela ne crée-t-il pas un problème de maintenance? Il faudrait refaire ces modifications à chaque mise en veille prolongée ou à chaque nouvelle installation?
user1242321
Étant donné que la question concerne JPA et qu'Hibernate n'est même pas mentionné, cela ne répond pas à la question
Neil Stockton
5
  1. @Column(columnDefinition='...') ne fonctionne pas lorsque vous définissez la contrainte par défaut dans la base de données lors de l'insertion des données.
  2. Vous devez créer insertable = falseet supprimer columnDefinition='...'de l'annotation, puis la base de données insérera automatiquement la valeur par défaut de la base de données.
  3. Par exemple, lorsque vous définissez le sexe varchar est masculin par défaut dans la base de données.
  4. Il vous suffit d'ajouter insertable = falseHibernate / JPA, cela fonctionnera.
Appesh
la source
Bonne réponse. Et voici expliquer-sur-insérer-faux-actualisable-faux
Shihe Zhang
Et ce n'est pas une bonne option, si vous voulez parfois définir sa valeur.
Shihe Zhang
@ShiheZhang utilisant false ne me permet pas de définir la valeur?
fvildoso
3
@PrePersist
void preInsert() {
    if (this.dateOfConsent == null)
        this.dateOfConsent = LocalDateTime.now();
    if(this.consentExpiry==null)
        this.consentExpiry = this.dateOfConsent.plusMonths(3);
}

Dans mon cas, en raison du champ LocalDateTime que j'ai utilisé, il est recommandé en raison de l'indépendance du fournisseur

Mohammed Rafeeq
la source
2

Ni les annotations JPA ni Hibernate ne prennent en charge la notion de valeur de colonne par défaut. Pour contourner cette limitation, définissez toutes les valeurs par défaut juste avant d'appeler une mise en veille prolongée save()ou update()sur la session. Cela aussi étroitement que possible (à moins qu'Hibernate ne définisse les valeurs par défaut) imite le comportement de la base de données qui définit les valeurs par défaut lorsqu'elle enregistre une ligne dans une table.

Contrairement à la définition des valeurs par défaut dans la classe de modèle comme le suggère cette réponse alternative , cette approche garantit également que les requêtes de critères qui utilisent un Exampleobjet comme prototype pour la recherche continueront de fonctionner comme auparavant. Lorsque vous définissez la valeur par défaut d'un attribut nullable (celui qui a un type non primitif) dans une classe de modèle, une requête Hibernate par exemple n'ignorera plus la colonne associée là où auparavant elle l'ignorerait car elle était nulle.

Derek Mahar
la source
Dans la solution précédente, l'auteur a mentionné ColumnDefault ("")
nikolai.serdiuk
@ nikolai.serdiuk que l'annotation ColumnDefault a été ajoutée des années après la rédaction de cette réponse. En 2010, c'était correct, il n'y avait pas une telle annotation (en fait, il n'y avait pas d'annotations, seulement une configuration xml).
jwenting
1

Ce n'est pas possible dans JPA.

Voici ce que vous pouvez faire avec l'annotation de colonne: http://java.sun.com/javaee/5/docs/api/javax/persistence/Column.html

PEELY
la source
1
Ne pas pouvoir spécifier une valeur par défaut semble une sérieuse lacune des annotations JPA.
Derek Mahar
2
JDO le permet dans sa définition ORM, donc JPA devrait inclure cela ... un jour
user383680
Vous pouvez certainement le faire, avec l'attribut columnDefinition, comme l'a répondu Cameron Pope.
IntelliData
@DerekMahar ce n'est pas le seul défaut de la spécification JPA. C'est une bonne spécification, mais ce n'est pas parfait
jwenting
1

Si vous utilisez un double, vous pouvez utiliser ce qui suit:

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

private Double grolsh;

Oui, c'est spécifique à la base de données.

Gal Bracha
la source
0

Vous pouvez définir la valeur par défaut dans le concepteur de base de données ou lorsque vous créez la table. Par exemple, dans SQL Server, vous pouvez définir le coffre-fort par défaut d'un champ Date sur ( getDate()). Utilisez insertable=falsecomme indiqué dans la définition de votre colonne. JPA ne spécifiera pas cette colonne dans les insertions et la base de données générera la valeur pour vous.

Dave Anderson
la source
-2

Tu dois insertable=false dans votre @Columnannotation. JPA ignorera alors cette colonne lors de l'insertion dans la base de données et la valeur par défaut sera utilisée.

Voir ce lien: http://mariemjabloun.blogspot.com/2014/03/resolved-set-database-default-value-in.html

MariemJab
la source
2
Alors comment u insérera la valeur donnée @runtime ??
Ashish Ratan
Oui c'est vrai. nullable=falseéchouera avec SqlException: Caused by: java.sql.SQLException: Column 'opening_times_created' cannot be null. Ici, j'ai oublié de définir l'horodatage "créé" avec openingTime.setOpeningCreated(new Date()). C'est une bonne façon d'avoir de la cohérence, mais c'est ce que l'interrogateur ne demandait pas.
Roland