Why doesn't Hibernate L2 cache turn on? - spring-boot

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_,

Related

Hibernate JoinTableFilter annotation

I'm using Hibernate to map my entities and I'm having a problem. I want my entity to retrieve a list of entity from another table, linked with a join table. I also want to filter on the join table to only retrieve objets whose boolean_value is set to false.
It currently works without filtering, using #JoinTable annotation. I'm facing difficulties when it comes about #FilterJoinTable which seems not to be working.
Here is what I tried to do:
#Entity
#Table(name = "table_a")
#FilterDef(name="checkValue")
#Filter(name = "checkValue")
public class AEntity {
// id ...
#ManyToMany
#JoinTable(name = "my_join_table",
joinColumns = #JoinColumn(name = "a_id"),
inverseJoinColumns = #JoinColumn(name = "b_id"))
#FilterJoinTable(name = "checkValue", condition = "boolean_value = FALSE")
private List<BEntity> objets;
}
Currently it returns all the objects from table_b without filtering.
Any idea of what I'm doing wrong ?
You have to use the #Where annotation, as #FilterJoinTable allows defining Hibernate filters which have to be enabled explicitly with Session#enableFilter(String):
#ManyToMany
#JoinTable(name = "my_join_table",
joinColumns = #JoinColumn(name = "a_id"),
inverseJoinColumns = #JoinColumn(name = "b_id"))
#Where(clause = "boolean_value = FALSE")
private List<BEntity> objets;

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.

Manytoone lazy property not loaded using join fetch jpql in Spring Boot

I'm using spring boot 2.3.0, hibernate 5 and MySQL 8.0
I'm trying to initialize a ManyToOne relationship using join fetch in my JQPL query but it doesn't work because the entity is not loaded.
In the entity i have:
#Entity
public class Academic_Record {
#JoinColumn(name = "product_id", nullable = false)
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Product product;
.....
In the repository:
#Query(value = "select ar from Academic_Record ar " +
"join fetch ar.product p " +
"where ar.enrollmentStudent.id = :enrollmentStudentId " +
"and ar.product.id = :productId")
Academic_Record findByEnrollmentStudentIdAndProductIdWithProduct(Long enrollmentStudentId, Integer productId);
But if try:
Academic_Record academicRecord = academicRecordDao.findByEnrollmentStudentIdAndProductIdWithProduct(1,2);
when i watch product property of academicRecord in debug mode, I only see a proxy object and not the product loaded.
What's wrong?
Thanks a lot
This is the default behavior for FetchType.LAZY, the proxy will be resolved when you try to get the product. Use FetchType.EAGER instead if you want to load the product at the same time.
#JoinColumn(name = "product_id", nullable = false)
#ManyToOne(fetch = FetchType.EAGER, optional = false)
private Product product;

Hibernate performs update and delete on custom JPQL

I am trying to update the fields of an entity that has a ManyToMany relationship, however, as I just want to update the table fields and ignore the ManyToMany relationship. The relationship is between the Company and UserSystem entities, it was defined in the relationship that company_user_system is the union table of the entities. The problem is that when executing my update in Company, always before my update, Hibernate makes an update in company and the relationship delete in user_system_company and this erases the relationship between Company and UserSystem and I don't understand why these two queries occur if I don't execut.
These are the queries, the first and second are not executed by my code:
Hibernate: update company set active=?, email=?, identification_code=?, trading_name=?, update_on=? where id=?
Hibernate: delete from company_user_system where company_id=?
Hibernate: update company set email=?, phone=?, corporate_name=?, trading_name=?, identification_code=?, email=?, phone2=? where id=?
Hibernate: select company0_.id as id1_0_, company0_.active as active2_0_, company0_.corporate_name as corporat3_0_, company0_.created_on as created_4_0_, company0_.email as email5_0_, company0_.email2 as email6_0_, company0_.identification_code as identifi7_0_, company0_.phone as phone8_0_, company0_.phone2 as phone9_0_, company0_.trading_name as trading10_0_, company0_.update_on as update_11_0_ from company company0_ where company0_.id=?
Following is the update implementation code:
public class CompanyRepositoryImpl implements CompanyRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
public Company updateCompanyFields(Company company) {
// ... fieldSql implementation omitted
String sql = "UPDATE Company SET "+ fieldsSql +" WHERE id = :id ";
Query query = entityManager.createQuery(sql);
// set the values for the fields
for (Method method : getMethods) {
query.setParameter(lowercaseFirstCharInString(cutGetInMethods(method.getName())), method.invoke(company));
}
// set id
query.setParameter("id", company.getId());
// execute update and search the database to return the updated object
if (query.executeUpdate() == 1) {
query = entityManager.createQuery("SELECT c FROM Company c WHERE c.id = :id");
query.setParameter("id", company.getId());
Company getCompany = (Company) query.getResultList().get(0);
return getCompany;
}
return null;
}
// ... Other methods omitted
}
Repository Code:
#Repository
public interface CompanyRepository extends JpaRepository<Company, Long>, JpaSpecificationExecutor<Company> , CompanyRepositoryCustom {
#Modifying
Company updateCompanyFields(Company company);
}
Company entity code, I just added the attributes that I think may contain something useful to try to solve the problem:
#Entity
#DynamicUpdate
#Table(name = "company")
public class Company implements Serializable {
#CreationTimestamp
#Column(name = "created_on", nullable = false)
private Instant createdOn;
#UpdateTimestamp
#Column(name = "update_on")
private Instant updateOn;
#ManyToMany
#JoinTable(
name = "company_user_system",
joinColumns = #JoinColumn(
name = "company_id", referencedColumnName = "id"
),
inverseJoinColumns = #JoinColumn(
name = "user_system_id", referencedColumnName = "id"
)
)
private Set<UserSystem> userSystems = new HashSet<>();
}
The UserSystem class defines the relationship as follows:
#ManyToMany(mappedBy = "userSystems")
private Set<Company> companies = new HashSet<>();
What may be causing this update and delete before my update?
This happens because you changed somewhere the value(s) of your relationship. EntityManager tracks such changes and marks the entity as dirty. When you execute a custom SQL query Hibernate will perform all the pending queries (submit any dirty entities).
You may prevent it by calling EntityManager.clear().

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