Skip to content

Commit 153a179

Browse files
committed
SPR-16956 - Propagate read-only status as FlushMode.MANUAL to Hibernate Session
1 parent c97acbb commit 153a179

File tree

8 files changed

+201
-6
lines changed

8 files changed

+201
-6
lines changed

spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,8 @@ protected void doBegin(Object transaction, TransactionDefinition definition) {
495495
if (definition.isReadOnly() && txObject.isNewSession()) {
496496
// Just set to MANUAL in case of a new Session for this transaction.
497497
session.setFlushMode(FlushMode.MANUAL);
498+
txObject.getSessionHolder().setPreviousReadOnly( session.isDefaultReadOnly() );
499+
session.setDefaultReadOnly(true);
498500
}
499501

500502
if (!definition.isReadOnly() && !txObject.isNewSession()) {
@@ -717,6 +719,7 @@ protected void doCleanupAfterCompletion(Object transaction) {
717719
}
718720
if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
719721
session.setFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
722+
session.setDefaultReadOnly(txObject.getSessionHolder().isPreviousReadOnly());
720723
}
721724
if (!this.allowResultAccessAfterCompletion && !this.hibernateManagedSession) {
722725
disconnectOnCompletion(session);

spring-orm/src/main/java/org/springframework/orm/hibernate5/SessionHolder.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public class SessionHolder extends ResourceHolderSupport {
4646
@Nullable
4747
private FlushMode previousFlushMode;
4848

49+
private boolean previousReadOnly;
4950

5051
public SessionHolder(Session session) {
5152
Assert.notNull(session, "Session must not be null");
@@ -75,12 +76,20 @@ public FlushMode getPreviousFlushMode() {
7576
return this.previousFlushMode;
7677
}
7778

79+
public boolean isPreviousReadOnly() {
80+
return previousReadOnly;
81+
}
82+
83+
public void setPreviousReadOnly(boolean previousReadOnly) {
84+
this.previousReadOnly = previousReadOnly;
85+
}
7886

7987
@Override
8088
public void clear() {
8189
super.clear();
8290
this.transaction = null;
8391
this.previousFlushMode = null;
92+
this.previousReadOnly = false;
8493
}
8594

8695
}

spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@ else if (isolationLevelNeeded) {
165165

166166
// Adapt flush mode and store previous isolation level, if any.
167167
FlushMode previousFlushMode = prepareFlushMode(session, definition.isReadOnly());
168-
return new SessionTransactionData(session, previousFlushMode, preparedCon, previousIsolationLevel);
168+
boolean previousReadOnly = prepareReadOnly(session, definition.isReadOnly());
169+
return new SessionTransactionData(session, previousFlushMode, preparedCon, previousIsolationLevel, previousReadOnly);
169170
}
170171

171172
@Override
@@ -174,7 +175,8 @@ public Object prepareTransaction(EntityManager entityManager, boolean readOnly,
174175

175176
Session session = getSession(entityManager);
176177
FlushMode previousFlushMode = prepareFlushMode(session, readOnly);
177-
return new SessionTransactionData(session, previousFlushMode, null, null);
178+
boolean previousReadOnly = prepareReadOnly(session, readOnly);
179+
return new SessionTransactionData(session, previousFlushMode, null, null, previousReadOnly);
178180
}
179181

180182
@SuppressWarnings("deprecation")
@@ -200,6 +202,12 @@ protected FlushMode prepareFlushMode(Session session, boolean readOnly) throws P
200202
return null;
201203
}
202204

205+
protected boolean prepareReadOnly(Session session, boolean readOnly) {
206+
boolean previousReadOnly = session.isDefaultReadOnly();
207+
session.setDefaultReadOnly(readOnly);
208+
return previousReadOnly;
209+
}
210+
203211
@Override
204212
public void cleanupTransaction(@Nullable Object transactionData) {
205213
if (transactionData instanceof SessionTransactionData) {
@@ -332,20 +340,24 @@ private static class SessionTransactionData {
332340
@Nullable
333341
private final Integer previousIsolationLevel;
334342

343+
private final boolean previousReadOnly;
344+
335345
public SessionTransactionData(Session session, @Nullable FlushMode previousFlushMode,
336-
@Nullable Connection preparedCon, @Nullable Integer previousIsolationLevel) {
346+
@Nullable Connection preparedCon, @Nullable Integer previousIsolationLevel, boolean previousReadOnly) {
337347

338348
this.session = session;
339349
this.previousFlushMode = previousFlushMode;
340350
this.preparedCon = preparedCon;
341351
this.previousIsolationLevel = previousIsolationLevel;
352+
this.previousReadOnly = previousReadOnly;
342353
}
343354

344355
@SuppressWarnings("deprecation")
345356
public void resetSessionState() {
346357
if (this.previousFlushMode != null) {
347358
this.session.setFlushMode(this.previousFlushMode);
348359
}
360+
session.setDefaultReadOnly(previousReadOnly);
349361
if (this.preparedCon != null && this.session.isConnected()) {
350362
Connection conToReset = HibernateConnectionHandle.doGetConnection(this.session);
351363
if (conToReset != this.preparedCon) {

spring-test/src/test/java/org/springframework/test/context/junit4/orm/HibernateSessionFlushingTests.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818

1919
import javax.persistence.PersistenceException;
2020

21+
import org.hibernate.Session;
2122
import org.hibernate.SessionFactory;
2223
import org.hibernate.exception.ConstraintViolationException;
24+
2325
import org.junit.Before;
2426
import org.junit.Test;
2527

@@ -29,9 +31,12 @@
2931
import org.springframework.test.context.junit4.orm.domain.DriversLicense;
3032
import org.springframework.test.context.junit4.orm.domain.Person;
3133
import org.springframework.test.context.junit4.orm.service.PersonService;
34+
import org.springframework.transaction.annotation.Transactional;
35+
import org.springframework.transaction.support.TransactionTemplate;
3236

33-
import static org.junit.Assert.*;
34-
import static org.springframework.test.transaction.TransactionTestUtils.*;
37+
import static org.junit.Assert.assertEquals;
38+
import static org.junit.Assert.assertNotNull;
39+
import static org.springframework.test.transaction.TransactionTestUtils.assertInTransaction;
3540

3641
/**
3742
* Transactional integration tests regarding <i>manual</i> session flushing with
@@ -52,7 +57,6 @@ public class HibernateSessionFlushingTests extends AbstractTransactionalJUnit4Sp
5257
@Autowired
5358
private SessionFactory sessionFactory;
5459

55-
5660
protected int countRowsInPersonTable() {
5761
return countRowsInTable("person");
5862
}
@@ -77,6 +81,18 @@ public void findSam() {
7781
assertEquals("Verifying Sam's driver's license number", Long.valueOf(1234), driversLicense.getNumber());
7882
}
7983

84+
@Test
85+
@Transactional(readOnly = true)
86+
public void readOnlySession() {
87+
Session session = sessionFactory.getCurrentSession();
88+
Person sam = personService.findByName(SAM);
89+
sam.setName("Vlad");
90+
//By setting setDefaultReadOnly(true), the user can no longer modify any entity.
91+
session.flush();
92+
session.refresh(sam);
93+
assertEquals("Sam", sam.getName());
94+
}
95+
8096
@Test
8197
public void saveJuergenWithDriversLicense() {
8298
DriversLicense driversLicense = new DriversLicense(2L, 2222L);
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.transaction.ejb;
18+
19+
import java.util.concurrent.atomic.AtomicReference;
20+
import javax.persistence.EntityManager;
21+
import javax.persistence.PersistenceContext;
22+
23+
import org.junit.After;
24+
import org.junit.Before;
25+
import org.junit.Test;
26+
27+
import org.springframework.beans.factory.annotation.Autowired;
28+
import org.springframework.orm.jpa.JpaTransactionManager;
29+
import org.springframework.test.context.ContextConfiguration;
30+
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
31+
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
32+
import org.springframework.test.context.transaction.ejb.dao.TestEntityService;
33+
import org.springframework.test.context.transaction.ejb.model.TestEntity;
34+
import org.springframework.transaction.TransactionStatus;
35+
import org.springframework.transaction.annotation.Transactional;
36+
import org.springframework.transaction.support.TransactionCallback;
37+
import org.springframework.transaction.support.TransactionTemplate;
38+
39+
import static org.junit.Assert.assertEquals;
40+
41+
/**
42+
*
43+
* @author Vlad MIhalcea
44+
*/
45+
@ContextConfiguration("required-tx-config.xml")
46+
public class ReadOnlyJpaTxDaoTests extends AbstractJUnit4SpringContextTests {
47+
48+
protected static final String TEST_NAME = "test-name";
49+
50+
@PersistenceContext
51+
private EntityManager em;
52+
53+
@Autowired
54+
private TestEntityService testEntityService;
55+
56+
@Autowired
57+
protected JpaTransactionManager transactionManager;
58+
59+
@Before
60+
public void init() {
61+
new TransactionTemplate(transactionManager).execute(
62+
(TransactionCallback<Void>) status -> {
63+
TestEntity testEntity = new TestEntity();
64+
testEntity.setName(TEST_NAME);
65+
66+
em.persist(testEntity);
67+
return null;
68+
} );
69+
}
70+
71+
@Test
72+
public void testReadOnlyTx() {
73+
AtomicReference<TestEntity> entityHolder = new AtomicReference<>();
74+
assertEquals(0, em.find(TestEntity.class, TEST_NAME).getCount());
75+
76+
testEntityService.onReadOnly( em -> {
77+
TestEntity entity = em.find(TestEntity.class, TEST_NAME);
78+
entity.setCount(100);
79+
//By setting setDefaultReadOnly(true), the user can no longer modify any entity.
80+
em.flush();
81+
em.refresh(entity);
82+
entityHolder.set(entity);
83+
});
84+
85+
assertEquals(0, entityHolder.get().getCount());
86+
}
87+
88+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.transaction.ejb.dao;
18+
19+
import java.util.function.Consumer;
20+
import javax.persistence.EntityManager;
21+
22+
/**
23+
* @author Vlad Mihalcea
24+
*/
25+
public interface TestEntityService {
26+
27+
void onReadOnly(Consumer<EntityManager> callback);
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2002-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.transaction.ejb.dao;
18+
19+
import java.util.function.Consumer;
20+
import javax.persistence.EntityManager;
21+
import javax.persistence.PersistenceContext;
22+
23+
import org.springframework.stereotype.Service;
24+
import org.springframework.transaction.annotation.Transactional;
25+
26+
@Service
27+
public class TestEntityServiceImpl implements TestEntityService {
28+
29+
@PersistenceContext
30+
protected EntityManager em;
31+
32+
@Override
33+
@Transactional(readOnly = true)
34+
public void onReadOnly(Consumer<EntityManager> callback) {
35+
callback.accept(em);
36+
}
37+
}

spring-test/src/test/resources/org/springframework/test/context/transaction/ejb/required-tx-config.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,6 @@
66

77
<bean class="org.springframework.test.context.transaction.ejb.dao.RequiredEjbTxTestEntityDao" />
88

9+
<bean class="org.springframework.test.context.transaction.ejb.dao.TestEntityServiceImpl" />
10+
911
</beans>

0 commit comments

Comments
 (0)