diff --git a/docs/src/docs/asciidoc/guides/httpsession-jdbc-xml.adoc b/docs/src/docs/asciidoc/guides/httpsession-jdbc-xml.adoc
index a795a35..c0a7d89 100644
--- a/docs/src/docs/asciidoc/guides/httpsession-jdbc-xml.adoc
+++ b/docs/src/docs/asciidoc/guides/httpsession-jdbc-xml.adoc
@@ -85,6 +85,7 @@ The filter is what is in charge of replacing the `HttpSession` implementation to
In this instance Spring Session is backed by a relational database.
<2> We create a `dataSource` that connects Spring Session to an embedded instance of H2 database.
We configure the H2 database to create database tables using the SQL script which is included in Spring Session.
+<3> We create a `transactionManager` that manages transactions for previously configured `dataSource`.
== XML Servlet Container Initialization
diff --git a/docs/src/docs/asciidoc/guides/httpsession-jdbc.adoc b/docs/src/docs/asciidoc/guides/httpsession-jdbc.adoc
index 6437b65..178d659 100644
--- a/docs/src/docs/asciidoc/guides/httpsession-jdbc.adoc
+++ b/docs/src/docs/asciidoc/guides/httpsession-jdbc.adoc
@@ -83,6 +83,7 @@ The filter is what is in charge of replacing the `HttpSession` implementation to
In this instance Spring Session is backed by a relational database.
<2> We create a `dataSource` that connects Spring Session to an embedded instance of H2 database.
We configure the H2 database to create database tables using the SQL script which is included in Spring Session.
+<3> We create a `transactionManager` that manages transactions for previously configured `dataSource`.
== Java Servlet Container Initialization
diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc
index 6637617..960e421 100644
--- a/docs/src/docs/asciidoc/index.adoc
+++ b/docs/src/docs/asciidoc/index.adoc
@@ -1012,7 +1012,7 @@ A typical example of how to create a new instance can be seen below:
include::{indexdoc-tests}[tags=new-jdbcoperationssessionrepository]
----
-For additional information on how to create and configure a `JdbcTemplate`, refer to the Spring Framework Reference Documentation.
+For additional information on how to create and configure `JdbcTemplate` and `PlatformTransactionManager`, refer to the Spring Framework Reference Documentation.
[[api-jdbcoperationssessionrepository-config]]
==== EnableJdbcHttpSession
@@ -1056,6 +1056,11 @@ And with MySQL database:
include::{session-main-resources-dir}org/springframework/session/jdbc/schema-mysql.sql[]
----
+==== Transaction management
+
+All JDBC operations in `JdbcOperationsSessionRepository` are executed in a transactional manner.
+Transactions are executed with propagation set to `REQUIRES_NEW` in order to avoid unexpected behavior due to interference with existing transactions (for example, executing `save` operation in a thread that already participates in a read-only transaction).
+
[[community]]
== Spring Session Community
diff --git a/docs/src/test/java/docs/IndexDocTests.java b/docs/src/test/java/docs/IndexDocTests.java
index 09e376a..de21388 100644
--- a/docs/src/test/java/docs/IndexDocTests.java
+++ b/docs/src/test/java/docs/IndexDocTests.java
@@ -20,6 +20,7 @@ import org.junit.Test;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.mock.web.MockServletContext;
import org.springframework.session.ExpiringSession;
import org.springframework.session.MapSessionRepository;
@@ -28,6 +29,7 @@ import org.springframework.session.SessionRepository;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
+import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@@ -124,8 +126,12 @@ public class IndexDocTests {
// ... configure JdbcTemplate ...
+ PlatformTransactionManager transactionManager = new DataSourceTransactionManager();
+
+ // ... configure transactionManager ...
+
SessionRepository extends ExpiringSession> repository =
- new JdbcOperationsSessionRepository(jdbcTemplate);
+ new JdbcOperationsSessionRepository(jdbcTemplate, transactionManager);
// end::new-jdbcoperationssessionrepository[]
}
diff --git a/spring-session/src/integration-test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryITests.java b/spring-session/src/integration-test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryITests.java
index 0758869..d09e427 100644
--- a/spring-session/src/integration-test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryITests.java
+++ b/spring-session/src/integration-test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryITests.java
@@ -46,6 +46,7 @@ import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;
@@ -114,6 +115,15 @@ public class JdbcOperationsSessionRepositoryITests {
assertThat(this.repository.getSession(toSave.getId())).isNull();
}
+ @Test
+ @Transactional(readOnly = true)
+ public void savesInReadOnlyTransaction() {
+ JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
+ .createSession();
+
+ this.repository.save(toSave);
+ }
+
@Test
public void putAllOnSingleAttrDoesNotRemoveOld() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
diff --git a/spring-session/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java b/spring-session/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java
index a515cdb..720343b 100644
--- a/spring-session/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java
+++ b/spring-session/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java
@@ -50,6 +50,13 @@ import org.springframework.session.ExpiringSession;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.Session;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
+import org.springframework.transaction.TransactionStatus;
+import org.springframework.transaction.support.TransactionCallback;
+import org.springframework.transaction.support.TransactionCallbackWithoutResult;
+import org.springframework.transaction.support.TransactionOperations;
+import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@@ -63,7 +70,14 @@ import org.springframework.util.StringUtils;
*
* JdbcTemplate jdbcTemplate = new JdbcTemplate();
*
- * JdbcOperationsSessionRepository sessionRepository = new JdbcOperationsSessionRepository(jdbcTemplate);
+ * // ... configure jdbcTemplate ...
+ *
+ * PlatformTransactionManager transactionManager = new DataSourceTransactionManager();
+ *
+ * // ... configure transactionManager ...
+ *
+ * JdbcOperationsSessionRepository sessionRepository =
+ * new JdbcOperationsSessionRepository(jdbcTemplate, transactionManager);
*
*
* For additional information on how to create and configure a JdbcTemplate, refer to the
@@ -121,6 +135,8 @@ public class JdbcOperationsSessionRepository implements
private final JdbcOperations jdbcOperations;
+ private final TransactionOperations transactionOperations;
+
private final RowMapper mapper = new ExpiringSessionMapper();
/**
@@ -140,22 +156,26 @@ public class JdbcOperationsSessionRepository implements
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
- * default ${JdbcOperations} to manage sessions.
+ * default {@link JdbcOperations} to manage sessions.
* @param dataSource the {@link DataSource} to use
+ * @param transactionManager the {@link PlatformTransactionManager} to use
*/
- public JdbcOperationsSessionRepository(DataSource dataSource) {
- this(createDefaultTemplate(dataSource));
+ public JdbcOperationsSessionRepository(DataSource dataSource,
+ PlatformTransactionManager transactionManager) {
+ this(createDefaultJdbcTemplate(dataSource), transactionManager);
}
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
- * provided ${JdbcOperations} to manage sessions.
+ * provided {@link JdbcOperations} to manage sessions.
* @param jdbcOperations the {@link JdbcOperations} to use
+ * @param transactionManager the {@link PlatformTransactionManager} to use
*/
- public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations) {
+ public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations,
+ PlatformTransactionManager transactionManager) {
Assert.notNull(jdbcOperations, "JdbcOperations must not be null");
this.jdbcOperations = jdbcOperations;
-
+ this.transactionOperations = createTransactionTemplate(transactionManager);
this.conversionService = createDefaultConversionService();
}
@@ -185,7 +205,6 @@ public class JdbcOperationsSessionRepository implements
/**
* Sets the {@link ConversionService} to use.
- *
* @param conversionService the converter to set
*/
public void setConversionService(ConversionService conversionService) {
@@ -203,49 +222,65 @@ public class JdbcOperationsSessionRepository implements
public void save(final JdbcSession session) {
if (session.isNew()) {
- this.jdbcOperations.update(getQuery(CREATE_SESSION_QUERY),
- new PreparedStatementSetter() {
+ this.transactionOperations.execute(new TransactionCallbackWithoutResult() {
- public void setValues(PreparedStatement ps) throws SQLException {
- ps.setString(1, session.getId());
- ps.setLong(2, session.getLastAccessedTime());
- ps.setString(3, session.getPrincipalName());
- JdbcOperationsSessionRepository.this.lobHandler
- .getLobCreator()
- .setBlobAsBytes(ps, 4, serialize(session.delegate));
- }
+ protected void doInTransactionWithoutResult(TransactionStatus status) {
+ JdbcOperationsSessionRepository.this.jdbcOperations.update(
+ getQuery(CREATE_SESSION_QUERY),
+ new PreparedStatementSetter() {
- });
+ public void setValues(PreparedStatement ps) throws SQLException {
+ ps.setString(1, session.getId());
+ ps.setLong(2, session.getLastAccessedTime());
+ ps.setString(3, session.getPrincipalName());
+ serialize(ps, 4, session.delegate);
+ }
+
+ });
+ }
+
+ });
}
else {
if (session.isAttributesChanged()) {
- this.jdbcOperations.update(getQuery(UPDATE_SESSION_QUERY),
- new PreparedStatementSetter() {
+ this.transactionOperations.execute(new TransactionCallbackWithoutResult() {
- public void setValues(PreparedStatement ps)
- throws SQLException {
- ps.setLong(1, session.getLastAccessedTime());
- ps.setString(2, session.getPrincipalName());
- JdbcOperationsSessionRepository.this.lobHandler
- .getLobCreator().setBlobAsBytes(ps, 3,
- serialize(session.delegate));
- ps.setString(4, session.getId());
- }
+ protected void doInTransactionWithoutResult(TransactionStatus status) {
+ JdbcOperationsSessionRepository.this.jdbcOperations.update(
+ getQuery(UPDATE_SESSION_QUERY),
+ new PreparedStatementSetter() {
- });
+ public void setValues(PreparedStatement ps)
+ throws SQLException {
+ ps.setLong(1, session.getLastAccessedTime());
+ ps.setString(2, session.getPrincipalName());
+ serialize(ps, 3, session.delegate);
+ ps.setString(4, session.getId());
+ }
+
+ });
+ }
+
+ });
}
else if (session.isLastAccessTimeChanged()) {
- this.jdbcOperations.update(
- getQuery(UPDATE_SESSION_LAST_ACCESS_TIME_QUERY),
- new PreparedStatementSetter() {
+ this.transactionOperations.execute(new TransactionCallbackWithoutResult() {
- public void setValues(PreparedStatement ps)
- throws SQLException {
- ps.setLong(1, session.getLastAccessedTime());
- ps.setString(2, session.getId());
- }
+ protected void doInTransactionWithoutResult(TransactionStatus status) {
+ JdbcOperationsSessionRepository.this.jdbcOperations.update(
+ getQuery(UPDATE_SESSION_LAST_ACCESS_TIME_QUERY),
+ new PreparedStatementSetter() {
- });
+ public void setValues(PreparedStatement ps)
+ throws SQLException {
+ ps.setLong(1, session.getLastAccessedTime());
+ ps.setString(2, session.getId());
+ }
+
+ });
+ }
+
+ });
}
else {
return;
@@ -254,14 +289,22 @@ public class JdbcOperationsSessionRepository implements
session.clearChangeFlags();
}
- public JdbcSession getSession(String id) {
- ExpiringSession session = null;
- try {
- session = this.jdbcOperations.queryForObject(getQuery(GET_SESSION_QUERY),
- new Object[] { id }, this.mapper);
- }
- catch (EmptyResultDataAccessException ignored) {
- }
+ public JdbcSession getSession(final String id) {
+ ExpiringSession session = this.transactionOperations.execute(new TransactionCallback() {
+
+ public ExpiringSession doInTransaction(TransactionStatus status) {
+ try {
+ return JdbcOperationsSessionRepository.this.jdbcOperations.queryForObject(
+ getQuery(GET_SESSION_QUERY),
+ new Object[] { id },
+ JdbcOperationsSessionRepository.this.mapper);
+ }
+ catch (EmptyResultDataAccessException ignored) {
+ return null;
+ }
+ }
+
+ });
if (session != null) {
if (session.isExpired()) {
@@ -274,19 +317,33 @@ public class JdbcOperationsSessionRepository implements
return null;
}
- public void delete(String id) {
- this.jdbcOperations.update(getQuery(DELETE_SESSION_QUERY), id);
+ public void delete(final String id) {
+ this.transactionOperations.execute(new TransactionCallbackWithoutResult() {
+
+ protected void doInTransactionWithoutResult(TransactionStatus status) {
+ JdbcOperationsSessionRepository.this.jdbcOperations.update(
+ getQuery(DELETE_SESSION_QUERY), id);
+ }
+
+ });
}
public Map findByIndexNameAndIndexValue(String indexName,
- String indexValue) {
+ final String indexValue) {
if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
return Collections.emptyMap();
}
- List sessions = this.jdbcOperations.query(
- getQuery(LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY),
- new Object[] { indexValue }, this.mapper);
+ List sessions = this.transactionOperations.execute(new TransactionCallback>() {
+
+ public List doInTransaction(TransactionStatus status) {
+ return JdbcOperationsSessionRepository.this.jdbcOperations.query(
+ getQuery(LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY),
+ new Object[] { indexValue },
+ JdbcOperationsSessionRepository.this.mapper);
+ }
+
+ });
Map sessionMap = new HashMap(
sessions.size());
@@ -305,36 +362,42 @@ public class JdbcOperationsSessionRepository implements
? this.defaultMaxInactiveInterval
: MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
- long sessionsValidFromTime = now - (maxInactiveIntervalSeconds * 1000);
+ final long sessionsValidFromTime = now - (maxInactiveIntervalSeconds * 1000);
if (logger.isDebugEnabled()) {
logger.debug(
"Cleaning up sessions older than " + new Date(sessionsValidFromTime));
}
- int deletedCount = this.jdbcOperations.update(
- getQuery(DELETE_SESSIONS_BY_LAST_ACCESS_TIME_QUERY),
- sessionsValidFromTime);
+ int deletedCount = this.transactionOperations.execute(new TransactionCallback() {
+
+ public Integer doInTransaction(TransactionStatus transactionStatus) {
+ return JdbcOperationsSessionRepository.this.jdbcOperations.update(
+ getQuery(DELETE_SESSIONS_BY_LAST_ACCESS_TIME_QUERY),
+ sessionsValidFromTime);
+ }
+
+ });
if (logger.isDebugEnabled()) {
logger.debug("Cleaned up " + deletedCount + " expired sessions");
}
}
- private static JdbcTemplate createDefaultTemplate(DataSource dataSource) {
+ private static JdbcTemplate createDefaultJdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.afterPropertiesSet();
return jdbcTemplate;
}
- protected String getQuery(String base) {
- return StringUtils.replace(base, "%TABLE_NAME%", this.tableName);
- }
-
- private byte[] serialize(ExpiringSession session) {
- return (byte[]) this.conversionService.convert(session,
- TypeDescriptor.valueOf(ExpiringSession.class),
- TypeDescriptor.valueOf(byte[].class));
+ private static TransactionTemplate createTransactionTemplate(
+ PlatformTransactionManager transactionManager) {
+ TransactionTemplate transactionTemplate = new TransactionTemplate(
+ transactionManager);
+ transactionTemplate.setPropagationBehavior(
+ TransactionDefinition.PROPAGATION_REQUIRES_NEW);
+ transactionTemplate.afterPropertiesSet();
+ return transactionTemplate;
}
private static GenericConversionService createDefaultConversionService() {
@@ -346,6 +409,26 @@ public class JdbcOperationsSessionRepository implements
return converter;
}
+ protected String getQuery(String base) {
+ return StringUtils.replace(base, "%TABLE_NAME%", this.tableName);
+ }
+
+ private void serialize(PreparedStatement ps, int paramIndex, ExpiringSession session)
+ throws SQLException {
+ this.lobHandler.getLobCreator().setBlobAsBytes(ps, paramIndex,
+ (byte[]) this.conversionService.convert(session,
+ TypeDescriptor.valueOf(ExpiringSession.class),
+ TypeDescriptor.valueOf(byte[].class)));
+ }
+
+ private ExpiringSession deserialize(ResultSet rs, String columnName)
+ throws SQLException {
+ return (ExpiringSession) this.conversionService.convert(
+ this.lobHandler.getBlobAsBytes(rs, columnName),
+ TypeDescriptor.valueOf(byte[].class),
+ TypeDescriptor.valueOf(ExpiringSession.class));
+ }
+
/**
* The {@link ExpiringSession} to use for {@link JdbcOperationsSessionRepository}.
*
@@ -473,12 +556,7 @@ public class JdbcOperationsSessionRepository implements
private class ExpiringSessionMapper implements RowMapper {
public ExpiringSession mapRow(ResultSet rs, int rowNum) throws SQLException {
- ExpiringSession session = (ExpiringSession) JdbcOperationsSessionRepository
- .this.conversionService.convert(
- JdbcOperationsSessionRepository.this.lobHandler
- .getBlobAsBytes(rs, "SESSION_BYTES"),
- TypeDescriptor.valueOf(byte[].class),
- TypeDescriptor.valueOf(ExpiringSession.class));
+ ExpiringSession session = deserialize(rs, "SESSION_BYTES");
session.setLastAccessedTime(rs.getLong("LAST_ACCESS_TIME"));
return session;
}
diff --git a/spring-session/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfiguration.java b/spring-session/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfiguration.java
index dd49a69..278ef9f 100644
--- a/spring-session/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfiguration.java
+++ b/spring-session/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfiguration.java
@@ -33,6 +33,7 @@ import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
+import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.StringUtils;
/**
@@ -71,9 +72,10 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
@Bean
public JdbcOperationsSessionRepository sessionRepository(
- @Qualifier("springSessionJdbcOperations") JdbcOperations jdbcOperations) {
- JdbcOperationsSessionRepository sessionRepository = new JdbcOperationsSessionRepository(
- jdbcOperations);
+ @Qualifier("springSessionJdbcOperations") JdbcOperations jdbcOperations,
+ PlatformTransactionManager transactionManager) {
+ JdbcOperationsSessionRepository sessionRepository =
+ new JdbcOperationsSessionRepository(jdbcOperations, transactionManager);
String tableName = getTableName();
if (StringUtils.hasText(tableName)) {
sessionRepository.setTableName(tableName);
diff --git a/spring-session/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java b/spring-session/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java
index b2e5da1..077fdb8 100644
--- a/spring-session/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java
+++ b/spring-session/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java
@@ -26,6 +26,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
@@ -38,6 +39,8 @@ import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.TransactionDefinition;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.AdditionalMatchers.and;
@@ -48,6 +51,7 @@ import static org.mockito.Matchers.contains;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Matchers.startsWith;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -72,17 +76,21 @@ public class JdbcOperationsSessionRepositoryTests {
@Mock
private JdbcOperations jdbcOperations;
+ @Mock
+ private PlatformTransactionManager transactionManager;
+
private JdbcOperationsSessionRepository repository;
@Before
public void setUp() {
- this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
+ this.repository = new JdbcOperationsSessionRepository(
+ this.jdbcOperations, this.transactionManager);
}
@Test
public void constructorDataSource() {
JdbcOperationsSessionRepository repository = new JdbcOperationsSessionRepository(
- this.dataSource);
+ this.dataSource, this.transactionManager);
assertThat(ReflectionTestUtils.getField(repository, "jdbcOperations"))
.isNotNull();
@@ -93,7 +101,7 @@ public class JdbcOperationsSessionRepositoryTests {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Property 'dataSource' is required");
- new JdbcOperationsSessionRepository((DataSource) null);
+ new JdbcOperationsSessionRepository((DataSource) null, this.transactionManager);
}
@Test
@@ -101,7 +109,15 @@ public class JdbcOperationsSessionRepositoryTests {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("JdbcOperations must not be null");
- new JdbcOperationsSessionRepository((JdbcOperations) null);
+ new JdbcOperationsSessionRepository((JdbcOperations) null, this.transactionManager);
+ }
+
+ @Test
+ public void constructorNullTransactionManager() {
+ this.thrown.expect(IllegalArgumentException.class);
+ this.thrown.expectMessage("Property 'transactionManager' is required");
+
+ new JdbcOperationsSessionRepository(this.jdbcOperations, null);
}
@Test
@@ -168,6 +184,7 @@ public class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
+ assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT"),
isA(PreparedStatementSetter.class));
}
@@ -181,6 +198,7 @@ public class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
+ assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(
and(startsWith("UPDATE"), contains("SESSION_BYTES")),
isA(PreparedStatementSetter.class));
@@ -195,6 +213,7 @@ public class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
+ assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(
and(startsWith("UPDATE"), not(contains("SESSION_BYTES"))),
isA(PreparedStatementSetter.class));
@@ -219,6 +238,7 @@ public class JdbcOperationsSessionRepositoryTests {
.getSession(sessionId);
assertThat(session).isNull();
+ assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).queryForObject(startsWith("SELECT"),
eq(new Object[] { sessionId }), isA(RowMapper.class));
}
@@ -236,6 +256,7 @@ public class JdbcOperationsSessionRepositoryTests {
.getSession(expired.getId());
assertThat(session).isNull();
+ assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).queryForObject(startsWith("SELECT"),
eq(new Object[] { expired.getId() }), isA(RowMapper.class));
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"),
@@ -256,6 +277,7 @@ public class JdbcOperationsSessionRepositoryTests {
assertThat(session.getId()).isEqualTo(saved.getId());
assertThat(session.isNew()).isFalse();
assertThat(session.getAttribute("savedName")).isEqualTo("savedValue");
+ assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).queryForObject(startsWith("SELECT"),
eq(new Object[] { saved.getId() }), isA(RowMapper.class));
}
@@ -266,6 +288,7 @@ public class JdbcOperationsSessionRepositoryTests {
this.repository.delete(sessionId);
+ assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), eq(sessionId));
}
@@ -290,6 +313,7 @@ public class JdbcOperationsSessionRepositoryTests {
principal);
assertThat(sessions).isEmpty();
+ assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(startsWith("SELECT"),
eq(new Object[] { principal }), isA(RowMapper.class));
}
@@ -315,6 +339,7 @@ public class JdbcOperationsSessionRepositoryTests {
principal);
assertThat(sessions).hasSize(2);
+ assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(startsWith("SELECT"),
eq(new Object[] { principal }), isA(RowMapper.class));
}
@@ -323,7 +348,16 @@ public class JdbcOperationsSessionRepositoryTests {
public void cleanupExpiredSessions() {
this.repository.cleanUpExpiredSessions();
+ assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), anyLong());
}
+ private void assertPropagationRequiresNew() {
+ ArgumentCaptor argument =
+ ArgumentCaptor.forClass(TransactionDefinition.class);
+ verify(this.transactionManager, atLeastOnce()).getTransaction(argument.capture());
+ assertThat(argument.getValue().getPropagationBehavior())
+ .isEqualTo(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
+ }
+
}
diff --git a/spring-session/src/test/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfigurationTests.java b/spring-session/src/test/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfigurationTests.java
index 628653b..b6eb449 100644
--- a/spring-session/src/test/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfigurationTests.java
+++ b/spring-session/src/test/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfigurationTests.java
@@ -31,6 +31,7 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import org.springframework.test.util.ReflectionTestUtils;
+import org.springframework.transaction.PlatformTransactionManager;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -162,6 +163,11 @@ public class JdbcHttpSessionConfigurationTests {
return mock(DataSource.class);
}
+ @Bean
+ public PlatformTransactionManager transactionManager() {
+ return mock(PlatformTransactionManager.class);
+ }
+
}
@Configuration