Enable transaction management for JdbcOperationsSessionRepository operations

This commit is contained in:
Vedran Pavić
2016-04-06 06:39:36 +02:00
committed by Rob Winch
parent 1e1d24895c
commit 3f819a94b1
9 changed files with 225 additions and 82 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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[]
}

View File

@@ -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

View File

@@ -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;
* <pre class="code">
* JdbcTemplate jdbcTemplate = new JdbcTemplate();
*
* JdbcOperationsSessionRepository sessionRepository = new JdbcOperationsSessionRepository(jdbcTemplate);
* // ... configure jdbcTemplate ...
*
* PlatformTransactionManager transactionManager = new DataSourceTransactionManager();
*
* // ... configure transactionManager ...
*
* JdbcOperationsSessionRepository sessionRepository =
* new JdbcOperationsSessionRepository(jdbcTemplate, transactionManager);
* </pre>
*
* 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<ExpiringSession> 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<ExpiringSession>() {
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<String, JdbcSession> findByIndexNameAndIndexValue(String indexName,
String indexValue) {
final String indexValue) {
if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
return Collections.emptyMap();
}
List<ExpiringSession> sessions = this.jdbcOperations.query(
getQuery(LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY),
new Object[] { indexValue }, this.mapper);
List<ExpiringSession> sessions = this.transactionOperations.execute(new TransactionCallback<List<ExpiringSession>>() {
public List<ExpiringSession> doInTransaction(TransactionStatus status) {
return JdbcOperationsSessionRepository.this.jdbcOperations.query(
getQuery(LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY),
new Object[] { indexValue },
JdbcOperationsSessionRepository.this.mapper);
}
});
Map<String, JdbcSession> sessionMap = new HashMap<String, JdbcSession>(
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<Integer>() {
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<ExpiringSession> {
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;
}

View File

@@ -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);

View File

@@ -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<TransactionDefinition> argument =
ArgumentCaptor.forClass(TransactionDefinition.class);
verify(this.transactionManager, atLeastOnce()).getTransaction(argument.capture());
assertThat(argument.getValue().getPropagationBehavior())
.isEqualTo(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
}
}

View File

@@ -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