Spring JPA Versioned Entity hibernate errors - spring

Is there a better way to update a versioned entity other than to catch exception and retrying ?
Below is the code
#Entity
#AllArgsConstructor
#Builder
#NoArgsConstructor
#Data
#IdClass(com.test.domain.EmployeeKey.class)
public class Employee {
#Id
private Integer employeeId;
private String name;
private Double commission;
#Id
private LocalDate inputDate;
#Version
private Integer version;
}
public class EmployeeKey implements Serializable {
private Integer employeeId;
private java.time.LocalDate inputDate;
}
#SpringBootApplication
#Slf4j
public class SampleApplication {
public static void main(final String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
#Component
public class AppStartupRunner implements ApplicationRunner {
#Autowired
EmployeeRepo employeeRepo;
#Override
public void run(ApplicationArguments args) throws Exception {
log.info("Your application started with option names : {}", args.getOptionNames());
List<Employee> empList = new ArrayList();
Employee employeeComm1 = Employee.builder().employeeId(1).name("Sam").commission(100.45).inputDate(LocalDate.of(2019,10,01)).build();
Employee employeeComm2 = Employee.builder().employeeId(1).name("Sam").commission(87.54).inputDate(LocalDate.of(2019,10,01)).build();
Employee employeeComm3 = Employee.builder().employeeId(2).name("John").commission(56.78).inputDate(LocalDate.of(2019,10,01)).build();
Employee employeeComm4 = Employee.builder().employeeId(3).name("Katie").commission(65.23).inputDate(LocalDate.of(2019,10,01)).build();
empList.add(employeeComm1);
empList.add(employeeComm3);
empList.add(employeeComm4);
empList.add(employeeComm2);
// employeeRepo.saveAll(empList);
for (Employee emp: empList) {
try {
employeeRepo.save(emp);
}
catch(Exception e) {
if (employeeRepo.findByEmployeeIdAndInputDate(emp.getEmployeeId(), emp.getInputDate()).isPresent()) {
Employee empFromDb = employeeRepo.findByEmployeeIdAndInputDate(emp.getEmployeeId(), emp.getInputDate()).get();
empFromDb.setCommission(emp.getCommission());
empFromDb.setName(emp.getName());
employeeRepo.save(empFromDb);
}
}
}
}
}
}
Output shown below
Hibernate: insert into employee (commission, name, version, employee_id, input_date) values (?, ?, ?, ?, ?)
Hibernate: insert into employee (commission, name, version, employee_id, input_date) values (?, ?, ?, ?, ?)
Hibernate: insert into employee (commission, name, version, employee_id, input_date) values (?, ?, ?, ?, ?)
Hibernate: insert into employee (commission, name, version, employee_id, input_date) values (?, ?, ?, ?, ?)
2019-10-15 16:43:10.372 WARN 17216 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1, SQLState: 23000
2019-10-15 16:43:10.372 ERROR 17216 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : ORA-00001: unique constraint (I706446.SYS_C008493) violated
Hibernate: select employee0_.employee_id as employee_id1_0_, employee0_.input_date as input_date2_0_, employee0_.commission as commission3_0_, employee0_.name as name4_0_, employee0_.version as version5_0_ from employee employee0_ where employee0_.employee_id=? and employee0_.input_date=?
Hibernate: select employee0_.employee_id as employee_id1_0_, employee0_.input_date as input_date2_0_, employee0_.commission as commission3_0_, employee0_.name as name4_0_, employee0_.version as version5_0_ from employee employee0_ where employee0_.employee_id=? and employee0_.input_date=?
Hibernate: select employee0_.employee_id as employee_id1_0_0_, employee0_.input_date as input_date2_0_0_, employee0_.commission as commission3_0_0_, employee0_.name as name4_0_0_, employee0_.version as version5_0_0_ from employee employee0_ where employee0_.employee_id=? and employee0_.input_date=?
Hibernate: update employee set commission=?, name=?, version=? where employee_id=? and input_date=? and version=?

I expect an hibernate to issue an update with Version column
incrementing
Your expectation is incorrect. You use version not as it is intended to be used. The purpose of versioning is to provide optimistic locking and not simply indicate how many times has the entity changed. The intended usage scenario is following: before updating an entity you should read it from the database, then modify, then save. Hibernate checks if the version you are trying to save is the same as in the database.
A) If the same, it will automatically increment version and save your entity, i.e. it will update the existing record in the database.
B) If the version in the entity you are trying to save is less than the version in the database, it means you are trying to save an outdated version. It means you are not aware of changes that were done in the meanwhile. In such case Hibernate will throw an exception.

Related

"JdbcSQLIntegrityConstraintViolationException: Unique index or primary key violation" Exception after upgrading to Spring Boot 2.7

After upgrading my project to Spring Boot 2.7, my tests started failing with this error:
ERROR ---[ main] o.h.engine.jdbc.spi.SqlExceptionHelper :Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.MODEL(ID) ( /* key:1 */ CAST(1 AS BIGINT), 'Model 1 Description')"; SQL statement:
insert into model (id, description) values (default, ?, ?, ?, ?, ?, ?, ?) [23505-212]
This is what I have in my is my data.sql to pre-load the data for my tests using H2:
INSERT INTO Model(id, description) VALUES (1, 'Model 1 Description');
This is my Entity:
#Entity
public class Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String description;
// ...
And the error is triggered when this test is executed:
#Test
void whenModelCreated_thenSuccess() {
Model1 newModel = new Model("First Test Model");
modelRepository.save(newModel);
// ...
}
It seems Boot 2.7 upgraded to its H2 dependency to 2.x, which is not backward compatible and introduces several changes: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.7-Release-Notes#h2-21
As indicated in the H2 "migration-to-v2" guide, there have been changes in how identity columns and sequences are handled. Apparently, the H2 database engine has to manage all the generated ids in order to keep track of them, and by indicating the id to be inserted in the DB explicitly, we're bypassing the database engine, and generating issues.
Changing my data.sql INSERT statement to use the default keyword to leave it up to the DB engine to take care of assigning the id itself, the isso got fixed:
INSERT INTO Model(id, description) VALUES (default, 'Model 1 Description');

spring data jpa: Insert instead of delete

I have a basic SpringBoot app. using Spring Initializer, JPA, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file. I have created this Repository class:
#Repository
public interface MenuRepository extends CrudRepository<Menu, Long> {
#Cacheable("menus")
#Query("select cur from Menu cur where cur.MenuId = ?1")
Menu findMenuByMenuId (String MenuId);
#Cacheable("menus")
#Query("select cur from Menu cur where lower (cur.symbol) = lower (?1) ")
Menu findMenuBySymbol (String symbol);
and this service:
#Service
public class MenuService {
#Autowired
protected MenuRepository menuRepository;
#Autowired
MenuPriceService menuPriceService;
#Transactional
public Menu save (Menu menu) {
return menuRepository.save (menu);
}
#Transactional
public void delete (Menu menu) {
menuRepository.delete (menu);
}
..
}
}
and this JUNIT test:
#Test
#Transactional
public void testDelete () {
String menuName = System.currentTimeMillis()+"";
Menu menu = new Menu();
menu.setMenuId(menuName);
menuService.delete (menu);
}
but this is what I see in the console
Hibernate:
insert
into
t_menu
(...)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
While removing #Transactional as suggested in another answer might make the behavior go away, I'd question the validity of the test.
The question really is: why do you try to delete an entity that is not in the database?
Solution:
If you simulate the case where don't know if the entity exists, use deleteById it will throw an exception when the id is not present in the database and you can, of course, catch that.
Some background to how and why this insert happens:
delete(T entity) is implemented through SimpleJpaRepository.delete(T entity) which basically contains this single line of code.
em.remove(em.contains(entity) ? entity : em.merge(entity));
As you can see it does a merge on the entity if the entity isn't present in the EntityManager. Without the merge you'd might end up with duplicate entities in the session if another instance with same class and Id does exist in the EntityManager. But since your entity doesn't exist at all this triggers an insert into the database.
You might think it as this: You create an instance, you make the EntityManager know about it (by telling it to delete it) and you delete it, so these are the steps that need to occur in the database -> insert + delete.
remove #Transactional:
#Test
public void testDelete () {
String menuName = System.currentTimeMillis()+"";
Menu menu = new Menu();
menu.setMenuId(menuName);
menuService.delete (menu);
}

Hibernate exception: detached entity passed to persist, in one-direction ManyoOne relationship

I am new to Hibernate and I encountered the classic "detached entity passed to persist" exception. I read a few similar questions here but non of them can apply to my situation.
I have 2 entities, Department and DeptEmpCode. Department has a foreign key that references DeptEmpCode. Many Departments may share one code, So it is many to one.
The entity code is as follows:
Department:
#Entity
#Table(name = "department")
public class Department {
private Integer id;
...
private DeptEmpCode status;
...
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "status")
public DeptEmpCode getStatus() {
return status;
}
public void setStatus(DeptEmpCode status) {
this.status = status;
}
}
DeptEmpCode:
#Entity
#Table(name = "code")
public class DeptEmpCode {
private Integer id;
....
}
I have omitted unnecessary code for easier read.
So when I hit the "add Department" button on the webpage, spring framework creates a Department object for me. Then I use "new" to create a DeptEmpCode object, and call Department's setStatus() to associate the DeptEmpCode object to the Department. Then I persist the Department. The code is as follows:
public void saveDept(Department dept) {
if(dept.getStatus() == null){
DeptEmpCode status = new DeptEmpCode();
status.setId(Constants.DEFAUL_DEPT_STATUS_ID);
dept.setStatus(status);
}
deptDAO.save(dept); //nothing but a persist() call
}
So what should be the problem? Should I make it bi-directional or its something else?(If I remove "cascadeType.ALL" then it would be a foreign key violation). Thanks!
since your Department object is already created. Use cascadeType.MERGE instead of cascadeType.ALL
When you use cascadeType.ALL, it will think the transaction is PERSISTED, it tries to PERSIST Department as well and that doesn't work since Department already is in the db. But with CascadeType.MERGE the Department is automatically merged(update) instead.
Update:
Service
#Override
#Transactional
public void saveDept(Department dept) {
if(dept.getStatus() == null){
DeptEmpCode status = new DeptEmpCode();
status.setId(Constants.DEFAUL_DEPT_STATUS_ID);
status.setType("DEFAULT");
status.setValue("DEFAULT");
dept.setStatus(status);
}
deptDAO.update(dept);
}
DAO
#Override
public void update(Department dept) {
em.merge(dept);
}
Explanation:
since status.id is a primary key and you set the value by yourself. When you call save it will create the status first, so it means the status has already (detached object) register in the database(not yet commit), if you still use em.persist(dept), it will also try to persist the detach object (status) and the department. so we should use merge which will merge the status and insert a new department.
you can see below is how hibernate insert your record.
however, if you don't set the status.id value and let it auto-generate, then it will persist department and status at the same time. so you won't have the problem. In your case, I know you want to assign the status.id to default which is one. So you should use merge as you will have the 2nd and 3rd department that use the default status id (which status id already in db).
Hibernate: insert into code (DES, INACTIVE_IND, CODE_TYPE, VALUE) values (?, ?, ?, ?)
Hibernate: insert into department (contact, des, dept_email, dept_name, status) values (?, ?, ?, ?, ?)

How can I implement Hibernate massive batch insert into this Spring Boot application?

I am pretty new in Hibernate and I have the following problem: I have to perform a massive insert operation (I need to insert a lot of the model object and each of these object contains a byte[] field mapping a blob field on the DB).
I am using Spring Boot (so I need to configure it on Spring Boot) and Spring Data JPA.
So I have the following situation:
1) RoomMedia my model object:
#Entity
#Table(name = "room_media")
public class RoomMedia {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
//#Column(name = "id_room")
//private Long idRoom;
#ManyToOne
#JoinColumn(name = "id_room", nullable = false) // da rinominare anche sul DB in room_fk
private Room room;
#Lob
#Column(name = "media")
private byte[] media;
private String description;
private Date time_stamp;
public RoomMedia() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
........................................................................
........................................................................
GETTER AND SETTER METHODS
........................................................................
........................................................................
}
Then into another class I have this code that persist a list of RoomMedia objects:
roomMediaDAO.save(mediaListToBeInserted);
where mediaListToBeInserted is a List.
The problm is that doing in this way it is very slow because this statment generate insertion queries like these:
Hibernate: insert into room_media (description, media, id_room, time_stamp) values (?, ?, ?, ?)
Hibernate: insert into room_media (description, media, id_room, time_stamp) values (?, ?, ?, ?)
Hibernate: insert into room_media (description, media, id_room, time_stamp) values (?, ?, ?, ?)
Hibernate: insert into room_media (description, media, id_room, time_stamp) values (?, ?, ?, ?)
Hibernate: insert into room_media (description, media, id_room, time_stamp) values (?, ?, ?, ?)
............................................................................
............................................................................
............................................................................
So the records are inserted one by one.
I need to insert these records massively, genereting a code like this:
INSERT INTO MyTable ( Column1, Column2 ) VALUES
( Value1, Value2 ),
( Value1, Value2 ),
( Value1, Value2 ),
.................................
.................................
.................................
( Value1, Value2 )
Reading this documentation:
https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/batch.html
I know that I can set Hibernate to perform Batch Insert that should do something like this.
But I have some doubts:
1) Reading the previous documentation link it says that:
Hibernate disables insert batching at the JDBC level transparently if
you use an identity identifier generator.
and I have that my primary key of my model object is annoted with:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
So what it means? That I can't do massive insert of a list of these objects? It seem me strange that there is no way to do it.
In the previous article it explain:
You can also do this kind of work in a process where interaction with
the second-level cache is completely disabled:
hibernate.cache.use_second_level_cache false
However, this is not absolutely necessary, since we can explicitly set
the CacheMode to disable interaction with the second-level cache.
So it seem to me that I can do it in some way but I don't know what is this second-level cache and why it have to be disabled to perform batch insert of object having an identity identifier generator setted.
2) In my Spring Boot project I have this application.properties file containing the following settings related to the database connection and Hibernate configuration. So I think that this should be the point where to add the set to handle the batch massive insert. But what have I to set?
#No auth protected
endpoints.shutdown.sensitive=true
#Enable shutdown endpoint
endpoints.shutdown.enabled=true
spring.datasource.url = jdbc:mysql://XX.YY.ZZZ.WW:3306/BeTriviusTest?useUnicode=true&characterEncoding=UTF-8
spring.datasource.username = MyUserName
spring.datasource.password = MyPswd
spring.datasource.driverClassName=com.mysql.jdbc.Driver
# Keep the connection alive if idle for a long time (needed in production)
spring.datasource.testWhileIdle = true
spring.datasource.validationQuery = SELECT 1
# Show or not log for each sql query
spring.jpa.show-sql = true
spring.jpa.database = mysql
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = validate
# Naming strategy
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
# Use spring.jpa.properties.* for Hibernate native properties (the prefix is
# stripped before adding them to the entity manager)
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.spatial.dialect.mysql.MySQLSpatial5InnoDBDialect
spring.jpa.database-platform = org.hibernate.spatial.dialect.mysql.MySQLSpatial5InnoDBDialect

spring data jpa save manually assign identifier

i use spring save
HighWay highWay = new HighWay();
highWay.setId("000");
HighWayRepository hRepository = (HighWayRepository) context
.getBean("highWayRepository");
hRepository.save(highWay);
hRepository.flush();
public interface HighWayRepository extends JpaRepository<HighWay, String> {
}
the table is like f_id varchar(256) NOT NULL,
public class HighWay {
#Id
#Column(name="f_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;}
but throws exception
Caused by: java.sql.SQLException: Field 'f_id' doesn't have a default
value at
com.mysql.jdbc.SQLError.createSQLException(SQLError.java:946) at
com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2870) at
com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1573) at
com.mysql.jdbc.ServerPreparedStatement.serverExecute(ServerPreparedStatement.java:1169)
at
com.mysql.jdbc.ServerPreparedStatement.executeInternal(ServerPreparedStatement.java:693)
at
com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1404)
at
com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1318)
at
com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1303)
at
com.alibaba.druid.pool.DruidPooledPreparedStatement.executeUpdate(DruidPooledPreparedStatement.java:253)
at
org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:208)
... 51 more
i check the document,
Id-Property inspection (default) By default Spring Data JPA inspects the Id-Property of the given Entity. If the Id-Property is null, then the entity will be assumed as new, otherwise as not new.
the sql is like Hibernate: insert into us_highway (blockTime, blockType, endNum, predictTime, publishTime, roadName, situation, startNum, tips) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
not id is insert!
because i want to assign id manually,the id-property is not null if new ,how to configure to save ?
If you are assigning Id manually.Remove below annotation on id.
#GeneratedValue(strategy = GenerationType.IDENTITY)

Resources