Skip to content

Commit 4e975eb

Browse files
committed
HHH-19708 allow customization of the initial CacheMode via creation options
motivation for this in Javadoc
1 parent 2c4a021 commit 4e975eb

File tree

7 files changed

+116
-18
lines changed

7 files changed

+116
-18
lines changed

hibernate-core/src/main/java/org/hibernate/SessionBuilder.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,22 @@ public interface SessionBuilder {
177177
* <li>a non-read-only session will connect to a writable replica.
178178
* </ul>
179179
* <p>
180+
* When read/write replication is in use, it's strongly recommended
181+
* that the session be created with the {@linkplain #initialCacheMode
182+
* initial cache mode} set to {@link CacheMode#GET}, to avoid writing
183+
* stale data read from a read-only replica to the second-level cache.
184+
* Hibernate cannot possibly guarantee that data read from a read-only
185+
* replica is up to date.
186+
* <p>
187+
* When read/write replication is in use, it's possible that an item
188+
* read from the second-level cache might refer to data which does not
189+
* yet exist in the read-only replica. In this situation, an exception
190+
* occurs when the association is fetched. To completely avoid this
191+
* possibility, the {@linkplain #initialCacheMode initial cache mode}
192+
* must be set to {@link CacheMode#IGNORE}. However, it's also usually
193+
* possible to structure data access code in a way which eliminates
194+
* this possibility.
195+
* <p>
180196
* If a session is created in read-only mode, then it cannot be
181197
* changed to read-write mode, and any call to
182198
* {@link Session#setDefaultReadOnly(boolean)} with fail. On the
@@ -193,6 +209,16 @@ public interface SessionBuilder {
193209
@Incubating
194210
SessionBuilder readOnly(boolean readOnly);
195211

212+
/**
213+
* Specify the initial {@link CacheMode} for the session.
214+
*
215+
* @return {@code this}, for method chaining
216+
* @since 7.2
217+
*
218+
* @see SharedSessionContract#getCacheMode()
219+
*/
220+
SessionBuilder initialCacheMode(CacheMode cacheMode);
221+
196222
/**
197223
* Add one or more {@link SessionEventListener} instances to the list of
198224
* listeners for the new session to be built.

hibernate-core/src/main/java/org/hibernate/SharedSessionBuilder.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ public interface SharedSessionBuilder extends SessionBuilder {
9696
@Override
9797
SharedSessionBuilder tenantIdentifier(Object tenantIdentifier);
9898

99+
@Override
100+
SharedSessionBuilder readOnly(boolean readOnly);
101+
102+
@Override
103+
SharedSessionBuilder initialCacheMode(CacheMode cacheMode);
104+
99105
@Override
100106
SharedSessionBuilder eventListeners(SessionEventListener... listeners);
101107

hibernate-core/src/main/java/org/hibernate/StatelessSessionBuilder.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,22 @@ public interface StatelessSessionBuilder {
6464
* <li>a read-only session will connect to a read-only replica, but
6565
* <li>a non-read-only session will connect to a writable replica.
6666
* </ul>
67+
* <p>
68+
* When read/write replication is in use, it's strongly recommended
69+
* that the session be created with the {@linkplain #initialCacheMode
70+
* initial cache mode} set to {@link CacheMode#GET}, to avoid writing
71+
* stale data read from a read-only replica to the second-level cache.
72+
* Hibernate cannot possibly guarantee that data read from a read-only
73+
* replica is up to date. It's also possible for a read-only session to
74+
* <p>
75+
* When read/write replication is in use, it's possible that an item
76+
* read from the second-level cache might refer to data which does not
77+
* yet exist in the read-only replica. In this situation, an exception
78+
* occurs when the association is fetched. To completely avoid this
79+
* possibility, the {@linkplain #initialCacheMode initial cache mode}
80+
* must be set to {@link CacheMode#IGNORE}. However, it's also usually
81+
* possible to structure data access code in a way which eliminates
82+
* this possibility.
6783
*
6884
* @return {@code this}, for method chaining
6985
* @since 7.2
@@ -74,6 +90,16 @@ public interface StatelessSessionBuilder {
7490
@Incubating
7591
StatelessSessionBuilder readOnly(boolean readOnly);
7692

93+
/**
94+
* Specify the initial {@link CacheMode} for the session.
95+
*
96+
* @return {@code this}, for method chaining
97+
* @since 7.2
98+
*
99+
* @see SharedSessionContract#getCacheMode()
100+
*/
101+
StatelessSessionBuilder initialCacheMode(CacheMode cacheMode);
102+
77103
/**
78104
* Applies the given statement inspection function to the session.
79105
*

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

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -186,27 +186,29 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
186186

187187
public AbstractSharedSessionContract(SessionFactoryImpl factory, SessionCreationOptions options) {
188188
this.factory = factory;
189-
this.factoryOptions = factory.getSessionFactoryOptions();
190-
this.jdbcServices = factory.getJdbcServices();
191189

190+
factoryOptions = factory.getSessionFactoryOptions();
191+
jdbcServices = factory.getJdbcServices();
192192
cacheTransactionSynchronization = factory.getCache().getRegionFactory().createTransactionContext( this );
193+
193194
tenantIdentifier = getTenantId( factoryOptions, options );
194195
readOnly = options.isReadOnly();
196+
cacheMode = options.getInitialCacheMode();
195197
interceptor = interpret( options.getInterceptor() );
196198
jdbcTimeZone = options.getJdbcTimeZone();
199+
197200
sessionEventsManager = createSessionEventsManager( factoryOptions, options );
198201
entityNameResolver = new CoordinatingEntityNameResolver( factory, interceptor );
199202

200-
setCriteriaCopyTreeEnabled( factoryOptions.isCriteriaCopyTreeEnabled() );
201-
setCriteriaPlanCacheEnabled( factoryOptions.isCriteriaPlanCacheEnabled() );
202-
setNativeJdbcParametersIgnored( factoryOptions.getNativeJdbcParametersIgnored() );
203-
setCacheMode( factoryOptions.getInitialSessionCacheMode() );
203+
criteriaCopyTreeEnabled = factoryOptions.isCriteriaCopyTreeEnabled();
204+
criteriaPlanCacheEnabled = factoryOptions.isCriteriaPlanCacheEnabled();
205+
nativeJdbcParametersIgnored = factoryOptions.getNativeJdbcParametersIgnored();
204206

205-
final StatementInspector statementInspector = interpret( options.getStatementInspector() );
207+
final var statementInspector = interpret( options.getStatementInspector() );
206208

207209
isTransactionCoordinatorShared = isTransactionCoordinatorShared( options );
208210
if ( isTransactionCoordinatorShared ) {
209-
final SharedSessionCreationOptions sharedOptions = (SharedSessionCreationOptions) options;
211+
final var sharedOptions = (SharedSessionCreationOptions) options;
210212
if ( options.getConnection() != null ) {
211213
throw new SessionException( "Cannot simultaneously share transaction context and specify connection" );
212214
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.List;
99
import java.util.TimeZone;
1010

11+
import org.hibernate.CacheMode;
1112
import org.hibernate.FlushMode;
1213
import org.hibernate.Interceptor;
1314
import org.hibernate.SessionEventListener;
@@ -48,6 +49,8 @@ public interface SessionCreationOptions {
4849

4950
boolean isReadOnly();
5051

52+
CacheMode getInitialCacheMode();
53+
5154
boolean isIdentifierRollbackEnabled();
5255

5356
TimeZone getJdbcTimeZone();

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

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import javax.naming.StringRefAddr;
2727

2828
import jakarta.persistence.TypedQuery;
29+
import org.hibernate.CacheMode;
2930
import org.hibernate.ConnectionAcquisitionMode;
3031
import org.hibernate.ConnectionReleaseMode;
3132
import org.hibernate.CustomEntityDirtinessStrategy;
@@ -34,6 +35,7 @@
3435
import org.hibernate.HibernateException;
3536
import org.hibernate.Interceptor;
3637
import org.hibernate.Session;
38+
import org.hibernate.SessionBuilder;
3739
import org.hibernate.SessionEventListener;
3840
import org.hibernate.SessionFactory;
3941
import org.hibernate.SessionFactoryObserver;
@@ -1130,6 +1132,7 @@ public static class SessionBuilderImpl implements SessionBuilderImplementor, Ses
11301132
private boolean autoClear;
11311133
private Object tenantIdentifier;
11321134
private boolean readOnly;
1135+
private CacheMode cacheMode;
11331136
private boolean identifierRollback;
11341137
private TimeZone jdbcTimeZone;
11351138
private boolean explicitNoInterceptor;
@@ -1148,20 +1151,21 @@ public SessionBuilderImpl(SessionFactoryImpl sessionFactory) {
11481151
this.sessionFactory = sessionFactory;
11491152

11501153
// set up default builder values...
1151-
final SessionFactoryOptions sessionFactoryOptions = sessionFactory.getSessionFactoryOptions();
1152-
statementInspector = sessionFactoryOptions.getStatementInspector();
1153-
connectionHandlingMode = sessionFactoryOptions.getPhysicalConnectionHandlingMode();
1154-
autoClose = sessionFactoryOptions.isAutoCloseSessionEnabled();
1155-
defaultBatchFetchSize = sessionFactoryOptions.getDefaultBatchFetchSize();
1156-
subselectFetchEnabled = sessionFactoryOptions.isSubselectFetchEnabled();
1157-
identifierRollback = sessionFactoryOptions.isIdentifierRollbackEnabled();
1154+
final var options = sessionFactory.getSessionFactoryOptions();
1155+
statementInspector = options.getStatementInspector();
1156+
connectionHandlingMode = options.getPhysicalConnectionHandlingMode();
1157+
autoClose = options.isAutoCloseSessionEnabled();
1158+
defaultBatchFetchSize = options.getDefaultBatchFetchSize();
1159+
subselectFetchEnabled = options.isSubselectFetchEnabled();
1160+
identifierRollback = options.isIdentifierRollbackEnabled();
1161+
cacheMode = options.getInitialSessionCacheMode();
11581162

11591163
final var currentTenantIdentifierResolver =
11601164
sessionFactory.getCurrentTenantIdentifierResolver();
11611165
if ( currentTenantIdentifierResolver != null ) {
11621166
tenantIdentifier = currentTenantIdentifierResolver.resolveCurrentTenantIdentifier();
11631167
}
1164-
jdbcTimeZone = sessionFactoryOptions.getJdbcTimeZone();
1168+
jdbcTimeZone = options.getJdbcTimeZone();
11651169
}
11661170

11671171

@@ -1243,6 +1247,11 @@ public boolean isReadOnly() {
12431247
return readOnly;
12441248
}
12451249

1250+
@Override
1251+
public CacheMode getInitialCacheMode() {
1252+
return cacheMode;
1253+
}
1254+
12461255
@Override
12471256
public boolean isIdentifierRollbackEnabled() {
12481257
return identifierRollback;
@@ -1353,6 +1362,12 @@ public SessionBuilderImpl readOnly(boolean readOnly) {
13531362
return this;
13541363
}
13551364

1365+
@Override
1366+
public SessionBuilder initialCacheMode(CacheMode cacheMode) {
1367+
this.cacheMode = cacheMode;
1368+
return this;
1369+
}
1370+
13561371
@Override
13571372
public SessionBuilderImpl identifierRollback(boolean identifierRollback) {
13581373
this.identifierRollback = identifierRollback;
@@ -1396,10 +1411,13 @@ public static class StatelessSessionBuilderImpl implements StatelessSessionBuild
13961411
private Connection connection;
13971412
private Object tenantIdentifier;
13981413
private boolean readOnly;
1414+
private CacheMode cacheMode;
13991415

14001416
public StatelessSessionBuilderImpl(SessionFactoryImpl sessionFactory) {
14011417
this.sessionFactory = sessionFactory;
1402-
this.statementInspector = sessionFactory.getSessionFactoryOptions().getStatementInspector();
1418+
final var options = sessionFactory.getSessionFactoryOptions();
1419+
statementInspector = options.getStatementInspector();
1420+
cacheMode = options.getInitialSessionCacheMode();
14031421

14041422
final var tenantIdentifierResolver = sessionFactory.getCurrentTenantIdentifierResolver();
14051423
if ( tenantIdentifierResolver != null ) {
@@ -1436,6 +1454,12 @@ public StatelessSessionBuilder readOnly(boolean readOnly) {
14361454
return this;
14371455
}
14381456

1457+
@Override
1458+
public StatelessSessionBuilder initialCacheMode(CacheMode cacheMode) {
1459+
this.cacheMode = cacheMode;
1460+
return this;
1461+
}
1462+
14391463
@Override @Deprecated
14401464
public StatelessSessionBuilder statementInspector(StatementInspector statementInspector) {
14411465
this.statementInspector = statementInspector;
@@ -1516,6 +1540,11 @@ public boolean isReadOnly() {
15161540
return readOnly;
15171541
}
15181542

1543+
@Override
1544+
public CacheMode getInitialCacheMode() {
1545+
return cacheMode;
1546+
}
1547+
15191548
@Override
15201549
public Object getTenantIdentifierValue() {
15211550
return tenantIdentifier;

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2112,12 +2112,18 @@ public SharedSessionBuilderImpl tenantIdentifier(Object tenantIdentifier) {
21122112
}
21132113

21142114
@Override
2115-
public SessionFactoryImpl.SessionBuilderImpl readOnly(boolean readOnly) {
2115+
public SharedSessionBuilderImpl readOnly(boolean readOnly) {
21162116
super.readOnly( readOnly );
21172117
readOnlyChanged = true;
21182118
return this;
21192119
}
21202120

2121+
@Override
2122+
public SharedSessionBuilderImpl initialCacheMode(CacheMode cacheMode) {
2123+
super.initialCacheMode( cacheMode );
2124+
return this;
2125+
}
2126+
21212127
@Override
21222128
public SharedSessionBuilderImpl interceptor() {
21232129
super.interceptor( session.getInterceptor() );

0 commit comments

Comments
 (0)