Migrate hibernate from 6.1.7.Final to 7.1.1.Final
Change-Id: Ib5855c283ce6c65401009db8b42e4aae9afa3669
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index eecc454..4cd1904 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -9,9 +9,4 @@
directory: "/" # Location of package manifests
schedule:
interval: "daily"
- ignore:
- # Hibernate >=6.2 is incompatible
- - dependency-name: "org.hibernate.orm:hibernate-*"
- versions:
- - ">= 6.2.0"
open-pull-requests-limit: 50
diff --git a/pom.xml b/pom.xml
index 31d98b4..2a7f13a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,8 +9,7 @@
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jersey.version>3.1.11</jersey.version>
- <hibernate.ehcache.version>6.0.0.Alpha7</hibernate.ehcache.version>
- <hibernate.version>6.1.7.Final</hibernate.version>
+ <hibernate.version>7.1.1.Final</hibernate.version>
<spring.version>6.2.11</spring.version>
<!-- spring6.version is used in jersey and defined here
to make sure that jersey uses the correct spring version-->
@@ -523,17 +522,7 @@
<artifactId>hibernate-jpamodelgen</artifactId>
<version>${hibernate.version}</version>
</dependency>
- <dependency>
- <groupId>org.hibernate.orm</groupId>
- <artifactId>hibernate-ehcache</artifactId>
- <version>${hibernate.ehcache.version}</version>
- <exclusions>
- <exclusion>
- <groupId>org.hibernate</groupId>
- <artifactId>hibernate-core</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
+ <!-- Remove hibernate-ehcache (Ehcache 2 integration removed in modern Hibernate) -->
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-c3p0</artifactId>
@@ -586,6 +575,11 @@
<!-- Utilities -->
<dependency>
+ <groupId>net.sf.ehcache</groupId>
+ <artifactId>ehcache</artifactId>
+ <version>2.10.6</version>
+ </dependency>
+ <dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.14.0</version>
diff --git a/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java b/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
index 6c68c13..b2c09cf 100644
--- a/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
+++ b/src/main/java/de/ids_mannheim/korap/dao/RoleDao.java
@@ -165,7 +165,36 @@
return new HashSet<Role>(resultList);
}
- public Role retrieveRoleByGroupIdQueryIdPrivilege (int groupId, int queryId,
+ /**
+ * Retrieve all roles associated with a given query id, including their members.
+ */
+ public List<Role> retrieveRolesByQueryIdWithMembers(int queryId) {
+ CriteriaBuilder cb = entityManager.getCriteriaBuilder();
+ CriteriaQuery<Role> cq = cb.createQuery(Role.class);
+
+ Root<Role> role = cq.from(Role.class);
+ role.fetch(Role_.userGroupMembers, JoinType.LEFT);
+ role.fetch(Role_.userGroup, JoinType.INNER);
+ // query is optional for some roles, but we filter roles linked to the query
+ cq.select(role);
+ cq.where(cb.equal(role.get(Role_.query).get(QueryDO_.id), queryId));
+
+ TypedQuery<Role> q = entityManager.createQuery(cq);
+ return q.getResultList();
+ }
+
+ /**
+ * Bulk delete all roles associated with a given query id.
+ */
+ public void deleteRolesByQueryId(int queryId) {
+ CriteriaBuilder cb = entityManager.getCriteriaBuilder();
+ CriteriaDelete<Role> delete = cb.createCriteriaDelete(Role.class);
+ Root<Role> role = delete.from(Role.class);
+ delete.where(cb.equal(role.get(Role_.query).get(QueryDO_.id), queryId));
+ entityManager.createQuery(delete).executeUpdate();
+ }
+
+ public Role retrieveRoleByGroupIdQueryIdPrivilege(int groupId, int queryId,
PrivilegeType p) throws KustvaktException {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
diff --git a/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java b/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
index 2545122..8627f1d 100644
--- a/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
+++ b/src/main/java/de/ids_mannheim/korap/dao/UserGroupDao.java
@@ -111,7 +111,23 @@
"groupId: " + groupId);
}
- // EM: this seems weird
+ // Before deleting the group, detach role associations from members to
+ // avoid transient role references during flush
+ CriteriaBuilder cb = entityManager.getCriteriaBuilder();
+ CriteriaQuery<UserGroupMember> cq = cb.createQuery(UserGroupMember.class);
+ Root<UserGroupMember> memberRoot = cq.from(UserGroupMember.class);
+ cq.select(memberRoot);
+ cq.where(cb.equal(memberRoot.get(UserGroupMember_.group).get("id"), groupId));
+ @SuppressWarnings("unchecked")
+ List<UserGroupMember> members = entityManager.createQuery(cq).getResultList();
+ for (UserGroupMember m : members) {
+ if (!entityManager.contains(m)) {
+ m = entityManager.merge(m);
+ }
+ m.setRoles(new HashSet<>());
+ entityManager.merge(m);
+ }
+
if (!entityManager.contains(group)) {
group = entityManager.merge(group);
}
diff --git a/src/main/java/de/ids_mannheim/korap/dao/UserGroupMemberDao.java b/src/main/java/de/ids_mannheim/korap/dao/UserGroupMemberDao.java
index 86ab9dd..e2da736 100644
--- a/src/main/java/de/ids_mannheim/korap/dao/UserGroupMemberDao.java
+++ b/src/main/java/de/ids_mannheim/korap/dao/UserGroupMemberDao.java
@@ -42,12 +42,38 @@
public void addMember (UserGroupMember member) throws KustvaktException {
ParameterChecker.checkObjectValue(member, "userGroupMember");
+ if (member.getRoles() != null) {
+ java.util.Set<Role> normalized = new java.util.HashSet<Role>();
+ for (Role role : member.getRoles()) {
+ if (role.getId() == 0) {
+ entityManager.persist(role);
+ normalized.add(role);
+ } else {
+ Role attached = entityManager.contains(role) ? role : entityManager.merge(role);
+ normalized.add(attached);
+ }
+ }
+ member.setRoles(normalized);
+ }
entityManager.persist(member);
entityManager.flush();
}
public void updateMember (UserGroupMember member) throws KustvaktException {
ParameterChecker.checkObjectValue(member, "UserGroupMember");
+ if (member.getRoles() != null) {
+ java.util.Set<Role> normalized = new java.util.HashSet<Role>();
+ for (Role role : member.getRoles()) {
+ if (role.getId() == 0) {
+ entityManager.persist(role);
+ normalized.add(role);
+ } else {
+ Role attached = entityManager.contains(role) ? role : entityManager.merge(role);
+ normalized.add(attached);
+ }
+ }
+ member.setRoles(normalized);
+ }
entityManager.merge(member);
}
@@ -76,7 +102,7 @@
Root<UserGroupMember> root = query.from(UserGroupMember.class);
Predicate predicate = criteriaBuilder.and(
- criteriaBuilder.equal(root.get(UserGroupMember_.group),
+ criteriaBuilder.equal(root.get(UserGroupMember_.group).get("id"),
groupId),
criteriaBuilder.equal(root.get(UserGroupMember_.userId),
userId));
@@ -108,7 +134,7 @@
Join<UserGroupMember, Role> memberRole = root.join("roles");
Predicate predicate = criteriaBuilder.and(
- criteriaBuilder.equal(root.get(UserGroupMember_.group),
+ criteriaBuilder.equal(root.get(UserGroupMember_.group).get("id"),
groupId),
criteriaBuilder.equal(memberRole.get(Role_.NAME), role));
@@ -136,7 +162,7 @@
Root<UserGroupMember> root = query.from(UserGroupMember.class);
Predicate predicate = criteriaBuilder.and(criteriaBuilder
- .equal(root.get(UserGroupMember_.group), groupId));
+ .equal(root.get(UserGroupMember_.group).get("id"), groupId));
query.select(root);
query.where(predicate);
diff --git a/src/main/java/de/ids_mannheim/korap/entity/UserGroupMember.java b/src/main/java/de/ids_mannheim/korap/entity/UserGroupMember.java
index d5d54ae..30f690c 100644
--- a/src/main/java/de/ids_mannheim/korap/entity/UserGroupMember.java
+++ b/src/main/java/de/ids_mannheim/korap/entity/UserGroupMember.java
@@ -4,6 +4,7 @@
import de.ids_mannheim.korap.constant.PredefinedRole;
import jakarta.persistence.Column;
+import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
@@ -50,7 +51,7 @@
* describe a member.
*
*/
- @ManyToMany(fetch = FetchType.EAGER)
+ @ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.MERGE })
@JoinTable(name = "group_member_role", joinColumns = @JoinColumn(name = "group_member_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"), uniqueConstraints = @UniqueConstraint(columnNames = {
"group_member_id", "role_id" }))
private Set<Role> roles;
diff --git a/src/main/java/de/ids_mannheim/korap/service/QueryService.java b/src/main/java/de/ids_mannheim/korap/service/QueryService.java
index e3b0ce6..f329ab0 100644
--- a/src/main/java/de/ids_mannheim/korap/service/QueryService.java
+++ b/src/main/java/de/ids_mannheim/korap/service/QueryService.java
@@ -178,10 +178,29 @@
else if (query.getCreatedBy().equals(deletedBy)
|| adminDao.isAdmin(deletedBy)) {
- if (query.getType().equals(ResourceType.PUBLISHED)) {
- UserGroup group = userGroupDao
- .retrieveHiddenGroupByQueryName(queryName);
- userGroupDao.deleteGroup(group.getId(), deletedBy);
+ // If published, fetch the hidden group BEFORE deleting roles, so we can remove
+ // it later
+ UserGroup hiddenGroup = null;
+ boolean isPublished = query.getType().equals(ResourceType.PUBLISHED);
+ if (isPublished) {
+ hiddenGroup = userGroupDao.retrieveHiddenGroupByQueryName(queryName);
+ }
+
+ // Detach member-role links and delete all roles linked to the query
+ List<Role> queryRoles = roleDao.retrieveRolesByQueryIdWithMembers(query.getId());
+ for (Role role : queryRoles) {
+ if (role.getUserGroupMembers() != null) {
+ for (UserGroupMember m : role.getUserGroupMembers()) {
+ if (m.getRoles() != null && m.getRoles().remove(role)) {
+ memberDao.updateMember(m);
+ }
+ }
+ }
+ }
+ roleDao.deleteRolesByQueryId(query.getId());
+
+ if (isPublished && hiddenGroup != null) {
+ userGroupDao.deleteGroup(hiddenGroup.getId(), deletedBy);
}
if (type.equals(QueryType.VIRTUAL_CORPUS)
&& VirtualCorpusCache.contains(queryName)) {
diff --git a/src/main/resources/default-config.xml b/src/main/resources/default-config.xml
index a4a30d9..c82010f 100644
--- a/src/main/resources/default-config.xml
+++ b/src/main/resources/default-config.xml
@@ -125,9 +125,7 @@
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
<prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}
- </prop>
- <prop key="hibernate.cache.provider_class">${hibernate.cache.provider}</prop>
- <prop key="hibernate.cache.region.factory_class">${hibernate.cache.region.factory}</prop>
+ </prop>
<prop key="hibernate.jdbc.time_zone">${hibernate.jdbc.time_zone}</prop>
</props>
</property>
diff --git a/src/main/resources/properties/hibernate.properties b/src/main/resources/properties/hibernate.properties
index e394a88..bd073e5 100644
--- a/src/main/resources/properties/hibernate.properties
+++ b/src/main/resources/properties/hibernate.properties
@@ -3,6 +3,7 @@
hibernate.show_sql=false
hibernate.cache.use_query_cache=false
hibernate.cache.use_second_level_cache=false
-hibernate.cache.provider=org.hibernate.cache.EhCacheProvider
-hibernate.cache.region.factory=org.hibernate.cache.ehcache.EhCacheRegionFactory
-hibernate.jdbc.time_zone=UTC
\ No newline at end of file
+hibernate.jdbc.time_zone=UTC
+# Ehcache 2 integration removed; disable legacy cache settings
+hibernate.cache.provider=
+hibernate.cache.region.factory=
diff --git a/src/test/java/de/ids_mannheim/korap/dao/VirtualCorpusDaoTest.java b/src/test/java/de/ids_mannheim/korap/dao/VirtualCorpusDaoTest.java
index 9e94ca5..d5773a9 100644
--- a/src/test/java/de/ids_mannheim/korap/dao/VirtualCorpusDaoTest.java
+++ b/src/test/java/de/ids_mannheim/korap/dao/VirtualCorpusDaoTest.java
@@ -73,9 +73,14 @@
"experimental", false, "system", null, null);
});
- assertEquals("Converting `org.hibernate.exception.GenericJDBCException` "
- + "to JPA `PersistenceException` : could not execute statement",
- exception.getMessage());
+ String msg = exception.getMessage();
+ // Hibernate 7 exposes provider/DB-specific message; assert key parts
+ org.junit.jupiter.api.Assertions.assertTrue(
+ msg.contains("could not execute statement"),
+ () -> "Unexpected message: " + msg);
+ org.junit.jupiter.api.Assertions.assertTrue(
+ msg.contains("UNIQUE") || msg.contains("constraint"),
+ () -> "Expected unique constraint error in message: " + msg);
}
@Test
diff --git a/src/test/resources/test-config.xml b/src/test/resources/test-config.xml
index b5b676b..05311db 100644
--- a/src/test/resources/test-config.xml
+++ b/src/test/resources/test-config.xml
@@ -147,11 +147,8 @@
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.cache.use_query_cache">${hibernate.cache.use_query_cache}</prop>
<prop key="hibernate.cache.use_second_level_cache">${hibernate.cache.use_second_level_cache}
- </prop>
- <prop key="hibernate.cache.provider_class">${hibernate.cache.provider}</prop>
- <prop key="hibernate.cache.region.factory_class">${hibernate.cache.region.factory}</prop>
- <prop key="hibernate.jdbc.time_zone">${hibernate.jdbc.time_zone}</prop>
- <!-- <prop key="net.sf.ehcache.configurationResourceName">classpath:ehcache.xml</prop> -->
+ </prop>
+ <prop key="hibernate.jdbc.time_zone">${hibernate.jdbc.time_zone}</prop>
</props>
</property>
</bean>
diff --git a/src/test/resources/test-hibernate.properties b/src/test/resources/test-hibernate.properties
index e394a88..63697a7 100644
--- a/src/test/resources/test-hibernate.properties
+++ b/src/test/resources/test-hibernate.properties
@@ -3,6 +3,6 @@
hibernate.show_sql=false
hibernate.cache.use_query_cache=false
hibernate.cache.use_second_level_cache=false
-hibernate.cache.provider=org.hibernate.cache.EhCacheProvider
-hibernate.cache.region.factory=org.hibernate.cache.ehcache.EhCacheRegionFactory
-hibernate.jdbc.time_zone=UTC
\ No newline at end of file
+hibernate.jdbc.time_zone=UTC
+hibernate.cache.provider=
+hibernate.cache.region.factory=