spring data jpa save manually assign identifier - spring

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)

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');

Hibernate insert fails - java.sql.SQLException: Invalid arguments in call

I'm trying to insert the user object to Oracle with Hibernate. Object is loaded with values entered in the user registration form.
id is #GeneratedValue
pass is #Transient
These are the properties of User and UserType classes:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(length = 50, nullable = false)
#NotBlank(message = "Boş bırakılamaz.")
#Size(min=2, max = 50)
private String firstName;
#Column(length = 50, nullable = false)
#NotBlank(message = "Boş bırakılamaz.")
#Size(min=2, max = 50)
private String lastName;
#Column(length = 50, nullable = false)
#NotBlank(message = "Boş bırakılamaz.")
#Size(min=2, max = 50)
private String userName;
#Column(columnDefinition = "char(128)")
private String passHash;
#Column(columnDefinition = "char(32)")
private String salt;
#ManyToOne
#NotNull(message = "Boş bırakılamaz.")
private UserType userType;
#Transient
#NotBlank(message = "Boş bırakılamaz.")
#Size(min=4)
private String pass;
}
#Entity
public class UserType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(length = 50, nullable = false)
#NotBlank(message = "Boş bırakılamaz.")
private String name;
}
This is the Oracle DDL:
create table DH_USER
(
ID NUMBER(19) generated as identity
primary key,
FIRSTNAME VARCHAR2(50 char) not null,
LASTNAME VARCHAR2(50 char) not null,
PASSHASH CHAR(128),
SALT CHAR(32),
USERNAME VARCHAR2(50 char) not null,
USERTYPE_ID NUMBER(19) not null
constraint FKO3DS41MXQLO527MM8H8J7F0FL
references DH_USERTYPE
)
create table DH_USERTYPE
(
ID NUMBER(19) generated as identity
primary key,
NAME VARCHAR2(50 char) not null
)
After adding logging.level.org.hibernate.SQL=DEBUG and logging.level.org.hibernate.type=TRACE to application.properties file, critical part of the log is now like this:
2020-12-08 15:41:59.256 INFO 6676 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
2020-12-08 15:41:59.356 DEBUG 6676 --- [nio-8080-exec-2] org.hibernate.SQL : select usertype0_.id as id1_1_0_, usertype0_.name as name2_1_0_ from DH_UserType usertype0_ where usertype0_.id=?
Hibernate: select usertype0_.id as id1_1_0_, usertype0_.name as name2_1_0_ from DH_UserType usertype0_ where usertype0_.id=?
2020-12-08 15:41:59.379 TRACE 6676 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [2]
2020-12-08 15:41:59.481 TRACE 6676 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicExtractor : extracted value ([name2_1_0_] : [VARCHAR]) - [Yönetici]
2020-12-08 15:41:59.830 DEBUG 6676 --- [nio-8080-exec-2] org.hibernate.SQL : insert into DH_User (id, firstName, lastName, passHash, salt, userName, userType_id) values (default, ?, ?, ?, ?, ?, ?)
Hibernate: insert into DH_User (id, firstName, lastName, passHash, salt, userName, userType_id) values (default, ?, ?, ?, ?, ?, ?)
2020-12-08 15:41:59.834 WARN 6676 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 17068, SQLState: 99999
2020-12-08 15:41:59.834 ERROR 6676 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper : Invalid arguments in call
2020-12-08 15:41:59.863 ERROR 6676 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.orm.jpa.JpaSystemException: could not prepare statement; nested exception is org.hibernate.exception.GenericJDBCException: could not prepare statement] with root cause
Why does Hibernate add the id field into the insert statement?
id field is "generated as identity" so doesn't need to be involved in the query.
And why does it try to insert "default" into id column? It should use null instead of default, as null is the value of user.id at that point in the code.
DH_User (id, firstName, lastName, passHash, salt, userName, userType_id) values (default, ?, ?, ?, ?, ?, ?)
Oracle should support the DEFAULT syntax, I've seen it being used like here: https://www.techrepublic.com/article/oracle-tip-how-to-use-default-values-with-database-columns/
Maybe there are different editions of Oracle and yours does not have support for this? Or maybe you need to use GENERATED BY DEFAULT AS IDENTITY? Anyway, you can override this by subclassing org.hibernate.dialect.identity.Oracle12cIdentityColumnSupport and org.hibernate.dialect.Oracle12cDialect which then has to return that subclass instance in getIdentityColumnSupport().
In a custom Oracle12cIdentityColumnSupport class you can also use null for getIdentityInsertString instead of default, but then make sure you also change getIdentityColumnString to use generated by default on null as identity.
By the way, I would recommend using sequences whenever possible for performance reasons. The use of sequences and sequence caching allows Hibernate to defer/reorder and batch inserts which will improve performance drastically when inserting a lot.
Changing the annotation of id field as
#GeneratedValue(strategy = GenerationType.SEQUENCE)
worked. But I don't understand why.
And I don't want to use a sequence. I want to use an auto-generated ID and get a value back post insertion.
I need an annotation resulting exactly:
GENERATED BY DEFAULT ON NULL AS IDENTITY
I tried to use the columnDefinition. You can see that "not null" is appended automatically even I use nullable = true.
As Christian suggested, I tried Oracle12cIdentityColumnSupport method and successfully changed Hibernate's way of generating IDENTITY ddl. But I got the same error, Invalid arguments in call :(
Seems like the only way for me is GenerationType.SEQUENCE
AFAIK, generated by default on null as identity is the least restrictive way of defining id columns and allowing null id's while inserting.

Spring JPA Versioned Entity hibernate errors

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.

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

Resources