Harmonize naming of session repositories

Resolves: #1455
This commit is contained in:
Vedran Pavic
2019-09-18 20:50:38 +02:00
parent 96715e04f2
commit 8cc8fbb7fd
64 changed files with 3029 additions and 2888 deletions

View File

@@ -26,13 +26,12 @@ import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
/**
* Abstract base class for Testcontainers based {@link JdbcOperationsSessionRepository}
* integration tests.
* Base class for Testcontainers based {@link JdbcIndexedSessionRepository} integration
* tests.
*
* @author Vedran Pavic
*/
abstract class AbstractContainerJdbcOperationsSessionRepositoryITests
extends AbstractJdbcOperationsSessionRepositoryITests {
abstract class AbstractContainerJdbcIndexedSessionRepositoryITests extends AbstractJdbcIndexedSessionRepositoryITests {
static class BaseContainerConfig extends BaseConfig {

View File

@@ -38,6 +38,7 @@ import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.MapSession;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository.JdbcSession;
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
@@ -46,18 +47,18 @@ import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Abstract base class for {@link JdbcOperationsSessionRepository} integration tests.
* Base class for {@link JdbcIndexedSessionRepository} integration tests.
*
* @author Vedran Pavic
*/
abstract class AbstractJdbcOperationsSessionRepositoryITests {
abstract class AbstractJdbcIndexedSessionRepositoryITests {
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
private static final String INDEX_NAME = FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME;
@Autowired
private JdbcOperationsSessionRepository repository;
private JdbcIndexedSessionRepository repository;
private SecurityContext context;
@@ -76,10 +77,10 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void saveWhenNoAttributesThenCanBeFound() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(toSave.getId());
JdbcSession session = this.repository.findById(toSave.getId());
assertThat(session).isNotNull();
assertThat(session.isChanged()).isFalse();
@@ -90,7 +91,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
void saves() {
String username = "saves-" + System.currentTimeMillis();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
String expectedAttributeName = "a";
String expectedAttributeValue = "b";
toSave.setAttribute(expectedAttributeName, expectedAttributeValue);
@@ -103,7 +104,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(toSave.getId());
JdbcSession session = this.repository.findById(toSave.getId());
assertThat(session.getId()).isEqualTo(toSave.getId());
assertThat(session.isChanged()).isFalse();
@@ -120,14 +121,14 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
@Transactional(readOnly = true)
void savesInReadOnlyTransaction() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
this.repository.save(toSave);
}
@Test
void putAllOnSingleAttrDoesNotRemoveOld() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute("a", "b");
this.repository.save(toSave);
@@ -138,7 +139,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
this.repository.save(toSave);
toSave = this.repository.findById(toSave.getId());
JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(toSave.getId());
JdbcSession session = this.repository.findById(toSave.getId());
assertThat(session.isChanged()).isFalse();
assertThat(session.getDelta()).isEmpty();
assertThat(session.getAttributeNames().size()).isEqualTo(2);
@@ -150,7 +151,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void updateLastAccessedTime() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setLastAccessedTime(Instant.now().minusSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
this.repository.save(toSave);
@@ -159,7 +160,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
toSave.setLastAccessedTime(lastAccessedTime);
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(toSave.getId());
JdbcSession session = this.repository.findById(toSave.getId());
assertThat(session).isNotNull();
assertThat(session.isChanged()).isFalse();
@@ -172,13 +173,13 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void findByPrincipalName() {
String principalName = "findByPrincipalName" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalName);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
@@ -194,15 +195,15 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void findByPrincipalNameExpireRemovesIndex() {
String principalName = "findByPrincipalNameExpireRemovesIndex" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
toSave.setLastAccessedTime(Instant.now().minusSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
this.repository.save(toSave);
this.repository.cleanUpExpiredSessions();
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalName);
assertThat(findByPrincipalName).hasSize(0);
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
@@ -211,7 +212,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void findByPrincipalNameNoPrincipalNameChange() {
String principalName = "findByPrincipalNameNoPrincipalNameChange" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
@@ -219,8 +220,8 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalName);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
@@ -233,7 +234,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void findByPrincipalNameNoPrincipalNameChangeReload() {
String principalName = "findByPrincipalNameNoPrincipalNameChangeReload" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
@@ -243,8 +244,8 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalName);
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
@@ -257,7 +258,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void findByDeletedPrincipalName() {
String principalName = "findByDeletedPrincipalName" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
@@ -265,8 +266,8 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
toSave.setAttribute(INDEX_NAME, null);
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalName);
assertThat(findByPrincipalName).isEmpty();
}
@@ -275,7 +276,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
void findByChangedPrincipalName() {
String principalName = "findByChangedPrincipalName" + UUID.randomUUID();
String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
@@ -283,8 +284,8 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
toSave.setAttribute(INDEX_NAME, principalNameChanged);
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalName);
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalNameChanged);
@@ -300,17 +301,17 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void findByDeletedPrincipalNameReload() {
String principalName = "findByDeletedPrincipalName" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession getSession = this.repository.findById(toSave.getId());
JdbcSession getSession = this.repository.findById(toSave.getId());
getSession.setAttribute(INDEX_NAME, null);
this.repository.save(getSession);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalName);
assertThat(findByPrincipalName).isEmpty();
}
@@ -319,18 +320,18 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
void findByChangedPrincipalNameReload() {
String principalName = "findByChangedPrincipalName" + UUID.randomUUID();
String principalNameChanged = "findByChangedPrincipalName" + UUID.randomUUID();
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(INDEX_NAME, principalName);
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession getSession = this.repository.findById(toSave.getId());
JdbcSession getSession = this.repository.findById(toSave.getId());
getSession.setAttribute(INDEX_NAME, principalNameChanged);
this.repository.save(getSession);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, principalName);
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
principalName);
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, principalNameChanged);
@@ -345,13 +346,13 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void findBySecurityPrincipalName() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
@@ -366,15 +367,15 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void findBySecurityPrincipalNameExpireRemovesIndex() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
toSave.setLastAccessedTime(Instant.now().minusSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
this.repository.save(toSave);
this.repository.cleanUpExpiredSessions();
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getSecurityName());
assertThat(findByPrincipalName).hasSize(0);
assertThat(findByPrincipalName.keySet()).doesNotContain(toSave.getId());
@@ -382,7 +383,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void findByPrincipalNameNoSecurityPrincipalNameChange() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
@@ -390,8 +391,8 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
@@ -403,7 +404,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void findByPrincipalNameNoSecurityPrincipalNameChangeReload() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
@@ -413,8 +414,8 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
toSave.setAttribute("other", "value");
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getSecurityName());
assertThat(findByPrincipalName).hasSize(1);
assertThat(findByPrincipalName.keySet()).containsOnly(toSave.getId());
@@ -426,7 +427,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void findByDeletedSecurityPrincipalName() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
@@ -434,15 +435,15 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
toSave.setAttribute(SPRING_SECURITY_CONTEXT, null);
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getSecurityName());
assertThat(findByPrincipalName).isEmpty();
}
@Test
void findByChangedSecurityPrincipalName() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
@@ -450,8 +451,8 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext);
this.repository.save(toSave);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getSecurityName());
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getChangedSecurityName());
@@ -466,35 +467,35 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void findByDeletedSecurityPrincipalNameReload() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession getSession = this.repository.findById(toSave.getId());
JdbcSession getSession = this.repository.findById(toSave.getId());
getSession.setAttribute(INDEX_NAME, null);
this.repository.save(getSession);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getChangedSecurityName());
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getChangedSecurityName());
assertThat(findByPrincipalName).isEmpty();
}
@Test
void findByChangedSecurityPrincipalNameReload() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(SPRING_SECURITY_CONTEXT, this.context);
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession getSession = this.repository.findById(toSave.getId());
JdbcSession getSession = this.repository.findById(toSave.getId());
getSession.setAttribute(SPRING_SECURITY_CONTEXT, this.changedContext);
this.repository.save(getSession);
Map<String, JdbcOperationsSessionRepository.JdbcSession> findByPrincipalName = this.repository
.findByIndexNameAndIndexValue(INDEX_NAME, getSecurityName());
Map<String, JdbcSession> findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME,
getSecurityName());
assertThat(findByPrincipalName).isEmpty();
findByPrincipalName = this.repository.findByIndexNameAndIndexValue(INDEX_NAME, getChangedSecurityName());
@@ -509,7 +510,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void cleanupInactiveSessionsUsingRepositoryDefinedInterval() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
this.repository.save(session);
@@ -537,7 +538,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
// gh-580
@Test
void cleanupInactiveSessionsUsingSessionDefinedInterval() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
session.setMaxInactiveInterval(Duration.ofMinutes(45));
this.repository.save(session);
@@ -567,12 +568,12 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
void changeSessionIdWhenOnlyChangeId() {
String attrName = "changeSessionId";
String attrValue = "changeSessionId-value";
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
toSave.setAttribute(attrName, attrValue);
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession findById = this.repository.findById(toSave.getId());
JdbcSession findById = this.repository.findById(toSave.getId());
assertThat(findById.<String>getAttribute(attrName)).isEqualTo(attrValue);
@@ -583,7 +584,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(this.repository.findById(originalFindById)).isNull();
JdbcOperationsSessionRepository.JdbcSession findByChangeSessionId = this.repository.findById(changeSessionId);
JdbcSession findByChangeSessionId = this.repository.findById(changeSessionId);
assertThat(findByChangeSessionId.isChanged()).isFalse();
assertThat(findByChangeSessionId.getDelta()).isEmpty();
@@ -592,7 +593,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void changeSessionIdWhenChangeTwice() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
this.repository.save(toSave);
@@ -612,11 +613,11 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
String attrName = "changeSessionId";
String attrValue = "changeSessionId-value";
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
this.repository.save(toSave);
JdbcOperationsSessionRepository.JdbcSession findById = this.repository.findById(toSave.getId());
JdbcSession findById = this.repository.findById(toSave.getId());
findById.setAttribute(attrName, attrValue);
@@ -627,7 +628,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
assertThat(this.repository.findById(originalFindById)).isNull();
JdbcOperationsSessionRepository.JdbcSession findByChangeSessionId = this.repository.findById(changeSessionId);
JdbcSession findByChangeSessionId = this.repository.findById(changeSessionId);
assertThat(findByChangeSessionId.isChanged()).isFalse();
assertThat(findByChangeSessionId.getDelta()).isEmpty();
@@ -636,7 +637,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test
void changeSessionIdWhenHasNotSaved() {
JdbcOperationsSessionRepository.JdbcSession toSave = this.repository.createSession();
JdbcSession toSave = this.repository.createSession();
String originalId = toSave.getId();
toSave.changeSessionId();
@@ -648,7 +649,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test // gh-1070
void saveUpdatedAddAndModifyAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
this.repository.save(session);
session = this.repository.findById(session.getId());
session.setAttribute("testName", "testValue1");
@@ -661,7 +662,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test // gh-1070
void saveUpdatedAddAndRemoveAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
this.repository.save(session);
session = this.repository.findById(session.getId());
session.setAttribute("testName", "testValue");
@@ -674,7 +675,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test // gh-1070
void saveUpdatedModifyAndRemoveAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
session.setAttribute("testName", "testValue1");
this.repository.save(session);
session = this.repository.findById(session.getId());
@@ -688,7 +689,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test // gh-1070
void saveUpdatedRemoveAndAddAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
session.setAttribute("testName", "testValue1");
this.repository.save(session);
session = this.repository.findById(session.getId());
@@ -702,7 +703,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test // gh-1031
void saveDeleted() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
this.repository.save(session);
session = this.repository.findById(session.getId());
this.repository.deleteById(session.getId());
@@ -714,7 +715,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test // gh-1031
void saveDeletedAddAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
this.repository.save(session);
session = this.repository.findById(session.getId());
this.repository.deleteById(session.getId());
@@ -727,7 +728,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
@Test // gh-1133
void sessionFromStoreResolvesAttributesLazily() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
session.setAttribute("attribute1", "value1");
session.setAttribute("attribute2", "value2");
this.repository.save(session);
@@ -749,7 +750,7 @@ abstract class AbstractJdbcOperationsSessionRepositoryITests {
String attributeName = "largeAttribute";
int arraySize = 4000;
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
session.setAttribute(attributeName, new byte[arraySize]);
this.repository.save(session);
session = this.repository.findById(session.getId());

View File

@@ -27,15 +27,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcOperationsSessionRepository} using IBM DB2 11.x
* database.
* Integration tests for {@link JdbcIndexedSessionRepository} using IBM DB2 11.x database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class Db211JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests {
class Db211JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {

View File

@@ -28,14 +28,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcOperationsSessionRepository} using Derby database.
* Integration tests for {@link JdbcIndexedSessionRepository} using Derby database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class DerbyJdbcOperationsSessionRepositoryITests extends AbstractJdbcOperationsSessionRepositoryITests {
class DerbyJdbcIndexedSessionRepositoryITests extends AbstractJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseConfig {

View File

@@ -28,14 +28,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcOperationsSessionRepository} using H2 database.
* Integration tests for {@link JdbcIndexedSessionRepository} using H2 database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class H2JdbcOperationsSessionRepositoryITests extends AbstractJdbcOperationsSessionRepositoryITests {
class H2JdbcIndexedSessionRepositoryITests extends AbstractJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseConfig {

View File

@@ -28,14 +28,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcOperationsSessionRepository} using HSQLDB database.
* Integration tests for {@link JdbcIndexedSessionRepository} using HSQLDB database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class HsqldbJdbcOperationsSessionRepositoryITests extends AbstractJdbcOperationsSessionRepositoryITests {
class HsqldbJdbcIndexedSessionRepositoryITests extends AbstractJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseConfig {

View File

@@ -27,15 +27,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcOperationsSessionRepository} using MariaDB 10.x
* database.
* Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB 10.x database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MariaDb10JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests {
class MariaDb10JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {

View File

@@ -27,15 +27,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcOperationsSessionRepository} using MariaDB 5.x
* database.
* Integration tests for {@link JdbcIndexedSessionRepository} using MariaDB 5.x database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MariaDb5JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests {
class MariaDb5JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {

View File

@@ -27,14 +27,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcOperationsSessionRepository} using MySQL 5.x database.
* Integration tests for {@link JdbcIndexedSessionRepository} using MySQL 5.x database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MySql5JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests {
class MySql5JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {

View File

@@ -27,14 +27,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcOperationsSessionRepository} using MySQL 8.x database.
* Integration tests for {@link JdbcIndexedSessionRepository} using MySQL 8.x database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class MySql8JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests {
class MySql8JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {

View File

@@ -31,7 +31,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.util.ClassUtils;
/**
* Integration tests for {@link JdbcOperationsSessionRepository} using Oracle database.
* Integration tests for {@link JdbcIndexedSessionRepository} using Oracle database.
* <p>
* This test is conditional on presence of Oracle JDBC driver on the classpath and
* Testcontainers property {@code oracle.container.image} being set.
@@ -41,7 +41,7 @@ import org.springframework.util.ClassUtils;
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class OracleJdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests {
class OracleJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@BeforeAll
static void setUpClass() {

View File

@@ -27,7 +27,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcOperationsSessionRepository} using PostgreSQL 10.x
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 10.x
* database.
*
* @author Vedran Pavic
@@ -35,7 +35,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class PostgreSql10JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests {
class PostgreSql10JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {

View File

@@ -27,7 +27,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcOperationsSessionRepository} using PostgreSQL 11.x
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 11.x
* database.
*
* @author Vedran Pavic
@@ -35,7 +35,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class PostgreSql11JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests {
class PostgreSql11JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {

View File

@@ -27,7 +27,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcOperationsSessionRepository} using PostgreSQL 9.x
* Integration tests for {@link JdbcIndexedSessionRepository} using PostgreSQL 9.x
* database.
*
* @author Vedran Pavic
@@ -35,7 +35,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class PostgreSql9JdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests {
class PostgreSql9JdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {

View File

@@ -27,15 +27,15 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
/**
* Integration tests for {@link JdbcOperationsSessionRepository} using Microsoft SQL
* Server 2017 database.
* Integration tests for {@link JdbcIndexedSessionRepository} using Microsoft SQL Server
* 2017 database.
*
* @author Vedran Pavic
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration
class SqlServerJdbcOperationsSessionRepositoryITests extends AbstractContainerJdbcOperationsSessionRepositoryITests {
class SqlServerJdbcIndexedSessionRepositoryITests extends AbstractContainerJdbcIndexedSessionRepositoryITests {
@Configuration
static class Config extends BaseContainerConfig {

View File

@@ -0,0 +1,867 @@
/*
* Copyright 2014-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.session.jdbc;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.serializer.support.DeserializingConverter;
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.ResultSetExtractor;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.session.DelegatingIndexResolver;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.FlushMode;
import org.springframework.session.IndexResolver;
import org.springframework.session.MapSession;
import org.springframework.session.PrincipalNameIndexResolver;
import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* A {@link org.springframework.session.SessionRepository} implementation that uses
* Spring's {@link JdbcOperations} to store sessions in a relational database. This
* implementation does not support publishing of session events.
* <p>
* An example of how to create a new instance can be seen below:
*
* <pre class="code">
* JdbcTemplate jdbcTemplate = new JdbcTemplate();
*
* // ... configure jdbcTemplate ...
*
* TransactionTemplate transactionTemplate = new TransactionTemplate();
*
* // ... configure transactionTemplate ...
*
* JdbcIndexedSessionRepository sessionRepository =
* new JdbcIndexedSessionRepository(jdbcTemplate, transactionTemplate);
* </pre>
*
* 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>
* By default, this implementation uses <code>SPRING_SESSION</code> and
* <code>SPRING_SESSION_ATTRIBUTES</code> tables to store sessions. Note that the table
* name can be customized using the {@link #setTableName(String)} method. In that case the
* table used to store attributes will be named using the provided table name, suffixed
* with <code>_ATTRIBUTES</code>.
*
* Depending on your database, the table definition can be described as below:
*
* <pre class="code">
* CREATE TABLE SPRING_SESSION (
* PRIMARY_ID CHAR(36) NOT NULL,
* SESSION_ID CHAR(36) NOT NULL,
* CREATION_TIME BIGINT NOT NULL,
* LAST_ACCESS_TIME BIGINT NOT NULL,
* MAX_INACTIVE_INTERVAL INT NOT NULL,
* EXPIRY_TIME BIGINT NOT NULL,
* PRINCIPAL_NAME VARCHAR(100),
* CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
* );
*
* CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
* CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (EXPIRY_TIME);
* CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
*
* CREATE TABLE SPRING_SESSION_ATTRIBUTES (
* SESSION_PRIMARY_ID CHAR(36) NOT NULL,
* ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
* ATTRIBUTE_BYTES BYTEA NOT NULL,
* CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
* CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
* );
*
* CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_PRIMARY_ID);
* </pre>
*
* Due to the differences between the various database vendors, especially when it comes
* to storing binary data, make sure to use SQL script specific to your database. Scripts
* for most major database vendors are packaged as
* <code>org/springframework/session/jdbc/schema-*.sql</code>, where <code>*</code> is the
* target database type.
*
* @author Vedran Pavic
* @author Craig Andrews
* @since 2.2.0
*/
public class JdbcIndexedSessionRepository
implements FindByIndexNameSessionRepository<JdbcIndexedSessionRepository.JdbcSession> {
/**
* The default name of database table used by Spring Session to store sessions.
*/
public static final String DEFAULT_TABLE_NAME = "SPRING_SESSION";
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
// @formatter:off
private static final String CREATE_SESSION_QUERY = "INSERT INTO %TABLE_NAME%(PRIMARY_ID, SESSION_ID, CREATION_TIME, LAST_ACCESS_TIME, MAX_INACTIVE_INTERVAL, EXPIRY_TIME, PRINCIPAL_NAME) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?)";
// @formatter:on
// @formatter:off
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = "INSERT INTO %TABLE_NAME%_ATTRIBUTES(SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ "SELECT PRIMARY_ID, ?, ? "
+ "FROM %TABLE_NAME% "
+ "WHERE SESSION_ID = ?";
// @formatter:on
// @formatter:off
private static final String GET_SESSION_QUERY = "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES "
+ "FROM %TABLE_NAME% S "
+ "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID "
+ "WHERE S.SESSION_ID = ?";
// @formatter:on
// @formatter:off
private static final String UPDATE_SESSION_QUERY = "UPDATE %TABLE_NAME% SET SESSION_ID = ?, LAST_ACCESS_TIME = ?, MAX_INACTIVE_INTERVAL = ?, EXPIRY_TIME = ?, PRINCIPAL_NAME = ? "
+ "WHERE PRIMARY_ID = ?";
// @formatter:on
// @formatter:off
private static final String UPDATE_SESSION_ATTRIBUTE_QUERY = "UPDATE %TABLE_NAME%_ATTRIBUTES SET ATTRIBUTE_BYTES = ? "
+ "WHERE SESSION_PRIMARY_ID = ? "
+ "AND ATTRIBUTE_NAME = ?";
// @formatter:on
// @formatter:off
private static final String DELETE_SESSION_ATTRIBUTE_QUERY = "DELETE FROM %TABLE_NAME%_ATTRIBUTES "
+ "WHERE SESSION_PRIMARY_ID = ? "
+ "AND ATTRIBUTE_NAME = ?";
// @formatter:on
// @formatter:off
private static final String DELETE_SESSION_QUERY = "DELETE FROM %TABLE_NAME% "
+ "WHERE SESSION_ID = ?";
// @formatter:on
// @formatter:off
private static final String LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY = "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES "
+ "FROM %TABLE_NAME% S "
+ "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID "
+ "WHERE S.PRINCIPAL_NAME = ?";
// @formatter:on
// @formatter:off
private static final String DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY = "DELETE FROM %TABLE_NAME% "
+ "WHERE EXPIRY_TIME < ?";
// @formatter:on
private static final Log logger = LogFactory.getLog(JdbcIndexedSessionRepository.class);
private final JdbcOperations jdbcOperations;
private final TransactionOperations transactionOperations;
private final ResultSetExtractor<List<JdbcSession>> extractor = new SessionResultSetExtractor();
private final IndexResolver<JdbcSession> indexResolver;
/**
* The name of database table used by Spring Session to store sessions.
*/
private String tableName = DEFAULT_TABLE_NAME;
private String createSessionQuery;
private String createSessionAttributeQuery;
private String getSessionQuery;
private String updateSessionQuery;
private String updateSessionAttributeQuery;
private String deleteSessionAttributeQuery;
private String deleteSessionQuery;
private String listSessionsByPrincipalNameQuery;
private String deleteSessionsByExpiryTimeQuery;
/**
* If non-null, this value is used to override the default value for
* {@link JdbcSession#setMaxInactiveInterval(Duration)}.
*/
private Integer defaultMaxInactiveInterval;
private ConversionService conversionService;
private LobHandler lobHandler;
private FlushMode flushMode = FlushMode.ON_SAVE;
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
/**
* Create a new {@link JdbcIndexedSessionRepository} 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 JdbcIndexedSessionRepository(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();
}
/**
* Set the name of database table used to store sessions.
* @param tableName the database table name
*/
public void setTableName(String tableName) {
Assert.hasText(tableName, "Table name must not be empty");
this.tableName = tableName.trim();
prepareQueries();
}
/**
* Set the custom SQL query used to create the session.
* @param createSessionQuery the SQL query string
*/
public void setCreateSessionQuery(String createSessionQuery) {
Assert.hasText(createSessionQuery, "Query must not be empty");
this.createSessionQuery = createSessionQuery;
}
/**
* Set the custom SQL query used to create the session attribute.
* @param createSessionAttributeQuery the SQL query string
*/
public void setCreateSessionAttributeQuery(String createSessionAttributeQuery) {
Assert.hasText(createSessionAttributeQuery, "Query must not be empty");
this.createSessionAttributeQuery = createSessionAttributeQuery;
}
/**
* Set the custom SQL query used to retrieve the session.
* @param getSessionQuery the SQL query string
*/
public void setGetSessionQuery(String getSessionQuery) {
Assert.hasText(getSessionQuery, "Query must not be empty");
this.getSessionQuery = getSessionQuery;
}
/**
* Set the custom SQL query used to update the session.
* @param updateSessionQuery the SQL query string
*/
public void setUpdateSessionQuery(String updateSessionQuery) {
Assert.hasText(updateSessionQuery, "Query must not be empty");
this.updateSessionQuery = updateSessionQuery;
}
/**
* Set the custom SQL query used to update the session attribute.
* @param updateSessionAttributeQuery the SQL query string
*/
public void setUpdateSessionAttributeQuery(String updateSessionAttributeQuery) {
Assert.hasText(updateSessionAttributeQuery, "Query must not be empty");
this.updateSessionAttributeQuery = updateSessionAttributeQuery;
}
/**
* Set the custom SQL query used to delete the session attribute.
* @param deleteSessionAttributeQuery the SQL query string
*/
public void setDeleteSessionAttributeQuery(String deleteSessionAttributeQuery) {
Assert.hasText(deleteSessionAttributeQuery, "Query must not be empty");
this.deleteSessionAttributeQuery = deleteSessionAttributeQuery;
}
/**
* Set the custom SQL query used to delete the session.
* @param deleteSessionQuery the SQL query string
*/
public void setDeleteSessionQuery(String deleteSessionQuery) {
Assert.hasText(deleteSessionQuery, "Query must not be empty");
this.deleteSessionQuery = deleteSessionQuery;
}
/**
* Set the custom SQL query used to retrieve the sessions by principal name.
* @param listSessionsByPrincipalNameQuery the SQL query string
*/
public void setListSessionsByPrincipalNameQuery(String listSessionsByPrincipalNameQuery) {
Assert.hasText(listSessionsByPrincipalNameQuery, "Query must not be empty");
this.listSessionsByPrincipalNameQuery = listSessionsByPrincipalNameQuery;
}
/**
* Set the custom SQL query used to delete the sessions by last access time.
* @param deleteSessionsByExpiryTimeQuery the SQL query string
*/
public void setDeleteSessionsByExpiryTimeQuery(String deleteSessionsByExpiryTimeQuery) {
Assert.hasText(deleteSessionsByExpiryTimeQuery, "Query must not be empty");
this.deleteSessionsByExpiryTimeQuery = deleteSessionsByExpiryTimeQuery;
}
/**
* Set the maximum inactive interval in seconds between requests before newly created
* sessions will be invalidated. A negative time indicates that the session will never
* timeout. The default is 1800 (30 minutes).
* @param defaultMaxInactiveInterval the maximum inactive interval in seconds
*/
public void setDefaultMaxInactiveInterval(Integer defaultMaxInactiveInterval) {
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
}
public void setLobHandler(LobHandler lobHandler) {
Assert.notNull(lobHandler, "LobHandler must not be null");
this.lobHandler = lobHandler;
}
/**
* Sets the {@link ConversionService} to use.
* @param conversionService the converter to set
*/
public void setConversionService(ConversionService conversionService) {
Assert.notNull(conversionService, "conversionService must not be null");
this.conversionService = conversionService;
}
/**
* Set the flush mode. Default is {@link FlushMode#ON_SAVE}.
* @param flushMode the flush mode
*/
public void setFlushMode(FlushMode flushMode) {
Assert.notNull(flushMode, "flushMode must not be null");
this.flushMode = flushMode;
}
/**
* Set the save mode.
* @param saveMode the save mode
*/
public void setSaveMode(SaveMode saveMode) {
Assert.notNull(saveMode, "saveMode must not be null");
this.saveMode = saveMode;
}
@Override
public JdbcSession createSession() {
MapSession delegate = new MapSession();
if (this.defaultMaxInactiveInterval != null) {
delegate.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
}
JdbcSession session = new JdbcSession(delegate, UUID.randomUUID().toString(), true);
session.flushIfRequired();
return session;
}
@Override
public void save(final JdbcSession session) {
session.save();
}
@Override
public JdbcSession findById(final String id) {
final JdbcSession session = this.transactionOperations.execute((status) -> {
List<JdbcSession> sessions = JdbcIndexedSessionRepository.this.jdbcOperations.query(
JdbcIndexedSessionRepository.this.getSessionQuery, (ps) -> ps.setString(1, id),
JdbcIndexedSessionRepository.this.extractor);
if (sessions.isEmpty()) {
return null;
}
return sessions.get(0);
});
if (session != null) {
if (session.isExpired()) {
deleteById(id);
}
else {
return session;
}
}
return null;
}
@Override
public void deleteById(final String id) {
this.transactionOperations.execute(() -> JdbcIndexedSessionRepository.this.jdbcOperations
.update(JdbcIndexedSessionRepository.this.deleteSessionQuery, id));
}
@Override
public Map<String, JdbcSession> findByIndexNameAndIndexValue(String indexName, final String indexValue) {
if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
return Collections.emptyMap();
}
List<JdbcSession> sessions = this.transactionOperations
.execute((status) -> JdbcIndexedSessionRepository.this.jdbcOperations.query(
JdbcIndexedSessionRepository.this.listSessionsByPrincipalNameQuery,
(ps) -> ps.setString(1, indexValue), JdbcIndexedSessionRepository.this.extractor));
Map<String, JdbcSession> sessionMap = new HashMap<>(sessions.size());
for (JdbcSession session : sessions) {
sessionMap.put(session.getId(), session);
}
return sessionMap;
}
private void insertSessionAttributes(JdbcSession session, List<String> attributeNames) {
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.createSessionAttributeQuery, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, attributeName);
getLobHandler().getLobCreator().setBlobAsBytes(ps, 2,
serialize(session.getAttribute(attributeName)));
ps.setString(3, session.getId());
}
@Override
public int getBatchSize() {
return attributeNames.size();
}
});
}
else {
this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
ps.setString(1, attributeName);
getLobHandler().getLobCreator().setBlobAsBytes(ps, 2, serialize(session.getAttribute(attributeName)));
ps.setString(3, session.getId());
});
}
}
private void updateSessionAttributes(JdbcSession session, List<String> attributeNames) {
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.updateSessionAttributeQuery, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
getLobHandler().getLobCreator().setBlobAsBytes(ps, 1,
serialize(session.getAttribute(attributeName)));
ps.setString(2, session.primaryKey);
ps.setString(3, attributeName);
}
@Override
public int getBatchSize() {
return attributeNames.size();
}
});
}
else {
this.jdbcOperations.update(this.updateSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
getLobHandler().getLobCreator().setBlobAsBytes(ps, 1, serialize(session.getAttribute(attributeName)));
ps.setString(2, session.primaryKey);
ps.setString(3, attributeName);
});
}
}
private void deleteSessionAttributes(JdbcSession session, List<String> attributeNames) {
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.deleteSessionAttributeQuery, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, session.primaryKey);
ps.setString(2, attributeName);
}
@Override
public int getBatchSize() {
return attributeNames.size();
}
});
}
else {
this.jdbcOperations.update(this.deleteSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
ps.setString(1, session.primaryKey);
ps.setString(2, attributeName);
});
}
}
public void cleanUpExpiredSessions() {
Integer deletedCount = this.transactionOperations
.execute((status) -> JdbcIndexedSessionRepository.this.jdbcOperations.update(
JdbcIndexedSessionRepository.this.deleteSessionsByExpiryTimeQuery, System.currentTimeMillis()));
if (logger.isDebugEnabled()) {
logger.debug("Cleaned up " + deletedCount + " expired sessions");
}
}
private static GenericConversionService createDefaultConversionService() {
GenericConversionService converter = new GenericConversionService();
converter.addConverter(Object.class, byte[].class, new SerializingConverter());
converter.addConverter(byte[].class, Object.class, new DeserializingConverter());
return converter;
}
private String getQuery(String base) {
return StringUtils.replace(base, "%TABLE_NAME%", this.tableName);
}
private void prepareQueries() {
this.createSessionQuery = getQuery(CREATE_SESSION_QUERY);
this.createSessionAttributeQuery = getQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
this.getSessionQuery = getQuery(GET_SESSION_QUERY);
this.updateSessionQuery = getQuery(UPDATE_SESSION_QUERY);
this.updateSessionAttributeQuery = getQuery(UPDATE_SESSION_ATTRIBUTE_QUERY);
this.deleteSessionAttributeQuery = getQuery(DELETE_SESSION_ATTRIBUTE_QUERY);
this.deleteSessionQuery = getQuery(DELETE_SESSION_QUERY);
this.listSessionsByPrincipalNameQuery = getQuery(LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY);
this.deleteSessionsByExpiryTimeQuery = getQuery(DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY);
}
private LobHandler getLobHandler() {
return this.lobHandler;
}
private byte[] serialize(Object object) {
return (byte[]) this.conversionService.convert(object, TypeDescriptor.valueOf(Object.class),
TypeDescriptor.valueOf(byte[].class));
}
private Object deserialize(byte[] bytes) {
return this.conversionService.convert(bytes, TypeDescriptor.valueOf(byte[].class),
TypeDescriptor.valueOf(Object.class));
}
private enum DeltaValue {
ADDED, UPDATED, REMOVED
}
private static <T> Supplier<T> value(T value) {
return (value != null) ? () -> value : null;
}
private static <T> Supplier<T> lazily(Supplier<T> supplier) {
Supplier<T> lazySupplier = new Supplier<T>() {
private T value;
@Override
public T get() {
if (this.value == null) {
this.value = supplier.get();
}
return this.value;
}
};
return (supplier != null) ? lazySupplier : null;
}
/**
* The {@link Session} to use for {@link JdbcIndexedSessionRepository}.
*
* @author Vedran Pavic
*/
final class JdbcSession implements Session {
private final Session delegate;
private final String primaryKey;
private boolean isNew;
private boolean changed;
private Map<String, DeltaValue> delta = new HashMap<>();
JdbcSession(MapSession delegate, String primaryKey, boolean isNew) {
this.delegate = delegate;
this.primaryKey = primaryKey;
this.isNew = isNew;
if (this.isNew || (JdbcIndexedSessionRepository.this.saveMode == SaveMode.ALWAYS)) {
getAttributeNames().forEach((attributeName) -> this.delta.put(attributeName, DeltaValue.UPDATED));
}
}
boolean isNew() {
return this.isNew;
}
boolean isChanged() {
return this.changed;
}
Map<String, DeltaValue> getDelta() {
return this.delta;
}
void clearChangeFlags() {
this.isNew = false;
this.changed = false;
this.delta.clear();
}
Instant getExpiryTime() {
return getLastAccessedTime().plus(getMaxInactiveInterval());
}
@Override
public String getId() {
return this.delegate.getId();
}
@Override
public String changeSessionId() {
this.changed = true;
return this.delegate.changeSessionId();
}
@Override
public <T> T getAttribute(String attributeName) {
Supplier<T> supplier = this.delegate.getAttribute(attributeName);
if (supplier == null) {
return null;
}
T attributeValue = supplier.get();
if (attributeValue != null
&& JdbcIndexedSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) {
this.delta.put(attributeName, DeltaValue.UPDATED);
}
return attributeValue;
}
@Override
public Set<String> getAttributeNames() {
return this.delegate.getAttributeNames();
}
@Override
public void setAttribute(String attributeName, Object attributeValue) {
boolean attributeExists = (this.delegate.getAttribute(attributeName) != null);
boolean attributeRemoved = (attributeValue == null);
if (!attributeExists && attributeRemoved) {
return;
}
if (attributeExists) {
if (attributeRemoved) {
this.delta.merge(attributeName, DeltaValue.REMOVED,
(oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? null : deltaValue);
}
else {
this.delta.merge(attributeName, DeltaValue.UPDATED, (oldDeltaValue,
deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? oldDeltaValue : deltaValue);
}
}
else {
this.delta.merge(attributeName, DeltaValue.ADDED, (oldDeltaValue,
deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? oldDeltaValue : DeltaValue.UPDATED);
}
this.delegate.setAttribute(attributeName, value(attributeValue));
if (PRINCIPAL_NAME_INDEX_NAME.equals(attributeName) || SPRING_SECURITY_CONTEXT.equals(attributeName)) {
this.changed = true;
}
flushIfRequired();
}
@Override
public void removeAttribute(String attributeName) {
setAttribute(attributeName, null);
}
@Override
public Instant getCreationTime() {
return this.delegate.getCreationTime();
}
@Override
public void setLastAccessedTime(Instant lastAccessedTime) {
this.delegate.setLastAccessedTime(lastAccessedTime);
this.changed = true;
flushIfRequired();
}
@Override
public Instant getLastAccessedTime() {
return this.delegate.getLastAccessedTime();
}
@Override
public void setMaxInactiveInterval(Duration interval) {
this.delegate.setMaxInactiveInterval(interval);
this.changed = true;
flushIfRequired();
}
@Override
public Duration getMaxInactiveInterval() {
return this.delegate.getMaxInactiveInterval();
}
@Override
public boolean isExpired() {
return this.delegate.isExpired();
}
private void flushIfRequired() {
if (JdbcIndexedSessionRepository.this.flushMode == FlushMode.IMMEDIATE) {
save();
}
}
private void save() {
if (this.isNew) {
JdbcIndexedSessionRepository.this.transactionOperations.execute(() -> {
Map<String, String> indexes = JdbcIndexedSessionRepository.this.indexResolver
.resolveIndexesFor(JdbcSession.this);
JdbcIndexedSessionRepository.this.jdbcOperations
.update(JdbcIndexedSessionRepository.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 {
JdbcIndexedSessionRepository.this.transactionOperations.execute(() -> {
if (JdbcSession.this.changed) {
Map<String, String> indexes = JdbcIndexedSessionRepository.this.indexResolver
.resolveIndexesFor(JdbcSession.this);
JdbcIndexedSessionRepository.this.jdbcOperations
.update(JdbcIndexedSessionRepository.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();
}
}
private class SessionResultSetExtractor implements ResultSetExtractor<List<JdbcSession>> {
@Override
public List<JdbcSession> extractData(ResultSet rs) throws SQLException, DataAccessException {
List<JdbcSession> sessions = new ArrayList<>();
while (rs.next()) {
String id = rs.getString("SESSION_ID");
JdbcSession session;
if (sessions.size() > 0 && getLast(sessions).getId().equals(id)) {
session = getLast(sessions);
}
else {
MapSession delegate = new MapSession(id);
String primaryKey = rs.getString("PRIMARY_ID");
delegate.setCreationTime(Instant.ofEpochMilli(rs.getLong("CREATION_TIME")));
delegate.setLastAccessedTime(Instant.ofEpochMilli(rs.getLong("LAST_ACCESS_TIME")));
delegate.setMaxInactiveInterval(Duration.ofSeconds(rs.getInt("MAX_INACTIVE_INTERVAL")));
session = new JdbcSession(delegate, primaryKey, false);
}
String attributeName = rs.getString("ATTRIBUTE_NAME");
if (attributeName != null) {
byte[] bytes = getLobHandler().getBlobAsBytes(rs, "ATTRIBUTE_BYTES");
session.delegate.setAttribute(attributeName, lazily(() -> deserialize(bytes)));
}
sessions.add(session);
}
return sessions;
}
private JdbcSession getLast(List<JdbcSession> sessions) {
return sessions.get(sessions.size() - 1);
}
}
}

View File

@@ -16,229 +16,25 @@
package org.springframework.session.jdbc;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.serializer.support.DeserializingConverter;
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.ResultSetExtractor;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.session.DelegatingIndexResolver;
import org.springframework.session.FindByIndexNameSessionRepository;
import org.springframework.session.FlushMode;
import org.springframework.session.IndexResolver;
import org.springframework.session.MapSession;
import org.springframework.session.PrincipalNameIndexResolver;
import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
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.Assert;
import org.springframework.util.StringUtils;
/**
* A {@link org.springframework.session.SessionRepository} implementation that uses
* Spring's {@link JdbcOperations} to store sessions in a relational database. This
* implementation does not support publishing of session events.
* <p>
* An example of how to create a new instance can be seen below:
*
* <pre class="code">
* JdbcTemplate jdbcTemplate = new JdbcTemplate();
*
* // ... configure jdbcTemplate ...
*
* TransactionTemplate transactionTemplate = new TransactionTemplate();
*
* // ... configure transactionTemplate ...
*
* JdbcOperationsSessionRepository sessionRepository =
* new JdbcOperationsSessionRepository(jdbcTemplate, transactionTemplate);
* </pre>
*
* 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>
* By default, this implementation uses <code>SPRING_SESSION</code> and
* <code>SPRING_SESSION_ATTRIBUTES</code> tables to store sessions. Note that the table
* name can be customized using the {@link #setTableName(String)} method. In that case the
* table used to store attributes will be named using the provided table name, suffixed
* with <code>_ATTRIBUTES</code>.
*
* Depending on your database, the table definition can be described as below:
*
* <pre class="code">
* CREATE TABLE SPRING_SESSION (
* PRIMARY_ID CHAR(36) NOT NULL,
* SESSION_ID CHAR(36) NOT NULL,
* CREATION_TIME BIGINT NOT NULL,
* LAST_ACCESS_TIME BIGINT NOT NULL,
* MAX_INACTIVE_INTERVAL INT NOT NULL,
* EXPIRY_TIME BIGINT NOT NULL,
* PRINCIPAL_NAME VARCHAR(100),
* CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
* );
*
* CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
* CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (EXPIRY_TIME);
* CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
*
* CREATE TABLE SPRING_SESSION_ATTRIBUTES (
* SESSION_PRIMARY_ID CHAR(36) NOT NULL,
* ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
* ATTRIBUTE_BYTES BYTEA NOT NULL,
* CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
* CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
* );
*
* CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_PRIMARY_ID);
* </pre>
*
* Due to the differences between the various database vendors, especially when it comes
* to storing binary data, make sure to use SQL script specific to your database. Scripts
* for most major database vendors are packaged as
* <code>org/springframework/session/jdbc/schema-*.sql</code>, where <code>*</code> is the
* target database type.
* This {@link SessionRepository} implementation is kept in order to support migration to
* {@link JdbcIndexedSessionRepository} in a backwards compatible manner.
*
* @author Vedran Pavic
* @author Craig Andrews
* @since 1.2.0
* @deprecated since 2.2.0 in favor of {@link JdbcIndexedSessionRepository}
*/
public class JdbcOperationsSessionRepository
implements FindByIndexNameSessionRepository<JdbcOperationsSessionRepository.JdbcSession> {
/**
* The default name of database table used by Spring Session to store sessions.
*/
public static final String DEFAULT_TABLE_NAME = "SPRING_SESSION";
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
// @formatter:off
private static final String CREATE_SESSION_QUERY = "INSERT INTO %TABLE_NAME%(PRIMARY_ID, SESSION_ID, CREATION_TIME, LAST_ACCESS_TIME, MAX_INACTIVE_INTERVAL, EXPIRY_TIME, PRINCIPAL_NAME) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?)";
// @formatter:on
// @formatter:off
private static final String CREATE_SESSION_ATTRIBUTE_QUERY = "INSERT INTO %TABLE_NAME%_ATTRIBUTES(SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) "
+ "SELECT PRIMARY_ID, ?, ? "
+ "FROM %TABLE_NAME% "
+ "WHERE SESSION_ID = ?";
// @formatter:on
// @formatter:off
private static final String GET_SESSION_QUERY = "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES "
+ "FROM %TABLE_NAME% S "
+ "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID "
+ "WHERE S.SESSION_ID = ?";
// @formatter:on
// @formatter:off
private static final String UPDATE_SESSION_QUERY = "UPDATE %TABLE_NAME% SET SESSION_ID = ?, LAST_ACCESS_TIME = ?, MAX_INACTIVE_INTERVAL = ?, EXPIRY_TIME = ?, PRINCIPAL_NAME = ? "
+ "WHERE PRIMARY_ID = ?";
// @formatter:on
// @formatter:off
private static final String UPDATE_SESSION_ATTRIBUTE_QUERY = "UPDATE %TABLE_NAME%_ATTRIBUTES SET ATTRIBUTE_BYTES = ? "
+ "WHERE SESSION_PRIMARY_ID = ? "
+ "AND ATTRIBUTE_NAME = ?";
// @formatter:on
// @formatter:off
private static final String DELETE_SESSION_ATTRIBUTE_QUERY = "DELETE FROM %TABLE_NAME%_ATTRIBUTES "
+ "WHERE SESSION_PRIMARY_ID = ? "
+ "AND ATTRIBUTE_NAME = ?";
// @formatter:on
// @formatter:off
private static final String DELETE_SESSION_QUERY = "DELETE FROM %TABLE_NAME% "
+ "WHERE SESSION_ID = ?";
// @formatter:on
// @formatter:off
private static final String LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY = "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES "
+ "FROM %TABLE_NAME% S "
+ "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID "
+ "WHERE S.PRINCIPAL_NAME = ?";
// @formatter:on
// @formatter:off
private static final String DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY = "DELETE FROM %TABLE_NAME% "
+ "WHERE EXPIRY_TIME < ?";
// @formatter:on
private static final Log logger = LogFactory.getLog(JdbcOperationsSessionRepository.class);
private final JdbcOperations jdbcOperations;
private final TransactionOperations transactionOperations;
private final ResultSetExtractor<List<JdbcSession>> extractor = new SessionResultSetExtractor();
private final IndexResolver<JdbcSession> indexResolver;
/**
* The name of database table used by Spring Session to store sessions.
*/
private String tableName = DEFAULT_TABLE_NAME;
private String createSessionQuery;
private String createSessionAttributeQuery;
private String getSessionQuery;
private String updateSessionQuery;
private String updateSessionAttributeQuery;
private String deleteSessionAttributeQuery;
private String deleteSessionQuery;
private String listSessionsByPrincipalNameQuery;
private String deleteSessionsByExpiryTimeQuery;
/**
* If non-null, this value is used to override the default value for
* {@link JdbcSession#setMaxInactiveInterval(Duration)}.
*/
private Integer defaultMaxInactiveInterval;
private ConversionService conversionService;
private LobHandler lobHandler;
private FlushMode flushMode = FlushMode.ON_SAVE;
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
@Deprecated
public class JdbcOperationsSessionRepository extends JdbcIndexedSessionRepository {
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
@@ -246,346 +42,42 @@ public class JdbcOperationsSessionRepository
* sessions.
* @param jdbcOperations the {@link JdbcOperations} to use
* @param transactionOperations the {@link TransactionOperations} to use
* @see JdbcIndexedSessionRepository#JdbcIndexedSessionRepository(JdbcOperations,
* TransactionOperations)
*/
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();
super(jdbcOperations, transactionOperations);
}
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
* provided {@link JdbcOperations} to manage sessions.
* Create a new {@link JdbcIndexedSessionRepository} instance which uses the provided
* {@link JdbcOperations} to manage sessions.
* <p>
* The created instance will execute all data access operations in a transaction with
* 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)}
* {@link JdbcIndexedSessionRepository#JdbcIndexedSessionRepository(JdbcOperations, TransactionOperations)}
*/
@Deprecated
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations,
PlatformTransactionManager transactionManager) {
this(jdbcOperations, createTransactionTemplate(transactionManager));
super(jdbcOperations, createTransactionTemplate(transactionManager));
}
/**
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
* provided {@link JdbcOperations} to manage sessions.
* Create a new {@link JdbcIndexedSessionRepository} instance which uses the provided
* {@link JdbcOperations} to manage sessions.
* <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)}
* {@link JdbcIndexedSessionRepository#JdbcIndexedSessionRepository(JdbcOperations, TransactionOperations)}
*/
@Deprecated
public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations) {
this(jdbcOperations, TransactionOperations.withoutTransaction());
}
/**
* Set the name of database table used to store sessions.
* @param tableName the database table name
*/
public void setTableName(String tableName) {
Assert.hasText(tableName, "Table name must not be empty");
this.tableName = tableName.trim();
prepareQueries();
}
/**
* Set the custom SQL query used to create the session.
* @param createSessionQuery the SQL query string
*/
public void setCreateSessionQuery(String createSessionQuery) {
Assert.hasText(createSessionQuery, "Query must not be empty");
this.createSessionQuery = createSessionQuery;
}
/**
* Set the custom SQL query used to create the session attribute.
* @param createSessionAttributeQuery the SQL query string
*/
public void setCreateSessionAttributeQuery(String createSessionAttributeQuery) {
Assert.hasText(createSessionAttributeQuery, "Query must not be empty");
this.createSessionAttributeQuery = createSessionAttributeQuery;
}
/**
* Set the custom SQL query used to retrieve the session.
* @param getSessionQuery the SQL query string
*/
public void setGetSessionQuery(String getSessionQuery) {
Assert.hasText(getSessionQuery, "Query must not be empty");
this.getSessionQuery = getSessionQuery;
}
/**
* Set the custom SQL query used to update the session.
* @param updateSessionQuery the SQL query string
*/
public void setUpdateSessionQuery(String updateSessionQuery) {
Assert.hasText(updateSessionQuery, "Query must not be empty");
this.updateSessionQuery = updateSessionQuery;
}
/**
* Set the custom SQL query used to update the session attribute.
* @param updateSessionAttributeQuery the SQL query string
*/
public void setUpdateSessionAttributeQuery(String updateSessionAttributeQuery) {
Assert.hasText(updateSessionAttributeQuery, "Query must not be empty");
this.updateSessionAttributeQuery = updateSessionAttributeQuery;
}
/**
* Set the custom SQL query used to delete the session attribute.
* @param deleteSessionAttributeQuery the SQL query string
*/
public void setDeleteSessionAttributeQuery(String deleteSessionAttributeQuery) {
Assert.hasText(deleteSessionAttributeQuery, "Query must not be empty");
this.deleteSessionAttributeQuery = deleteSessionAttributeQuery;
}
/**
* Set the custom SQL query used to delete the session.
* @param deleteSessionQuery the SQL query string
*/
public void setDeleteSessionQuery(String deleteSessionQuery) {
Assert.hasText(deleteSessionQuery, "Query must not be empty");
this.deleteSessionQuery = deleteSessionQuery;
}
/**
* Set the custom SQL query used to retrieve the sessions by principal name.
* @param listSessionsByPrincipalNameQuery the SQL query string
*/
public void setListSessionsByPrincipalNameQuery(String listSessionsByPrincipalNameQuery) {
Assert.hasText(listSessionsByPrincipalNameQuery, "Query must not be empty");
this.listSessionsByPrincipalNameQuery = listSessionsByPrincipalNameQuery;
}
/**
* Set the custom SQL query used to delete the sessions by last access time.
* @param deleteSessionsByExpiryTimeQuery the SQL query string
*/
public void setDeleteSessionsByExpiryTimeQuery(String deleteSessionsByExpiryTimeQuery) {
Assert.hasText(deleteSessionsByExpiryTimeQuery, "Query must not be empty");
this.deleteSessionsByExpiryTimeQuery = deleteSessionsByExpiryTimeQuery;
}
/**
* Set the maximum inactive interval in seconds between requests before newly created
* sessions will be invalidated. A negative time indicates that the session will never
* timeout. The default is 1800 (30 minutes).
* @param defaultMaxInactiveInterval the maximum inactive interval in seconds
*/
public void setDefaultMaxInactiveInterval(Integer defaultMaxInactiveInterval) {
this.defaultMaxInactiveInterval = defaultMaxInactiveInterval;
}
public void setLobHandler(LobHandler lobHandler) {
Assert.notNull(lobHandler, "LobHandler must not be null");
this.lobHandler = lobHandler;
}
/**
* Sets the {@link ConversionService} to use.
* @param conversionService the converter to set
*/
public void setConversionService(ConversionService conversionService) {
Assert.notNull(conversionService, "conversionService must not be null");
this.conversionService = conversionService;
}
/**
* Set the flush mode. Default is {@link FlushMode#ON_SAVE}.
* @param flushMode the flush mode
*/
public void setFlushMode(FlushMode flushMode) {
Assert.notNull(flushMode, "flushMode must not be null");
this.flushMode = flushMode;
}
/**
* Set the save mode.
* @param saveMode the save mode
*/
public void setSaveMode(SaveMode saveMode) {
Assert.notNull(saveMode, "saveMode must not be null");
this.saveMode = saveMode;
}
@Override
public JdbcSession createSession() {
MapSession delegate = new MapSession();
if (this.defaultMaxInactiveInterval != null) {
delegate.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
}
JdbcSession session = new JdbcSession(delegate, UUID.randomUUID().toString(), true);
session.flushIfRequired();
return session;
}
@Override
public void save(final JdbcSession session) {
session.save();
}
@Override
public JdbcSession findById(final String id) {
final JdbcSession session = this.transactionOperations.execute((status) -> {
List<JdbcSession> sessions = JdbcOperationsSessionRepository.this.jdbcOperations.query(
JdbcOperationsSessionRepository.this.getSessionQuery, (ps) -> ps.setString(1, id),
JdbcOperationsSessionRepository.this.extractor);
if (sessions.isEmpty()) {
return null;
}
return sessions.get(0);
});
if (session != null) {
if (session.isExpired()) {
deleteById(id);
}
else {
return session;
}
}
return null;
}
@Override
public void deleteById(final String id) {
this.transactionOperations.execute(() -> JdbcOperationsSessionRepository.this.jdbcOperations
.update(JdbcOperationsSessionRepository.this.deleteSessionQuery, id));
}
@Override
public Map<String, JdbcSession> findByIndexNameAndIndexValue(String indexName, final String indexValue) {
if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) {
return Collections.emptyMap();
}
List<JdbcSession> sessions = this.transactionOperations
.execute((status) -> JdbcOperationsSessionRepository.this.jdbcOperations.query(
JdbcOperationsSessionRepository.this.listSessionsByPrincipalNameQuery,
(ps) -> ps.setString(1, indexValue), JdbcOperationsSessionRepository.this.extractor));
Map<String, JdbcSession> sessionMap = new HashMap<>(sessions.size());
for (JdbcSession session : sessions) {
sessionMap.put(session.getId(), session);
}
return sessionMap;
}
private void insertSessionAttributes(JdbcSession session, List<String> attributeNames) {
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.createSessionAttributeQuery, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, attributeName);
getLobHandler().getLobCreator().setBlobAsBytes(ps, 2,
serialize(session.getAttribute(attributeName)));
ps.setString(3, session.getId());
}
@Override
public int getBatchSize() {
return attributeNames.size();
}
});
}
else {
this.jdbcOperations.update(this.createSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
ps.setString(1, attributeName);
getLobHandler().getLobCreator().setBlobAsBytes(ps, 2, serialize(session.getAttribute(attributeName)));
ps.setString(3, session.getId());
});
}
}
private void updateSessionAttributes(JdbcSession session, List<String> attributeNames) {
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.updateSessionAttributeQuery, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
getLobHandler().getLobCreator().setBlobAsBytes(ps, 1,
serialize(session.getAttribute(attributeName)));
ps.setString(2, session.primaryKey);
ps.setString(3, attributeName);
}
@Override
public int getBatchSize() {
return attributeNames.size();
}
});
}
else {
this.jdbcOperations.update(this.updateSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
getLobHandler().getLobCreator().setBlobAsBytes(ps, 1, serialize(session.getAttribute(attributeName)));
ps.setString(2, session.primaryKey);
ps.setString(3, attributeName);
});
}
}
private void deleteSessionAttributes(JdbcSession session, List<String> attributeNames) {
Assert.notEmpty(attributeNames, "attributeNames must not be null or empty");
if (attributeNames.size() > 1) {
this.jdbcOperations.batchUpdate(this.deleteSessionAttributeQuery, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
String attributeName = attributeNames.get(i);
ps.setString(1, session.primaryKey);
ps.setString(2, attributeName);
}
@Override
public int getBatchSize() {
return attributeNames.size();
}
});
}
else {
this.jdbcOperations.update(this.deleteSessionAttributeQuery, (ps) -> {
String attributeName = attributeNames.get(0);
ps.setString(1, session.primaryKey);
ps.setString(2, attributeName);
});
}
}
public void cleanUpExpiredSessions() {
Integer deletedCount = this.transactionOperations
.execute((status) -> JdbcOperationsSessionRepository.this.jdbcOperations.update(
JdbcOperationsSessionRepository.this.deleteSessionsByExpiryTimeQuery,
System.currentTimeMillis()));
if (logger.isDebugEnabled()) {
logger.debug("Cleaned up " + deletedCount + " expired sessions");
}
super(jdbcOperations, TransactionOperations.withoutTransaction());
}
private static TransactionTemplate createTransactionTemplate(PlatformTransactionManager transactionManager) {
@@ -596,316 +88,4 @@ public class JdbcOperationsSessionRepository
return transactionTemplate;
}
private static GenericConversionService createDefaultConversionService() {
GenericConversionService converter = new GenericConversionService();
converter.addConverter(Object.class, byte[].class, new SerializingConverter());
converter.addConverter(byte[].class, Object.class, new DeserializingConverter());
return converter;
}
private String getQuery(String base) {
return StringUtils.replace(base, "%TABLE_NAME%", this.tableName);
}
private void prepareQueries() {
this.createSessionQuery = getQuery(CREATE_SESSION_QUERY);
this.createSessionAttributeQuery = getQuery(CREATE_SESSION_ATTRIBUTE_QUERY);
this.getSessionQuery = getQuery(GET_SESSION_QUERY);
this.updateSessionQuery = getQuery(UPDATE_SESSION_QUERY);
this.updateSessionAttributeQuery = getQuery(UPDATE_SESSION_ATTRIBUTE_QUERY);
this.deleteSessionAttributeQuery = getQuery(DELETE_SESSION_ATTRIBUTE_QUERY);
this.deleteSessionQuery = getQuery(DELETE_SESSION_QUERY);
this.listSessionsByPrincipalNameQuery = getQuery(LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY);
this.deleteSessionsByExpiryTimeQuery = getQuery(DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY);
}
private LobHandler getLobHandler() {
return this.lobHandler;
}
private byte[] serialize(Object object) {
return (byte[]) this.conversionService.convert(object, TypeDescriptor.valueOf(Object.class),
TypeDescriptor.valueOf(byte[].class));
}
private Object deserialize(byte[] bytes) {
return this.conversionService.convert(bytes, TypeDescriptor.valueOf(byte[].class),
TypeDescriptor.valueOf(Object.class));
}
private enum DeltaValue {
ADDED, UPDATED, REMOVED
}
private static <T> Supplier<T> value(T value) {
return (value != null) ? () -> value : null;
}
private static <T> Supplier<T> lazily(Supplier<T> supplier) {
Supplier<T> lazySupplier = new Supplier<T>() {
private T value;
@Override
public T get() {
if (this.value == null) {
this.value = supplier.get();
}
return this.value;
}
};
return (supplier != null) ? lazySupplier : null;
}
/**
* The {@link Session} to use for {@link JdbcOperationsSessionRepository}.
*
* @author Vedran Pavic
*/
final class JdbcSession implements Session {
private final Session delegate;
private final String primaryKey;
private boolean isNew;
private boolean changed;
private Map<String, DeltaValue> delta = new HashMap<>();
JdbcSession(MapSession delegate, String primaryKey, boolean isNew) {
this.delegate = delegate;
this.primaryKey = primaryKey;
this.isNew = isNew;
if (this.isNew || (JdbcOperationsSessionRepository.this.saveMode == SaveMode.ALWAYS)) {
getAttributeNames().forEach((attributeName) -> this.delta.put(attributeName, DeltaValue.UPDATED));
}
}
boolean isNew() {
return this.isNew;
}
boolean isChanged() {
return this.changed;
}
Map<String, DeltaValue> getDelta() {
return this.delta;
}
void clearChangeFlags() {
this.isNew = false;
this.changed = false;
this.delta.clear();
}
Instant getExpiryTime() {
return getLastAccessedTime().plus(getMaxInactiveInterval());
}
@Override
public String getId() {
return this.delegate.getId();
}
@Override
public String changeSessionId() {
this.changed = true;
return this.delegate.changeSessionId();
}
@Override
public <T> T getAttribute(String attributeName) {
Supplier<T> supplier = this.delegate.getAttribute(attributeName);
if (supplier == null) {
return null;
}
T attributeValue = supplier.get();
if (attributeValue != null
&& JdbcOperationsSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) {
this.delta.put(attributeName, DeltaValue.UPDATED);
}
return attributeValue;
}
@Override
public Set<String> getAttributeNames() {
return this.delegate.getAttributeNames();
}
@Override
public void setAttribute(String attributeName, Object attributeValue) {
boolean attributeExists = (this.delegate.getAttribute(attributeName) != null);
boolean attributeRemoved = (attributeValue == null);
if (!attributeExists && attributeRemoved) {
return;
}
if (attributeExists) {
if (attributeRemoved) {
this.delta.merge(attributeName, DeltaValue.REMOVED,
(oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? null : deltaValue);
}
else {
this.delta.merge(attributeName, DeltaValue.UPDATED, (oldDeltaValue,
deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? oldDeltaValue : deltaValue);
}
}
else {
this.delta.merge(attributeName, DeltaValue.ADDED, (oldDeltaValue,
deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? oldDeltaValue : DeltaValue.UPDATED);
}
this.delegate.setAttribute(attributeName, value(attributeValue));
if (PRINCIPAL_NAME_INDEX_NAME.equals(attributeName) || SPRING_SECURITY_CONTEXT.equals(attributeName)) {
this.changed = true;
}
flushIfRequired();
}
@Override
public void removeAttribute(String attributeName) {
setAttribute(attributeName, null);
}
@Override
public Instant getCreationTime() {
return this.delegate.getCreationTime();
}
@Override
public void setLastAccessedTime(Instant lastAccessedTime) {
this.delegate.setLastAccessedTime(lastAccessedTime);
this.changed = true;
flushIfRequired();
}
@Override
public Instant getLastAccessedTime() {
return this.delegate.getLastAccessedTime();
}
@Override
public void setMaxInactiveInterval(Duration interval) {
this.delegate.setMaxInactiveInterval(interval);
this.changed = true;
flushIfRequired();
}
@Override
public Duration getMaxInactiveInterval() {
return this.delegate.getMaxInactiveInterval();
}
@Override
public boolean isExpired() {
return this.delegate.isExpired();
}
private void flushIfRequired() {
if (JdbcOperationsSessionRepository.this.flushMode == FlushMode.IMMEDIATE) {
save();
}
}
private void save() {
if (this.isNew) {
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(() -> {
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();
}
}
private class SessionResultSetExtractor implements ResultSetExtractor<List<JdbcSession>> {
@Override
public List<JdbcSession> extractData(ResultSet rs) throws SQLException, DataAccessException {
List<JdbcSession> sessions = new ArrayList<>();
while (rs.next()) {
String id = rs.getString("SESSION_ID");
JdbcSession session;
if (sessions.size() > 0 && getLast(sessions).getId().equals(id)) {
session = getLast(sessions);
}
else {
MapSession delegate = new MapSession(id);
String primaryKey = rs.getString("PRIMARY_ID");
delegate.setCreationTime(Instant.ofEpochMilli(rs.getLong("CREATION_TIME")));
delegate.setLastAccessedTime(Instant.ofEpochMilli(rs.getLong("LAST_ACCESS_TIME")));
delegate.setMaxInactiveInterval(Duration.ofSeconds(rs.getInt("MAX_INACTIVE_INTERVAL")));
session = new JdbcSession(delegate, primaryKey, false);
}
String attributeName = rs.getString("ATTRIBUTE_NAME");
if (attributeName != null) {
byte[] bytes = getLobHandler().getBlobAsBytes(rs, "ATTRIBUTE_BYTES");
session.delegate.setAttribute(attributeName, lazily(() -> deserialize(bytes)));
}
sessions.add(session);
}
return sessions;
}
private JdbcSession getLast(List<JdbcSession> sessions) {
return sessions.get(sessions.size() - 1);
}
}
}

View File

@@ -25,11 +25,11 @@ import java.lang.annotation.Target;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
/**
* Qualifier annotation for a {@link DataSource} to be injected in
* {@link JdbcOperationsSessionRepository}.
* {@link JdbcIndexedSessionRepository}.
*
* @author Vedran Pavic
* @since 2.0.0

View File

@@ -32,7 +32,7 @@ import org.springframework.session.SaveMode;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
import org.springframework.session.web.http.SessionRepositoryFilter;
/**
@@ -91,7 +91,7 @@ public @interface EnableJdbcHttpSession {
* The name of database table used by Spring Session to store sessions.
* @return the database table name
*/
String tableName() default JdbcOperationsSessionRepository.DEFAULT_TABLE_NAME;
String tableName() default JdbcIndexedSessionRepository.DEFAULT_TABLE_NAME;
/**
* The cron expression for expired session cleanup job. By default runs every minute.

View File

@@ -49,7 +49,7 @@ import org.springframework.session.MapSession;
import org.springframework.session.SaveMode;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
import org.springframework.session.web.http.SessionRepositoryFilter;
import org.springframework.transaction.PlatformTransactionManager;
@@ -81,7 +81,7 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
private Integer maxInactiveIntervalInSeconds = MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS;
private String tableName = JdbcOperationsSessionRepository.DEFAULT_TABLE_NAME;
private String tableName = JdbcIndexedSessionRepository.DEFAULT_TABLE_NAME;
private String cleanupCron = DEFAULT_CLEANUP_CRON;
@@ -101,19 +101,19 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
private ConversionService conversionService;
private List<SessionRepositoryCustomizer<JdbcOperationsSessionRepository>> sessionRepositoryCustomizers;
private List<SessionRepositoryCustomizer<JdbcIndexedSessionRepository>> sessionRepositoryCustomizers;
private ClassLoader classLoader;
private StringValueResolver embeddedValueResolver;
@Bean
public JdbcOperationsSessionRepository sessionRepository() {
public JdbcIndexedSessionRepository sessionRepository() {
JdbcTemplate jdbcTemplate = createJdbcTemplate(this.dataSource);
if (this.transactionOperations == null) {
this.transactionOperations = createTransactionTemplate(this.transactionManager);
}
JdbcOperationsSessionRepository sessionRepository = new JdbcOperationsSessionRepository(jdbcTemplate,
JdbcIndexedSessionRepository sessionRepository = new JdbcIndexedSessionRepository(jdbcTemplate,
this.transactionOperations);
if (StringUtils.hasText(this.tableName)) {
sessionRepository.setTableName(this.tableName);
@@ -214,7 +214,7 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
@Autowired(required = false)
public void setSessionRepositoryCustomizer(
ObjectProvider<SessionRepositoryCustomizer<JdbcOperationsSessionRepository>> sessionRepositoryCustomizers) {
ObjectProvider<SessionRepositoryCustomizer<JdbcIndexedSessionRepository>> sessionRepositoryCustomizers) {
this.sessionRepositoryCustomizers = sessionRepositoryCustomizers.orderedStream().collect(Collectors.toList());
}

View File

@@ -43,8 +43,7 @@ import org.springframework.session.FlushMode;
import org.springframework.session.MapSession;
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.session.jdbc.JdbcIndexedSessionRepository.JdbcSession;
import org.springframework.transaction.support.TransactionOperations;
import static org.assertj.core.api.Assertions.assertThat;
@@ -59,51 +58,41 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link JdbcOperationsSessionRepository}.
* Tests for {@link JdbcIndexedSessionRepository}.
*
* @author Vedran Pavic
* @author Craig Andrews
* @since 1.2.0
*/
class JdbcOperationsSessionRepositoryTests {
class JdbcIndexedSessionRepositoryTests {
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
@Mock
private JdbcOperations jdbcOperations;
private JdbcOperationsSessionRepository repository;
private JdbcIndexedSessionRepository repository;
@BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations,
this.repository = new JdbcIndexedSessionRepository(this.jdbcOperations,
TransactionOperations.withoutTransaction());
}
@Test
void constructorNullJdbcOperations() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new JdbcOperationsSessionRepository(null, TransactionOperations.withoutTransaction()))
.isThrownBy(() -> new JdbcIndexedSessionRepository(null, TransactionOperations.withoutTransaction()))
.withMessage("jdbcOperations must not be null");
}
@Test
void constructorNullTransactionOperations() {
assertThatIllegalArgumentException()
.isThrownBy(
() -> new JdbcOperationsSessionRepository(this.jdbcOperations, (TransactionOperations) null))
.isThrownBy(() -> new JdbcIndexedSessionRepository(this.jdbcOperations, 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
void setTableNameNull() {
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setTableName(null))
@@ -250,7 +239,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test
void createSessionDefaultMaxInactiveInterval() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
assertThat(session.isNew()).isTrue();
assertThat(session.getMaxInactiveInterval()).isEqualTo(new MapSession().getMaxInactiveInterval());
@@ -262,7 +251,7 @@ class JdbcOperationsSessionRepositoryTests {
int interval = 1;
this.repository.setDefaultMaxInactiveInterval(interval);
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
assertThat(session.isNew()).isTrue();
assertThat(session.getMaxInactiveInterval()).isEqualTo(Duration.ofSeconds(interval));
@@ -280,7 +269,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test
void saveNewWithoutAttributes() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
this.repository.save(session);
@@ -291,7 +280,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test
void saveNewWithSingleAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
session.setAttribute("testName", "testValue");
this.repository.save(session);
@@ -306,7 +295,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test
void saveNewWithMultipleAttributes() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
session.setAttribute("testName1", "testValue1");
session.setAttribute("testName2", "testValue2");
@@ -322,8 +311,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test
void saveUpdatedAddSingleAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
"primaryKey", false);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setAttribute("testName", "testValue");
this.repository.save(session);
@@ -336,8 +324,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test
void saveUpdatedAddMultipleAttributes() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
"primaryKey", false);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setAttribute("testName1", "testValue1");
session.setAttribute("testName2", "testValue2");
@@ -351,8 +338,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test
void saveUpdatedModifySingleAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
"primaryKey", false);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setAttribute("testName", "testValue");
session.clearChangeFlags();
session.setAttribute("testName", "testValue");
@@ -367,8 +353,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test
void saveUpdatedModifyMultipleAttributes() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
"primaryKey", false);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setAttribute("testName1", "testValue1");
session.setAttribute("testName2", "testValue2");
session.clearChangeFlags();
@@ -385,8 +370,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test
void saveUpdatedRemoveSingleAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
"primaryKey", false);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setAttribute("testName", "testValue");
session.clearChangeFlags();
session.removeAttribute("testName");
@@ -401,8 +385,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test
void saveUpdatedRemoveNonExistingAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
"primaryKey", false);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.removeAttribute("testName");
this.repository.save(session);
@@ -413,8 +396,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test
void saveUpdatedRemoveMultipleAttributes() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
"primaryKey", false);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setAttribute("testName1", "testValue1");
session.setAttribute("testName2", "testValue2");
session.clearChangeFlags();
@@ -431,8 +413,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test // gh-1070
void saveUpdatedAddAndModifyAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
"primaryKey", false);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setAttribute("testName", "testValue1");
session.setAttribute("testName", "testValue2");
@@ -446,8 +427,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test // gh-1070
void saveUpdatedAddAndRemoveAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
"primaryKey", false);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setAttribute("testName", "testValue");
session.removeAttribute("testName");
@@ -459,8 +439,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test // gh-1070
void saveUpdatedModifyAndRemoveAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
"primaryKey", false);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setAttribute("testName", "testValue1");
session.clearChangeFlags();
session.setAttribute("testName", "testValue2");
@@ -476,8 +455,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test // gh-1070
void saveUpdatedRemoveAndAddAttribute() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
"primaryKey", false);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setAttribute("testName", "testValue1");
session.clearChangeFlags();
session.removeAttribute("testName");
@@ -493,8 +471,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test
void saveUpdatedLastAccessedTime() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
"primaryKey", false);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
session.setLastAccessedTime(Instant.now());
this.repository.save(session);
@@ -507,8 +484,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test
void saveUnchanged() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
"primaryKey", false);
JdbcSession session = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
this.repository.save(session);
@@ -523,7 +499,7 @@ class JdbcOperationsSessionRepositoryTests {
given(this.jdbcOperations.query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class))).willReturn(Collections.emptyList());
JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(sessionId);
JdbcSession session = this.repository.findById(sessionId);
assertThat(session).isNull();
verify(this.jdbcOperations, times(1)).query(isA(String.class), isA(PreparedStatementSetter.class),
@@ -538,7 +514,7 @@ class JdbcOperationsSessionRepositoryTests {
given(this.jdbcOperations.query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class))).willReturn(Collections.singletonList(expired));
JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(expired.getId());
JdbcSession session = this.repository.findById(expired.getId());
assertThat(session).isNull();
verify(this.jdbcOperations, times(1)).query(isA(String.class), isA(PreparedStatementSetter.class),
@@ -554,7 +530,7 @@ class JdbcOperationsSessionRepositoryTests {
given(this.jdbcOperations.query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class))).willReturn(Collections.singletonList(saved));
JdbcOperationsSessionRepository.JdbcSession session = this.repository.findById(saved.getId());
JdbcSession session = this.repository.findById(saved.getId());
assertThat(session.getId()).isEqualTo(saved.getId());
assertThat(session.isNew()).isFalse();
@@ -576,8 +552,7 @@ class JdbcOperationsSessionRepositoryTests {
void findByIndexNameAndIndexValueUnknownIndexName() {
String indexValue = "testIndexValue";
Map<String, JdbcOperationsSessionRepository.JdbcSession> sessions = this.repository
.findByIndexNameAndIndexValue("testIndexName", indexValue);
Map<String, JdbcSession> sessions = this.repository.findByIndexNameAndIndexValue("testIndexName", indexValue);
assertThat(sessions).isEmpty();
verifyNoMoreInteractions(this.jdbcOperations);
@@ -590,7 +565,7 @@ class JdbcOperationsSessionRepositoryTests {
given(this.jdbcOperations.query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class))).willReturn(Collections.emptyList());
Map<String, JdbcOperationsSessionRepository.JdbcSession> sessions = this.repository
Map<String, JdbcSession> sessions = this.repository
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
assertThat(sessions).isEmpty();
@@ -614,7 +589,7 @@ class JdbcOperationsSessionRepositoryTests {
given(this.jdbcOperations.query(isA(String.class), isA(PreparedStatementSetter.class),
isA(ResultSetExtractor.class))).willReturn(saved);
Map<String, JdbcOperationsSessionRepository.JdbcSession> sessions = this.repository
Map<String, JdbcSession> sessions = this.repository
.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, principal);
assertThat(sessions).hasSize(2);
@@ -631,7 +606,7 @@ class JdbcOperationsSessionRepositoryTests {
@Test // gh-1120
void getAttributeNamesAndRemove() {
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
JdbcSession session = this.repository.createSession();
session.setAttribute("attribute1", "value1");
session.setAttribute("attribute2", "value2");
@@ -649,8 +624,7 @@ class JdbcOperationsSessionRepositoryTests {
delegate.setAttribute("attribute1", (Supplier<String>) () -> "value1");
delegate.setAttribute("attribute2", (Supplier<String>) () -> "value2");
delegate.setAttribute("attribute3", (Supplier<String>) () -> "value3");
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(delegate,
UUID.randomUUID().toString(), false);
JdbcSession session = this.repository.new JdbcSession(delegate, UUID.randomUUID().toString(), false);
session.getAttribute("attribute2");
session.setAttribute("attribute3", "value4");
this.repository.save(session);
@@ -666,8 +640,7 @@ class JdbcOperationsSessionRepositoryTests {
delegate.setAttribute("attribute1", (Supplier<String>) () -> "value1");
delegate.setAttribute("attribute2", (Supplier<String>) () -> "value2");
delegate.setAttribute("attribute3", (Supplier<String>) () -> "value3");
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(delegate,
UUID.randomUUID().toString(), false);
JdbcSession session = this.repository.new JdbcSession(delegate, UUID.randomUUID().toString(), false);
session.getAttribute("attribute2");
session.setAttribute("attribute3", "value4");
this.repository.save(session);
@@ -685,8 +658,7 @@ class JdbcOperationsSessionRepositoryTests {
delegate.setAttribute("attribute1", (Supplier<String>) () -> "value1");
delegate.setAttribute("attribute2", (Supplier<String>) () -> "value2");
delegate.setAttribute("attribute3", (Supplier<String>) () -> "value3");
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(delegate,
UUID.randomUUID().toString(), false);
JdbcSession session = this.repository.new JdbcSession(delegate, UUID.randomUUID().toString(), false);
session.getAttribute("attribute2");
session.setAttribute("attribute3", "value4");
this.repository.save(session);

View File

@@ -35,7 +35,7 @@ import org.springframework.mock.env.MockEnvironment;
import org.springframework.session.FlushMode;
import org.springframework.session.SaveMode;
import org.springframework.session.config.SessionRepositoryCustomizer;
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
import org.springframework.session.jdbc.JdbcIndexedSessionRepository;
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.transaction.PlatformTransactionManager;
@@ -51,7 +51,6 @@ import static org.mockito.Mockito.mock;
*
* @author Vedran Pavic
* @author Eddú Meléndez
* @since 1.2.0
*/
class JdbcHttpSessionConfigurationTests {
@@ -81,7 +80,7 @@ class JdbcHttpSessionConfigurationTests {
void defaultConfiguration() {
registerAndRefresh(DataSourceConfiguration.class, DefaultConfiguration.class);
JdbcOperationsSessionRepository sessionRepository = this.context.getBean(JdbcOperationsSessionRepository.class);
JdbcIndexedSessionRepository sessionRepository = this.context.getBean(JdbcIndexedSessionRepository.class);
assertThat(sessionRepository).isNotNull();
assertThat(sessionRepository).extracting("transactionOperations")
.hasFieldOrPropertyWithValue("propagationBehavior", TransactionDefinition.PROPAGATION_REQUIRES_NEW);
@@ -91,7 +90,7 @@ class JdbcHttpSessionConfigurationTests {
void customTableNameAnnotation() {
registerAndRefresh(DataSourceConfiguration.class, CustomTableNameAnnotationConfiguration.class);
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class);
assertThat(repository).isNotNull();
assertThat(ReflectionTestUtils.getField(repository, "tableName")).isEqualTo(TABLE_NAME);
}
@@ -100,7 +99,7 @@ class JdbcHttpSessionConfigurationTests {
void customTableNameSetter() {
registerAndRefresh(DataSourceConfiguration.class, CustomTableNameSetterConfiguration.class);
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class);
assertThat(repository).isNotNull();
assertThat(ReflectionTestUtils.getField(repository, "tableName")).isEqualTo(TABLE_NAME);
}
@@ -110,7 +109,7 @@ class JdbcHttpSessionConfigurationTests {
registerAndRefresh(DataSourceConfiguration.class,
CustomMaxInactiveIntervalInSecondsAnnotationConfiguration.class);
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class);
assertThat(repository).isNotNull();
assertThat(ReflectionTestUtils.getField(repository, "defaultMaxInactiveInterval"))
.isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
@@ -120,7 +119,7 @@ class JdbcHttpSessionConfigurationTests {
void customMaxInactiveIntervalInSecondsSetter() {
registerAndRefresh(DataSourceConfiguration.class, CustomMaxInactiveIntervalInSecondsSetterConfiguration.class);
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class);
assertThat(repository).isNotNull();
assertThat(ReflectionTestUtils.getField(repository, "defaultMaxInactiveInterval"))
.isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
@@ -176,7 +175,7 @@ class JdbcHttpSessionConfigurationTests {
void qualifiedDataSourceConfiguration() {
registerAndRefresh(DataSourceConfiguration.class, QualifiedDataSourceConfiguration.class);
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class);
DataSource dataSource = this.context.getBean("qualifiedDataSource", DataSource.class);
assertThat(repository).isNotNull();
assertThat(dataSource).isNotNull();
@@ -189,7 +188,7 @@ class JdbcHttpSessionConfigurationTests {
void primaryDataSourceConfiguration() {
registerAndRefresh(DataSourceConfiguration.class, PrimaryDataSourceConfiguration.class);
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class);
DataSource dataSource = this.context.getBean("primaryDataSource", DataSource.class);
assertThat(repository).isNotNull();
assertThat(dataSource).isNotNull();
@@ -202,7 +201,7 @@ class JdbcHttpSessionConfigurationTests {
void qualifiedAndPrimaryDataSourceConfiguration() {
registerAndRefresh(DataSourceConfiguration.class, QualifiedAndPrimaryDataSourceConfiguration.class);
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class);
DataSource dataSource = this.context.getBean("qualifiedDataSource", DataSource.class);
assertThat(repository).isNotNull();
assertThat(dataSource).isNotNull();
@@ -215,7 +214,7 @@ class JdbcHttpSessionConfigurationTests {
void namedDataSourceConfiguration() {
registerAndRefresh(DataSourceConfiguration.class, NamedDataSourceConfiguration.class);
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class);
DataSource dataSource = this.context.getBean("dataSource", DataSource.class);
assertThat(repository).isNotNull();
assertThat(dataSource).isNotNull();
@@ -236,7 +235,7 @@ class JdbcHttpSessionConfigurationTests {
void customTransactionOperationsConfiguration() {
registerAndRefresh(DataSourceConfiguration.class, CustomTransactionOperationsConfiguration.class);
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class);
TransactionOperations transactionOperations = this.context.getBean(TransactionOperations.class);
assertThat(repository).isNotNull();
assertThat(transactionOperations).isNotNull();
@@ -247,7 +246,7 @@ class JdbcHttpSessionConfigurationTests {
void customLobHandlerConfiguration() {
registerAndRefresh(DataSourceConfiguration.class, CustomLobHandlerConfiguration.class);
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class);
LobHandler lobHandler = this.context.getBean(LobHandler.class);
assertThat(repository).isNotNull();
assertThat(lobHandler).isNotNull();
@@ -258,7 +257,7 @@ class JdbcHttpSessionConfigurationTests {
void customConversionServiceConfiguration() {
registerAndRefresh(DataSourceConfiguration.class, CustomConversionServiceConfiguration.class);
JdbcOperationsSessionRepository repository = this.context.getBean(JdbcOperationsSessionRepository.class);
JdbcIndexedSessionRepository repository = this.context.getBean(JdbcIndexedSessionRepository.class);
ConversionService conversionService = this.context.getBean("springSessionConversionService",
ConversionService.class);
assertThat(repository).isNotNull();
@@ -279,7 +278,7 @@ class JdbcHttpSessionConfigurationTests {
@Test
void sessionRepositoryCustomizer() {
registerAndRefresh(DataSourceConfiguration.class, SessionRepositoryCustomizerConfiguration.class);
JdbcOperationsSessionRepository sessionRepository = this.context.getBean(JdbcOperationsSessionRepository.class);
JdbcIndexedSessionRepository sessionRepository = this.context.getBean(JdbcIndexedSessionRepository.class);
assertThat(sessionRepository).hasFieldOrPropertyWithValue("defaultMaxInactiveInterval",
MAX_INACTIVE_INTERVAL_IN_SECONDS);
}
@@ -488,13 +487,13 @@ class JdbcHttpSessionConfigurationTests {
@Bean
@Order(0)
public SessionRepositoryCustomizer<JdbcOperationsSessionRepository> sessionRepositoryCustomizerOne() {
public SessionRepositoryCustomizer<JdbcIndexedSessionRepository> sessionRepositoryCustomizerOne() {
return (sessionRepository) -> sessionRepository.setDefaultMaxInactiveInterval(0);
}
@Bean
@Order(1)
public SessionRepositoryCustomizer<JdbcOperationsSessionRepository> sessionRepositoryCustomizerTwo() {
public SessionRepositoryCustomizer<JdbcIndexedSessionRepository> sessionRepositoryCustomizerTwo() {
return (sessionRepository) -> sessionRepository
.setDefaultMaxInactiveInterval(MAX_INACTIVE_INTERVAL_IN_SECONDS);
}