Enable transaction management for JdbcOperationsSessionRepository operations
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user