Establecer valores predeterminados para columnas en JPA

245

¿Es posible establecer un valor predeterminado para las columnas en JPA, y si, cómo se hace usando anotaciones?

homaxto
fuente
¿existe en la sepcificación JPA dónde puedo poner el valor predeterminado para una columna como el valor para otra columna, porque quiero que esté oculto
Shareef
3
Relacionado: Hibernate tiene @ org.hibernate.annotations.ColumnDefault ("foo")
Ondra Žižka

Respuestas:

230

En realidad, es posible en JPA, aunque un poco de pirateo utilizando la columnDefinitionpropiedad de la @Columnanotación, por ejemplo:

@Column(name="Price", columnDefinition="Decimal(10,2) default '100.00'")
Cameron Pope
fuente
3
10 es la parte entera, y 2 es la parte decimal del número, entonces: 1234567890.12 sería un número compatible.
Nathan Feger
23
Tenga en cuenta también que esta definición de columna no es necesariamente independiente de la base de datos, y ciertamente no está vinculada automáticamente al tipo de datos en Java.
Nathan Feger
47
Después de crear una entidad con un campo nulo y persistirlo, no tendría el valor establecido en él. No es una solución ...
Pascal Thivent
18
No @NathanFeger, 10 es la longitud y 2 la parte decimal. Entonces 1234567890.12 no sería un número compatible, pero 12345678.90 es válido.
GPrimola
44
Necesitaría insertable=falsesi la columna es anulable (y para evitar el argumento de columna innecesario).
Eckes
314

Puedes hacer lo siguiente:

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

¡Ahí! Acaba de usar cero como valor predeterminado.

Tenga en cuenta que esto le servirá si solo accede a la base de datos desde esta aplicación. Si otras aplicaciones también usan la base de datos, entonces debe hacer esta verificación desde la base de datos usando el atributo de anotación columnDefinition de Cameron , o de alguna otra manera.

Pablo Venturino
fuente
38
Esta es la forma correcta de hacerlo si desea que sus entidades tengan el valor correcto después de la inserción. +1
Pascal Thivent
16
Establecer el valor predeterminado de un atributo anulable (uno que tiene un tipo no primitivo) en la clase de modelo interrumpirá las consultas de criterios que usan un Exampleobjeto como prototipo para la búsqueda. Después de establecer un valor predeterminado, una consulta de ejemplo de Hibernate ya no ignorará la columna asociada donde anteriormente la ignoraría porque era nula. Un mejor enfoque es establecer todos los valores predeterminados justo antes de invocar un Hibernate save()o update(). Esto imita mejor el comportamiento de la base de datos que establece los valores predeterminados cuando guarda una fila.
Derek Mahar
1
@Harry: esta es la forma correcta de establecer valores predeterminados, excepto cuando está utilizando consultas de criterios que utilizan un ejemplo como prototipo para la búsqueda.
Derek Mahar
12
Esto no garantiza que el valor predeterminado se establezca para los tipos no primitivos (configuración, nullpor ejemplo). Usar @PrePersisty @PreUpdatees una mejor opción en mi humilde opinión.
Jasper
3
Esta es la forma correcta de especificar el valor predeterminado para la columna. columnDefinitionla propiedad no es independiente de la base de datos y @PrePersistanula su configuración antes de insertarla, el "valor predeterminado" es otra cosa, el valor predeterminado se usa cuando el valor no se establece explícitamente.
Utku Özdemir
110

otro enfoque es usar javax.persistence.PrePersist

@PrePersist
void preInsert() {
   if (this.createdTime == null)
       this.createdTime = new Date();
}
Husin Wijaya
fuente
1
Utilicé un enfoque como este para agregar y actualizar marcas de tiempo. Funcionó muy bien.
Espina
10
¿No debería ser esto if (createdt != null) createdt = new Date();o algo así? En este momento, esto anulará un valor especificado explícitamente, lo que parece hacer que realmente no sea un valor predeterminado.
Max Nanasy
16
@MaxNanasyif (createdt == null) createdt = new Date();
Shane
¿Importaría si el cliente y la base de datos están en diferentes zonas horarias? Me imagino que usando algo como "TIMESTAMP DEFAULT CURRENT_TIMESTAMP" obtendría la hora actual en el servidor de la base de datos, y "createdt = new Date ()" obtendría la hora actual en el código de Java, pero estas dos veces podrían no ser las mismas si el cliente se está conectando a un servidor en una zona horaria diferente.
FrustratedWithFormsDesigner
Se agregó el nullcheque.
Ondra Žižka
49

En 2017, JPA 2.1 todavía solo tiene @Column(columnDefinition='...')en qué poner la definición literal de SQL de la columna. Lo cual es bastante inflexible y lo obliga a declarar también los otros aspectos, como el tipo, en cortocircuito de la visión de la implementación de JPA al respecto.

Sin embargo, Hibernate tiene esto:

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

Identifica el valor predeterminado para aplicar a la columna asociada a través de DDL.

Dos notas a eso:

1) No tengas miedo de no ser estándar. Trabajando como desarrollador de JBoss, he visto bastantes procesos de especificación. La especificación es básicamente la línea de base que los grandes jugadores en un campo determinado están dispuestos a comprometerse a apoyar durante la próxima década más o menos. Es cierto para la seguridad, para la mensajería, ORM no es la diferencia (aunque JPA cubre bastante). Mi experiencia como desarrollador es que, en una aplicación compleja, tarde o temprano necesitarás una API no estándar de todos modos. Y @ColumnDefaultes un ejemplo cuando supera los aspectos negativos del uso de una solución no estándar.

2) Es agradable cómo todos saludan a @PrePersist o a la inicialización del miembro constructor. Pero eso NO es lo mismo. ¿Qué hay de las actualizaciones masivas de SQL? ¿Qué hay de las declaraciones que no establecen la columna? DEFAULTtiene su rol y eso no se puede sustituir inicializando un miembro de clase Java.

Ondra Žižka
fuente
¿Qué versión de las anotaciones de hibernación puedo usar para Wildfly 12, me da un error con una versión específica de esto? Gracias de antemano!
Fernando Pie
Gracias Ondra ¿Hay más soluciones en 2019?
Alan
1
@ Alan no está en stock JPA, lamentablemente.
Jwenting
13

JPA no admite eso y sería útil si lo hiciera. El uso de columnDefinition es específico de DB y no es aceptable en muchos casos. establecer un valor predeterminado en la clase no es suficiente cuando recupera un registro que tiene valores nulos (lo que generalmente ocurre cuando vuelve a ejecutar las pruebas antiguas de DBUnit). Lo que hago es esto:

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

El auto-boxeo de Java ayuda mucho en eso.

Marco
fuente
¡No abuses de los acomodadores! Son para establecer un parámetro en un campo de objeto. ¡Nada mas! Mejor use el constructor predeterminado para los valores predeterminados.
Roland
@Roland es bueno en teoría, pero no siempre funciona cuando se trata de una base de datos existente que ya contiene valores nulos donde su aplicación desea no tenerlos. Debería hacer una conversión de la base de datos primero donde hace que la columna no sea nula y establezca un valor predeterminado sensible al mismo tiempo, y luego lidiar con la reacción de otras aplicaciones que suponen que de hecho es anulable (es decir, si incluso puede modificar la base de datos).
Jwenting
Si se trata de #JPA y setters / getters, siempre deben ser setter / getter puros; de lo contrario, algún día su aplicación crecerá y crecerá y se convertirá en una pesadilla de mantenimiento. El mejor consejo es KISS aquí (no soy gay, LOL).
Roland
10

Al ver que me topé con esto desde Google mientras intentaba resolver el mismo problema, solo voy a agregar la solución que preparé en caso de que alguien lo encuentre útil.

Desde mi punto de vista, solo hay 1 soluciones para este problema: @PrePersist. Si lo hace en @PrePersist, debe verificar si el valor ya se ha establecido.

TC1
fuente
44
+1: definitivamente optaría por el @PrePersistcaso de uso de OP. @Column(columnDefinition=...)No parece muy elegante.
Piotr Nowicki
@PrePersist no lo ayudará si ya hay datos en la tienda con valores nulos. No hará mágicamente una conversión de base de datos por usted.
Jwenting
10
@Column(columnDefinition="tinyint(1) default 1")

Acabo de probar el problema. Funciona bien Gracias por la pista.


Sobre los comentarios:

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

Este no establece el valor de columna predeterminado en la base de datos (por supuesto).

asd
fuente
99
No funciona bien a nivel de objeto (no obtendrá el valor predeterminado de la base de datos después de una inserción en su entidad). Por defecto en el nivel de Java.
Pascal Thivent
Funciona (especialmente si especifica insertable = false en la base de datos, pero desafortunadamente la versión en caché no se actualiza (ni siquiera cuando JPA selecciona la tabla y las columnas). Al menos no con hibernación: - / pero incluso si funcionaría el adicional el
viaje de
9

puedes usar el java reflect api:

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

Esto es común:

    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
fuente
2
Me gusta esta solución, pero tengo un punto de debilidad, creo que sería importante verificar si la columna es anulable o no antes de establecer el valor predeterminado.
Luis Carlos
Si lo prefiere, también puede poner el código Field[] fields = object.getClass().getDeclaredFields();en el for(). Y también agregue finala su parámetro / excepciones capturadas ya que no desea objectser modificado por accidente. También agregue un control sobre null: if (null == object) { throw new NullPointerException("Parameter 'object' is null"); }. Eso asegura que object.getClass()sea ​​seguro invocar y no desencadena a NPE. La razón es evitar errores por parte de programadores perezosos. ;-)
Roland
7

Yo uso columnDefinitiony funciona muy bien

@Column(columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")

private Date createdDate;
Pinzas
fuente
66
Esto parece que hace que su proveedor de implementación jpa sea específico.
Udo se celebró el
Solo hace que el proveedor DDL sea específico. Pero es muy probable que tenga hacks DDL específicos del proveedor de todos modos. Sin embargo, como se mencionó anteriormente, el valor (incluso cuando insertable = false) aparece en la base de datos pero no en el caché de la entidad de la sesión (al menos no en hibernación).
eckes
7

No puede hacer esto con la anotación de columna. Creo que la única forma es establecer el valor predeterminado cuando se crea un objeto. Quizás el constructor predeterminado sería el lugar correcto para hacer eso.

Timo
fuente
Buena idea. Lamentablemente no hay una anotación genérica o atributo para @Columnalrededor. Y también extraño los comentarios establecidos (tomados de Java Doctag).
Roland
5

En mi caso, modifiqué el código fuente de hibernate-core, bueno, para introducir una nueva anotación @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 );

Bueno, esta es una solución solo para hibernación.

Xiè Jìléi
fuente
2
Si bien aprecio el esfuerzo de las personas que contribuyen activamente al proyecto de código abierto, rechacé esta respuesta porque JPA es una especificación estándar de Java además de cualquier OR / M, el OP solicitó una forma de JPA para especificar valores predeterminados y su parche funciona solo para Hibernate. Si esto fuera NHibernate, en el que no hay una especificación de super-partes para la persistencia (NPA ni siquiera es compatible con MS EF), habría votado a favor de este tipo de parche. La verdad es que JPA es bastante limitado en comparación con los requisitos de ORM (un ejemplo: sin índices secundarios). De todos modos felicitaciones al esfuerzo
usr-local-ΕΨΗΕΛΩΝ
¿Esto no crea un problema de mantenimiento? ¿Debería rehacer estos cambios cada vez que hiberne las actualizaciones o en cada nueva instalación?
user1242321
Dado que la pregunta es sobre JPA, e Hibernate ni siquiera se menciona, esto no responde a la pregunta
Neil Stockton
5
  1. @Column(columnDefinition='...') no funciona cuando establece la restricción predeterminada en la base de datos al insertar los datos.
  2. Debe hacer insertable = falsey eliminar columnDefinition='...'de la anotación, la base de datos insertará automáticamente el valor predeterminado de la base de datos.
  3. Por ejemplo, cuando configura varchar, el género es masculino por defecto en la base de datos.
  4. Solo necesita agregar insertable = falseHibernate / JPA, funcionará.
Appesh
fuente
Buena respuesta. Y aquí está explica-acerca de-insertable-falso-actualizable-falso
Shihe Zhang
Y no es una buena opción, si desea establecer su valor a veces.
Shihe Zhang
@ShiheZhang usando false no me deja establecer el valor?
fvildoso
3
@PrePersist
void preInsert() {
    if (this.dateOfConsent == null)
        this.dateOfConsent = LocalDateTime.now();
    if(this.consentExpiry==null)
        this.consentExpiry = this.dateOfConsent.plusMonths(3);
}

En mi caso, debido a que el campo es LocalDateTime utilicé esto, se recomienda debido a la independencia del proveedor

Mohammed Rafeeq
fuente
2

Ni las anotaciones JPA ni Hibernate admiten la noción de un valor de columna predeterminado. Como solución a esta limitación, establezca todos los valores predeterminados justo antes de invocar un Hibernate save()o update()en la sesión. Esto lo más cerca posible (sin Hibernate estableciendo los valores predeterminados) imita el comportamiento de la base de datos que establece los valores predeterminados cuando guarda una fila en una tabla.

A diferencia de establecer los valores predeterminados en la clase de modelo como sugiere esta respuesta alternativa , este enfoque también garantiza que las consultas de criterios que usan un Exampleobjeto como prototipo para la búsqueda continúen funcionando como antes. Cuando establece el valor predeterminado de un atributo anulable (uno que tiene un tipo no primitivo) en una clase de modelo, una consulta por ejemplo de Hibernate ya no ignorará la columna asociada donde anteriormente la ignoraría porque era nula.

Derek Mahar
fuente
En la solución anterior, el autor mencionó ColumnDefault ("")
nikolai.serdiuk
@ nikolai.serdiuk que la anotación ColumnDefault se agregó años después de que se escribió esta respuesta. En 2010 fue correcto, no hubo tal anotación (de hecho, no hubo anotaciones, solo configuración xml).
Jwenting
1

Esto no es posible en JPA.

Esto es lo que puede hacer con la anotación Column: http://java.sun.com/javaee/5/docs/api/javax/persistence/Column.html

PEELY
fuente
1
No poder especificar un valor predeterminado parece una grave deficiencia de las anotaciones JPA.
Derek Mahar
2
JDO lo permite en su definición ORM, por lo que JPA debería incluir esto ... un día
user383680
Definitivamente puedes hacerlo, con el atributo columnDefinition, como respondió Cameron Pope.
IntelliData
@DerekMahar no es el único déficit en la especificación JPA. Es una buena especificación, pero no es perfecta
comenzó el
1

Si usa un doble, puede usar lo siguiente:

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

private Double grolsh;

Sí, es específico de db.

Gal Bracha
fuente
0

Puede definir el valor predeterminado en el diseñador de la base de datos o cuando crea la tabla. Por ejemplo, en SQL Server, puede establecer la bóveda predeterminada de un campo Fecha en ( getDate()). Use insertable=falsecomo se menciona en la definición de su columna. JPA no especificará esa columna en las inserciones y la base de datos generará el valor para usted.

Dave Anderson
fuente
-2

Necesitas insertable=falseen tu @Columnanotación. JPA ignorará esa columna mientras se inserta en la base de datos y se usará el valor predeterminado.

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

MariemJab
fuente
2
Entonces, ¿cómo va a insertar el valor dado @runtime?
Ashish Ratan
Si eso es verdad. nullable=falsefallará con un SqlException: Caused by: java.sql.SQLException: Column 'opening_times_created' cannot be null. Aquí olvidé establecer la marca de tiempo "creado" con openingTime.setOpeningCreated(new Date()). Esta es una buena manera de tener consistencia, pero eso es lo que el interrogador no estaba preguntando.
Roland