Skip to content

Commit 866926f

Browse files
committed
HHH-18546 Add test for issue
1 parent fd344ff commit 866926f

File tree

1 file changed

+317
-0
lines changed

1 file changed

+317
-0
lines changed
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.cache;
6+
7+
import org.hibernate.cache.CacheException;
8+
import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext;
9+
import org.hibernate.cache.cfg.spi.DomainDataRegionConfig;
10+
import org.hibernate.cache.cfg.spi.EntityDataCachingConfig;
11+
import org.hibernate.cache.internal.DefaultCacheKeysFactory;
12+
import org.hibernate.cache.spi.CacheKeysFactory;
13+
import org.hibernate.cache.spi.DomainDataRegion;
14+
import org.hibernate.cache.spi.access.AccessType;
15+
import org.hibernate.cache.spi.access.EntityDataAccess;
16+
import org.hibernate.cache.spi.access.SoftLock;
17+
import org.hibernate.cache.spi.support.AbstractEntityDataAccess;
18+
import org.hibernate.cache.spi.support.DomainDataRegionImpl;
19+
import org.hibernate.cache.spi.support.DomainDataStorageAccess;
20+
import org.hibernate.cache.spi.support.RegionFactoryTemplate;
21+
import org.hibernate.cfg.AvailableSettings;
22+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
23+
24+
import org.hibernate.testing.cache.CachingRegionFactory;
25+
import org.hibernate.testing.cache.MapStorageAccessImpl;
26+
import org.hibernate.testing.orm.junit.DomainModel;
27+
import org.hibernate.testing.orm.junit.JiraKey;
28+
import org.hibernate.testing.orm.junit.ServiceRegistry;
29+
import org.hibernate.testing.orm.junit.SessionFactory;
30+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
31+
import org.hibernate.testing.orm.junit.Setting;
32+
import org.hibernate.testing.orm.junit.SettingProvider;
33+
import org.junit.jupiter.api.AfterEach;
34+
import org.junit.jupiter.api.BeforeEach;
35+
import org.junit.jupiter.api.Test;
36+
37+
import jakarta.persistence.Cacheable;
38+
import jakarta.persistence.Entity;
39+
import jakarta.persistence.Id;
40+
import jakarta.persistence.Table;
41+
42+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
43+
44+
@DomainModel(
45+
annotatedClasses = {
46+
PendingBulkOperationCleanupActionTest.Customer.class
47+
}
48+
)
49+
@SessionFactory()
50+
@ServiceRegistry(
51+
settings = {
52+
@Setting(name = AvailableSettings.USE_SECOND_LEVEL_CACHE, value = "true"),
53+
@Setting(name = AvailableSettings.ALLOW_UPDATE_OUTSIDE_TRANSACTION, value = "true"),
54+
},
55+
settingProviders = @SettingProvider(
56+
settingName = AvailableSettings.CACHE_REGION_FACTORY,
57+
provider = PendingBulkOperationCleanupActionTest.CacheRegionFactoryProvider.class
58+
)
59+
)
60+
@JiraKey( "HHH-18546" )
61+
public class PendingBulkOperationCleanupActionTest {
62+
63+
private static final TestCachingRegionFactory CACHING_REGION_FACTORY = new TestCachingRegionFactory();
64+
65+
public static class CacheRegionFactoryProvider implements SettingProvider.Provider<TestCachingRegionFactory> {
66+
@Override
67+
public TestCachingRegionFactory getSetting() {
68+
return CACHING_REGION_FACTORY;
69+
}
70+
}
71+
72+
@BeforeEach
73+
public void before(SessionFactoryScope scope) {
74+
scope.inTransaction(
75+
session ->
76+
session.persist( new Customer( 1, "Samuel" ) )
77+
);
78+
CACHING_REGION_FACTORY.getTestDomainDataRegion().getTestEntityDataAccess().reset();
79+
}
80+
81+
@AfterEach
82+
public void after(SessionFactoryScope scope) {
83+
scope.inTransaction(
84+
session ->
85+
session.createQuery( "delete Customer" ).executeUpdate()
86+
);
87+
CACHING_REGION_FACTORY.getTestDomainDataRegion().getTestEntityDataAccess().reset();
88+
}
89+
90+
@Test
91+
public void testUpdateCachedEntity(SessionFactoryScope scope) {
92+
scope.inTransaction(
93+
session ->
94+
session.createNativeQuery( "update Customer set id = id" ).executeUpdate()
95+
);
96+
assertThat( isLockRegionCalled() )
97+
.as( "EntityDataAccess lockRegion method has not been called" )
98+
.isTrue();
99+
// Region unlock is a BulkOperationCleanUpAction executed after Transaction commit
100+
assertThat( isUnlockRegionCalled() )
101+
.as( "EntityDataAccess unlockRegion method has not been called" )
102+
.isTrue();
103+
}
104+
105+
@Test
106+
public void testPendingBulkOperationActionsAreExecutedWhenSessionIsClosed(SessionFactoryScope scope) {
107+
scope.inSession(
108+
session ->
109+
session.createNativeQuery( "update Customer set id = id" ).executeUpdate()
110+
);
111+
112+
assertThat( isLockRegionCalled() )
113+
.as( "EntityDataAccess lockRegion method has not been called" )
114+
.isTrue();
115+
116+
// Because the update is executed outside a transaction, region unlock BulkOperationCleanUpAction has not been executed
117+
// and when the session is closed it's a pending action
118+
assertThat( isUnlockRegionCalled() )
119+
.as( "EntityDataAccess unlockRegion method has not been called" )
120+
.isTrue();
121+
}
122+
123+
private static boolean isUnlockRegionCalled() {
124+
return CACHING_REGION_FACTORY.getTestDomainDataRegion()
125+
.getTestEntityDataAccess()
126+
.isUnlockRegionCalled();
127+
}
128+
129+
private static boolean isLockRegionCalled() {
130+
return CACHING_REGION_FACTORY.getTestDomainDataRegion()
131+
.getTestEntityDataAccess()
132+
.isLockRegionCalled();
133+
}
134+
135+
@Entity(name = "Customer")
136+
@Table(name = "Customer")
137+
@Cacheable
138+
public static class Customer {
139+
@Id
140+
private int id;
141+
142+
private String name;
143+
144+
public Customer() {
145+
}
146+
147+
public Customer(int id, String name) {
148+
this.id = id;
149+
this.name = name;
150+
}
151+
152+
public int getId() {
153+
return id;
154+
}
155+
156+
public void setId(int id) {
157+
this.id = id;
158+
}
159+
160+
public String getName() {
161+
return name;
162+
}
163+
164+
public void setName(String name) {
165+
this.name = name;
166+
}
167+
}
168+
169+
public static class TestCachingRegionFactory extends CachingRegionFactory {
170+
private final CacheKeysFactory cacheKeysFactory;
171+
private TestDomainDataRegion testDomainDataRegion;
172+
173+
public TestCachingRegionFactory() {
174+
this.cacheKeysFactory = DefaultCacheKeysFactory.INSTANCE;
175+
}
176+
177+
@Override
178+
public DomainDataRegion buildDomainDataRegion(
179+
DomainDataRegionConfig regionConfig, DomainDataRegionBuildingContext buildingContext) {
180+
if ( testDomainDataRegion == null ) {
181+
testDomainDataRegion = new TestDomainDataRegion(
182+
regionConfig,
183+
this,
184+
new MapStorageAccessImpl(),
185+
cacheKeysFactory,
186+
buildingContext
187+
);
188+
}
189+
return testDomainDataRegion;
190+
}
191+
192+
@Override
193+
protected void releaseFromUse() {
194+
testDomainDataRegion = null;
195+
}
196+
197+
public TestDomainDataRegion getTestDomainDataRegion() {
198+
return testDomainDataRegion;
199+
}
200+
}
201+
202+
public static class TestDomainDataRegion extends DomainDataRegionImpl {
203+
204+
TestEntityDataAccess testEntityDataAccess;
205+
206+
public TestDomainDataRegion(
207+
DomainDataRegionConfig regionConfig,
208+
RegionFactoryTemplate regionFactory,
209+
DomainDataStorageAccess domainDataStorageAccess,
210+
CacheKeysFactory defaultKeysFactory,
211+
DomainDataRegionBuildingContext buildingContext) {
212+
super( regionConfig, regionFactory, domainDataStorageAccess, defaultKeysFactory, buildingContext );
213+
}
214+
215+
@Override
216+
public EntityDataAccess generateEntityAccess(EntityDataCachingConfig entityAccessConfig) {
217+
if ( testEntityDataAccess == null ) {
218+
testEntityDataAccess = new TestEntityDataAccess(
219+
this,
220+
getEffectiveKeysFactory(),
221+
getCacheStorageAccess()
222+
);
223+
}
224+
return testEntityDataAccess;
225+
}
226+
227+
public TestEntityDataAccess getTestEntityDataAccess() {
228+
return testEntityDataAccess;
229+
}
230+
231+
@Override
232+
public void destroy() throws CacheException {
233+
testEntityDataAccess = null;
234+
}
235+
}
236+
237+
public static class TestEntityDataAccess extends AbstractEntityDataAccess {
238+
239+
private boolean isUnlockRegionCalled = false;
240+
private boolean lockRegionCalled = false;
241+
242+
public TestEntityDataAccess(
243+
DomainDataRegion region,
244+
CacheKeysFactory cacheKeysFactory,
245+
DomainDataStorageAccess storageAccess) {
246+
super( region, cacheKeysFactory, storageAccess );
247+
}
248+
249+
@Override
250+
public boolean insert(SharedSessionContractImplementor session, Object key, Object value, Object version) {
251+
return false;
252+
}
253+
254+
@Override
255+
public boolean afterInsert(SharedSessionContractImplementor session, Object key, Object value, Object version) {
256+
return false;
257+
}
258+
259+
@Override
260+
public boolean update(
261+
SharedSessionContractImplementor session,
262+
Object key,
263+
Object value,
264+
Object currentVersion,
265+
Object previousVersion) {
266+
return false;
267+
}
268+
269+
@Override
270+
public boolean afterUpdate(
271+
SharedSessionContractImplementor session,
272+
Object key,
273+
Object value,
274+
Object currentVersion,
275+
Object previousVersion,
276+
SoftLock lock) {
277+
return false;
278+
}
279+
280+
@Override
281+
public AccessType getAccessType() {
282+
return null;
283+
}
284+
285+
@Override
286+
public SoftLock lockRegion() {
287+
lockRegionCalled = true;
288+
return super.lockRegion();
289+
}
290+
291+
@Override
292+
public void unlockRegion(SoftLock lock) {
293+
super.unlockRegion( lock );
294+
isUnlockRegionCalled = true;
295+
}
296+
297+
@Override
298+
public void destroy() {
299+
super.destroy();
300+
isUnlockRegionCalled = false;
301+
}
302+
303+
public boolean isUnlockRegionCalled() {
304+
return isUnlockRegionCalled;
305+
}
306+
307+
public boolean isLockRegionCalled() {
308+
return lockRegionCalled;
309+
}
310+
311+
public void reset() {
312+
this.isUnlockRegionCalled = false;
313+
this.lockRegionCalled = false;
314+
}
315+
316+
}
317+
}

0 commit comments

Comments
 (0)