Establecer los valores predeterminados para las columnas en JPA

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

En realidad, es posible en JPA, aunque un poco de un truco usando la propiedad columnDefinition de la anotación @Column , por ejemplo:

 @Column(name="Price", columnDefinition="Decimal(10,2) default '100.00'") 

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 está accediendo 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 utilizando el atributo de anotación columnDefinition de Cameron , o de alguna otra manera.

Otro enfoque es usar javax.persistence.PrePersist

 @PrePersist void preInsert() { if (this.createdTime == null) this.createdTime = new Date(); } 

En 2017, JPA aún tiene solo @Column(columnDefinition='...') a la cual le pone la definición literal de SQL de la columna. Lo cual es bastante inflexible y te obliga a declarar también los otros aspectos, como tipo, cortocircuitando la visión de la implementación de JPA sobre ese asunto.

Sin embargo, Hibernate tiene esto:

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

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

Hibernate 4.3 documentos (4.3 para mostrar que ha estado aquí por bastante tiempo)
Manual de Hibernate en Valor predeterminado para la columna de la base de datos

Dos notas a eso:

1) No tengas miedo de ir no estándar. Trabajando como desarrollador de JBoss, he visto algunos procesos de especificación. La especificación es básicamente la línea de base que los grandes jugadores en el campo dado están dispuestos a comprometerse a apoyar durante la próxima década más o menos. Es cierto para la seguridad, para los mensajes, ORM no es diferente (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 @ColumnDefault es un ejemplo cuando supera los negativos de la solución estándar.

2) Es bueno cómo todos marcan la inicialización de @PrePersist o miembro de constructor. Pero eso NO es lo mismo. ¿Qué hay de las actualizaciones SQL a granel? ¿Qué hay de las declaraciones que no configuran la columna? DEFAULT tiene su función y eso no es sustituible al inicializar el miembro de la clase Java.

JPA no lo admite y sería útil si lo hiciera. Usar 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 antiguas pruebas 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-boxing de Java ayuda mucho en eso.

Viendo que me encontré con esto de Google mientras trato de resolver el mismo problema, voy a tirar la solución que cociné en caso de que alguien lo encuentre útil.

Desde mi punto de vista, solo hay 1 solución para este problema: @PrePersist. Si lo haces en @PrePersist, debes verificar si el valor ya se ha establecido.

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

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

Yo uso columnDefinition y funciona muy bien

 @Column(columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP") private Date createdDate; 

No puede hacer esto con la anotación de la columna. Creo que la única forma es establecer el valor predeterminado cuando se crea un objeto. Tal vez el constructor predeterminado sea el lugar correcto para hacerlo.

puedes usar la api de java reflect:

  @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(); } } } 

En mi caso, modifiqué el código fuente hibernate-core, bueno, para introducir una nueva anotación @DefaultValue :

 commit 34199cba96b6b1dc42d0d19c066bd4d119b553d5 Author: Lenik  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 de hibernación.

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 save() Hibernate o update() en la sesión. Esto es lo más cercano posible (a menos que Hibernate establezca 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 asegura que las consultas de criterios que usan un objeto Example como un prototipo para la búsqueda continuarán funcionando como antes. Cuando establece el valor predeterminado de un atributo que admite nulos (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.

Esto no es posible en JPA.

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

Si usa un doble, puede usar lo siguiente:

 @Column(columnDefinition="double precision default '96'") private Double grolsh; 

Sí, es db específico.

  1. @Column(columnDefinition='...') no funciona cuando configura la restricción predeterminada en la base de datos al insertar los datos.
  2. columnDefinition='...' hacer que insertable = false y eliminar columnDefinition='...' de la anotación, luego 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 = false en Hibernate / JPA, funcionará.
 @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 siendo LocalDateTime lo usé, se recomienda debido a la independencia del proveedor

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 de Fecha en ( getDate() ). Use insertable=false como 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.

Necesita insertable=false en su @Column anotació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