¿Es posible establecer un valor predeterminado para las columnas en JPA, y si, cómo se hace usando anotaciones?
¿Es posible establecer un valor predeterminado para las columnas en JPA, y si, cómo se hace usando anotaciones?
Respuestas:
En realidad, es posible en JPA, aunque un poco de pirateo utilizando la columnDefinition
propiedad de la @Column
anotación, por ejemplo:
@Column(name="Price", columnDefinition="Decimal(10,2) default '100.00'")
insertable=false
si la columna es anulable (y para evitar el argumento de columna innecesario).
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.
Example
objeto 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.
null
por ejemplo). Usar @PrePersist
y @PreUpdate
es una mejor opción en mi humilde opinión.
columnDefinition
la propiedad no es independiente de la base de datos y @PrePersist
anula 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.
otro enfoque es usar javax.persistence.PrePersist
@PrePersist
void preInsert() {
if (this.createdTime == null)
this.createdTime = new Date();
}
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.
if (createdt == null) createdt = new Date();
null
cheque.
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 @ColumnDefault
es 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? DEFAULT
tiene su rol y eso no se puede sustituir inicializando un miembro de clase Java.
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.
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.
@PrePersist
caso de uso de OP. @Column(columnDefinition=...)
No parece muy elegante.
@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).
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();
}
}
}
Field[] fields = object.getClass().getDeclaredFields();
en el for()
. Y también agregue final
a su parámetro / excepciones capturadas ya que no desea object
ser 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. ;-)
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 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.
@Column
alrededor. Y también extraño los comentarios establecidos (tomados de Java Doctag).
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.
@Column(columnDefinition='...')
no funciona cuando establece la restricción predeterminada en la base de datos al insertar los datos.insertable = false
y eliminar columnDefinition='...'
de la anotación, la base de datos insertará automáticamente el valor predeterminado de la base de datos.insertable = false
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 es LocalDateTime utilicé esto, se recomienda debido a la independencia del proveedor
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 Example
objeto 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.
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
Si usa un doble, puede usar lo siguiente:
@Column(columnDefinition="double precision default '96'")
private Double grolsh;
Sí, es específico de db.
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=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.
Necesitas insertable=false
en tu @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
nullable=false
fallará 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.