Improve support for customizing JDBC session store transaction behavior

Resolves: #1469
This commit is contained in:
Vedran Pavic
2019-08-23 23:26:11 +02:00
parent bcdd05a0bc
commit f6c82f1eee
5 changed files with 189 additions and 252 deletions

View File

@@ -30,7 +30,6 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.mock.web.MockServletContext;
import org.springframework.session.MapSession;
import org.springframework.session.MapSessionRepository;
@@ -42,7 +41,7 @@ import org.springframework.session.data.redis.RedisOperationsSessionRepository;
import org.springframework.session.hazelcast.HazelcastSessionRepository;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
@@ -162,14 +161,14 @@ class IndexDocTests {
// tag::new-jdbcoperationssessionrepository[]
JdbcTemplate jdbcTemplate = new JdbcTemplate();
// ... configure JdbcTemplate ...
// ... configure jdbcTemplate ...
PlatformTransactionManager transactionManager = new DataSourceTransactionManager();
TransactionTemplate transactionTemplate = new TransactionTemplate();
// ... configure transactionManager ...
// ... configure transactionTemplate ...
SessionRepository<? extends Session> repository = new JdbcOperationsSessionRepository(jdbcTemplate,
transactionManager);
transactionTemplate);
// end::new-jdbcoperationssessionrepository[]
}

View File

@@ -42,7 +42,6 @@ import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
@@ -56,8 +55,6 @@ import org.springframework.session.SaveMode;
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.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
@@ -75,16 +72,16 @@ import org.springframework.util.StringUtils;
*
* // ... configure jdbcTemplate ...
*
* PlatformTransactionManager transactionManager = new DataSourceTransactionManager();
* TransactionTemplate transactionTemplate = new TransactionTemplate();
*
* // ... configure transactionManager ...
* // ... configure transactionTemplate ...
*
* JdbcOperationsSessionRepository sessionRepository =
* new JdbcOperationsSessionRepository(jdbcTemplate, transactionManager);
* new JdbcOperationsSessionRepository(jdbcTemplate, transactionTemplate);
* </pre>
*
* For additional information on how to create and configure {@link JdbcTemplate} and
* {@link PlatformTransactionManager}, refer to the <a href=
* For additional information on how to create and configure {@code JdbcTemplate} and
* {@code TransactionTemplate}, refer to the <a href=
* "https://docs.spring.io/spring/docs/current/spring-framework-reference/html/spring-data-tier.html">
* Spring Framework Reference Documentation</a>.
* <p>
@@ -200,12 +197,12 @@ public class JdbcOperationsSessionRepository
private final JdbcOperations jdbcOperations;
private final TransactionOperations transactionOperations;
private final ResultSetExtractor<List<JdbcSession>> extractor = new SessionResultSetExtractor();
private final IndexResolver<JdbcSession> indexResolver;
private TransactionOperations transactionOperations = TransactionOperations.withoutTransaction();
/**
* The name of database table used by Spring Session to store sessions.
*/
@@ -237,12 +234,30 @@ public class JdbcOperationsSessionRepository
private ConversionService conversionService;
private LobHandler lobHandler = new DefaultLobHandler();
private LobHandler lobHandler;
private FlushMode flushMode = FlushMode.ON_SAVE;
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
* provided {@link JdbcOperations} and {@link TransactionOperations} to manage
* sessions.
* @param jdbcOperations the {@link JdbcOperations} to use
* @param transactionOperations the {@link TransactionOperations} to use
*/
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations, TransactionOperations transactionOperations) {
Assert.notNull(jdbcOperations, "jdbcOperations must not be null");
Assert.notNull(transactionOperations, "transactionOperations must not be null");
this.jdbcOperations = jdbcOperations;
this.transactionOperations = transactionOperations;
this.indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>());
this.conversionService = createDefaultConversionService();
this.lobHandler = new DefaultLobHandler();
prepareQueries();
}
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
* provided {@link JdbcOperations} to manage sessions.
@@ -251,12 +266,13 @@ public class JdbcOperationsSessionRepository
* propagation level of {@link TransactionDefinition#PROPAGATION_REQUIRES_NEW}.
* @param jdbcOperations the {@link JdbcOperations} to use
* @param transactionManager the {@link PlatformTransactionManager} to use
* @deprecated since 2.2.0 in favor of
* {@link #JdbcOperationsSessionRepository(JdbcOperations, TransactionOperations)}
*/
@Deprecated
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations,
PlatformTransactionManager transactionManager) {
this(jdbcOperations);
Assert.notNull(transactionManager, "TransactionManager must not be null");
this.transactionOperations = createTransactionTemplate(transactionManager);
this(jdbcOperations, createTransactionTemplate(transactionManager));
}
/**
@@ -265,13 +281,12 @@ public class JdbcOperationsSessionRepository
* <p>
* The created instance will not execute data access operations in a transaction.
* @param jdbcOperations the {@link JdbcOperations} to use
* @deprecated since 2.2.0 in favor of
* {@link #JdbcOperationsSessionRepository(JdbcOperations, TransactionOperations)}
*/
@Deprecated
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations) {
Assert.notNull(jdbcOperations, "JdbcOperations must not be null");
this.jdbcOperations = jdbcOperations;
this.indexResolver = new DelegatingIndexResolver<>(new PrincipalNameIndexResolver<>());
this.conversionService = createDefaultConversionService();
prepareQueries();
this(jdbcOperations, TransactionOperations.withoutTransaction());
}
/**
@@ -448,15 +463,8 @@ public class JdbcOperationsSessionRepository
@Override
public void deleteById(final String id) {
this.transactionOperations.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
JdbcOperationsSessionRepository.this.jdbcOperations
.update(JdbcOperationsSessionRepository.this.deleteSessionQuery, id);
}
});
this.transactionOperations.execute(() -> JdbcOperationsSessionRepository.this.jdbcOperations
.update(JdbcOperationsSessionRepository.this.deleteSessionQuery, id));
}
@Override
@@ -581,6 +589,7 @@ public class JdbcOperationsSessionRepository
}
private static TransactionTemplate createTransactionTemplate(PlatformTransactionManager transactionManager) {
Assert.notNull(transactionManager, "transactionManager must not be null");
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
transactionTemplate.afterPropertiesSet();
@@ -805,71 +814,59 @@ public class JdbcOperationsSessionRepository
private void save() {
if (this.isNew) {
JdbcOperationsSessionRepository.this.transactionOperations
.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
Map<String, String> indexes = JdbcOperationsSessionRepository.this.indexResolver
.resolveIndexesFor(JdbcSession.this);
JdbcOperationsSessionRepository.this.jdbcOperations
.update(JdbcOperationsSessionRepository.this.createSessionQuery, (ps) -> {
ps.setString(1, JdbcSession.this.primaryKey);
ps.setString(2, getId());
ps.setLong(3, getCreationTime().toEpochMilli());
ps.setLong(4, getLastAccessedTime().toEpochMilli());
ps.setInt(5, (int) getMaxInactiveInterval().getSeconds());
ps.setLong(6, getExpiryTime().toEpochMilli());
ps.setString(7, indexes.get(PRINCIPAL_NAME_INDEX_NAME));
});
Set<String> attributeNames = getAttributeNames();
if (!attributeNames.isEmpty()) {
insertSessionAttributes(JdbcSession.this, new ArrayList<>(attributeNames));
}
}
});
JdbcOperationsSessionRepository.this.transactionOperations.execute(() -> {
Map<String, String> indexes = JdbcOperationsSessionRepository.this.indexResolver
.resolveIndexesFor(JdbcSession.this);
JdbcOperationsSessionRepository.this.jdbcOperations
.update(JdbcOperationsSessionRepository.this.createSessionQuery, (ps) -> {
ps.setString(1, JdbcSession.this.primaryKey);
ps.setString(2, getId());
ps.setLong(3, getCreationTime().toEpochMilli());
ps.setLong(4, getLastAccessedTime().toEpochMilli());
ps.setInt(5, (int) getMaxInactiveInterval().getSeconds());
ps.setLong(6, getExpiryTime().toEpochMilli());
ps.setString(7, indexes.get(PRINCIPAL_NAME_INDEX_NAME));
});
Set<String> attributeNames = getAttributeNames();
if (!attributeNames.isEmpty()) {
insertSessionAttributes(JdbcSession.this, new ArrayList<>(attributeNames));
}
});
}
else {
JdbcOperationsSessionRepository.this.transactionOperations
.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
if (JdbcSession.this.changed) {
Map<String, String> indexes = JdbcOperationsSessionRepository.this.indexResolver
.resolveIndexesFor(JdbcSession.this);
JdbcOperationsSessionRepository.this.jdbcOperations
.update(JdbcOperationsSessionRepository.this.updateSessionQuery, (ps) -> {
ps.setString(1, getId());
ps.setLong(2, getLastAccessedTime().toEpochMilli());
ps.setInt(3, (int) getMaxInactiveInterval().getSeconds());
ps.setLong(4, getExpiryTime().toEpochMilli());
ps.setString(5, indexes.get(PRINCIPAL_NAME_INDEX_NAME));
ps.setString(6, JdbcSession.this.primaryKey);
});
}
List<String> addedAttributeNames = JdbcSession.this.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.ADDED).map(Map.Entry::getKey)
.collect(Collectors.toList());
if (!addedAttributeNames.isEmpty()) {
insertSessionAttributes(JdbcSession.this, addedAttributeNames);
}
List<String> updatedAttributeNames = JdbcSession.this.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.UPDATED)
.map(Map.Entry::getKey).collect(Collectors.toList());
if (!updatedAttributeNames.isEmpty()) {
updateSessionAttributes(JdbcSession.this, updatedAttributeNames);
}
List<String> removedAttributeNames = JdbcSession.this.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.REMOVED)
.map(Map.Entry::getKey).collect(Collectors.toList());
if (!removedAttributeNames.isEmpty()) {
deleteSessionAttributes(JdbcSession.this, removedAttributeNames);
}
}
});
JdbcOperationsSessionRepository.this.transactionOperations.execute(() -> {
if (JdbcSession.this.changed) {
Map<String, String> indexes = JdbcOperationsSessionRepository.this.indexResolver
.resolveIndexesFor(JdbcSession.this);
JdbcOperationsSessionRepository.this.jdbcOperations
.update(JdbcOperationsSessionRepository.this.updateSessionQuery, (ps) -> {
ps.setString(1, getId());
ps.setLong(2, getLastAccessedTime().toEpochMilli());
ps.setInt(3, (int) getMaxInactiveInterval().getSeconds());
ps.setLong(4, getExpiryTime().toEpochMilli());
ps.setString(5, indexes.get(PRINCIPAL_NAME_INDEX_NAME));
ps.setString(6, JdbcSession.this.primaryKey);
});
}
List<String> addedAttributeNames = JdbcSession.this.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.ADDED).map(Map.Entry::getKey)
.collect(Collectors.toList());
if (!addedAttributeNames.isEmpty()) {
insertSessionAttributes(JdbcSession.this, addedAttributeNames);
}
List<String> updatedAttributeNames = JdbcSession.this.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.UPDATED).map(Map.Entry::getKey)
.collect(Collectors.toList());
if (!updatedAttributeNames.isEmpty()) {
updateSessionAttributes(JdbcSession.this, updatedAttributeNames);
}
List<String> removedAttributeNames = JdbcSession.this.delta.entrySet().stream()
.filter((entry) -> entry.getValue() == DeltaValue.REMOVED).map(Map.Entry::getKey)
.collect(Collectors.toList());
if (!removedAttributeNames.isEmpty()) {
deleteSessionAttributes(JdbcSession.this, removedAttributeNames);
}
});
}
clearChangeFlags();
}

View File

@@ -50,6 +50,9 @@ import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
@@ -87,6 +90,8 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
private PlatformTransactionManager transactionManager;
private TransactionOperations transactionOperations;
private LobHandler lobHandler;
private ConversionService springSessionConversionService;
@@ -100,8 +105,11 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
@Bean
public JdbcOperationsSessionRepository sessionRepository() {
JdbcTemplate jdbcTemplate = createJdbcTemplate(this.dataSource);
if (this.transactionOperations == null) {
this.transactionOperations = createTransactionTemplate(this.transactionManager);
}
JdbcOperationsSessionRepository sessionRepository = new JdbcOperationsSessionRepository(jdbcTemplate,
this.transactionManager);
this.transactionOperations);
if (StringUtils.hasText(this.tableName)) {
sessionRepository.setTableName(this.tableName);
}
@@ -123,7 +131,7 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
sessionRepository.setConversionService(this.conversionService);
}
else {
sessionRepository.setConversionService(createConversionServiceWithBeanClassLoader());
sessionRepository.setConversionService(createConversionServiceWithBeanClassLoader(this.classLoader));
}
return sessionRepository;
}
@@ -173,6 +181,12 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
this.transactionManager = transactionManager;
}
@Autowired(required = false)
@Qualifier("springSessionTransactionOperations")
public void setTransactionOperations(TransactionOperations transactionOperations) {
this.transactionOperations = transactionOperations;
}
@Autowired(required = false)
@Qualifier("springSessionLobHandler")
public void setLobHandler(LobHandler lobHandler) {
@@ -230,10 +244,17 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
return jdbcTemplate;
}
private GenericConversionService createConversionServiceWithBeanClassLoader() {
private static TransactionTemplate createTransactionTemplate(PlatformTransactionManager transactionManager) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
transactionTemplate.afterPropertiesSet();
return transactionTemplate;
}
private static GenericConversionService createConversionServiceWithBeanClassLoader(ClassLoader classLoader) {
GenericConversionService conversionService = new GenericConversionService();
conversionService.addConverter(Object.class, byte[].class, new SerializingConverter());
conversionService.addConverter(byte[].class, Object.class, new DeserializingConverter(this.classLoader));
conversionService.addConverter(byte[].class, Object.class, new DeserializingConverter(classLoader));
return conversionService;
}

View File

@@ -28,6 +28,8 @@ import java.util.function.Supplier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
@@ -43,24 +45,18 @@ import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository.JdbcSession;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionOperations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.endsWith;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* Tests for {@link JdbcOperationsSessionRepository}.
@@ -73,29 +69,39 @@ class JdbcOperationsSessionRepositoryTests {
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
private JdbcOperations jdbcOperations = mock(JdbcOperations.class);
private PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class);
@Mock
private JdbcOperations jdbcOperations;
private JdbcOperationsSessionRepository repository;
@BeforeEach
void setUp() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations, this.transactionManager);
MockitoAnnotations.initMocks(this);
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations,
TransactionOperations.withoutTransaction());
}
@Test
void constructorNullJdbcOperations() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new JdbcOperationsSessionRepository(null, this.transactionManager))
.withMessage("JdbcOperations must not be null");
.isThrownBy(() -> new JdbcOperationsSessionRepository(null, TransactionOperations.withoutTransaction()))
.withMessage("jdbcOperations must not be null");
}
@Test
void constructorNullTransactionManager() {
void constructorNullTransactionOperations() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new JdbcOperationsSessionRepository(this.jdbcOperations, null))
.withMessage("TransactionManager must not be null");
.isThrownBy(
() -> new JdbcOperationsSessionRepository(this.jdbcOperations, (TransactionOperations) null))
.withMessage("transactionOperations must not be null");
}
@Test
@SuppressWarnings("deprecation")
void constructorNullTransactionManager() {
assertThatIllegalArgumentException().isThrownBy(
() -> new JdbcOperationsSessionRepository(this.jdbcOperations, (PlatformTransactionManager) null))
.withMessage("transactionManager must not be null");
}
@Test
@@ -248,7 +254,7 @@ class JdbcOperationsSessionRepositoryTests {
assertThat(session.isNew()).isTrue();
assertThat(session.getMaxInactiveInterval()).isEqualTo(new MapSession().getMaxInactiveInterval());
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -260,7 +266,7 @@ class JdbcOperationsSessionRepositoryTests {
assertThat(session.isNew()).isTrue();
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(interval));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -268,7 +274,6 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.setFlushMode(FlushMode.IMMEDIATE);
JdbcSession session = this.repository.createSession();
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("INSERT"), isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@@ -280,9 +285,8 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT"), isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -293,12 +297,11 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION("),
isA(PreparedStatementSetter.class));
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -310,12 +313,11 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION("),
isA(PreparedStatementSetter.class));
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
isA(BatchPreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -327,10 +329,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -343,10 +344,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
isA(BatchPreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -360,10 +360,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("UPDATE SPRING_SESSION_ATTRIBUTES SET"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -379,10 +378,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("UPDATE SPRING_SESSION_ATTRIBUTES SET"),
isA(BatchPreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -396,10 +394,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE FROM SPRING_SESSION_ATTRIBUTES WHERE"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -411,8 +408,7 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -428,10 +424,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).batchUpdate(startsWith("DELETE FROM SPRING_SESSION_ATTRIBUTES WHERE"),
isA(BatchPreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test // gh-1070
@@ -444,10 +439,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test // gh-1070
@@ -460,8 +454,7 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test // gh-1070
@@ -476,10 +469,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("DELETE FROM SPRING_SESSION_ATTRIBUTES WHERE"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test // gh-1070
@@ -494,10 +486,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("UPDATE SPRING_SESSION_ATTRIBUTES SET"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -509,10 +500,9 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("UPDATE SPRING_SESSION SET"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -523,7 +513,7 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
assertThat(session.isNew()).isFalse();
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -536,7 +526,6 @@ class JdbcOperationsSessionRepositoryTests {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(sessionId);
assertThat(session).isNull();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class));
}
@@ -552,7 +541,6 @@ class JdbcOperationsSessionRepositoryTests {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(expired.getId());
assertThat(session).isNull();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class));
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), eq(expired.getId()));
@@ -571,7 +559,6 @@ class JdbcOperationsSessionRepositoryTests {
assertThat(session.getId()).isEqualTo(saved.getId());
assertThat(session.isNew()).isFalse();
assertThat(session.<String>getAttribute("savedName")).isEqualTo("savedValue");
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class));
}
@@ -582,7 +569,6 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.deleteById(sessionId);
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), eq(sessionId));
}
@@ -594,7 +580,7 @@ class JdbcOperationsSessionRepositoryTests {
.findByIndexNameAndIndexValue("testIndexName", indexValue);
assertThat(sessions).isEmpty();
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -608,7 +594,6 @@ class JdbcOperationsSessionRepositoryTests {
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
assertThat(sessions).isEmpty();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class));
}
@@ -633,7 +618,6 @@ class JdbcOperationsSessionRepositoryTests {
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
assertThat(sessions).hasSize(2);
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class));
}
@@ -642,7 +626,6 @@ class JdbcOperationsSessionRepositoryTests {
void cleanupExpiredSessions() {
this.repository.cleanUpExpiredSessions();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), anyLong());
}
@@ -659,84 +642,6 @@ class JdbcOperationsSessionRepositoryTests {
assertThat(session.getAttributeNames()).isEmpty();
}
@Test
void saveNewWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
this.repository.save(session);
verify(this.jdbcOperations, times(1)).update(startsWith("INSERT INTO SPRING_SESSION"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
void saveUpdatedWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
"primaryKey", false);
session.setLastAccessedTime(Instant.now());
this.repository.save(session);
verify(this.jdbcOperations, times(1)).update(startsWith("UPDATE SPRING_SESSION"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
@SuppressWarnings("unchecked")
void findByIdWithoutTransaction() {
given(this.jdbcOperations.query(anyString(), any(PreparedStatementSetter.class), any(ResultSetExtractor.class)))
.willReturn(Collections.emptyList());
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.findById("testSessionId");
verify(this.jdbcOperations, times(1)).query(endsWith("WHERE S.SESSION_ID = ?"),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
void deleteByIdWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.deleteById("testSessionId");
verify(this.jdbcOperations, times(1)).update(eq("DELETE FROM SPRING_SESSION WHERE SESSION_ID = ?"),
anyString());
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
@SuppressWarnings("unchecked")
void findByIndexNameAndIndexValueWithoutTransaction() {
given(this.jdbcOperations.query(anyString(), any(PreparedStatementSetter.class), any(ResultSetExtractor.class)))
.willReturn(Collections.emptyList());
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME,
"testIndexValue");
verify(this.jdbcOperations, times(1)).query(endsWith("WHERE S.PRINCIPAL_NAME = ?"),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
void cleanUpExpiredSessionsWithoutTransaction() {
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
this.repository.cleanUpExpiredSessions();
verify(this.jdbcOperations, times(1)).update(eq("DELETE FROM SPRING_SESSION WHERE EXPIRY_TIME < ?"), anyLong());
verifyZeroInteractions(this.jdbcOperations);
verifyZeroInteractions(this.transactionManager);
}
@Test
void saveWithSaveModeOnSetAttribute() {
this.repository.setSaveMode(SaveMode.ON_SET_ATTRIBUTE);
@@ -751,7 +656,7 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.save(session);
verify(this.jdbcOperations).update(startsWith("UPDATE SPRING_SESSION_ATTRIBUTES SET"),
isA(PreparedStatementSetter.class));
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -770,7 +675,7 @@ class JdbcOperationsSessionRepositoryTests {
.forClass(BatchPreparedStatementSetter.class);
verify(this.jdbcOperations).batchUpdate(startsWith("UPDATE SPRING_SESSION_ATTRIBUTES SET"), captor.capture());
assertThat(captor.getValue().getBatchSize()).isEqualTo(2);
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -789,7 +694,7 @@ class JdbcOperationsSessionRepositoryTests {
.forClass(BatchPreparedStatementSetter.class);
verify(this.jdbcOperations).batchUpdate(startsWith("UPDATE SPRING_SESSION_ATTRIBUTES SET"), captor.capture());
assertThat(captor.getValue().getBatchSize()).isEqualTo(3);
verifyZeroInteractions(this.jdbcOperations);
verifyNoMoreInteractions(this.jdbcOperations);
}
@Test
@@ -798,7 +703,6 @@ class JdbcOperationsSessionRepositoryTests {
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
String attrName = "someAttribute";
session.setAttribute(attrName, "someValue");
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("INSERT INTO SPRING_SESSION_ATTRIBUTES("),
isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
@@ -811,7 +715,6 @@ class JdbcOperationsSessionRepositoryTests {
cached.setAttribute("attribute1", "value1");
JdbcSession session = this.repository.new JdbcSession(cached, "primaryKey", false);
session.removeAttribute("attribute1");
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("DELETE FROM SPRING_SESSION_ATTRIBUTES WHERE"),
isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
@@ -822,7 +725,6 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.setFlushMode(FlushMode.IMMEDIATE);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setMaxInactiveInterval(Duration.ofSeconds(1));
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("UPDATE SPRING_SESSION SET"), isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
@@ -832,16 +734,8 @@ class JdbcOperationsSessionRepositoryTests {
this.repository.setFlushMode(FlushMode.IMMEDIATE);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setLastAccessedTime(Instant.now());
assertPropagationRequiresNew();
verify(this.jdbcOperations).update(startsWith("UPDATE SPRING_SESSION SET"), isA(PreparedStatementSetter.class));
verifyNoMoreInteractions(this.jdbcOperations);
}
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

@@ -37,6 +37,8 @@ import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionOperations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -77,7 +79,10 @@ class JdbcHttpSessionConfigurationTests {
void defaultConfiguration() {
registerAndRefresh(DataSourceConfiguration.class, DefaultConfiguration.class);
assertThat(this.context.getBean(JdbcOperationsSessionRepository.class)).isNotNull();
JdbcOperationsSessionRepository sessionRepository = this.context.getBean(JdbcOperationsSessionRepository.class);
assertThat(sessionRepository).isNotNull();
assertThat(sessionRepository).extracting("transactionOperations")
.hasFieldOrPropertyWithValue("propagationBehavior", TransactionDefinition.PROPAGATION_REQUIRES_NEW);
}
@Test
@@ -225,6 +230,17 @@ class JdbcHttpSessionConfigurationTests {
.withMessageContaining("expected single matching bean but found 2");
}
@Test
void customTransactionOperationsConfiguration() {
registerAndRefresh(DataSourceConfiguration.class, CustomTransactionOperationsConfiguration.class);
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
TransactionOperations transactionOperations = this.context.getBean(TransactionOperations.class);
assertThat(repository).isNotNull();
assertThat(transactionOperations).isNotNull();
assertThat(repository).hasFieldOrPropertyWithValue("transactionOperations", transactionOperations);
}
@Test
void customLobHandlerConfiguration() {
registerAndRefresh(DataSourceConfiguration.class, CustomLobHandlerConfiguration.class);
@@ -417,6 +433,16 @@ class JdbcHttpSessionConfigurationTests {
}
@EnableJdbcHttpSession
static class CustomTransactionOperationsConfiguration {
@Bean
public TransactionOperations springSessionTransactionOperations() {
return TransactionOperations.withoutTransaction();
}
}
@EnableJdbcHttpSession
static class CustomLobHandlerConfiguration {