Skip to content

Commit d4d0613

Browse files
committed
HHH-19737 - Support Envers with StatelessSession
1 parent d4b8c5b commit d4d0613

File tree

5 files changed

+248
-111
lines changed

5 files changed

+248
-111
lines changed

hibernate-core/src/main/java/org/hibernate/event/internal/PostInsertEventListenerStandardImpl.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
public class PostInsertEventListenerStandardImpl implements PostInsertEventListener, CallbackRegistryConsumer {
1919
private CallbackRegistry callbackRegistry;
2020

21+
public PostInsertEventListenerStandardImpl() {
22+
}
23+
2124
@Override
2225
public void injectCallbackRegistry(CallbackRegistry callbackRegistry) {
2326
this.callbackRegistry = callbackRegistry;

hibernate-core/src/main/java/org/hibernate/internal/StatelessSessionImpl.java

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@
44
*/
55
package org.hibernate.internal;
66

7-
import java.util.List;
8-
import java.util.Set;
9-
import java.util.function.BiConsumer;
10-
7+
import jakarta.persistence.EntityGraph;
118
import jakarta.persistence.PersistenceException;
9+
import jakarta.transaction.SystemException;
1210
import org.hibernate.AssertionFailure;
1311
import org.hibernate.FlushMode;
1412
import org.hibernate.HibernateException;
@@ -19,18 +17,10 @@
1917
import org.hibernate.TransientObjectException;
2018
import org.hibernate.UnresolvableObjectException;
2119
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
22-
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
23-
import org.hibernate.cache.spi.access.CollectionDataAccess;
24-
import org.hibernate.cache.spi.access.EntityDataAccess;
25-
import org.hibernate.cache.CacheException;
2620
import org.hibernate.cache.spi.access.SoftLock;
2721
import org.hibernate.collection.spi.CollectionSemantics;
2822
import org.hibernate.collection.spi.PersistentCollection;
2923
import org.hibernate.engine.internal.TransactionCompletionCallbacksImpl;
30-
import org.hibernate.engine.spi.CollectionEntry;
31-
import org.hibernate.engine.spi.EffectiveEntityGraph;
32-
import org.hibernate.engine.spi.EntityEntry;
33-
import org.hibernate.engine.spi.EntityHolder;
3424
import org.hibernate.engine.spi.EntityKey;
3525
import org.hibernate.engine.spi.LoadQueryInfluencers;
3626
import org.hibernate.engine.spi.PersistenceContext;
@@ -75,11 +65,12 @@
7565
import org.hibernate.loader.internal.CacheLoadHelper;
7666
import org.hibernate.persister.collection.CollectionPersister;
7767
import org.hibernate.persister.entity.EntityPersister;
78-
79-
import jakarta.persistence.EntityGraph;
80-
import jakarta.transaction.SystemException;
8168
import org.hibernate.stat.spi.StatisticsImplementor;
8269

70+
import java.util.List;
71+
import java.util.Set;
72+
import java.util.function.BiConsumer;
73+
8374
import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable;
8475
import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable;
8576
import static org.hibernate.engine.internal.PersistenceContexts.createPersistenceContext;
@@ -556,6 +547,8 @@ protected Object idToUpsert(Object entity, EntityPersister persister) {
556547

557548
// Hibernate Reactive may need to call this
558549
protected boolean firePreInsert(Object entity, Object id, Object[] state, EntityPersister persister) {
550+
getFactory().getEventEngine().getCallbackRegistry().preCreate( entity );
551+
559552
if ( eventListenerGroups.eventListenerGroup_PRE_INSERT.isEmpty() ) {
560553
return false;
561554
}
@@ -571,6 +564,8 @@ protected boolean firePreInsert(Object entity, Object id, Object[] state, Entity
571564

572565
// Hibernate Reactive may need to call this
573566
protected boolean firePreUpdate(Object entity, Object id, Object[] state, EntityPersister persister) {
567+
getFactory().getEventEngine().getCallbackRegistry().preUpdate( entity );
568+
574569
if ( eventListenerGroups.eventListenerGroup_PRE_UPDATE.isEmpty() ) {
575570
return false;
576571
}
@@ -601,6 +596,8 @@ protected boolean firePreUpsert(Object entity, Object id, Object[] state, Entity
601596

602597
// Hibernate Reactive may need to call this
603598
protected boolean firePreDelete(Object entity, Object id, EntityPersister persister) {
599+
getFactory().getEventEngine().getCallbackRegistry().preRemove( entity );
600+
604601
if ( eventListenerGroups.eventListenerGroup_PRE_DELETE.isEmpty() ) {
605602
return false;
606603
}
@@ -1306,23 +1303,20 @@ public void afterTransactionBegin() {
13061303

13071304
@Override
13081305
public void beforeTransactionCompletion() {
1306+
transactionCompletionCallbacks.beforeTransactionCompletion();
13091307
flushBeforeTransactionCompletion();
13101308
beforeTransactionCompletionEvents();
13111309
}
13121310

13131311
@Override
13141312
public void afterTransactionCompletion(boolean successful, boolean delayed) {
1315-
processAfterCompletions( successful );
1313+
transactionCompletionCallbacks.afterTransactionCompletion( successful );
13161314
afterTransactionCompletionEvents( successful );
13171315
if ( shouldAutoClose() && !isClosed() ) {
13181316
managedClose();
13191317
}
13201318
}
13211319

1322-
private void processAfterCompletions(boolean successful) {
1323-
transactionCompletionCallbacks.afterTransactionCompletion( successful );
1324-
}
1325-
13261320
@Override
13271321
public boolean isTransactionInProgress() {
13281322
return connectionProvided || super.isTransactionInProgress();
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.stateless.events;
6+
7+
import jakarta.persistence.Entity;
8+
import jakarta.persistence.Id;
9+
import jakarta.persistence.PostPersist;
10+
import jakarta.persistence.PostRemove;
11+
import jakarta.persistence.PostUpdate;
12+
import jakarta.persistence.PrePersist;
13+
import jakarta.persistence.PreRemove;
14+
import jakarta.persistence.PreUpdate;
15+
import jakarta.persistence.Table;
16+
import org.hibernate.testing.orm.junit.DomainModel;
17+
import org.hibernate.testing.orm.junit.SessionFactory;
18+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
19+
import org.junit.jupiter.api.AfterEach;
20+
import org.junit.jupiter.api.Test;
21+
22+
import static org.assertj.core.api.Assertions.assertThat;
23+
24+
/**
25+
* @author Steve Ebersole
26+
*/
27+
@SuppressWarnings("JUnitMalformedDeclaration")
28+
@DomainModel(annotatedClasses = SimpleStatelessSessionTests.Person.class)
29+
@SessionFactory
30+
public class SimpleStatelessSessionTests {
31+
32+
@Test
33+
void simpleSession(SessionFactoryScope factoryScope) {
34+
Person.resetCallbackState();
35+
36+
factoryScope.inTransaction( (session) -> {
37+
final Person john = new Person( 1, "John" );
38+
session.persist( john );
39+
} );
40+
41+
checkCallbackCounts( 1, 0, 0 );
42+
43+
factoryScope.inTransaction( (session) -> {
44+
final Person john = session.find( Person.class, 1 );
45+
john.name = "Jonathan";
46+
} );
47+
48+
checkCallbackCounts( 1, 1, 0 );
49+
50+
factoryScope.inTransaction( (session) -> {
51+
final Person john = session.find( Person.class, 1 );
52+
session.remove( john );
53+
} );
54+
55+
checkCallbackCounts( 1, 1, 1 );
56+
}
57+
58+
@Test
59+
void simpleStatelessSession(SessionFactoryScope factoryScope) {
60+
Person.resetCallbackState();
61+
62+
factoryScope.inStatelessTransaction( (session) -> {
63+
final Person john = new Person( 1, "John" );
64+
session.insert( john );
65+
} );
66+
67+
checkCallbackCounts( 1, 0, 0 );
68+
69+
factoryScope.inStatelessTransaction( (session) -> {
70+
final Person john = session.get( Person.class, 1 );
71+
john.name = "Jonathan";
72+
session.update( john );
73+
} );
74+
75+
checkCallbackCounts( 1, 1, 0 );
76+
77+
factoryScope.inStatelessTransaction( (session) -> {
78+
final Person john = session.get( Person.class, 1 );
79+
session.delete( john );
80+
} );
81+
82+
checkCallbackCounts( 1, 1, 1 );
83+
}
84+
85+
private void checkCallbackCounts(int insert, int update, int delete) {
86+
assertThat( Person.beforeInsertCalls ).isEqualTo( insert );
87+
assertThat( Person.afterInsertCalls ).isEqualTo( insert );
88+
89+
assertThat( Person.beforeUpdateCalls ).isEqualTo( update );
90+
assertThat( Person.afterUpdateCalls ).isEqualTo( update );
91+
92+
assertThat( Person.beforeDeleteCalls ).isEqualTo( delete );
93+
assertThat( Person.afterDeleteCalls ).isEqualTo( delete );
94+
95+
}
96+
97+
@AfterEach
98+
void dropTestData(SessionFactoryScope factoryScope) {
99+
factoryScope.dropData();
100+
}
101+
102+
@Entity(name="Person")
103+
@Table(name="persons")
104+
public static class Person {
105+
public static int beforeInsertCalls;
106+
public static int afterInsertCalls;
107+
108+
public static int beforeUpdateCalls;
109+
public static int afterUpdateCalls;
110+
111+
public static int beforeDeleteCalls;
112+
public static int afterDeleteCalls;
113+
114+
@Id
115+
private Integer id;
116+
private String name;
117+
118+
public Person() {
119+
}
120+
121+
public Person(Integer id, String name) {
122+
this.id = id;
123+
this.name = name;
124+
}
125+
126+
@PrePersist
127+
public void beforeInsert() {
128+
beforeInsertCalls++;
129+
}
130+
131+
@PostPersist
132+
public void afterInsert() {
133+
afterInsertCalls++;
134+
}
135+
136+
@PreUpdate
137+
public void beforeUpdate() {
138+
beforeUpdateCalls++;
139+
}
140+
141+
@PostUpdate
142+
public void afterUpdate() {
143+
afterUpdateCalls++;
144+
}
145+
146+
@PreRemove
147+
public void beforeDelete() {
148+
beforeDeleteCalls++;
149+
}
150+
151+
@PostRemove
152+
public void afterDelete() {
153+
afterDeleteCalls++;
154+
}
155+
156+
public static void resetCallbackState() {
157+
beforeInsertCalls = 0;
158+
afterInsertCalls = 0;
159+
160+
beforeUpdateCalls = 0;
161+
afterUpdateCalls = 0;
162+
163+
beforeDeleteCalls = 0;
164+
afterDeleteCalls = 0;
165+
}
166+
}
167+
}

hibernate-envers/src/main/java/org/hibernate/envers/internal/synchronization/AuditProcess.java

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
import java.util.Map;
1010
import java.util.Queue;
1111

12+
import org.hibernate.AssertionFailure;
1213
import org.hibernate.FlushMode;
1314
import org.hibernate.Session;
1415
import org.hibernate.action.spi.BeforeTransactionCompletionProcess;
1516
import org.hibernate.engine.spi.SessionImplementor;
1617
import org.hibernate.engine.spi.SharedSessionContractImplementor;
18+
import org.hibernate.engine.spi.StatelessSessionImplementor;
1719
import org.hibernate.envers.exception.AuditException;
1820
import org.hibernate.envers.internal.revisioninfo.RevisionInfoGenerator;
1921
import org.hibernate.envers.internal.synchronization.work.AuditWorkUnit;
@@ -109,23 +111,6 @@ public void addWorkUnit(AuditWorkUnit vwu) {
109111
}
110112
}
111113

112-
private void executeInSession(Session session) {
113-
// Making sure the revision data is persisted.
114-
final Object currentRevisionData = getCurrentRevisionData( session, true );
115-
116-
AuditWorkUnit vwu;
117-
118-
// First undoing any performed work units
119-
while ( (vwu = undoQueue.poll()) != null ) {
120-
vwu.undo( session );
121-
}
122-
123-
while ( (vwu = workUnits.poll()) != null ) {
124-
vwu.perform( session, revisionData );
125-
entityChangeNotifier.entityChanged( session, currentRevisionData, vwu );
126-
}
127-
}
128-
129114
public Object getCurrentRevisionData(Session session, boolean persist) {
130115
// Generating the revision data if not yet generated
131116
if ( revisionData == null ) {
@@ -151,11 +136,18 @@ public void doBeforeTransactionCompletion(SharedSessionContractImplementor sessi
151136
return;
152137
}
153138

154-
// see: http://www.jboss.com/index.html?module=bb&op=viewtopic&p=4178431
155-
if ( FlushMode.MANUAL.equals( session.getHibernateFlushMode() ) || session.isClosed() ) {
156-
// NOTE: at the moment, this only works with stateful sessions. making it work with
157-
// stateless would require the ability to create stateless sessions from another
158-
// sharing the original's underpinnings.
139+
if ( session instanceof StatelessSessionImplementor statelessSession ) {
140+
if ( statelessSession.isClosed() ) {
141+
// todo (EventSource) : this would require the ability to create
142+
// stateless sessions from another sharing the original's underpinnings
143+
// ala SharedSessionBuilder
144+
throw new AssertionFailure( "Auditing not supported on closed StatelessSessions" );
145+
}
146+
else {
147+
executeInStatelessSession( statelessSession );
148+
}
149+
}
150+
else if ( FlushMode.MANUAL.equals( session.getHibernateFlushMode() ) || session.isClosed() ) {
159151
if ( session instanceof SessionImplementor statefulSession ) {
160152
try (Session temporarySession = statefulSession.sessionWithOptions()
161153
.connection()
@@ -172,17 +164,37 @@ public void doBeforeTransactionCompletion(SharedSessionContractImplementor sessi
172164
}
173165
}
174166
else {
175-
// todo (EventSource) : this is the case we absolutely want to allow with StatelessSession.
176-
// but, unfortunately, requires some big changes to envers to support
177-
if ( session instanceof SessionImplementor statefulSession ) {
178-
executeInSession( statefulSession );
179-
}
180-
else {
181-
throw new UnsupportedOperationException( "Operation only supported with stateful Session, but encountered StatelessSession" );
182-
}
167+
executeInSession( (SessionImplementor) session );
183168

184169
// Explicitly flushing the session, as the auto-flush may have already happened.
185170
session.flush();
186171
}
187172
}
173+
174+
private void executeInSession(Session session) {
175+
// Making sure the revision data is persisted.
176+
final Object currentRevisionData = getCurrentRevisionData( session, true );
177+
178+
AuditWorkUnit vwu;
179+
180+
// First undoing any performed work units
181+
while ( (vwu = undoQueue.poll()) != null ) {
182+
vwu.undo( session );
183+
}
184+
185+
while ( (vwu = workUnits.poll()) != null ) {
186+
vwu.perform( session, revisionData );
187+
entityChangeNotifier.entityChanged( session, currentRevisionData, vwu );
188+
}
189+
}
190+
191+
private void executeInStatelessSession(StatelessSessionImplementor statelessSession) {
192+
// few considerations:
193+
// 1. should all operations on a SS (within a txn) be grouped as the same revision?
194+
// 2. work contracts need to change; they are internal, so no problem. 2 options -
195+
// a. have one set for stateful and another for stateless
196+
// b. have them accept Shared... and use instanceof internally
197+
// 3. AuditStrategy needs to change; this one is SPI however, though incubating
198+
throw new UnsupportedOperationException( "Not implemented yet" );
199+
}
188200
}

0 commit comments

Comments
 (0)