I have attribute in my Java entity like this:
#Basic(fetch = FetchType.LAZY) //I tried without this as well
#Column(name = "value_x", columnDefinition = "bigint default 0")
private Long valueX;
In table definition in pgAdmin I see:
value_x bigint DEFAULT 0,
but when object is inserted (with this attribute as null), column is empty/null not having 0 value inserted.
Anyone would know why it does not insert the default value? Using EclipseLink.
Null value is because JPA explicitly inserts a null value in to that column if no special handling is implemented. The columnDefinition does make the column creation with DEFAULT but it does not make JPA aware of/or obey it afterwards.
And it is a nullable column so there is no error. See what happens in plain SQL, think of this table:
create table example (
col1 bigint default 42,
col2 bigint default 99
);
Then make an insert like:
insert into example (col1) values (null);
then selecting:
select * from example;
would show result like:
col1 | col2
------+------
(null) | 99
If you need to have default values managed in the java side you need some special stuff in the Java side.
See for example this question and note that the accepted answer is not the working one but this answer. So setting the value when class is instantiated:
private Long valueX = 0;
Another way is as this answer for different question suggests, using #PrePersist:
#PrePersist
void prePersist() {
if (this.valueX == null)
this.valueX = 0;
}
I found another way to resolve the same problem, because when I create my own object and persist in database and didn´t respect the DDL with default value.
So I looked at my console, and the SQL generated, and saw that insert came with all fields, but only one propertie in my object has the value changed.
So I put in the model class this annotation.
#DynamicInsert
When is inserting data, the framework not insert null values or values that are not modified, making the insert shorter.
Also has #DynamicUpdate annotation.
Related
I was looking at solutions of mapping column default value in spring-data-jpa which lead me to documentation of #ColumnDefault annotation; and I wanted to try it out. So I updated an entity where we needed default values (today this is handled via a schema.sql where we define additional column behaviours) like so:
#Entity
#Getter
#Setter
#ToString
#DynamicInsert
#NoArgsConstructor
#Relation(collectionRelation = "customers")
public class Customer extends Organisation implements Identifiable<Long>, Serializable {
private static final long serialVersionUID = 8101819808147191270L;
#Column(nullable = false, updatable = false, length = 3)
#ColumnDefault("'INR'")
private String currency;
#Column(scale = 2)
#ColumnDefault("0.10")
private Double tds;
#Column(nullable = false, updatable = false, length = 3)
#ColumnDefault("'INV'")
private String invoicePrefix;
Now since I'm using the #ColumnDefault I expect that default values are configured for the column, which they are when I look at the database. However, when I run a test to insert values; the defaults fields are null. I configured Postgresql for the test (as production will be in Postgresql); While the DDL for table in Postgresql shows default values are added to
table definition
CREATE TABLE data_api_it.customer
(
currency character varying(3) COLLATE pg_catalog."default" NOT NULL DEFAULT 'INR'::character varying,
invoice_prefix character varying(3) COLLATE pg_catalog."default" NOT NULL DEFAULT 'INV'::character varying,
tds double precision DEFAULT 0.10,
id bigint NOT NULL,
CONSTRAINT customer_pkey PRIMARY KEY (id),
CONSTRAINT fk3afgab8nfy6ykn6b70uuh9v59 FOREIGN KEY (id)
REFERENCES data_api.organisation (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
Here's the test that fails
#Slf4j
#RunWith(SpringRunner.class)
#DataJpaTest(showSql = false)
#ContextConfiguration(classes = { DataApiJpaConfiguration.class })
#TestPropertySource(locations = {"classpath:application-it.properties","classpath:application-test.properties"})
#AutoConfigureTestDatabase(replace = Replace.NONE)
public class CustomerRepositoryIntegrationTest {
#Autowired
private CustomerRepository customerRepository;
#Test
public void testPrePersistAddsMandatoryFields() {
Customer bhau = new Customer();
bhau.setName("Bhau & Sons Pvt. Ltd.");
bhau.setDomain(RandomStringUtils.randomAlphanumeric(8));
bhau = customerRepository.saveAndFlush(bhau);
Customer badaBhau = customerRepository.findById(bhau.getId()).get();
assertThat(badaBhau.getTds().doubleValue()).isEqualTo(0.10);
assertThat(badaBhau.getInvoicePrefix()).isEqualTo("INV");
assertThat(badaBhau.getCurrency()).isEqualTo("INR");
}
}
The failure is a NullPointerException at assertThat(badaBhau.getTds().doubleValue()).isEqualTo(0.10); when I run on debug I notice that badaBhau indeed doesn't have the default values set when queried from the database. I then paused after the line when it persists bhau; browsed test database configured and noticed that the bhau entity isn't even saved to the database in the first place.
I also ran the app with Postgesql Dev DB trying to hit the API url for saving customer with the JSON data
{
"name":"Minty and Sons Pvt. Ltd.",
"pan": "AASONAL123",
"domain": "xy123456"
}
this call succeeds in creating the record of course with default values INR, INV, 0.10 for currency, invoice_prefix and tds respectively.
So while I understand and like how #ColumnDefault solves the default value issue for me; I'm utterly confused as to what why the test fails (or what am I doing wrong)
Due to Hibernate's first-level cache, bhau and badaBhau will be the same instance, viz. there will be no database lookup triggered by the following: instead the customer with the specified ID will be retrieved from the first level cache. You can verify this by enabling SQL logging.
Customer badaBhau = customerRepository.findById(bhau.getId()); //no db lookup
To have the value can force a database lookup by clearing the persistence context or by refreshing the persistent instance.
public class MyTestClass {
#PersistenceContext
private EntityManager em;
#Test
public void testDefaultFieldsArePopulated() {
Customer bhau = new Customer();
bhau.setName("Bhau & Sons Pvt. Ltd.");
bhau.setDomain(RandomStringUtils.randomAlphanumeric(8));
bhau = customerRepository.saveAndFlush(bhau);
em.clear(); //db lookup will now happen
Customer badaBhau = customerRepository.findById(bhau.getId());
assertThat(badaBhau.getTds().doubleValue()).isEqualTo(0.10);
assertThat(badaBhau.getInvoicePrefix()).isEqualTo("INV");
assertThat(badaBhau.getCurrency()).isEqualTo("INR");
}
}
https://howtodoinjava.com/hibernate/understanding-hibernate-first-level-cache-with-example/
Crizzis also raises a valid point in his answer and as the test passes it looks like Postgres is then replacing nulls with the column's default value. As you may not be able to depend on this across different database engines then you could also look at using Hibernates #DynamicInsert annotation on your entity which would create an insert statement with only non-null fields set.
https://docs.jboss.org/hibernate/orm/5.2/javadocs/org/hibernate/annotations/DynamicInsert.html
For inserting, should this entity use dynamic sql generation where
only non-null columns get referenced in the prepared sql statement?
Part of my answer was going to be what #AlanHay already mentioned.
Even when you follow his advice though, I'd still expect your test to fail. The purpose of #ColumnDefault is to use the specified default value with the column definition when executing DLL, no more, no less. The default value means: 'whenever the value for the column is not specified in an INSERT statement, use the following value instead'.
The thing is, you are specifying a value for the column, since your column is not excluded from the INSERT statement using insertable = false. Even if you didn't set a value to the property, Hibernate will send an explicit NULL. Your approach may work for currency and indexPrefix, since you marked them as nullable = false (this is DB-specific, and you'd have to consult the docs to see how Postgres handles such a situation). I highly doubt it will work for the tds column, since NULL is a valid value in that case.
I have an entity with composite id , I'm using hibernate's Multiple id properties without identifier type , like so :
#Entity
class MyEntity implements Serializable {
#Id
private Long id1;
#Id
private Long id2;
//... Getters , setters , hashcode , equals ...
}
The problem is that in my Database: id1 = 1 , id2 = 2
And if I want to add a row with : id1 = 2 , id2 = 2
I get an error ConstraintViolationException: Duplicate entry '2' for key 'id2'
I'm using hibernate 4.1.7,
The documentation link : http://docs.jboss.org/hibernate/orm/4.1/manual/en-US/html_single/#mapping-declaration-id
Update
I'm talking about a Hibernate-specific solution: Map multiple properties as #Id properties without declaring an external class to be the identifier type
Use EmbeddedId. Please refer this.
It's very possible the problem is not your code, but your db schema. Without knowing what DBMS you're using and the constraints/indexes on the table for MyEntity, it's impossible to say for sure. However my guess is that you have something like this:
CREATE UNIQUE INDEX ON my_entity (id1);
CREATE UNIQUE INDEX ON my_entity (id2);
which requires that each column independently contains only unique values, when you really want something like this:
CREATE UNIQUE INDEX ON my_entity (id1, id2);
which allows each column to contain duplicates of the same value, as long as the combination of both columns is unique.
There is a boolean field in Entity :
#Column(name = "FREEFLAG", columnDefinition = "NUMBER(0,1) default 0", nullable = false)
public boolean getFreeflag() {
return freeflag;
}
database - Oracle, field FREEFLAG - NUMBER(0,1)
I try to get object from db with Hibernate, but if the field in db is null i got a exception :
Exception in thread "main" org.hibernate.PropertyAccessException: Null value was assigned to a property of primitive type
Why default value doesn't work ?
How I can resolve this problem on server side? I have to have 0 or 1 value in db.
I got the solution - Default value not working in hibernate. But I still have to modify database. I have added DDL - alter table client modify freeflag default 0 not null
DML - update client set freeflag = 0 where freeflag is null; commit
In one of your comments you have said that your database allow null values for this column. The columnDefinition attribute is used by JPA Provider during schema generation process, so I can assume that you have not generated schema after you added columnDefinition attribute (nor nullable=false). Hints in columnDefinition which set default value were not applied to database, so it's nothing strange that you don't have this column defaulted.
Another way you can make it work is creating custom Converter for this field, which will save values in the way you need. More informations about converters implementation available here.
The columnDefinition is database dependent and hence might cause issues sometimes. What I got from searching on net is that it works sometimes but doesnt work in some cases. An alternative is to use the prePersist method technique described in below link:
http://www.coderanch.com/t/450124/ORM/databases/entity-Boolean-field-default
Hope this helps.
In my spring project, the tables in database are created automatically by Hibernate using my entity classes as base, but I insert some default values in the table manually (using pgAdmin3).
Because that, I am facing now this problem: when I try insert a value via Java code in one of the tables which already have values, I receive a error message, saying the primary key already exists in the database.
Anyone knows how to solve this problem?
UPDATE
That's how I declare my primary key in my class:
#Id
#Column(name = "id")
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
Call this SQL query once per table to set the sequence to the next free number:
SELECT setval('tblname_id_seq', max(id)) FROM tblname;
tblname being the actual name of the table.
Hibernate may use a different naming convention, or the sequence may have been renamed. If you can't find the sequence behind the serial column, check with (per documentation):
SELECT pg_get_serial_sequence(tblname, column_name)
More details:
Modify Django AutoField start value
How to import a CSV to postgresql that already has ID's assigned?
The problem here might be that you declare the id as a primitive instead of a wrapper.
So instead of:
private int id;
You should have:
private Integer id;
When you create the entity with the id is initialized as 0, instead of NULL.
That's why you get duplicate id constraint violation exceptions.
Only when the id is NULL the AUTO generation strategy will delegate the id assignment to the database.
Given this class mapped with JPA (using JPA 1.0 and Hibernate):
#Entity
public class Foo {
private int bar;
/* ... */
}
What happens if I try to load a record which has the BAR column set to NULL?
Can I specify how to set the bar field when the corresponding column is NULL?
Notes
I know this is not a best practice. The question is more out of curiosity and it is inspired by this situation:
The database table is a staging table: adding a NOT NULL constraint is impractical. Bad data is expected, and the point of my code is to validate, clean up and/or reject data before loading it into the "real" database.
I have acceptable default values for some fields. For example, a boolean flag which should default to false.
I would rather use objects if a column may contain null value because Matheus's idea introduces false data. NULL <> 0!
Exception
2.
#Column(name = “bar”, nullable = false, columnDefinition = “bigint(20) default 0″)
private int bar;
it solves your problem.