Adding a jpa record / bean with defaults [duplicate] - jpa

In my SQL Server 2000 database, I have a timestamp (in function not in data type) column of type DATETIME named lastTouched set to getdate() as its default value/binding.
I am using the Netbeans 6.5 generated JPA entity classes, and have this in my code
#Basic(optional = false)
#Column(name = "LastTouched")
#Temporal(TemporalType.TIMESTAMP)
private Date lastTouched;
However when I try to put the object into the database I get,
javax.persistence.PersistenceException: org.hibernate.PropertyValueException: not-null property references a null or transient value: com.generic.Stuff.lastTouched
I've tried setting the #Basic to (optional = true), but that throws an exception saying the database doesn't allow null values for the TIMESTAMP column, which it doesn't by design.
ERROR JDBCExceptionReporter - Cannot insert the value NULL into column 'LastTouched', table 'DatabaseName.dbo.Stuff'; column does not allow nulls. INSERT fails.
I previously got this to work in pure Hibernate, but I have since switched over to JPA and have no idea how to tell it that this column is supposed to be generated on the database side. Note that I am still using Hibernate as my JPA persistence layer.

I fixed the issue by changing the code to
#Basic(optional = false)
#Column(name = "LastTouched", insertable = false, updatable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date lastTouched;
So the timestamp column is ignored when generating SQL inserts. Not sure if this is the best way to go about this. Feedback is welcome.

I realize this is a bit late, but I've had success with annotating a timestamp column with
#Column(name="timestamp", columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
This should also work with CURRENT_DATE and CURRENT_TIME. I'm using JPA/Hibernate with Oracle, so YMMV.

#Column(nullable = false, updatable = false)
#CreationTimestamp
private Date created_at;
this worked for me.
more info

Add the #CreationTimestamp annotation:
#CreationTimestamp
#Column(name="timestamp", nullable = false, updatable = false, insertable = false)
private Timestamp timestamp;

If you are doing development in Java 8 and Hibernate 5 Or Spring Boot JPA then use following annotation directly
in your Entity class. Hibernate gets the current timestamp from the VM and will insert date and time in database.
public class YourEntity {
#Id
#GeneratedValue
private Long id;
private String name;
#CreationTimestamp
private LocalDateTime createdDateTime;
#UpdateTimestamp
private LocalDateTime updatedDateTime;
…
}

I do not think that every database has auto-update timestamps (e.g. Postgres). So I've decided to update this field manually everywhere in my code. This will work with every database:
thingy.setLastTouched(new Date());
HibernateUtil.save(thingy);
There are reasons to use triggers, but for most projects, this is not one of them. Triggers dig you even deeper into a specific database implementation.
MySQL 5.6.28 (Ubuntu 15.10, OpenJDK 64-Bit 1.8.0_66) seems to be very forgiving, not requiring anything beyond
#Column(name="LastTouched")
MySQL 5.7.9 (CentOS 6, OpenJDK 64-Bit 1.8.0_72) only works with
#Column(name="LastTouched", insertable=false, updatable=false)
not:
FAILED: removing #Temporal
FAILED: #Column(name="LastTouched", nullable=true)
FAILED: #Column(name="LastTouched", columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
My other system info (identical in both environments)
hibernate-entitymanager 5.0.2
hibernate-validator 5.2.2
mysql-connector-java 5.1.38

I have this working well using JPA2.0 and MySQL 5.5.10, for cases where I only care about the last time the row was modified. MySQL will create a timestamp on first insertion, and every time UPDATE is called on the row. (NOTE: this will be problematic if I cared whether or not the UPDATE actually made a change).
The "timestamp" column in this example is like a "last-touched" column.x`
The code below uses a separate column "version" for optimistic locking.
private long version;
private Date timeStamp
#Version
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
// columnDefinition could simply be = "TIMESTAMP", as the other settings are the MySQL default
#Column(name="timeStamp", columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
#Temporal(TemporalType.TIMESTAMP)
public Date getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(Date timeStamp) {
this.timeStamp = timeStamp;
}
(NOTE: #Version doesn't work on a MySQL "DATETIME" column, where the attribute type is "Date" in the Entity class. This was because Date was generating a value down to the millisecond, however MySQL was not storing the millisecond, so when it did a comparison between what was in the database, and the "attached" entity, it thought they had different version numbers)
From the MySQL manual regarding TIMESTAMP :
With neither DEFAULT nor ON UPDATE clauses, it is the same as DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP.

If you mark your entity with #DynamicInsert e.g.
#Entity
#DynamicInsert
#Table(name = "TABLE_NAME")
public class ClassName implements Serializable {
Hibernate will generate SQL without null values. Then the database will insert its own default value. This does have performance implications See [Dynamic Insert][1].

This also works for me:-
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "CREATE_DATE_TIME", nullable = false, updatable = false, insertable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
public Date getCreateDateTime() {
return createDateTime;
}
public void setCreateDateTime(Date createDateTime) {
this.createDateTime = createDateTime;
}

I'm posting this for people searching for an answer when using MySQL and Java Spring Boot JPA, like #immanuelRocha says, only have too #CreationTimeStamp to the #Column in Spring, and in MySQL set the default value to "CURRENT_TIMESTAMP".
In Spring add just the line :
#Column(name = "insert_date")
#CreationTimestamp
private Timestamp insert_date;

#Column(name = "LastTouched", insertable = false, updatable = false, columnDefinition = "TIMESTAMP default getdate()")
#Temporal(TemporalType.TIMESTAMP)
private Date LastTouched;`enter code here`

This worked for me:
#Column(name = "transactionCreatedDate", nullable = false, updatable = false, insertable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")

Related

How to avoid id collisions in Spring Data JPA/Hibernate-generated database?

We use a dockerized postgres database and have hibernate auto-generate the tables (using spring.jpa.hibernate.ddl-auto: create) for our integration tests. Using something like H2 is not an option because we do some database-specific operations in a few places, e.g. native SQL queries.
Is there any way to avoid id collisions when all entities use auto-incremented ids? Either by offsetting the start id or, better yet, having all tables use a shared sequence?
Schema is created when the docker container is launched, tables are created by Spring Data JPA/Hibernate
Example
Examples use kotlin syntax and assumes the "allopen"-plugin is applied for entities.
Sometimes we've had bugs where the wrong foreign key was used, e.g. something like this:
#Entity
class EntityOne(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false, columnDefinition = "SERIAL")
var id: Long,
)
#Entity
class EntityTwo(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false, columnDefinition = "SERIAL")
var id: Long,
)
#Entity
class JoinEntity(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false, columnDefinition = "SERIAL")
var id: Long,
#ManyToOne
#JoinColumn(name = "entity_one_id")
var entityOne: EntityOne,
#ManyToOne
#JoinColumn(name = "entity_two_id")
var entityTwo: EntityTwo,
)
#Repository
interface JoinEntityRepository : JpaRepository<JoinEntity, Long> {
//
// Bug here! Should be "WHERE entityOne.id = :entityOneId"
//
#Query("SELECT entityTwo FROM JoinEntity WHERE entityTwo.id = :entityOneId")
fun findEntityTwoByEntityOneId(entityOneId: Long): Collection<EntityTwo>
}
These bugs can in some circumstances be very hard to find because when the table is created, there may very well be an Entity2 with the same id as some Entity1, and so the query succeeds but the test fails somewhere down the line because while it is returning one or more Entity2, it's not the expected ones.
Even worse, depending on the scope of the test it may pass even if the wrong entity is fetched, or fail only when tests are run in a specific order (due to ids getting "out of sync"). So ideally it should fail to even find an entity when the wrong id is passed. But because the database structure is created from scratch and the ids are auto-incremented they always start at 1.
I found a solution to this.
In my resources/application.yml (in the test folder, you most likely do not want to do this in your main folder) I add spring.datasource.initialization-mode: always and a file data.sql.
The contents of data.sql are as follows:
DROP SEQUENCE IF EXISTS test_shared_sequence;
CREATE SEQUENCE test_shared_sequence;
ALTER TABLE entity_one ALTER COLUMN id SET DEFAULT nextval('test_shared_sequence');
ALTER TABLE entity_two ALTER COLUMN id SET DEFAULT nextval('test_shared_sequence');
After Spring has auto-generated the tables (using spring.jpa.hibernate.ddl-auto: create) it will run whatever is in this script, and the script will change all tables to auto-generate ids based on the same sequence, meaning that no two entities will ever have the same id regardless of which table they're stored in, and as such any query that looks in the wrong table for an id will fail consistently.

JPA generate partial unique key

I have a table with 3 columns
UUID - A UUID that is the primary key of the table
ID - A human readable ID of the resource (for a new resource, the ID should be automatically generated by a sequence)
Version - A version number
I am using JPA.
The table can contain multiple records with the same "human readable" ID and different versions.
I would like to be able to insert a new record without specifying the ID: the database should generate the ID automatically.
At the same time, when I need to insert a new version of the same resource, I would like to be able to insert a new row specifying the ID.
I have created a table where the UUID is the primary key, ID is defined as "integer generated by default as identity" and version is just an integer.
Using SQL query I can do what I want, but I do not know how to do it using JPA.
If I define the column as:
#Column(name="ID", insertable = false, updatable = false, nullable = false)
I can insert new records but the ID is always generated as new even if the resource already has one because the insert does not include the column.
If I define the column as:
#Column(name="ID", insertable = true, updatable = false, nullable = false)
The insert include the column and I am able to insert new rows specifying the ID but I cannot insert a row without the ID because the SQL generated is passing a null value for that column.
UPDATE
I have modified the configuration adding the annotation #Generated:
#Generated(value = GenerationTime.INSERT)
#Column(name="ID", updatable = false, nullable = false)
private Integer id;
With this, I am having the same problem: if I pass a value for id, the database is still generating a new one.
You can try to use #DynamicInsert annotation.
Assuming that you have the following table:
create table TST_MY_DATA
(
dt_id uuid,
dt_auto_id integer generated by default as identity,
dt_version integer,
primary key(dt_id)
);
Appropriate entity will look like this:
#Entity
#Table(name = "TST_MY_DATA")
#DynamicInsert
public class TestData
{
#Id
#GeneratedValue
#Column(name = "dt_id")
private UUID id;
// Unfortunately you cannot use #Generated annotation here,
// otherwise this column will be always absent in hibernate generated insert query
// #Generated(value = GenerationTime.INSERT)
#Column(name = "dt_auto_id")
private Long humanReadableId;
#Version
#Column(name = "dt_version")
private Long version;
// getters/setters
}
and then you can persist entities:
TestData test1 = new TestData();
session.persist(test1);
TestData test2 = new TestData();
test2.setHumanReadableId(27L);
session.persist(test2);
session.flush();
// here test1.getHumanReadableId() is null
/*
* You can use session.refresh(entity) only after session.flush() otherwise you will have:
* org.hibernate.UnresolvableObjectException: No row with the given identifier exists:
* [this instance does not yet exist as a row in the database#ff09c202-cd17-4d4a-baea-057e475fabb9]
**/
session.refresh(test1);
// here you can use the test1.getHumanReadableId() value fetched from DB

JPA EclipseLink: handle default value

I have the following column definition:
#Column(name="active", nullable=false, columnDefinition="BOOLEAN DEFAULT FALSE"
private Boolean active;
In the Postgres database the column active is defined as BOOLEAN NOT NULL DEFAULT FALSE
But when I insert a new record, without setting active, EclipseLink generates a null value for this field and the insert statement obviously fails because Postgres does not allow a null value for the NOT NULL column.
What am I doing wrong?
Well, if I define my field as
private boolean active;
then I will indirectly set the field to false. But I cannot use this trick with Date fields. So, I am looking for a solution which will work for all column types.
The columnDefinition is only used for the DDL creation and can be database specific. You may have problems switching the DB if you use it.
Using primitive types ensures that the value is not null, and without explicitly defining a value, the primitive's default is used. However, you can also define default values for non-primitive values by assigning them: private Boolean active = false;
That will also work with Date.
When you need complex default values you can use the PrePersist annotation:
#PrePersist
public void setDefaultValues() {
if (active == null) {
active = false;
}
if (value == null) {
//do complex stuff
}
}
If you NEVER are going to set the attribute, you can use #Column (insertable = false), if you set a value, this will be ignored anyway.
This is a very common setting on Date like
#Column(nullable = false, insertable = false, updatable = false, columnDefinition = "timestamp not null default CURRENT_TIMESTAMP")
#Temporal(TemporalType.TIMESTAMP)
private Date systemDate;

query for nextval sequence generation taking long time

I have a process in application where we insert 1000+ records in application. There is a attribute in the table as mentioned below.
#Index(name = "_IDX_UNIQUE_UUID", columnNames = {"UUID"})
#Column(name = "UUID", nullable = false)
private Long uuid;
During populating the entity class the value is set as following
entity.setUuid(service.getNextUuid());
which calls this method in service class:
public Long getNextUuid() {
Query query = entityManager.createNativeQuery("select nextval('seq_xxxx_uuid')");
//This takes lot of time to execute
Obj result = query.getSingleResult();
return ((BigInteger) result).longValue();
}
To get next value from sequence, it takes around 200-700 ms. This accumulates to several minutes if I try to insert 1000+ records. Moreover, the query is quite simple and executes in less than 1ms if I execute it in a database client.
I am using postgresql 9.4-1206-jdbc4, hibernate 4.2.0.Final and Spring 4.2.5.
It's slow, because you're going through all the layers of security and consistency checks of a JPA full query, plus you're creating a new NativeQuery object every time and not doing it though a #NamedNativeQuery, thus taking even more time to evaluate.
The solution is to annotate the column with:
#Id
#Index(name = "_IDX_UNIQUE_UUID", columnNames = {"UUID"})
#SequenceGenerator(name = "seq_xxxx_uuid_gen", sequenceName = "seq_xxxx_uuid", allocationSize = 1)
#GeneratedValue(generator = "seq_xxxx_uuid_gen")
#Column(name = "UUID", nullable = false)
private Long id;
You do want that allocationSize to be the same value as the increment_by value of your sequence, or you'll have other problems after the first persist.
If you need to use that Id for multiple objects (say it's part of a composite key, but only part of it is serial-generated), just grab it from the first one after persisting it.
Edit:
Alternatively you can forego the SequenceGenerator and use this:
#GeneratedValue(strategy = GenerationType.IDENTITY)
It may be faster, if the generated query is properly optimized and uses java.sql.Statement.getGeneratedKeys() upon insert.

Why won't my EJB Entity work with a non-relational table, when using createNativeQuery?

I'm trying to use entities with a MySQL ndbcluster table. This table type doesn't allow foreign keys, but up until now it hasn't been a problem with my entities.
However, I have run into a bit of a problem, when I try to load an entity using the EntityManager's createNativeQuery method. I need to use this method due to my inability to do this: How to make a CriteriaBuilder join with a custom "on" condition?
My MySQL table looks like this:
CREATE TABLE `category` (
`id` SMALLINT(6) NOT NULL AUTO_INCREMENT,
`category_id` SMALLINT(6) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `category_id` (`category_id`)
)
COLLATE='utf8_general_ci'
ENGINE=ndbcluster
ROW_FORMAT=DEFAULT
If I change the table engine to innodb, and add foreign keys, the createNativeQuery method works fine.
My entity class looks like this:
#Entity
#Table(name = "category")
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Short id;
#OneToMany(mappedBy = "categoryId")
private List<Category> categoryList;
#JoinColumn(name = "category_id", referencedColumnName = "id")
#ManyToOne
private Category categoryId;
public Category() {
}
// getters and setters
}
Even without foreign keys, this entity works fine when I use the CriteriaBuilder for a query, but unfortunately not everything is possible with the CriteriaBuilder.
I get an error when I call getResultList on a Query object created with createNativeQuery. I don't know if this is a bug, or if something should be added to my entity class to make this work.
The error says:
Exception [EclipseLink-6044] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.QueryException
Exception Description: The primary key read from the row [ArrayRecord(
=> 2519
=> 2463
=> Tools)] during the execution of the query was detected to be null. Primary keys must not contain null.
Query: ReadAllQuery(referenceClass=Category sql="select * from `category`")
at org.eclipse.persistence.exceptions.QueryException.nullPrimaryKeyInBuildingObject(QueryException.java:895)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildObject(ObjectBuilder.java:584)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildObject(ObjectBuilder.java:560)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.buildObject(ObjectLevelReadQuery.java:717)
at org.eclipse.persistence.queries.ReadAllQuery.registerResultInUnitOfWork(ReadAllQuery.java:769)
...
My table contains 1 row, where id=1 and category_id=null, so there are no primary keys with a null-value, despite what the error says. If I remove that row or set category_id=1, I don't get an error.
Need help, please.
Managed to make it work by switching from EclipseLink (JPA 2.0) to OpenJPA (JPA 2.0). Seems like there is a bug somewhere in EclipseLink 2.3.2 and/or GlassFish 3.1.2.2.
I've used EclipseLink (JPA 2.0) in another project of mine, using a slightly different version Netbeans + GlassFish 3.1.1, where I used createNativeQuery on an entity class for a non-relational myisam table. This never caused any problem. It really must be a bug.
But problem solved. Bye, bye EclipseLink, hello OpenJPA.
The issue is case sensitivity. In MySQL your column "id" will be defined in the database as "ID" unless you quote it. If you switch your mappings to upper case it should fix the issue (i.e. "ID").
You could also quote the column name ("'id'")
or set the persistence unit property,
"eclipselink.jpa.uppercase-column-names"="true"