I have an entity:
#Entity
public class VersionedDo {
#Version
#Column(name = "version")
private int version;
...
}
I want to update this entity without version increment in the some cases. Is there any way to do it without direct SQL update?
From Hibernate documentation :
Your application is forbidden from altering the version number set by Hibernate.
To artificially increase the version number, see the documentation for properties LockModeType.OPTIMISTIC_FORCE_INCREMENT or LockModeType.PESSIMISTIC_FORCE_INCREMENTcheck in the Hibernate Entity Manager reference documentation.
Therefore, using Hibernate, I guess you cannot force a version field to keep its original value when updating. You can only do the opposite.
You'll have to use a classic SQL query and don't forget to "refresh" your Hibernate object if needed.
Related
I am trying to persist a simple class using Spring, with hibernate/JPA and a PostgreSQL database.
The ID column of the table is a UUID, which I want to generate in code, not in the database.
This should be straightforward since hibernate and postgres have good support for UUIDs.
Each time I create a new instance and write it with save(), I get the following error:
o.h.j.JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "ID"; SQL statement: INSERT INTO DOODAHS (fieldA, fieldB) VALUES $1, $2) ...
This error indicates that it's expecting the ID column to be auto-populated (with some default value) when a row is inserted.
The class looks like this:
#lombok.Data
#lombok.AllArgsConstructor
#org.springframework.data.relational.core.mapping.Table("doodahs")
public class Doodah {
#org.springframework.data.annotation.Id
#javax.persistence.GeneratedValue(generator = "UUID")
#org.hibernate.annotations.GenericGenerator(name="UUID", strategy = "uuid2")
#javax.persistence.Column(nullable = false, unique = true)
private UUID id;
//... other fields
Things I have tried:
Annotate the field with #javax.persistence.Id (in addition to existing spring Id)
Annotate the field with #org.hibernate.annotations.Type(type = "pg-uuid")
Create the UUID myself - results in Spring complaining that it can't find the row with that id.
Specify strategy = "org.hibernate.id.UUIDGenerator"
Annotate class with #Entity
Replace spring #Id annotation with #javax.persistence.Id
I've seen useful answers here, here and here but none have worked so far.
NB the persistence is being handled by a class which looks like this:
#org.springframework.stereotype.Repository
public interface DoodahRepository extends CrudRepository<Doodah, UUID> ;
The DDL for the table is like this:
CREATE TABLE DOODAHS(id UUID not null, fieldA VARCHAR(10), fieldB VARCHAR(10));
Update
Thanks to Sve Kamenska, with whose help I finally got it working eventually. I ditched the JPA approach - and note that we are using R2DBC, not JDBC, so the answer didn't work straight away. Several sources (here, here, here, here, here and here) indicate that there is no auto Id generation for R2DBC. So you have to add a callback Bean to set your Id manually.
I updated the class as follows:
#Table("doodahs")
public class Doodah {
#org.springframework.data.annotation.Id
private UUID id;
I also added a Bean as follows:
#Bean
BeforeConvertCallback<Doodah> beforeConvertCallback() {
return (d, row, table) -> {
if (d.getId() == null){
d.id = UUID.randomUUID();
}
return Mono.just(d);
};
}
When a new object (with id = null, and isNew = true) is passed to the save() method, the callback method is invoked, and it sets the id.
Initially I tried using BeforeSaveCallback but it was being called too late in the process, resulting in the following exception:
JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "ID"....
Update
There are, at least, 2 types of Spring Data: JPA and JDBC.
The issue happens because you are mixing the 2 of them.
So, in order to fix, there are 2 solutions.
Solution 1 - Use Spring Data JDBC only.
Pom.xml dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
Generate ID.
Spring Data JDBC assumes that ID is generated on database level (like we already figured that out from log). If you try to save an entity with pre-defined id, Spring will assume that it is existing entity and will try to find it in the database and update. That is why you got this error in your attempt #3.
In order to generate UUID, you can:
Leave it to DB (it looks like Postgre allows to do it)
or Fill it in BeforeSaveCallback (more details here https://spring.io/blog/2021/09/09/spring-data-jdbc-how-to-use-custom-id-generation)
#Bean BeforeSaveCallback<Doodah> beforeSaveCallback() {
return (doodah, mutableAggregateChange) -> {
if (doodah.id == null) {
doodah.id = UUID.randomUUID();
}
return doodah;
};
}
Solution 2 - Use Spring Data JPA only
Pom.xml dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Generate ID.
Here you can, actually, use the approach with the UUID auto-generation, like you wanted to do initially
Use javax.persistence #Entity annotation instead of springdata #Table on the class-level
and Use #javax.persistence.Id and #javax.persistence.GeneratedValue with all defaults on id-field.
#javax.persistence.Id
#javax.persistence.GeneratedValue
private UUID id;
Other notes:
Specification of generator and strategy is not required, since it will generate based on the type of the id field (UUID in this case).
Specification of Column(nullable = false, unique = true) is not required either, since putting #Id annotation already assumes these constraints.
Initial answer before update
The main question: how do you save the entity? As id-generation is handled by JPA provider, Hibernate in this case. It is done during save method of em or repository. In order to create entities and ids Hibernate is looking for javax.persistence annotations, while you have Spring-specific, so I am wandering how do you save them.
And another question here: the error you provided INSERT INTO DOODAHS (fieldA, fieldB) VALUES $1, $2 shows that there is no id field in the insert-query at all. Did you just simplified the error-message and removed ID from it? Or this is original error and your code does not even "see" field ID? In that case the issue in not related to the id-generation, but rather is related to the question why your code does not see this field.
I'm updating an existing code that handles the copy or raw data from one table into multiple objects within the same database.
Previously, every kind of object had a generated PK using a sequence for each table.
Something like that :
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
In order to reuse existing IDs from the import table, we removed GeneratedValue for some entities, like that :
#Id
#Column(name = "id")
private Integer id;
For this entity, I did not change my JpaRepository, looking like this :
public interface EntityRepository extends JpaRepository<Entity, Integer> {
<S extends Entity> S save(S entity);
}
Now I'm struggling to understand the following behaviour, within a spring transaction (#Transactional) with the default propagation and isolation level :
With the #GeneratedValue on the entity, when I call entityRepository.save(entity) I can see with Hibernate show sql activated that an insert request is fired (however seems to be only in the cache since the database does not change)
Without the #GeneratedValue on the entity, only a select request is fired (no insert attempt)
This is a big issue when my Entity (without generated value) is mapped to MyOtherEntity (with generated value) in a one or many relationship.
I thus have the following error :
ERROR: insert or update on table "t_other_entity" violates foreign key constraint "other_entity_entity"
Détail : Key (entity_id)=(110) is not present in table "t_entity"
Seems legit since the insert has not been sent for Entity, but why ? Again, if I change the ID of the Entity and use #GeneratedValue I don't get any error.
I'm using Spring Boot 1.5.12, Java 8 and PostgreSQL 9
You're basically switching from automatically assigned identifiers to manually defined ones which has a couple of consequences both on the JPA and Spring Data level.
Database operation timing
On the plain JPA level, the persistence provider doesn't necessarily need to immediately execute a single insert as it doesn't have to obtain an identifier value. That's why it usually delays the execution of the statement until it needs to flush, which is on either an explicit call to EntityManager.flush(), a query execution as that requires the data in the database to be up to date to deliver correct results or transaction commit.
Spring Data JPA repositories automatically use default transactions on the call to save(…). However, if you're calling repositories within a method annotated with #Transactional in turn, the databse interaction might not occur until that method is left.
EntityManager.persist(…) VS. ….merge(…)
JPA requires the EntityManager client code to differentiate between persisting a completely new entity or applying changes to an existing one. Spring Data repositories w ant to free the client code from having to deal with this distinction as business code shouldn't be overloaded with that implementation detail. That means, Spring Data will somehow have to differentiate new entities from existing ones itself. The various strategies are described in the reference documentation.
In case of manually identifiers the default of inspecting the identifier property for null values will not work as the property will never be null by definition. A standard pattern is to tweak the entities to implement Persistable and keep a transient is-new-flag around and use entity callback annotations to flip the flag.
#MappedSuperclass
public abstract class AbstractEntity<ID extends SalespointIdentifier> implements Persistable<ID> {
private #Transient boolean isNew = true;
#Override
public boolean isNew() {
return isNew;
}
#PrePersist
#PostLoad
void markNotNew() {
this.isNew = false;
}
// More code…
}
isNew is declared transient so that it doesn't get persisted. The type implements Persistable so that the Spring Data JPA implementation of the repository's save(…) method will use that. The code above results in entities created from user code using new having the flag set to true, but any kind of database interaction (saving or loading) turning the entity into a existing one, so that save(…) will trigger EntityManager.persist(…) initially but ….merge(…) for all subsequent operations.
I took the chance to create DATAJPA-1600 and added a summary of this description to the reference docs.
I want to have tables located in different database schemas. But unfortunately, I can't achieve this with Spring Boot. Here steps to reproduce it.
Create a new Spring Boot project on http://start.spring.io version 2.0.5 (with derby and PostgreSQL dependencies)
Create simple entity
#Entity
#Table(name = "my_table")
public class MyTable {
#Id Integer id;
}
Add only next property to the application.properties with value 'update' or 'create' (if you try 'create-drop' then you get another error described here: https://github.com/spring-projects/spring-boot/issues/7706#issuecomment-268798059). Now Derby datasource will be used by default.
spring.jpa.hibernate.ddl-auto=create
Run a generated test or main class. Be sure all works fine.
Modify the entity, add attribute schema to the #Table annotation. Now the entity looks like:
#Entity
#Table(name = "my_table", schema = "my_schema")
public class MyTable {
#Id Integer id;
}
Run a test (or main class). This time I get an error while Spring Boot initialization process "java.sql.SQLSyntaxErrorException: Schema 'MY_SCHEMA' does not exist":
Full log listing is available here: https://gist.github.com/asaushkin/8d767c92b2e7025dd359f7be43eefdd6
Check on PostgreSQL. This error reproduces on a PostgreSQL instance too. Without the 'schema' attribute Spring Boot app runs perfect, but as soon as this attribute appears on the #Table annotation the exceptions are thrown.
Full log is here: https://gist.github.com/asaushkin/dd0d677964556bf943c4f013d4785372
My question is: why are schemas not created by Spring Boot?
These options can't resolve this issue too:
spring.jpa.properties.javax.persistence.schema-generation.create-database-schemas=true
spring.jpa.properties.hibernate.hbm2dll.create_namespaces=true
Links
https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#configurations-hbmddl
https://docs.spring.io/spring-boot/docs/current/reference/html/howto-data-access.html#howto-configure-jpa-properties
Update (11 March 2019):
I've just check the current behavior of the issue. I wonder, but currently with Derby driver all works fine and the table is created with the specified schema. But in PostgreSQL an error continues exists.
Generated SQL (for PostgreSQL) is:
create table my_schema.my_table (id int4 not null, primary key (id))
Check that are you specifying the database dialect in the application.properties file or not for more check this thread.
Unable to get spring boot to automatically create database schema
I had the same problem with PostgreSQL and JPA (ERROR o.h.e.jdbc.spi.SqlExceptionHelper - ERROR: relation "schema.table" does not exist) and I figured out this solution.
In your entities classes, add escape characters \", between database element´s name. For instance:
Use this form:
#Table(name = "\"USUARIO\"", schema="\"INVENTARIODB\"")
Rather than a typical way
#Table(name = "USUARIO", schema="INVENTARIODB")
The same applies for columns names
#Column(name = "\"ID\"", nullable = false, updatable = false)
private Long id;
Rather than
#Column(name = "ID", nullable = false, updatable = false)
private Long id;
UPDATE:
I discovered the reason that was causing the problem. I used Valentina Studio to create my DB, if I use capital letters (MYTABLE), instead lower-case letters (mytable) to create my tables, I had to use double quotes inside SQL statements. This is because PostgreSQL is case sensitive. If you can´t change your database then use my last solution. Also is a good idea to enable spring.jpa.show-sql=true property, so you can see hibernate´s queries and know what´s going on.
Rename spring.jpa.properties.javax.persistence.schema-generation.create-database-schemas to spring.jpa.properties.javax.persistence.create-database-schemas. In other words, remove '.schema-generation'.
I just had the same problem not with PostgreSQL but H2 - schemas weren't being created. But, as I've discovered, the problem is not with H2 (or, likely, PostgreSQL) but, rather, Hibernate (it deviates from the standard, regarding that nomenclature). That likely means that this solution will work for you too.
I have a Spring Boot 1.3.M1 web application using Spring Data JPA. For optimistic locking, I am doing the following:
Annotate the version column in the entity: #Version private long version;. I confirmed, by looking at the database table, that this field is incrementing properly.
When a user requests an entity for editing, sending the version field as well.
When the user presses submit after editing, receiving the version field as a hidden field or something.
Server side, fetching a fresh copy of the entity, and then updating the desired fields, along with the version field. Like this:
User user = userRepository.findOne(id);
user.setName(updatedUser.getName());
user.setVersion(updatedUser.getVersion());
userRepository.save(user);
I was expecting this to throw exception when the versions wouldn't match. But it doesn't. Googling, I found some posts saying that we can't set the #Vesion property of an attached entity, like I'm doing in the third statement above.
So, I am guessing that I'll have to manually check for the version mismatch and throw the exception myself. Would that be the correct way, or I am missing something?
Unfortunately, (at least for Hibernate) changing the #Version field manually is not going to make it another "version". i.e. Optimistic concurrency checking is done against the version value retrieved when entity is read, not the version field of entity when it is updated.
e.g.
This will work
Foo foo = fooRepo.findOne(id); // assume version is 2 here
foo.setSomeField(....);
// Assume at this point of time someone else change the record in DB,
// and incrementing version in DB to 3
fooRepo.flush(); // forcing an update, then Optimistic Concurrency exception will be thrown
However this will not work
Foo foo = fooRepo.findOne(id); // assume version is 2 here
foo.setSomeField(....);
foo.setVersion(1);
fooRepo.flush(); // forcing an update, no optimistic concurrency exception
// Coz Hibernate is "smart" enough to use the original 2 for comparison
There are some way to workaround this. The most straight-forward way is probably by implementing optimistic concurrency check by yourself. I used to have a util to do the "DTO to Model" data population and I have put that version checking logic there. Another way is to put the logic in setVersion() which, instead of really setting the version, it do the version checking:
class User {
private int version = 0;
//.....
public void setVersion(int version) {
if (this.version != version) {
throw new YourOwnOptimisticConcurrencyException();
}
}
//.....
}
You can also detach entity after reading it from db, this will lead to version check as well.
User user = userRepository.findOne(id);
userRepository.detach(user);
user.setName(updatedUser.getName());
user.setVersion(updatedUser.getVersion());
userRepository.save(user);
Spring repositories don't have detach method, you must implement it. An example:
public class BaseRepositoryImpl<T, PK extends Serializable> extends QuerydslJpaRepository<T, PK> {
private final EntityManager entityManager;
public BaseRepositoryImpl(JpaEntityInformation entityInformation, EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
}
public void detach(T entity) {
entityManager.detach(entity);
}
...
}
Part of the #AdrianShum answer is correct.
The version comparing behavior follows basically this steps:
Retrieve the versioned entity with its version number, lets called V1.
Suppose you modify some entity's property, then Hibernate increments the version number to V2 "in memory". It doesn't touch the database.
You commit the changes or they are automatically commited by the environment, then Hibernate will try to update the entity including its version number with V2 value. The update query generated by Hibernate will modify the registry of the entity only if it match the ID and previous version number (V1).
After the entity registry is successfully modified, the entity takes V2 as its actual version value.
Now suppose that between steps 1 and 3 the entity was modified by another transaction so its version number at step 3 isn't V1. Then as the version number are different the update query won't modify any registry, hibernate realize that and throw the exception.
You can simply test this behavior and check that the exception is thrown altering the version number directly on your database between steps 1 and 3.
Edit.
Don't know which JPA persistence provider are you using with Spring Data JPA but for more details about optimistic locking with JPA+Hibernate I suggest you to read chapter 10, section Controlling concurrent access, of the book Java Persistence with Hibernate (Hibernate in Action)
In addition to #Adrian Shum answer, I want to show how I solved this problem. If you want to manually change a version of Entity and perform an update to cause OptimisticConcurrencyException you can simply copy Entity with all its field, thus causing an entity to leave its context (same as EntityManager.detach()). In this way, it behaves in a proper way.
Entity entityCopy = new Entity();
entityCopy.setId(id);
... //copy fields
entityCopy.setVersion(0L); //set invalid version
repository.saveAndFlush(entityCopy); //boom! OptimisticConcurrencyException
EDIT:
the assembled version works, only if hibernate cache does not contain entity with the same id. This will not work:
Entity entityCopy = new Entity();
entityCopy.setId(repository.findOne(id).getId()); //instance loaded and cached
... //copy fields
entityCopy.setVersion(0L); //will be ignored due to cache
repository.saveAndFlush(entityCopy); //no exception thrown
Let's say we have an entity
#Entity
public class Person {
#Id int id;
#Basic String name;
#Basic String remark;
}
Let's say "remark" field is filled with big texts, but rarely used. So it would be good if when you run jpql: SELECT p FROM Person p, EclipseLink just executes sql select id, name from person
And than when you call person.getRemark(), it will get fetched with select remark from person where id = ?.
Is it possible with EclipseLink 2.1?
You can indeed define a fetch attribute in a Basic annotation and set it to LAZY. But let me quote what the specification says about it:
11.1.6 Basic Annotation
(...)
The EAGER strategy is a requirement
on the persistence provider runtime
that data must be eagerly fetched.
The LAZY strategy is a hint to the persistence provider runtime that
data should be fetched lazily when it
is first accessed. The implementation
is permitted to eagerly fetch data for
which the LAZY strategy hint has
been specified. In particular, lazy
fetching might only be available for
Basic mappings for which
property-based access is used.
In the particular case of EclipseLink, the behavior will depend on the context (Java EE vs Java SE) as explained in What You May Need to Know About EclipseLink JPA Lazy Loading.
In a Java EE environment (assuming the container implements the appropriate container contracts of the EJB 3.0 specification):
EclipseLink JPA performs lazy loading when the fetch attribute is set to javax.persistence.FetchType.LAZY.
In a Java SE environment:
By default, EclipseLink JPA ignores the
fetch attribute and default javax.persistence.FetchType.EAGER applies.
To configure EclipseLink JPA to perform lazy loading when the fetch attribute set to FetchType.LAZY, consider one of the following:
How to Configure Dynamic Weaving for JPA Entities Using the EclipseLink Agent
How to Configure Static Weaving for JPA Entities
Try add annotation #Basic(fetch = FetchType.LAZY)
#Entity
public class Person {
#Id int id;
#Basic String name;
#Basic(fetch = FetchType.LAZY) String remark;
}
We solved this problem (when using ActiveRecord and Hibernate) by putting the large string (usually a CLOB or BLOB) into it's own table with a FK to the main table (Person in this case.) Then it works like you want.