Spring Data JPA - Hibernate - Improper increment of #Version of the entity - spring

Entity Class : Sport
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Version;
import java.util.UUID;
#Entity
public class Sport {
#Id
private UUID id;
private String name;
private String description;
#Version
private Long version;
// getter and setter omitted
}
I have one IT named as RepositoryIT with one Test case :
#Rollback(value = false)
#Test
public void validate_version_increment(){
// GIVEN
final Sport sport = new Sport().setId(UUID.randomUUID()).setDescription("Cricket").setName("Cricket");
final Sport sport1 = sportRepository.save(sport);
// as per my understanding version should be 0
sport1.setDescription("Awesome");
final Sport sport2 = sportRepository.save(sport1);
// as per my understanding version should be 1
sport2.setDescription("Too many rules for beginners");
final Sport sport3 = sportRepository.save(sport2);
// now version should be 2 ? or not1
sport3.setDescription("Too many1 rules for beginners");
final Sport sport4 = sportRepository.save(sport3);
sport4.setDescription("Too many2 rules for beginners");
final Sport sport5 = sportRepository.save(sport4);
sport5.setDescription("Too many3 rules for experts");
sportRepository.save(sport5);
}
version is always 1 but it does update description. After saving sport1 version increments to 1 and then it does not care.
and this is the hibernate logs :
Hibernate: insert into sport (description, name, version, id) values (?, ?, ?, ?)
2020-12-04 20:23:19.350 DEBUG 13961 --- [ main] n.t.d.l.l.SLF4JQueryLoggingListener : Name:DS-Proxy, Connection:3, Time:3, Success:True, Type:Prepared, Batch:False, QuerySize:1, BatchSize:0, Query:["insert into sport (description, name, version, id) values (?, ?, ?, ?)"], Params:[(Cricket,Cricket,0,86aad234-6493-413d-83c1-89927be91948)]
Hibernate: update sport set description=?, name=?, version=? where id=? and version=?
2020-12-04 20:23:19.353 DEBUG 13961 --- [ main] n.t.d.l.l.SLF4JQueryLoggingListener : Name:DS-Proxy, Connection:3, Time:1, Success:True, Type:Prepared, Batch:False, QuerySize:1, BatchSize:0, Query:["update sport set description=?, name=?, version=? where id=? and version=?"], Params:[(Too many3 rules for experts,Cricket,1,86aad234-6493-413d-83c1-89927be91948,0)]
I can not understand, what is the rule of #Version increment ? I know Hibernate Dirty check will check for changes for managed entity and I am changing something in every update but maybe save() does not play any role and after only transaction ends on commit it increases the version.

The rule, first and foremost, is to only flush changes to the DB when necessary. Since it's a transactional method, you can make as many changes as you like, and until the changes actually make it into the DB when the transaction is committed, the version will not be incremented.
The version increment you are seeing is due to the final value being committed being different than the one originally fetched. The intermediate changes you make just make the original fetched entity dirty, but they don't make it to the DB, so - no version update.
Debug your test and you'll see that sport1, sport2 etc. all point to the same Java object. Also, no update statements happen until the transaction is committed. save only marks the entity as being tracked by JPA, it doesn't immediately issue any DB inserts/updates.

As it is a transaction, commit is happening after return statement. Changes are persisted in database while committing the transaction in this case. As you are not flushing explicitly, changes reflect in database only after commit. And before commit operation you are mapping the current state of entity.
Solution 1 : You can use flush explicitly
final Sport sport5 = sportRepository.saveAndFlush(sport4);
Solution 2 : You can commit your entity once the transaction is performed

Related

Spring MVC Entities Id (Generated value) separate counter

developping a new Java Spring MVC microservice i have encountered a minor issue.
When i send a creation request for any entity, the id generated always follows the previous one.
My event entity id configuration
My user entity id configuration
For example, this is what i got from these 2 requests
User creation request (you can see the id value is 1)
Event creation request (you can see the id value is 2)
The created event Id is the last created user Id + 1 which i obviously do not want to happen.
I want separate Id values for each entity. I want to know what i am doing wrong.
Thank you
Your solution worker pretty well ;)
I finally used it and added #SequenceGenerator annotation to initialize the count at 0.
#SequenceGenerator(name = "seq", initialValue = 0)
public class ClassName {
#Id
#GeneratedValue(generator = "seq")
private Integer id;
}
Thank you very much Daniel, that's kind of you.
You are using #GeneratedValue without providing a strategy.
Therefore it uses the AUTO strategy by default which indicates that the persistence provider should pick an appropriate strategy for the particular database.
As both ID columns share the same name I assume that both entities share one and the same generator.
Which results in
Create event entity with ID = 1 as the generator started at one
Create user entity with ID = 1 + 1 as new generated value is requested
You should think about using different sequences for generating separate IDs for each entity.
Following uses a DB sequence to generate an ID
#Id
#GeneratedValue(generator = "my_entity_name_seq")
private long id;
If I would not specify a concrete generator in the annotation hibernate ,for example will, create a default sequence called hibernate_sequence which is then used for all entities which use #GeneratedValue without specifying a generator. This then leads to incremented values over all tables / entities.

#SecondaryTable with where condition

I am creating entity for table created outside of my system. I want to bind data from other table to entity field by using #SecondaryTable (or possibly better solution), but only to do so if condition is met. IE. my table has 1 row, I want to bind data from other table (oneToMany) where certain condition is met (exactly one match from other table(transform to one to one)). Can I use #Where annotation and how? If not is there alternative?
Edit: here is the entity and additional info on the related table
#Entity
#Table(name = "RE_STORAGE_INSTANCE")
public class Movie {
#Id
#Column(name="ID_")
private Long id;
...
//Column I want to fetch
private Date dueDate;
}
Table RE_VARIABLES manyToOne to table RE_STORAGE_INSTANCE, contains fields: re_key, re_value. I want to fetch re_value only if 're_key' equals dueDate. Even though it's manyToOne, only one row of RE_VARIABLES contains due date for each RE_STORAGE_INSTANCE row.

Spring Data / Hibernate save entity with Postgres using Insert on Conflict Update Some fields

I have a domain object in Spring which I am saving using JpaRepository.save method and using Sequence generator from Postgres to generate id automatically.
#SequenceGenerator(initialValue = 1, name = "device_metric_gen", sequenceName = "device_metric_seq")
public class DeviceMetric extends BaseTimeModel {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "device_metric_gen")
#Column(nullable = false, updatable = false)
private Long id;
///// extra fields
My use-case requires to do an upsert instead of normal save operation (which I am aware will update if the id is present). I want to update an existing row if a combination of three columns (assume a composite unique) is present or else create a new row.
This is something similar to this:
INSERT INTO customers (name, email)
VALUES
(
'Microsoft',
'hotline#microsoft.com'
)
ON CONFLICT (name)
DO
UPDATE
SET email = EXCLUDED.email || ';' || customers.email;
One way of achieving the same in Spring-data that I can think of is:
Write a custom save operation in the service layer that
Does a get for the three-column and if a row is present
Set the same id in current object and do a repository.save
If no row present, do a normal repository.save
Problem with the above approach is that every insert now does a select and then save which makes two database calls whereas the same can be achieved by postgres insert on conflict feature with just one db call.
Any pointers on how to implement this in Spring Data?
One way is to write a native query insert into values (all fields here). The object in question has around 25 fields so I am looking for an another better way to achieve the same.
As #JBNizet mentioned, you answered your own question by suggesting reading for the data and then updating if found and inserting otherwise. Here's how you could do it using spring data and Optional.
Define a findByField1AndField2AndField3 method on your DeviceMetricRepository.
public interface DeviceMetricRepository extends JpaRepository<DeviceMetric, UUID> {
Optional<DeviceMetric> findByField1AndField2AndField3(String field1, String field2, String field3);
}
Use the repository in a service method.
#RequiredArgsConstructor
public class DeviceMetricService {
private final DeviceMetricRepository repo;
DeviceMetric save(String email, String phoneNumber) {
DeviceMetric deviceMetric = repo.findByField1AndField2AndField3("field1", "field", "field3")
.orElse(new DeviceMetric()); // create new object in a way that makes sense for you
deviceMetric.setEmail(email);
deviceMetric.setPhoneNumber(phoneNumber);
return repo.save(deviceMetric);
}
}
A word of advice on observability:
You mentioned that this is a high throughput use case in your system. Regardless of the approach taken, consider instrumenting timers around this save. This way you can measure the initial performance against any tunings you make in an objective way. Look at this an experiment and be prepared to pivot to other solutions as needed. If you are always reading these three columns together, ensure they are indexed. With these things in place, you may find that reading to determine update/insert is acceptable.
I would recommend using a named query to fetch a row based on your candidate keys. If a row is present, update it, otherwise create a new row. Both of these operations can be done using the save method.
#NamedQuery(name="getCustomerByNameAndEmail", query="select a from Customers a where a.name = :name and a.email = :email");
You can also use the #UniqueColumns() annotation on the entity to make sure that these columns always maintain uniqueness when grouped together.
Optional<Customers> customer = customerRepo.getCustomersByNameAndEmail(name, email);
Implement the above method in your repository. All it will do it call the query and pass the name and email as parameters. Make sure to return an Optional.empty() if there is no row present.
Customers c;
if (customer.isPresent()) {
c = customer.get();
c.setEmail("newemail#gmail.com");
c.setPhone("9420420420");
customerRepo.save(c);
} else {
c = new Customer(0, "name", "email", "5451515478");
customerRepo.save(c);
}
Pass the ID as 0 and JPA will insert a new row with the ID generated according to the sequence generator.
Although I never recommend using a number as an ID, if possible use a randomly generated UUID for the primary key, it will qurantee uniqueness and avoid any unexpected behaviour that may come with sequence generators.
With spring JPA it's pretty simple to implement this with clean java code.
Using Spring Data JPA's method T getOne(ID id), you're not querying the DB itself but you are using a reference to the DB object (proxy). Therefore when updating/saving the entity you are performing a one time operation.
To be able to modify the object Spring provides the #Transactional annotation which is a method level annotation that declares that the method starts a transaction and closes it only when the method itself ends its runtime.
You'd have to:
Start a jpa transaction
get the Db reference through getOne
modify the DB reference
save it on the database
close the transaction
Not having much visibility of your actual code I'm gonna abstract it as much as possible:
#Transactional
public void saveOrUpdate(DeviceMetric metric) {
DeviceMetric deviceMetric = metricRepository.getOne(metric.getId());
//modify it
deviceMetric.setName("Hello World!");
metricRepository.save(metric);
}
The tricky part is to not think the getOne as a SELECT from the DB. The database never gets called until the 'save' method.

Avoid duplicate primary keys after database intialization in spring-boot 2

The default #GeneratedValue strategy used to work in a spring boot 1.5 web app, without duplicate id conflicts of any type
... using a simple entity such as this one
// in my/package/Car.java
// ...
#Entity
public class Car {
private long id;
private String company;
private String model;
#Id
#GeneratedValue
public long getId() {
return id;
}
// ... more getters and setters
}
... and initializing the DB at start-up with
# in src/main/resources/import.sql
insert into car values (1, 'Tesla', 'Roadster');
... and later inserting another car with
Car c = new Car();
c.setCompany("Ford");
c.setModel("Pinto");
entityManager.persist(c);
entityManager.flush();
// expect no issue inserting, and a valid ID
log.info("Assigned ID is " + c.getId());
... used to result in a new Car with id 2. I do not really care about the generated ID, as long as there is no conflict. However, this same code now throws the following exception:
org.hsqldb.HsqlException: integrity constraint violation: unique constraint or index violation; SYS_PK_10095 table: CAR
(the DB is HSQL, and I would much rather not have to replace it)
... because the default sequence generation in hibernate 5.2 now does not take existing inserts into account.
What are my possible work-arounds to still allow the database to be initialized via import.sql? I know I can
use very large ids at initialization time (but this is just kicking the can down the road, and not a real solution: eventually the sequence will catch up and break things)
write my own sequence generator (but there has to be a much easier way of initializing a DB!)
use the old sequence generation (but again, why did they change it if there was no advantage to doing so? hibernate developers surely had some better way of initializing things in mind!).
somehow specify a starting value for new IDs (how do I do this in a fail-safe way? is there a property that can go into my application.properties to keep this centralized?)
I want to use this in the context of a spring-boot web app, and to keep it as simple and close to best practices as possible. Suggestions?
From version 5 SEQUENCE is used instead of IDENTITY for id generation. Migration from Hibernate 4 to 5
What happened?
You inserted record with ID 1 using script. Sequence remains at 1. It wants to insert 1 what is causing unique PK violation.
Solution
Don't use generation type auto. Use IDENTITY. Then inserting records by script, IDENTITY will be automatically increased. Also you don't need to insert ID value:
DECLARE temp_id INTEGER;
INSERT INTO CUSTOMERS VALUES (DEFAULT, firstname, lastname, CURRENT_TIMESTAMP);
SET temp_id = IDENTITY();
INSERT INTO ADDRESSES VALUES (DEFAULT, temp_id, address);

Returned entity ID is different from the actual data saved in the database

I'm using Spring Data JPA. When I'm inspecting the Entity returned by the save(Entity) method, it is different from the actual ID saved in the database. I'm using Oracle and my Entity ID config is using sequence from Oracle db.
#Id
#Column(name = "ID", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_NAME")
#SequenceGenerator(name = "SEQ_NAME", sequenceName = "SEQ_1", allocationSize = 1)
private BigDecimal id;
Example scenario:
Insert record1 using save(record1);
Inspected record1 to have ID = 1001
Checked the db and the ID is 1002.
All other data are OK except the ID.
EDIT:
I remove all other logic in the code and leave just the saving part to isolate the issue.
I just found out that there's a db trigger that calls the SEQ1.nextVal every time a record is getting inserted. Dropping the trigger and altering the table to have DEFAULT SEQ1.nextVal solves my problem.

Resources