É possível definir um valor padrão para colunas na JPA e se, como isso é feito usando anotações?
É possível definir um valor padrão para colunas na JPA e se, como isso é feito usando anotações?
Respostas:
Na verdade, é possível na JPA, embora um pouco de hack usando a columnDefinition
propriedade da @Column
anotação, por exemplo:
@Column(name="Price", columnDefinition="Decimal(10,2) default '100.00'")
insertable=false
se a coluna fosse anulável (e para evitar o argumento desnecessário da coluna).
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.
Example
objeto 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.
null
por exemplo). Usando @PrePersist
e @PreUpdate
é uma opção melhor imho.
columnDefinition
A propriedade não é independente do banco de dados e @PrePersist
substitui sua configuração antes da inserção; "valor padrão" é outra coisa; o valor padrão é usado quando o valor não é definido explicitamente.
outra abordagem é usar javax.persistence.PrePersist
@PrePersist
void preInsert() {
if (this.createdTime == null)
this.createdTime = new Date();
}
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.
if (createdt == null) createdt = new Date();
null
verificação.
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? DEFAULT
tem sua função e isso não é substituível ao inicializar um membro da classe Java.
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.
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.
@PrePersist
caso de uso do OP. @Column(columnDefinition=...)
não parece muito elegante.
@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).
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();
}
}
}
Field[] fields = object.getClass().getDeclaredFields();
no também for()
pode ser bom. E também adicione final
ao seu parâmetro / exceções capturadas, pois você não deseja object
ser 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. ;-)
Eu uso columnDefinition
e funciona muito bem
@Column(columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private Date createdDate;
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.
@Column
próximos. E também sinto falta de comentários sendo definidos (tirados do Java doctag).
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.
@Column(columnDefinition='...')
não funciona quando você define a restrição padrão no banco de dados enquanto insere os dados.insertable = false
e remover columnDefinition='...'
da anotação, para que o banco de dados insira automaticamente o valor padrão do banco de dados.insertable = false
Hibernate / JPA, ele funcionará.@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
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 Example
objeto 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.
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
Se você estiver usando um duplo, poderá usar o seguinte:
@Column(columnDefinition="double precision default '96'")
private Double grolsh;
Sim, é db específico.
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=false
conforme 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ê.
Você precisa de insertable=false
você@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
nullable=false
falhará 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.