OneToMany Relationship: referenced id is null - spring boot - spring-boot

I'm new to Spring-Data-Jpa so my question is may a bit silly. I'm stuck and would be glad to get help.
I have a entity base model and two entities with following structure:
#SuperBuilder(toBuilder = true)
#NoArgsConstructor
#AllArgsConstructor
#MappedSuperclass
public class BaseModel {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid")
private String id;
...more attributes + getter & setter
}
#Entity
#SuperBuilder(toBuilder = true)
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "project")
public class Project extends BaseModel {
#Column
private String name;
#OneToMany(cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
mappedBy = "project")
private List<Announcement> announcements = new ArrayList<>();
...more attributes + getter & setter
}
#Entity
#SuperBuilder(toBuilder = true)
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "announcement")
public class Announcement extends BaseModel {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="project_id", nullable = false)
private Project project;
...more attributes + getter & setter
}
I persist my Project and Announcement Entity as follows:
public ProjectDTO createProject(CreateProjectRequest request) {
Project projectEntity = projectRepository.save(
Project.builder()
.announcements(request.getAnnouncements() != null
? request.getAnnouncements().stream().map(dto -> modelMapper.map(dto, Announcement.class)).toList()
: null)
.history(List.of(
HistoryEntry.builder()
.action(Action.PROJECT_CREATED.getValue())
.build())
)
.status(Status.NEW)
.name(request.getName())
.build()
);
return modelMapper.map(projectEntity, ProjectDTO.class);
}
I activated JPA SQL Statement trace with values.
Here you can see that the binding parameter 3 (foreign key for project) in the second statement is null. There is also no follow-up update in announcement entity for the foreign key value.
How can i fix that?
Hibernate:
/* insert de.keycon.salesservice.model.entity.Project
*/ insert
into
project
(created, updated, name, status, id)
values
(?, ?, ?, ?, ?)
09:36:32.263 [http-nio-8081-exec-3] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [TIMESTAMP] - [2022-11-29T09:36:32.254055300]
09:36:32.264 [http-nio-8081-exec-3] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [TIMESTAMP] - [2022-11-29T09:36:32.255054700]
09:36:32.264 [http-nio-8081-exec-3] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [VARCHAR] - [Demo-Project]
09:36:32.264 [http-nio-8081-exec-3] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [4] as [VARCHAR] - [NEW]
09:36:32.264 [http-nio-8081-exec-3] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [5] as [VARCHAR] - [2c9fe08184c287bc0184c287d3600000]
Hibernate:
/* insert de.keycon.salesservice.model.entity.Announcement
*/ insert
into
announcement
(created, updated, project_id, requirement, id)
values
(?, ?, ?, ?, ?)
09:36:32.267 [http-nio-8081-exec-3] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [TIMESTAMP] - [2022-11-29T09:36:32.267443100]
09:36:32.267 [http-nio-8081-exec-3] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [TIMESTAMP] - [2022-11-29T09:36:32.267443100]
09:36:32.267 [http-nio-8081-exec-3] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [VARCHAR] - [null]
09:36:32.267 [http-nio-8081-exec-3] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [4] as [VARCHAR] - [Java 11, Cloud Native Experience, Maven or Gradle, Experience with Testing Framework]
09:36:32.267 [http-nio-8081-exec-3] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [5] as [VARCHAR] - [2c9fe08184c287bc0184c287d3730001]
EDIT:
I have already tried:
#JsonManagedReference and #JsonBackReference.
Change fetch.Lazy to fetch.Eager.
Set referencedColumnName = "id".

I answer my own question. I changed my createProject method to keep project also in announcements before persisting them. Now the id is correctly reflected.
public ProjectDTO createProject(CreateProjectRequest request) {
List<Announcement> announcements = request.getAnnouncements() != null
? request.getAnnouncements().stream().map(dto -> modelMapper.map(dto, Announcement.class)).toList()
: null;
Project project = Project.builder()
.announcements(announcements)
.history(List.of(
HistoryEntry.builder()
.action(Action.PROJECT_CREATED.getValue())
.build())
)
.status(Status.NEW)
.name(request.getName())
.build();
for (var announcement: announcements) {
announcement.setProject(project);
}
Project projectEntity = projectRepository.save(
project
);
return modelMapper.map(projectEntity, ProjectDTO.class);
}

Related

Why doesn't Hibernate L2 cache turn on?

I do not understand why my Hibernate L2 cache doesn't work.
I have Spring Boot application with following configuration
jpa:
database-platform: org.hibernate.dialect.PostgreSQL10Dialect
show-sql: true
properties:
hibernate:
format_sql: true
cache :
use_second_level_cache: true
region :
factory_class: org.hibernate.cache.ehcache.EhCacheRegionFactory
use_query_cache : true
provider_class : net.sf.ehcache.hibernate.SingletonEhCacheProvider
generate_statistics : true
use_structured_entries : true
Following entity mush be cached
#Entity
#Table(name = "employees", schema = "security", catalog = "")
//#SQLDelete(sql = "update security.employees set deleted_ts = current_timestamp where id=?")
//#Where(clause = "deleted_ts is null")
#Cacheable
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
class Employee : BaseUuidEntity(), CompanyRelated,UserRelated{
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
var user: User? = null
#ManyToOne
#JoinColumn(name = "company_id")
var company: Company? = null
#Column(name = "owner")
var owner: Boolean? = null
#ManyToOne
#JoinColumn(name = "role_id")
var role: Role? = null
#ElementCollection
#CollectionTable(
name = "employee_project",
schema = "security",
joinColumns = [JoinColumn(name = "employee_id")]
)
#Column(name = "project_id")
var projectIds: MutableSet<UUID>? = null
override fun getObjUserId(): UUID? {
return user?.id
}
override fun getObjCompanyId(): UUID? {
return company?.id
}
}
But in logs in second page request I have repeated query:
Hibernate:
select
employee0_.id as id1_37_,
employee0_.created_by as created_2_37_,
employee0_.created_ts as created_3_37_,
employee0_.deleted_by as deleted_4_37_,
employee0_.deleted_ts as deleted_5_37_,
employee0_.updated_by as updated_6_37_,
employee0_.updated_ts as updated_7_37_,
employee0_.company_id as company_9_37_,
employee0_.owner as owner8_37_,
employee0_.role_id as role_id10_37_,
employee0_.user_id as user_id11_37_
from
security.employees employee0_
left outer join
security.employee_project projectids1_
on employee0_.id=projectids1_.employee_id
where
projectids1_.project_id=?
The queries are called this way
...
employeeRepository.findByProjectIds(project.id!!)
...
interface EmployeeRepository : JpaSpecRepository<Employee> {
...
#PostFilter("hasObjectPermission(filterObject, 'Employee', 'View')")
fun findByProjectIds(projectId: UUID): MutableList<Employee>
...
You can see full log here https://gist.github.com/iva-nova-e-katerina/37ef0b489cc94d693c6e7640ff01f507
Please teach me how to turn on Hibernate L2 cache?
Probably a bit more TRACE level logs make situation clean:
2022-07-31 10:41:01,085 TRACE [http-nio-8080-exec-8] org.hibernate.type.descriptor.sql.BasicBinder: binding parameter [1] as [OTHER] - [dcc5ff7b-ec8b-45d7-be0f-bcf6a55f1206]
2022-07-31 10:41:01,086 DEBUG [http-nio-8080-exec-8] org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler: Filtering with expression: hasObjectPermission(filterObject, 'Employee', 'View')
2022-07-31 10:41:01,087 DEBUG [http-nio-8080-exec-8] org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler: Filtering collection with 0 elements
2022-07-31 10:41:01,087 DEBUG [http-nio-8080-exec-8] org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler: Retaining elements: []
2022-07-31 10:41:01,088 DEBUG [http-nio-8080-exec-8] org.hibernate.engine.jdbc.spi.SqlStatementLogger:
select
employee0_.id as id1_37_,
employee0_.created_by as created_2_37_,

Q: Transactional Code why does this work so well?

Hello my professionals I have a simple question here that I would like to beg to solve this..
this is an Entity of Member
#Entity
#Getter
#Builder
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#AllArgsConstructor
/*#ToString(of = {"id", "username", "age"})*/
public class Member {
#Id
/*#GeneratedValue(strategy = GenerationType.IDENTITY)*/
#Column(name = "member_id")
private Long id;
private String username;
private int age;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "member")
private List<Team> teams;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "member")
private List<Coach> coachs;
}
And this is an Entity of Coach
#Entity
#AllArgsConstructor
#Getter
#Builder
#Setter
#NoArgsConstructor
#ToString(of = {"id","name","career"})
public class Coach {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name= "coach_id")
private Long id;
#Column
private String name;
#Column
private String career;
#ManyToOne(fetch = FetchType.LAZY,cascade = ALL)
#JoinColumn(name = "member_id")
private Member member;
#OneToOne(fetch = FetchType.LAZY,cascade = ALL)
#JoinColumn(name = "team_id")
private Team team;
}
and This is Controller Code
#GetMapping("/member")
public void createUser(){
Member m = memberService.createMember();
Coach c = m.getCoachs().get(0);
log.info(c.getName());
}
and This is Service Code
private final MemberRepository memberRepository;
#Transactional
public Member createMember(){
return memberRepository.findMemberById(3L);
}
and the last this is RepositoryCode
Member findMemberById(Long id);
So my question is that when i printed out Coach's name at the controller on console
it printed out so well.
but what I know the Transaction is over from the service So the persistence container is closed that means coach name can't be imported cause it's LAZY loading and persistence container is closed but it was printed out well
I want to know the reason why ...
here are the console results Thanks !!
[2022-01-10 23:27:46.835] [http-nio-9000-exec-2] [] INFO o.a.c.c.C.[.[.[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
[2022-01-10 23:27:46.835] [http-nio-9000-exec-2] [] INFO o.s.w.s.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
[2022-01-10 23:27:46.855] [http-nio-9000-exec-2] [] INFO o.s.w.s.DispatcherServlet - Completed initialization in 19 ms
Hibernate:
/* select
generatedAlias0
from
Member as generatedAlias0
where
generatedAlias0.id=:param0 */ select
member0_.member_id as member_i1_1_,
member0_.age as age2_1_,
member0_.username as username3_1_
from
member member0_
where
member0_.member_id=?
[2022-01-10 23:27:47.007] [http-nio-9000-exec-2] [4c0222d3] INFO p6spy - #1641824867007 | took 15ms | statement | connection 1| url jdbc:mariadb://patrick-lab.cjeq2ffynlc2.ap-northeast-2.rds.amazonaws.com:3306/patricklab?characterEncoding=UTF-8&serverTimezone=UTC
/* select generatedAlias0 from Member as generatedAlias0 where generatedAlias0.id=:param0 */ select member0_.member_id as member_i1_1_, member0_.age as age2_1_, member0_.username as username3_1_ from member member0_ where member0_.member_id=?
/* select generatedAlias0 from Member as generatedAlias0 where generatedAlias0.id=:param0 */ select member0_.member_id as member_i1_1_, member0_.age as age2_1_, member0_.username as username3_1_ from member member0_ where member0_.member_id=3;
[2022-01-10 23:27:47.170] [http-nio-9000-exec-2] [4c0222d3] INFO p6spy - #1641824867170 | took 12ms | commit | connection 1| url jdbc:mariadb://patrick-lab.cjeq2ffynlc2.ap-northeast-2.rds.amazonaws.com:3306/patricklab?characterEncoding=UTF-8&serverTimezone=UTC
;
Hibernate:
select
coachs0_.member_id as member_i4_0_0_,
coachs0_.coach_id as coach_id1_0_0_,
coachs0_.coach_id as coach_id1_0_1_,
coachs0_.career as career2_0_1_,
coachs0_.member_id as member_i4_0_1_,
coachs0_.name as name3_0_1_,
coachs0_.team_id as team_id5_0_1_
from
coach coachs0_
where
coachs0_.member_id=?
[2022-01-10 23:27:47.200] [http-nio-9000-exec-2] [4c0222d3] INFO p6spy - #1641824867200 | took 12ms | statement | connection 1| url jdbc:mariadb://patrick-lab.cjeq2ffynlc2.ap-northeast-2.rds.amazonaws.com:3306/patricklab?characterEncoding=UTF-8&serverTimezone=UTC
select coachs0_.member_id as member_i4_0_0_, coachs0_.coach_id as coach_id1_0_0_, coachs0_.coach_id as coach_id1_0_1_, coachs0_.career as career2_0_1_, coachs0_.member_id as member_i4_0_1_, coachs0_.name as name3_0_1_, coachs0_.team_id as team_id5_0_1_ from coach coachs0_ where coachs0_.member_id=?
select coachs0_.member_id as member_i4_0_0_, coachs0_.coach_id as coach_id1_0_0_, coachs0_.coach_id as coach_id1_0_1_, coachs0_.career as career2_0_1_, coachs0_.member_id as member_i4_0_1_, coachs0_.name as name3_0_1_, coachs0_.team_id as team_id5_0_1_ from coach coachs0_ where coachs0_.member_id=3;
[2022-01-10 23:27:47.213] [http-nio-9000-exec-2] [4c0222d3] INFO m.p.l.m.c.MemberController - Coach1
I believe it is because you are using the spring-boot default setting which the spring.jpa.open-in-view is set to true .
This property enables OpenSessionInView pattern which you can simply think that a transaction will be opened automatically for you at the very first beginning when processing any HTTP request (e.g. in the Servlet Filter etc). Because of this , a transaction is actually already open before your service method executes and it is still active after your service method completes. Hence you will not experience any LazyInitializationException even after you access non-initialized properties outside the service method as the transaction is still active.
There is a strong debate about whether or not spring-boot should enable it by default in the past . You can refer this for more details if you are interested. I personally would recommend to turn it off.

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 Boot Data JPA #Version Returns 0 on Create but is 1 in the database

I have a project using spring-boot-starter-data-jpa version 2.2.6.RELEASE with an aOracle 12c database. I have a version field in my entity annotated with javax.persistance #Version annotation. When I save an entity for the first time, the query returns a version of 0, but in the database I can see it has been set to 1. It is like the transaction ends before the updated version is returned.
I've tried with both CrudRepository's save() method and JpaRepository's saveAndFlush() method, but neither work. Note, subsequent updates to the entity do return the correct version. This problem only happens when creating a new record.
When I use the EntityManager directly with saveAndRefresh(), it works as it should, but I'd like to avoid doing that if possible. Can someone please help?
UPDATE
Here are the entity, repository and service classes. I've tried with JpaRepository and saveAndFlush() too, but the outcome is the same:
#Getter
#Setter
#NoArgsConstructor
#ToString
#EqualsAndHashCode
#Entity
#Table(name = "TD_INCIDENTS")
public class Incident {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "INCIDENT_ID", nullable = false)
private Long incidentId;
#Column(name = "INCIDENT_STATUS")
private Integer statusCode;
#Version
#Column(name = "INCIDENT_VER_NUM")
private Long version;
}
#Repository
public interface IncidentRepository extends CrudRepository<Incident, Long> {
}
#Service
public class IncidentServiceImpl implements IncidentService {
private final IncidentRepository incidentRepository;
public IncidentServiceImpl(IncidentRepository incidentRepository)
{
this.caseRepository = caseRepository;
}
#Override
#Transactional(rollbackFor = Exception.class)
public Incident createIncident(String statusCode) {
var newIncident = new Incident();
newIncident.setStatusCode(1);
// the line below returns an incident with version 0, but in db it is 1
return incidentRepository.save(newIncident);
}
#Override
#Transactional
public Incident getIncident(Long incidentId) {
return incidentRepository.findById(incidentId);
}
#Override
#Transactional(rollbackFor = Exception.class)
public Incident updateIncident(Long incidentId, Integer statusCode, Long version) {
var incident = this.getIncident(incidentId);
if (incident != null) {
if (incident.getVersion().equals(version)) {
incident.setStatusCode(statusCode);
// the line below returns an incident with an updated version the same as in the db
return incidentRepository.save(incident);
}else {
throw new OptimisticLockException("Expected versions do not match");
}
}
return null;
}
}
UPDATE
Here is the log output from the insert:
2020-07-26 13:54:09.675 DEBUG 9140 --- [nio-8080-exec-1] org.hibernate.SQL :
insert
into
td_incidents
(incident_id, incident_status, incident_ver_num)
values
(default, ?, ?)
2020-07-26 13:54:09.701 TRACE 9140 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [INTEGER] - [1]
2020-07-26 13:54:09.703 TRACE 9140 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [BIGINT] - [0]
And here is the log output from the update. Note, I'm sending the version number as 1 in the request, as that's what it is in the db. If I send it as 0, the application will throw the OptimisticLockException
2020-07-26 13:56:29.346 DEBUG 9140 --- [nio-8080-exec-3] org.hibernate.SQL :
select
incident0_.incident_id as incident_id1_0_0_,
incident0_.incident_status as incident_status_2_0_0_,
incident0_.incident_ver_num as incident_ver_num_3_0_0_
from
td_incidents incident0_
where
incident0_.incident_id_id=?
2020-07-26 13:56:29.347 TRACE 9140 --- [nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1044]
2020-07-26 13:56:29.401 DEBUG 9140 --- [nio-8080-exec-3] org.hibernate.SQL :
update
td_incidents
set
incident_status=?,
incident_ver_num=?
where
incident_id=?
and incident_ver_num=?
2020-07-26 13:56:29.402 TRACE 9140 --- [nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [INTEGER] - [2]
2020-07-26 13:56:29.402 TRACE 9140 --- [nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [BIGINT] - [2]
2020-07-26 13:56:29.403 TRACE 9140 --- [nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder : binding parameter [3] as [BIGINT] - [1044]
2020-07-26 13:56:29.404 TRACE 9140 --- [nio-8080-exec-3] o.h.type.descriptor.sql.BasicBinder : binding parameter [4] as [BIGINT] - [1]
INCIDENT_VER_NUM field should not have autoincrement column but you seems to have an autoincrecment column as it is managed and incremented by JPA
If you can't remove autocrement, try this option and it could work. But I have used this option so far other fields that are generated by database not for #Version
#Version
#Column(name = "INCIDENT_VER_NUM", insertable = false, updatable = false)
#org.hibernate.annotations.Generated(value = GenerationTime.ALWAYS)
private Long version;

Hibernate performance: several queries instead of one after form:select in jsp

I was analyzing the performance of my aplication and I have notice the following. I have this query:
public List<Rol> findAll() {
return mySessionFactory.getCurrentSession().createQuery("from Rol").list();
}
And this returns this query:
SELECT rol0_._id as column1_2_, rol0_.description as descripc2_2_, rol0_.name as nombre6_2_, rol0_.status as status7_2_ FROM rol rol0_
This is fine, but in my jsp I have the following:
<form:select multiple="true" path="roles" required="true">
<form:options items="${roles}" itemValue="id" itemLabel="nombre" />
</form:select>
And this creates a new query for each rol:
DEBUG: org.hibernate.SQL - select rol0_._id as column1_2_0_, rol0_.description as descripc2_2_0_, rol0_.name as name6_2_0_, rol0_.status as status7_2_0_ from rol rol0_ where rol0_._id=?
TRACE: org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - 1
DEBUG: org.hibernate.SQL - select rol0_._id as column1_2_0_, rol0_.description as descripc2_2_0_, rol0_.name as name6_2_0_, rol0_.status as status7_2_0_ from rol rol0_ where rol0_._id=?
TRACE: org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - 2
DEBUG: org.hibernate.SQL - select rol0_._id as column1_2_0_, rol0_.description as descripc2_2_0_, rol0_.name as name_2_0_, rol0_.status as status7_2_0_ from rol rol0_ where rol0_._id=?
TRACE: org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - 20130915150706256
DEBUG: org.hibernate.SQL - select rol0_._id as column1_2_0_, rol0_.description as descripc2_2_0_, rol0_.name as name6_2_0_, rol0_.status as status7_2_0_ from rol rol0_ where rol0_._id=?
TRACE: org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - 3
In my controller I have:
List<Rol> roles = rolDao.findAll();
ModelAndView mav = new ModelAndView("usuarioNew");
mav.getModelMap().put("roles", roles);
Is there any way to avoid this?. The first query has all the information that I need, I dont want extra queries.
Edit:
#Entity
#Table(name = "rol", uniqueConstraints = { #UniqueConstraint(columnNames = "name") })
public class Rol implements Serializable{
#Id
#Column(name = "_id")
private String id;
#Column(name = "name")
#NotEmpty
private String name;
#Column(name = "description")
private String description;
#Column(name = "status")
private String status;
#NotEmpty
#Fetch(FetchMode.JOIN)
#OneToMany(mappedBy = "rolPermission_pk.rol", orphanRemoval = true, cascade=CascadeType.ALL)
private Set<Rol_Permission> permissions = new HashSet<Rol_Permission>(0);
....//getters, setters
}
Edit2:
public class UserRolCollectionEditor extends CustomCollectionEditor {
private final RolDAO rolDao;
public UserRolCollectionEditor (Class<?> collectionType, RolDAO rolDao) {
super(collectionType);
this.rolDao = rolDao;
}
#Override
protected Object convertElement(Object element) {
String rolId = (String) element;
Rol rol = rolDao.findById(rolId);
User_Rol usuRol = new User_Rol();
User user = new User();
usuRol.setUser(user);
usuRol.setRol(rol);
usuRol.setStatus("active");
return usuRol;
}
}
Edit3:
I have made some tests and the problem is with UserRolCollectionEditor and #Fetch(FetchMode.JOIN). If I delete the UserRolCollectionEditor and #Fetch(FetchMode.JOIN) from code, I get one query. If a delete only #Fetch(FetchMode.JOIN) I get extra queries. If a delete only UserRolCollectionEditor I get extra queries.. I dont know what to do..
I can't say why hibernate generate excessive queries, but they are created due to presence of persistence context (ie open session) when accessing ${roles} in your JSP.
To prevent such behavior you can try to close session beforehand (I think it's more like a workaround than a solution in your case). There are several way to achieve that (using session-per-request pattern):
Use mySessionFactory.openSession() and mySessionFactory.closeSession() in your DAO;
Use getCurrentSession().beginTransaction() and getCurrentSession().commitTransaction() in your DAO;
Create service layer and let Spring manage transactions.
Great spring layered webapp example
Dealing with sessions and transactions in Hibernate
UPDATE
In UserRolCollectionEditor line Rol rol = rolDao.findById(rolId); can create select queries if you use session.get() in underlying rolDao. If it is the case you can change it to session.load() instead to avoid extra queries. In short, common usage scenario for session.load() is to create associations between entity objects - it is exactly what you are doing in UserRolCollectionEditor.convertElement() method.
Short article shows difference between get and load

Resources