Add support for session save mode
This commit introduces SaveMode enum that individual SessionRepository implementations use to allow customization of the way they handle save operation in terms of tracking delta of changes. Resolves: #1466
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Supported modes of tracking and saving session changes to session store.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Vedran Pavic
|
||||
* @since 2.2.0
|
||||
*/
|
||||
public enum SaveMode {
|
||||
|
||||
/**
|
||||
* Save only changes made to session, for instance using
|
||||
* {@link Session#setAttribute(String, Object)}. In highly concurrent environments,
|
||||
* this mode minimizes the risk of attributes being overwritten during processing of
|
||||
* parallel requests.
|
||||
*/
|
||||
ON_SET_ATTRIBUTE,
|
||||
|
||||
/**
|
||||
* Same as {@link #ON_SET_ATTRIBUTE} with addition of saving attributes that have been
|
||||
* read using {@link Session#getAttribute(String)}.
|
||||
*/
|
||||
ON_GET_ATTRIBUTE,
|
||||
|
||||
/**
|
||||
* Always save all session attributes, regardless of the interaction with the session.
|
||||
* In highly concurrent environments, this mode increases the risk of attributes being
|
||||
* overwritten during processing of parallel requests.
|
||||
*/
|
||||
ALWAYS
|
||||
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import reactor.core.publisher.Mono;
|
||||
import org.springframework.data.redis.core.ReactiveRedisOperations;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.ReactiveSessionRepository;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -59,6 +60,8 @@ public class ReactiveRedisOperationsSessionRepository
|
||||
*/
|
||||
private Integer defaultMaxInactiveInterval;
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
public ReactiveRedisOperationsSessionRepository(ReactiveRedisOperations<String, Object> sessionRedisOperations) {
|
||||
Assert.notNull(sessionRedisOperations, "sessionRedisOperations cannot be null");
|
||||
this.sessionRedisOperations = sessionRedisOperations;
|
||||
@@ -90,6 +93,15 @@ public class ReactiveRedisOperationsSessionRepository
|
||||
Assert.notNull(redisFlushMode, "redisFlushMode cannot be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ReactiveRedisOperations} used for sessions.
|
||||
* @return the {@link ReactiveRedisOperations} used for sessions
|
||||
@@ -102,12 +114,11 @@ public class ReactiveRedisOperationsSessionRepository
|
||||
@Override
|
||||
public Mono<RedisSession> createSession() {
|
||||
return Mono.defer(() -> {
|
||||
RedisSession session = new RedisSession();
|
||||
|
||||
MapSession cached = new MapSession();
|
||||
if (this.defaultMaxInactiveInterval != null) {
|
||||
session.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
|
||||
cached.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
|
||||
}
|
||||
|
||||
RedisSession session = new RedisSession(cached, true);
|
||||
return Mono.just(session);
|
||||
});
|
||||
}
|
||||
@@ -132,7 +143,7 @@ public class ReactiveRedisOperationsSessionRepository
|
||||
.filter((map) -> !map.isEmpty())
|
||||
.map(new RedisSessionMapper(id))
|
||||
.filter((session) -> !session.isExpired())
|
||||
.map(RedisSession::new)
|
||||
.map((session) -> new RedisSession(session, false))
|
||||
.switchIfEmpty(Mono.defer(() -> deleteById(id).then(Mono.empty())));
|
||||
// @formatter:on
|
||||
}
|
||||
@@ -168,27 +179,20 @@ public class ReactiveRedisOperationsSessionRepository
|
||||
|
||||
private String originalSessionId;
|
||||
|
||||
/**
|
||||
* Creates a new instance ensuring to mark all of the new attributes to be
|
||||
* persisted in the next save operation.
|
||||
*/
|
||||
RedisSession() {
|
||||
this(new MapSession());
|
||||
this.delta.put(RedisSessionMapper.CREATION_TIME_KEY, getCreationTime().toEpochMilli());
|
||||
this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) getMaxInactiveInterval().getSeconds());
|
||||
this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli());
|
||||
this.isNew = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance from the provided {@link MapSession}.
|
||||
* @param mapSession the {@link MapSession} that represents the persisted session
|
||||
* that was retrieved. Cannot be null.
|
||||
*/
|
||||
RedisSession(MapSession mapSession) {
|
||||
Assert.notNull(mapSession, "mapSession cannot be null");
|
||||
this.cached = mapSession;
|
||||
this.originalSessionId = mapSession.getId();
|
||||
RedisSession(MapSession cached, boolean isNew) {
|
||||
this.cached = cached;
|
||||
this.isNew = isNew;
|
||||
this.originalSessionId = cached.getId();
|
||||
if (this.isNew) {
|
||||
this.delta.put(RedisSessionMapper.CREATION_TIME_KEY, cached.getCreationTime().toEpochMilli());
|
||||
this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY,
|
||||
(int) cached.getMaxInactiveInterval().getSeconds());
|
||||
this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, cached.getLastAccessedTime().toEpochMilli());
|
||||
}
|
||||
if (this.isNew || (ReactiveRedisOperationsSessionRepository.this.saveMode == SaveMode.ALWAYS)) {
|
||||
getAttributeNames().forEach((attributeName) -> this.delta.put(getAttributeKey(attributeName),
|
||||
cached.getAttribute(attributeName)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -203,7 +207,12 @@ public class ReactiveRedisOperationsSessionRepository
|
||||
|
||||
@Override
|
||||
public <T> T getAttribute(String attributeName) {
|
||||
return this.cached.getAttribute(attributeName);
|
||||
T attributeValue = this.cached.getAttribute(attributeName);
|
||||
if (attributeValue != null
|
||||
&& ReactiveRedisOperationsSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) {
|
||||
this.delta.put(getAttributeKey(attributeName), attributeValue);
|
||||
}
|
||||
return attributeValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -42,6 +42,7 @@ 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.events.SessionCreatedEvent;
|
||||
import org.springframework.session.events.SessionDeletedEvent;
|
||||
@@ -296,6 +297,8 @@ public class RedisOperationsSessionRepository
|
||||
|
||||
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
/**
|
||||
* Creates a new instance. For an example, refer to the class level javadoc.
|
||||
* @param sessionRedisOperations the {@link RedisOperations} to use for managing the
|
||||
@@ -363,6 +366,15 @@ public class RedisOperationsSessionRepository
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the database index to use. Defaults to {@link #DEFAULT_DATABASE}.
|
||||
* @param database the database index to use
|
||||
@@ -439,7 +451,7 @@ public class RedisOperationsSessionRepository
|
||||
if (!allowExpired && loaded.isExpired()) {
|
||||
return null;
|
||||
}
|
||||
RedisSession result = new RedisSession(loaded);
|
||||
RedisSession result = new RedisSession(loaded, false);
|
||||
result.originalLastAccessTime = loaded.getLastAccessedTime();
|
||||
return result;
|
||||
}
|
||||
@@ -483,9 +495,11 @@ public class RedisOperationsSessionRepository
|
||||
|
||||
@Override
|
||||
public RedisSession createSession() {
|
||||
Duration maxInactiveInterval = Duration.ofSeconds((this.defaultMaxInactiveInterval != null)
|
||||
? this.defaultMaxInactiveInterval : MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
|
||||
RedisSession session = new RedisSession(maxInactiveInterval);
|
||||
MapSession cached = new MapSession();
|
||||
if (this.defaultMaxInactiveInterval != null) {
|
||||
cached.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
|
||||
}
|
||||
RedisSession session = new RedisSession(cached, true);
|
||||
session.flushImmediateIfNecessary();
|
||||
return session;
|
||||
}
|
||||
@@ -674,32 +688,22 @@ public class RedisOperationsSessionRepository
|
||||
|
||||
private String originalSessionId;
|
||||
|
||||
/**
|
||||
* Creates a new instance ensuring to mark all of the new attributes to be
|
||||
* persisted in the next save operation.
|
||||
* @param maxInactiveInterval the amount of time that the {@link Session} should
|
||||
* be kept alive between client requests.
|
||||
*/
|
||||
RedisSession(Duration maxInactiveInterval) {
|
||||
this(new MapSession());
|
||||
this.cached.setMaxInactiveInterval(maxInactiveInterval);
|
||||
this.delta.put(RedisSessionMapper.CREATION_TIME_KEY, getCreationTime().toEpochMilli());
|
||||
this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) getMaxInactiveInterval().getSeconds());
|
||||
this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli());
|
||||
this.isNew = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance from the provided {@link MapSession}.
|
||||
* @param cached the {@link MapSession} that represents the persisted session that
|
||||
* was retrieved. Cannot be null.
|
||||
*/
|
||||
RedisSession(MapSession cached) {
|
||||
Assert.notNull(cached, "MapSession cannot be null");
|
||||
RedisSession(MapSession cached, boolean isNew) {
|
||||
this.cached = cached;
|
||||
this.isNew = isNew;
|
||||
this.originalSessionId = cached.getId();
|
||||
Map<String, String> indexes = RedisOperationsSessionRepository.this.indexResolver.resolveIndexesFor(this);
|
||||
this.originalPrincipalName = indexes.get(PRINCIPAL_NAME_INDEX_NAME);
|
||||
if (this.isNew) {
|
||||
this.delta.put(RedisSessionMapper.CREATION_TIME_KEY, cached.getCreationTime().toEpochMilli());
|
||||
this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY,
|
||||
(int) cached.getMaxInactiveInterval().getSeconds());
|
||||
this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, cached.getLastAccessedTime().toEpochMilli());
|
||||
}
|
||||
if (this.isNew || (RedisOperationsSessionRepository.this.saveMode == SaveMode.ALWAYS)) {
|
||||
getAttributeNames().forEach((attributeName) -> this.delta.put(getSessionAttrNameKey(attributeName),
|
||||
cached.getAttribute(attributeName)));
|
||||
}
|
||||
}
|
||||
|
||||
public void setNew(boolean isNew) {
|
||||
@@ -709,7 +713,8 @@ public class RedisOperationsSessionRepository
|
||||
@Override
|
||||
public void setLastAccessedTime(Instant lastAccessedTime) {
|
||||
this.cached.setLastAccessedTime(lastAccessedTime);
|
||||
this.putAndFlush(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli());
|
||||
this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli());
|
||||
flushImmediateIfNecessary();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -744,7 +749,8 @@ public class RedisOperationsSessionRepository
|
||||
@Override
|
||||
public void setMaxInactiveInterval(Duration interval) {
|
||||
this.cached.setMaxInactiveInterval(interval);
|
||||
this.putAndFlush(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) getMaxInactiveInterval().getSeconds());
|
||||
this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) getMaxInactiveInterval().getSeconds());
|
||||
flushImmediateIfNecessary();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -754,7 +760,12 @@ public class RedisOperationsSessionRepository
|
||||
|
||||
@Override
|
||||
public <T> T getAttribute(String attributeName) {
|
||||
return this.cached.getAttribute(attributeName);
|
||||
T attributeValue = this.cached.getAttribute(attributeName);
|
||||
if (attributeValue != null
|
||||
&& RedisOperationsSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) {
|
||||
this.delta.put(getSessionAttrNameKey(attributeName), attributeValue);
|
||||
}
|
||||
return attributeValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -765,13 +776,15 @@ public class RedisOperationsSessionRepository
|
||||
@Override
|
||||
public void setAttribute(String attributeName, Object attributeValue) {
|
||||
this.cached.setAttribute(attributeName, attributeValue);
|
||||
this.putAndFlush(getSessionAttrNameKey(attributeName), attributeValue);
|
||||
this.delta.put(getSessionAttrNameKey(attributeName), attributeValue);
|
||||
flushImmediateIfNecessary();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String attributeName) {
|
||||
this.cached.removeAttribute(attributeName);
|
||||
this.putAndFlush(getSessionAttrNameKey(attributeName), null);
|
||||
this.delta.put(getSessionAttrNameKey(attributeName), null);
|
||||
flushImmediateIfNecessary();
|
||||
}
|
||||
|
||||
private void flushImmediateIfNecessary() {
|
||||
@@ -780,11 +793,6 @@ public class RedisOperationsSessionRepository
|
||||
}
|
||||
}
|
||||
|
||||
private void putAndFlush(String a, Object v) {
|
||||
this.delta.put(a, v);
|
||||
this.flushImmediateIfNecessary();
|
||||
}
|
||||
|
||||
private void save() {
|
||||
saveChangeSessionId();
|
||||
saveDelta();
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.Set;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -52,6 +53,8 @@ public class SimpleRedisOperationsSessionRepository
|
||||
|
||||
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
/**
|
||||
* Create a new {@link SimpleRedisOperationsSessionRepository} instance.
|
||||
* @param sessionRedisOperations the {@link RedisOperations} to use for managing
|
||||
@@ -89,9 +92,20 @@ public class SimpleRedisOperationsSessionRepository
|
||||
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 RedisSession createSession() {
|
||||
RedisSession session = new RedisSession(this.defaultMaxInactiveInterval);
|
||||
MapSession cached = new MapSession();
|
||||
cached.setMaxInactiveInterval(this.defaultMaxInactiveInterval);
|
||||
RedisSession session = new RedisSession(cached, true);
|
||||
session.flushIfRequired();
|
||||
return session;
|
||||
}
|
||||
@@ -120,7 +134,7 @@ public class SimpleRedisOperationsSessionRepository
|
||||
deleteById(sessionId);
|
||||
return null;
|
||||
}
|
||||
return new RedisSession(session);
|
||||
return new RedisSession(session, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,6 +155,10 @@ public class SimpleRedisOperationsSessionRepository
|
||||
return this.keyNamespace + "sessions:" + sessionId;
|
||||
}
|
||||
|
||||
private static String getAttributeKey(String attributeName) {
|
||||
return RedisSessionMapper.ATTRIBUTE_PREFIX + attributeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* An internal {@link Session} implementation used by this {@link SessionRepository}.
|
||||
*/
|
||||
@@ -154,18 +172,20 @@ public class SimpleRedisOperationsSessionRepository
|
||||
|
||||
private String originalSessionId;
|
||||
|
||||
RedisSession(Duration maxInactiveInterval) {
|
||||
this(new MapSession());
|
||||
this.cached.setMaxInactiveInterval(maxInactiveInterval);
|
||||
this.delta.put(RedisSessionMapper.CREATION_TIME_KEY, getCreationTime().toEpochMilli());
|
||||
this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) getMaxInactiveInterval().getSeconds());
|
||||
this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli());
|
||||
this.isNew = true;
|
||||
}
|
||||
|
||||
RedisSession(MapSession cached) {
|
||||
RedisSession(MapSession cached, boolean isNew) {
|
||||
this.cached = cached;
|
||||
this.isNew = isNew;
|
||||
this.originalSessionId = cached.getId();
|
||||
if (this.isNew) {
|
||||
this.delta.put(RedisSessionMapper.CREATION_TIME_KEY, cached.getCreationTime().toEpochMilli());
|
||||
this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY,
|
||||
(int) cached.getMaxInactiveInterval().getSeconds());
|
||||
this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, cached.getLastAccessedTime().toEpochMilli());
|
||||
}
|
||||
if (this.isNew || (SimpleRedisOperationsSessionRepository.this.saveMode == SaveMode.ALWAYS)) {
|
||||
getAttributeNames().forEach((attributeName) -> this.delta.put(getAttributeKey(attributeName),
|
||||
cached.getAttribute(attributeName)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -180,7 +200,12 @@ public class SimpleRedisOperationsSessionRepository
|
||||
|
||||
@Override
|
||||
public <T> T getAttribute(String attributeName) {
|
||||
return this.cached.getAttribute(attributeName);
|
||||
T attributeValue = this.cached.getAttribute(attributeName);
|
||||
if (attributeValue != null
|
||||
&& SimpleRedisOperationsSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) {
|
||||
this.delta.put(getAttributeKey(attributeName), attributeValue);
|
||||
}
|
||||
return attributeValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -191,7 +216,8 @@ public class SimpleRedisOperationsSessionRepository
|
||||
@Override
|
||||
public void setAttribute(String attributeName, Object attributeValue) {
|
||||
this.cached.setAttribute(attributeName, attributeValue);
|
||||
putAttribute(RedisSessionMapper.ATTRIBUTE_PREFIX + attributeName, attributeValue);
|
||||
this.delta.put(getAttributeKey(attributeName), attributeValue);
|
||||
flushIfRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -207,7 +233,8 @@ public class SimpleRedisOperationsSessionRepository
|
||||
@Override
|
||||
public void setLastAccessedTime(Instant lastAccessedTime) {
|
||||
this.cached.setLastAccessedTime(lastAccessedTime);
|
||||
putAttribute(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli());
|
||||
this.delta.put(RedisSessionMapper.LAST_ACCESSED_TIME_KEY, getLastAccessedTime().toEpochMilli());
|
||||
flushIfRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -218,7 +245,8 @@ public class SimpleRedisOperationsSessionRepository
|
||||
@Override
|
||||
public void setMaxInactiveInterval(Duration interval) {
|
||||
this.cached.setMaxInactiveInterval(interval);
|
||||
putAttribute(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) getMaxInactiveInterval().getSeconds());
|
||||
this.delta.put(RedisSessionMapper.MAX_INACTIVE_INTERVAL_KEY, (int) getMaxInactiveInterval().getSeconds());
|
||||
flushIfRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -274,11 +302,6 @@ public class SimpleRedisOperationsSessionRepository
|
||||
this.delta.clear();
|
||||
}
|
||||
|
||||
private void putAttribute(String name, Object value) {
|
||||
this.delta.put(name, value);
|
||||
flushIfRequired();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
||||
@@ -119,4 +120,12 @@ public @interface EnableRedisHttpSession {
|
||||
*/
|
||||
String cleanupCron() default RedisHttpSessionConfiguration.DEFAULT_CLEANUP_CRON;
|
||||
|
||||
/**
|
||||
* Save mode for the session. The default is {@link SaveMode#ON_SET_ATTRIBUTE}, which
|
||||
* only saves changes made to session.
|
||||
* @return the save mode
|
||||
* @since 2.2.0
|
||||
*/
|
||||
SaveMode saveMode() default SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
|
||||
@@ -86,6 +87,8 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
||||
|
||||
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
private String cleanupCron = DEFAULT_CLEANUP_CRON;
|
||||
|
||||
private ConfigureRedisAction configureRedisAction = new ConfigureNotifyKeyspaceEventsAction();
|
||||
@@ -117,6 +120,7 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
||||
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
|
||||
}
|
||||
sessionRepository.setFlushMode(this.flushMode);
|
||||
sessionRepository.setSaveMode(this.saveMode);
|
||||
int database = resolveDatabase();
|
||||
sessionRepository.setDatabase(database);
|
||||
return sessionRepository;
|
||||
@@ -165,6 +169,10 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
||||
this.flushMode = flushMode;
|
||||
}
|
||||
|
||||
public void setSaveMode(SaveMode saveMode) {
|
||||
this.saveMode = saveMode;
|
||||
}
|
||||
|
||||
public void setCleanupCron(String cleanupCron) {
|
||||
this.cleanupCron = cleanupCron;
|
||||
}
|
||||
@@ -240,6 +248,7 @@ public class RedisHttpSessionConfiguration extends SpringHttpSessionConfiguratio
|
||||
flushMode = redisFlushMode.getFlushMode();
|
||||
}
|
||||
this.flushMode = flushMode;
|
||||
this.saveMode = attributes.getEnum("saveMode");
|
||||
String cleanupCron = attributes.getString("cleanupCron");
|
||||
if (StringUtils.hasText(cleanupCron)) {
|
||||
this.cleanupCron = cleanupCron;
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.ReactiveSessionRepository;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.config.annotation.web.server.EnableSpringWebSession;
|
||||
import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository;
|
||||
@@ -98,4 +99,12 @@ public @interface EnableRedisWebSession {
|
||||
@Deprecated
|
||||
RedisFlushMode redisFlushMode() default RedisFlushMode.ON_SAVE;
|
||||
|
||||
/**
|
||||
* Save mode for the session. The default is {@link SaveMode#ON_SET_ATTRIBUTE}, which
|
||||
* only saves changes made to session.
|
||||
* @return the save mode
|
||||
* @since 2.2.0
|
||||
*/
|
||||
SaveMode saveMode() default SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.config.annotation.web.server.SpringWebSessionConfiguration;
|
||||
import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
@@ -61,6 +62,8 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
|
||||
|
||||
private String redisNamespace = ReactiveRedisOperationsSessionRepository.DEFAULT_NAMESPACE;
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
private ReactiveRedisConnectionFactory redisConnectionFactory;
|
||||
|
||||
private RedisSerializer<Object> defaultRedisSerializer;
|
||||
@@ -78,6 +81,7 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
|
||||
if (StringUtils.hasText(this.redisNamespace)) {
|
||||
sessionRepository.setRedisKeyNamespace(this.redisNamespace);
|
||||
}
|
||||
sessionRepository.setSaveMode(this.saveMode);
|
||||
return sessionRepository;
|
||||
}
|
||||
|
||||
@@ -94,6 +98,10 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
|
||||
Assert.notNull(redisFlushMode, "redisFlushMode cannot be null");
|
||||
}
|
||||
|
||||
public void setSaveMode(SaveMode saveMode) {
|
||||
this.saveMode = saveMode;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setRedisConnectionFactory(
|
||||
@SpringSessionRedisConnectionFactory ObjectProvider<ReactiveRedisConnectionFactory> springSessionRedisConnectionFactory,
|
||||
@@ -132,6 +140,7 @@ public class RedisWebSessionConfiguration extends SpringWebSessionConfiguration
|
||||
if (StringUtils.hasText(redisNamespaceValue)) {
|
||||
this.redisNamespace = this.embeddedValueResolver.resolveStringValue(redisNamespaceValue);
|
||||
}
|
||||
this.saveMode = attributes.getEnum("saveMode");
|
||||
}
|
||||
|
||||
private ReactiveRedisTemplate<String, Object> createReactiveRedisTemplate() {
|
||||
|
||||
@@ -32,6 +32,7 @@ import reactor.test.StepVerifier;
|
||||
import org.springframework.data.redis.core.ReactiveHashOperations;
|
||||
import org.springframework.data.redis.core.ReactiveRedisOperations;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository.RedisSession;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
@@ -130,7 +131,7 @@ class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.expire(anyString(), any())).willReturn(Mono.just(true));
|
||||
|
||||
RedisSession newSession = this.repository.new RedisSession();
|
||||
RedisSession newSession = this.repository.new RedisSession(new MapSession(), true);
|
||||
StepVerifier.create(this.repository.save(newSession)).verifyComplete();
|
||||
|
||||
verify(this.redisOperations).opsForHash();
|
||||
@@ -154,7 +155,7 @@ class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.expire(anyString(), any())).willReturn(Mono.just(true));
|
||||
|
||||
RedisSession session = this.repository.new RedisSession(new MapSession(this.cached));
|
||||
RedisSession session = this.repository.new RedisSession(this.cached, false);
|
||||
|
||||
StepVerifier.create(this.repository.save(session)).verifyComplete();
|
||||
|
||||
@@ -170,7 +171,7 @@ class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.expire(anyString(), any())).willReturn(Mono.just(true));
|
||||
|
||||
RedisSession session = this.repository.new RedisSession(this.cached);
|
||||
RedisSession session = this.repository.new RedisSession(this.cached, false);
|
||||
session.setLastAccessedTime(Instant.ofEpochMilli(12345678L));
|
||||
StepVerifier.create(this.repository.save(session)).verifyComplete();
|
||||
|
||||
@@ -193,7 +194,7 @@ class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
given(this.redisOperations.expire(anyString(), any())).willReturn(Mono.just(true));
|
||||
|
||||
String attrName = "attrName";
|
||||
RedisSession session = this.repository.new RedisSession(this.cached);
|
||||
RedisSession session = this.repository.new RedisSession(this.cached, false);
|
||||
session.setAttribute(attrName, "attrValue");
|
||||
StepVerifier.create(this.repository.save(session)).verifyComplete();
|
||||
|
||||
@@ -216,7 +217,7 @@ class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
given(this.redisOperations.expire(anyString(), any())).willReturn(Mono.just(true));
|
||||
|
||||
String attrName = "attrName";
|
||||
RedisSession session = this.repository.new RedisSession(new MapSession());
|
||||
RedisSession session = this.repository.new RedisSession(new MapSession(), false);
|
||||
session.removeAttribute(attrName);
|
||||
StepVerifier.create(this.repository.save(session)).verifyComplete();
|
||||
|
||||
@@ -234,7 +235,7 @@ class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
@Test
|
||||
void redisSessionGetAttributes() {
|
||||
String attrName = "attrName";
|
||||
RedisSession session = this.repository.new RedisSession(this.cached);
|
||||
RedisSession session = this.repository.new RedisSession(this.cached, false);
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
|
||||
session.setAttribute(attrName, "attrValue");
|
||||
@@ -325,7 +326,7 @@ class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
|
||||
@Test // gh-1120
|
||||
void getAttributeNamesAndRemove() {
|
||||
RedisSession session = this.repository.new RedisSession(this.cached);
|
||||
RedisSession session = this.repository.new RedisSession(this.cached, false);
|
||||
session.setAttribute("attribute1", "value1");
|
||||
session.setAttribute("attribute2", "value2");
|
||||
|
||||
@@ -336,6 +337,78 @@ class ReactiveRedisOperationsSessionRepositoryTests {
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveWithSaveModeOnSetAttribute() {
|
||||
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
|
||||
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.expire(anyString(), any())).willReturn(Mono.just(true));
|
||||
this.repository.setSaveMode(SaveMode.ON_SET_ATTRIBUTE);
|
||||
MapSession delegate = new MapSession();
|
||||
delegate.setAttribute("attribute1", "value1");
|
||||
delegate.setAttribute("attribute2", "value2");
|
||||
delegate.setAttribute("attribute3", "value3");
|
||||
RedisSession session = this.repository.new RedisSession(delegate, false);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
StepVerifier.create(this.repository.save(session)).verifyComplete();
|
||||
verify(this.redisOperations).hasKey(anyString());
|
||||
verify(this.redisOperations).opsForHash();
|
||||
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
||||
assertThat(this.delta.getValue()).hasSize(1);
|
||||
verify(this.redisOperations).expire(anyString(), any());
|
||||
verifyZeroInteractions(this.redisOperations);
|
||||
verifyZeroInteractions(this.hashOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveWithSaveModeOnGetAttribute() {
|
||||
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
|
||||
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.expire(anyString(), any())).willReturn(Mono.just(true));
|
||||
this.repository.setSaveMode(SaveMode.ON_GET_ATTRIBUTE);
|
||||
MapSession delegate = new MapSession();
|
||||
delegate.setAttribute("attribute1", "value1");
|
||||
delegate.setAttribute("attribute2", "value2");
|
||||
delegate.setAttribute("attribute3", "value3");
|
||||
RedisSession session = this.repository.new RedisSession(delegate, false);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
StepVerifier.create(this.repository.save(session)).verifyComplete();
|
||||
verify(this.redisOperations).hasKey(anyString());
|
||||
verify(this.redisOperations).opsForHash();
|
||||
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
||||
assertThat(this.delta.getValue()).hasSize(2);
|
||||
verify(this.redisOperations).expire(anyString(), any());
|
||||
verifyZeroInteractions(this.redisOperations);
|
||||
verifyZeroInteractions(this.hashOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveWithSaveModeAlways() {
|
||||
given(this.redisOperations.hasKey(anyString())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.opsForHash()).willReturn(this.hashOperations);
|
||||
given(this.hashOperations.putAll(anyString(), any())).willReturn(Mono.just(true));
|
||||
given(this.redisOperations.expire(anyString(), any())).willReturn(Mono.just(true));
|
||||
this.repository.setSaveMode(SaveMode.ALWAYS);
|
||||
MapSession delegate = new MapSession();
|
||||
delegate.setAttribute("attribute1", "value1");
|
||||
delegate.setAttribute("attribute2", "value2");
|
||||
delegate.setAttribute("attribute3", "value3");
|
||||
RedisSession session = this.repository.new RedisSession(delegate, false);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
StepVerifier.create(this.repository.save(session)).verifyComplete();
|
||||
verify(this.redisOperations).hasKey(anyString());
|
||||
verify(this.redisOperations).opsForHash();
|
||||
verify(this.hashOperations).putAll(anyString(), this.delta.capture());
|
||||
assertThat(this.delta.getValue()).hasSize(3);
|
||||
verify(this.redisOperations).expire(anyString(), any());
|
||||
verifyZeroInteractions(this.redisOperations);
|
||||
verifyZeroInteractions(this.hashOperations);
|
||||
}
|
||||
|
||||
private Map<String, Object> map(Object... objects) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
if (objects == null) {
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.data.redis.RedisOperationsSessionRepository.RedisSession;
|
||||
import org.springframework.session.events.AbstractSessionEvent;
|
||||
@@ -146,7 +147,7 @@ class RedisOperationsSessionRepositoryTests {
|
||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||
|
||||
RedisSession session = this.redisRepository.new RedisSession(this.cached);
|
||||
RedisSession session = this.redisRepository.new RedisSession(this.cached, false);
|
||||
session.setLastAccessedTime(session.getLastAccessedTime());
|
||||
String originalId = session.getId();
|
||||
String changeSessionId = session.changeSessionId();
|
||||
@@ -194,7 +195,7 @@ class RedisOperationsSessionRepositoryTests {
|
||||
// gh-467
|
||||
@Test
|
||||
void saveSessionNothingChanged() {
|
||||
RedisSession session = this.redisRepository.new RedisSession(this.cached);
|
||||
RedisSession session = this.redisRepository.new RedisSession(this.cached, false);
|
||||
|
||||
this.redisRepository.save(session);
|
||||
|
||||
@@ -230,7 +231,7 @@ class RedisOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
void saveJavadoc() {
|
||||
RedisSession session = this.redisRepository.new RedisSession(this.cached);
|
||||
RedisSession session = this.redisRepository.new RedisSession(this.cached, false);
|
||||
session.setLastAccessedTime(session.getLastAccessedTime());
|
||||
|
||||
given(this.redisOperations.boundHashOps("spring:session:sessions:session-id"))
|
||||
@@ -252,7 +253,7 @@ class RedisOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
void saveLastAccessChanged() {
|
||||
RedisSession session = this.redisRepository.new RedisSession(new MapSession(this.cached));
|
||||
RedisSession session = this.redisRepository.new RedisSession(this.cached, false);
|
||||
session.setLastAccessedTime(Instant.ofEpochMilli(12345678L));
|
||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||
@@ -267,7 +268,7 @@ class RedisOperationsSessionRepositoryTests {
|
||||
@Test
|
||||
void saveSetAttribute() {
|
||||
String attrName = "attrName";
|
||||
RedisSession session = this.redisRepository.new RedisSession(new MapSession());
|
||||
RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false);
|
||||
session.setAttribute(attrName, "attrValue");
|
||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||
@@ -282,7 +283,7 @@ class RedisOperationsSessionRepositoryTests {
|
||||
@Test
|
||||
void saveRemoveAttribute() {
|
||||
String attrName = "attrName";
|
||||
RedisSession session = this.redisRepository.new RedisSession(new MapSession());
|
||||
RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false);
|
||||
session.removeAttribute(attrName);
|
||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||
@@ -295,7 +296,7 @@ class RedisOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
void saveExpired() {
|
||||
RedisSession session = this.redisRepository.new RedisSession(new MapSession());
|
||||
RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false);
|
||||
session.setMaxInactiveInterval(Duration.ZERO);
|
||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||
@@ -310,8 +311,7 @@ class RedisOperationsSessionRepositoryTests {
|
||||
@Test
|
||||
void redisSessionGetAttributes() {
|
||||
String attrName = "attrName";
|
||||
RedisSession session = this.redisRepository.new RedisSession(
|
||||
Duration.ofSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS));
|
||||
RedisSession session = this.redisRepository.createSession();
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
session.setAttribute(attrName, "attrValue");
|
||||
assertThat(session.getAttributeNames()).containsOnly(attrName);
|
||||
@@ -746,7 +746,7 @@ class RedisOperationsSessionRepositoryTests {
|
||||
void changeRedisNamespace() {
|
||||
String namespace = "foo:bar";
|
||||
this.redisRepository.setRedisKeyNamespace(namespace);
|
||||
RedisSession session = this.redisRepository.new RedisSession(new MapSession());
|
||||
RedisSession session = this.redisRepository.new RedisSession(new MapSession(), false);
|
||||
session.setMaxInactiveInterval(Duration.ZERO);
|
||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||
@@ -772,7 +772,7 @@ class RedisOperationsSessionRepositoryTests {
|
||||
|
||||
@Test // gh-1120
|
||||
void getAttributeNamesAndRemove() {
|
||||
RedisSession session = this.redisRepository.new RedisSession(this.cached);
|
||||
RedisSession session = this.redisRepository.new RedisSession(this.cached, false);
|
||||
session.setAttribute("attribute1", "value1");
|
||||
session.setAttribute("attribute2", "value2");
|
||||
|
||||
@@ -836,6 +836,57 @@ class RedisOperationsSessionRepositoryTests {
|
||||
verifyZeroInteractions(this.publisher);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveWithSaveModeOnSetAttribute() {
|
||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||
this.redisRepository.setSaveMode(SaveMode.ON_SET_ATTRIBUTE);
|
||||
MapSession delegate = new MapSession();
|
||||
delegate.setAttribute("attribute1", "value1");
|
||||
delegate.setAttribute("attribute2", "value2");
|
||||
delegate.setAttribute("attribute3", "value3");
|
||||
RedisSession session = this.redisRepository.new RedisSession(delegate, false);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.redisRepository.save(session);
|
||||
assertThat(getDelta()).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveWithSaveModeOnGetAttribute() {
|
||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||
this.redisRepository.setSaveMode(SaveMode.ON_GET_ATTRIBUTE);
|
||||
MapSession delegate = new MapSession();
|
||||
delegate.setAttribute("attribute1", "value1");
|
||||
delegate.setAttribute("attribute2", "value2");
|
||||
delegate.setAttribute("attribute3", "value3");
|
||||
RedisSession session = this.redisRepository.new RedisSession(delegate, false);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.redisRepository.save(session);
|
||||
assertThat(getDelta()).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveWithSaveModeAlways() {
|
||||
given(this.redisOperations.boundHashOps(anyString())).willReturn(this.boundHashOperations);
|
||||
given(this.redisOperations.boundSetOps(anyString())).willReturn(this.boundSetOperations);
|
||||
given(this.redisOperations.boundValueOps(anyString())).willReturn(this.boundValueOperations);
|
||||
this.redisRepository.setSaveMode(SaveMode.ALWAYS);
|
||||
MapSession delegate = new MapSession();
|
||||
delegate.setAttribute("attribute1", "value1");
|
||||
delegate.setAttribute("attribute2", "value2");
|
||||
delegate.setAttribute("attribute3", "value3");
|
||||
RedisSession session = this.redisRepository.new RedisSession(delegate, false);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.redisRepository.save(session);
|
||||
assertThat(getDelta()).hasSize(3);
|
||||
}
|
||||
|
||||
private String getKey(String id) {
|
||||
return "spring:session:sessions:" + id;
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.springframework.data.redis.core.HashOperations;
|
||||
import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.data.redis.SimpleRedisOperationsSessionRepository.RedisSession;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
@@ -125,6 +126,19 @@ class SimpleRedisOperationsSessionRepositoryTests {
|
||||
.withMessage("flushMode must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setSaveMode_ValidSaveMode_ShouldSetSaveMode() {
|
||||
this.sessionRepository.setSaveMode(SaveMode.ON_GET_ATTRIBUTE);
|
||||
assertThat(ReflectionTestUtils.getField(this.sessionRepository, "saveMode"))
|
||||
.isEqualTo(SaveMode.ON_GET_ATTRIBUTE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setSaveMode_NullSaveMode_ShouldThrowException() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.sessionRepository.setSaveMode(null))
|
||||
.withMessage("saveMode must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSession_DefaultMaxInactiveInterval_ShouldCreateSession() {
|
||||
RedisSession redisSession = this.sessionRepository.createSession();
|
||||
@@ -223,6 +237,69 @@ class SimpleRedisOperationsSessionRepositoryTests {
|
||||
verifyNoMoreInteractions(this.sessionHashOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_WithSaveModeOnSetAttribute_SholdSaveSession() {
|
||||
given(this.sessionRedisOperations.hasKey(eq(TEST_SESSION_KEY))).willReturn(true);
|
||||
this.sessionRepository.setSaveMode(SaveMode.ON_SET_ATTRIBUTE);
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
attributes.put("attribute1", "value1");
|
||||
attributes.put("attribute2", "value2");
|
||||
attributes.put("attribute3", "value3");
|
||||
RedisSession session = createTestSession(attributes);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.sessionRepository.save(session);
|
||||
verify(this.sessionRedisOperations).hasKey(eq(TEST_SESSION_KEY));
|
||||
verify(this.sessionRedisOperations).opsForHash();
|
||||
verify(this.sessionRedisOperations).expireAt(eq(TEST_SESSION_KEY), eq(getExpiry(session)));
|
||||
verify(this.sessionHashOperations).putAll(eq(TEST_SESSION_KEY), this.delta.capture());
|
||||
assertThat(this.delta.getValue()).hasSize(1);
|
||||
verifyNoMoreInteractions(this.sessionRedisOperations);
|
||||
verifyNoMoreInteractions(this.sessionHashOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveWithSaveModeOnGetAttribute() {
|
||||
given(this.sessionRedisOperations.hasKey(eq(TEST_SESSION_KEY))).willReturn(true);
|
||||
this.sessionRepository.setSaveMode(SaveMode.ON_GET_ATTRIBUTE);
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
attributes.put("attribute1", "value1");
|
||||
attributes.put("attribute2", "value2");
|
||||
attributes.put("attribute3", "value3");
|
||||
RedisSession session = createTestSession(attributes);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.sessionRepository.save(session);
|
||||
verify(this.sessionRedisOperations).hasKey(eq(TEST_SESSION_KEY));
|
||||
verify(this.sessionRedisOperations).opsForHash();
|
||||
verify(this.sessionRedisOperations).expireAt(eq(TEST_SESSION_KEY), eq(getExpiry(session)));
|
||||
verify(this.sessionHashOperations).putAll(eq(TEST_SESSION_KEY), this.delta.capture());
|
||||
assertThat(this.delta.getValue()).hasSize(2);
|
||||
verifyNoMoreInteractions(this.sessionRedisOperations);
|
||||
verifyNoMoreInteractions(this.sessionHashOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveWithSaveModeAlways() {
|
||||
given(this.sessionRedisOperations.hasKey(eq(TEST_SESSION_KEY))).willReturn(true);
|
||||
this.sessionRepository.setSaveMode(SaveMode.ALWAYS);
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
attributes.put("attribute1", "value1");
|
||||
attributes.put("attribute2", "value2");
|
||||
attributes.put("attribute3", "value3");
|
||||
RedisSession session = createTestSession(attributes);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.sessionRepository.save(session);
|
||||
verify(this.sessionRedisOperations).hasKey(eq(TEST_SESSION_KEY));
|
||||
verify(this.sessionRedisOperations).opsForHash();
|
||||
verify(this.sessionRedisOperations).expireAt(eq(TEST_SESSION_KEY), eq(getExpiry(session)));
|
||||
verify(this.sessionHashOperations).putAll(eq(TEST_SESSION_KEY), this.delta.capture());
|
||||
assertThat(this.delta.getValue()).hasSize(3);
|
||||
verifyNoMoreInteractions(this.sessionRedisOperations);
|
||||
verifyNoMoreInteractions(this.sessionHashOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void save_SessionNotExists_ShouldThrowException() {
|
||||
RedisSession session = createTestSession();
|
||||
@@ -315,12 +392,16 @@ class SimpleRedisOperationsSessionRepositoryTests {
|
||||
return result;
|
||||
}
|
||||
|
||||
private RedisSession createTestSession() {
|
||||
private RedisSession createTestSession(Map<String, Object> attributes) {
|
||||
MapSession cached = new MapSession(TEST_SESSION_ID);
|
||||
cached.setCreationTime(Instant.EPOCH);
|
||||
cached.setLastAccessedTime(Instant.EPOCH);
|
||||
cached.setAttribute("attribute1", "value1");
|
||||
return this.sessionRepository.new RedisSession(cached);
|
||||
attributes.forEach(cached::setAttribute);
|
||||
return this.sessionRepository.new RedisSession(cached, false);
|
||||
}
|
||||
|
||||
private RedisSession createTestSession() {
|
||||
return createTestSession(Collections.singletonMap("attribute1", "value1"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.springframework.data.redis.core.RedisOperations;
|
||||
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.data.redis.RedisFlushMode;
|
||||
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
|
||||
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
|
||||
@@ -142,6 +143,20 @@ class RedisHttpSessionConfigurationTests {
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeAnnotation() {
|
||||
registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionAnnotationConfiguration.class);
|
||||
assertThat(this.context.getBean(RedisOperationsSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeSetter() {
|
||||
registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionSetterConfiguration.class);
|
||||
assertThat(this.context.getBean(RedisOperationsSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void qualifiedConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, QualifiedConnectionFactoryRedisConfig.class);
|
||||
@@ -299,6 +314,20 @@ class RedisHttpSessionConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableRedisHttpSession(saveMode = SaveMode.ALWAYS)
|
||||
static class CustomSaveModeExpressionAnnotationConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomSaveModeExpressionSetterConfiguration extends RedisHttpSessionConfiguration {
|
||||
|
||||
CustomSaveModeExpressionSetterConfiguration() {
|
||||
setSaveMode(SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableRedisHttpSession
|
||||
static class QualifiedConnectionFactoryRedisConfig {
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.ReactiveRedisOperations;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository;
|
||||
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory;
|
||||
import org.springframework.session.data.redis.config.annotation.SpringSessionRedisOperations;
|
||||
@@ -107,6 +108,20 @@ class RedisWebSessionConfigurationTests {
|
||||
.isEqualTo(MAX_INACTIVE_INTERVAL_IN_SECONDS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeAnnotation() {
|
||||
registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionAnnotationConfiguration.class);
|
||||
assertThat(this.context.getBean(ReactiveRedisOperationsSessionRepository.class))
|
||||
.hasFieldOrPropertyWithValue("saveMode", SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeSetter() {
|
||||
registerAndRefresh(RedisConfig.class, CustomSaveModeExpressionSetterConfiguration.class);
|
||||
assertThat(this.context.getBean(ReactiveRedisOperationsSessionRepository.class))
|
||||
.hasFieldOrPropertyWithValue("saveMode", SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void qualifiedConnectionFactoryRedisConfig() {
|
||||
registerAndRefresh(RedisConfig.class, QualifiedConnectionFactoryRedisConfig.class);
|
||||
@@ -249,6 +264,20 @@ class RedisWebSessionConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableRedisWebSession(saveMode = SaveMode.ALWAYS)
|
||||
static class CustomSaveModeExpressionAnnotationConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomSaveModeExpressionSetterConfiguration extends RedisWebSessionConfiguration {
|
||||
|
||||
CustomSaveModeExpressionSetterConfiguration() {
|
||||
setSaveMode(SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableRedisWebSession
|
||||
static class QualifiedConnectionFactoryRedisConfig {
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ 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.events.AbstractSessionEvent;
|
||||
import org.springframework.session.events.SessionCreatedEvent;
|
||||
@@ -147,6 +148,8 @@ public class HazelcastSessionRepository
|
||||
|
||||
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
private IMap<String, MapSession> sessions;
|
||||
|
||||
private String sessionListenerId;
|
||||
@@ -220,13 +223,24 @@ public class HazelcastSessionRepository
|
||||
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 HazelcastSession createSession() {
|
||||
HazelcastSession result = new HazelcastSession();
|
||||
MapSession cached = new MapSession();
|
||||
if (this.defaultMaxInactiveInterval != null) {
|
||||
result.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
|
||||
cached.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
|
||||
}
|
||||
return result;
|
||||
HazelcastSession session = new HazelcastSession(cached, true);
|
||||
session.flushImmediateIfNecessary();
|
||||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -253,7 +267,7 @@ public class HazelcastSessionRepository
|
||||
entryProcessor.setMaxInactiveInterval(session.getMaxInactiveInterval());
|
||||
}
|
||||
if (!session.delta.isEmpty()) {
|
||||
entryProcessor.setDelta(session.delta);
|
||||
entryProcessor.setDelta(new HashMap<>(session.delta));
|
||||
}
|
||||
this.sessions.executeOnKey(session.getId(), entryProcessor);
|
||||
}
|
||||
@@ -274,7 +288,7 @@ public class HazelcastSessionRepository
|
||||
deleteById(saved.getId());
|
||||
return null;
|
||||
}
|
||||
return new HazelcastSession(saved);
|
||||
return new HazelcastSession(saved, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -290,7 +304,7 @@ public class HazelcastSessionRepository
|
||||
Collection<MapSession> sessions = this.sessions.values(Predicates.equal(PRINCIPAL_NAME_ATTRIBUTE, indexValue));
|
||||
Map<String, HazelcastSession> sessionMap = new HashMap<>(sessions.size());
|
||||
for (MapSession session : sessions) {
|
||||
sessionMap.put(session.getId(), new HazelcastSession(session));
|
||||
sessionMap.put(session.getId(), new HazelcastSession(session, false));
|
||||
}
|
||||
return sessionMap;
|
||||
}
|
||||
@@ -347,25 +361,14 @@ public class HazelcastSessionRepository
|
||||
|
||||
private Map<String, Object> delta = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Creates a new instance ensuring to mark all of the new attributes to be
|
||||
* persisted in the next save operation.
|
||||
*/
|
||||
HazelcastSession() {
|
||||
this(new MapSession());
|
||||
this.isNew = true;
|
||||
flushImmediateIfNecessary();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance from the provided {@link MapSession}.
|
||||
* @param cached the {@link MapSession} that represents the persisted session that
|
||||
* was retrieved. Cannot be {@code null}.
|
||||
*/
|
||||
HazelcastSession(MapSession cached) {
|
||||
Assert.notNull(cached, "MapSession cannot be null");
|
||||
HazelcastSession(MapSession cached, boolean isNew) {
|
||||
this.delegate = cached;
|
||||
this.isNew = isNew;
|
||||
this.originalId = cached.getId();
|
||||
if (this.isNew || (HazelcastSessionRepository.this.saveMode == SaveMode.ALWAYS)) {
|
||||
getAttributeNames()
|
||||
.forEach((attributeName) -> this.delta.put(attributeName, cached.getAttribute(attributeName)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -416,7 +419,11 @@ public class HazelcastSessionRepository
|
||||
|
||||
@Override
|
||||
public <T> T getAttribute(String attributeName) {
|
||||
return this.delegate.getAttribute(attributeName);
|
||||
T attributeValue = this.delegate.getAttribute(attributeName);
|
||||
if (attributeValue != null && HazelcastSessionRepository.this.saveMode.equals(SaveMode.ON_GET_ATTRIBUTE)) {
|
||||
this.delta.put(attributeName, attributeValue);
|
||||
}
|
||||
return attributeValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,6 +28,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.session.SessionRepository;
|
||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
||||
@@ -113,4 +114,12 @@ public @interface EnableHazelcastHttpSession {
|
||||
*/
|
||||
FlushMode flushMode() default FlushMode.ON_SAVE;
|
||||
|
||||
/**
|
||||
* Save mode for the session. The default is {@link SaveMode#ON_SET_ATTRIBUTE}, which
|
||||
* only saves changes made to session.
|
||||
* @return the save mode
|
||||
* @since 2.2.0
|
||||
*/
|
||||
SaveMode saveMode() default SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
||||
import org.springframework.session.hazelcast.HazelcastFlushMode;
|
||||
import org.springframework.session.hazelcast.HazelcastSessionRepository;
|
||||
@@ -56,6 +57,8 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur
|
||||
|
||||
private FlushMode flushMode = FlushMode.ON_SAVE;
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
private HazelcastInstance hazelcastInstance;
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
@@ -69,6 +72,7 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur
|
||||
}
|
||||
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
|
||||
sessionRepository.setFlushMode(this.flushMode);
|
||||
sessionRepository.setSaveMode(this.saveMode);
|
||||
return sessionRepository;
|
||||
}
|
||||
|
||||
@@ -89,6 +93,10 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur
|
||||
this.flushMode = flushMode;
|
||||
}
|
||||
|
||||
public void setSaveMode(SaveMode saveMode) {
|
||||
this.saveMode = saveMode;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setHazelcastInstance(
|
||||
@SpringSessionHazelcastInstance ObjectProvider<HazelcastInstance> springSessionHazelcastInstance,
|
||||
@@ -122,6 +130,7 @@ public class HazelcastHttpSessionConfiguration extends SpringHttpSessionConfigur
|
||||
flushMode = hazelcastFlushMode.getFlushMode();
|
||||
}
|
||||
this.flushMode = flushMode;
|
||||
this.saveMode = attributes.getEnum("saveMode");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.hazelcast.map.listener.MapListener;
|
||||
import com.hazelcast.query.impl.predicates.EqualPredicate;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -37,7 +38,9 @@ import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.hazelcast.HazelcastSessionRepository.HazelcastSession;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
@@ -83,6 +86,12 @@ class HazelcastSessionRepositoryTests {
|
||||
.withMessage("HazelcastInstance must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setSaveModeNull() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setSaveMode(null))
|
||||
.withMessage("saveMode must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSessionDefaultMaxInactiveInterval() {
|
||||
verify(this.sessions, times(1)).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
@@ -401,4 +410,61 @@ class HazelcastSessionRepositoryTests {
|
||||
assertThat(session.getAttributeNames()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void saveWithSaveModeOnSetAttribute() {
|
||||
verify(this.sessions).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
this.repository.setSaveMode(SaveMode.ON_SET_ATTRIBUTE);
|
||||
MapSession delegate = new MapSession();
|
||||
delegate.setAttribute("attribute1", "value1");
|
||||
delegate.setAttribute("attribute2", "value2");
|
||||
delegate.setAttribute("attribute3", "value3");
|
||||
HazelcastSession session = this.repository.new HazelcastSession(delegate, false);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.repository.save(session);
|
||||
ArgumentCaptor<SessionUpdateEntryProcessor> captor = ArgumentCaptor.forClass(SessionUpdateEntryProcessor.class);
|
||||
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
||||
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(1);
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void saveWithSaveModeOnGetAttribute() {
|
||||
verify(this.sessions).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
this.repository.setSaveMode(SaveMode.ON_GET_ATTRIBUTE);
|
||||
MapSession delegate = new MapSession();
|
||||
delegate.setAttribute("attribute1", "value1");
|
||||
delegate.setAttribute("attribute2", "value2");
|
||||
delegate.setAttribute("attribute3", "value3");
|
||||
HazelcastSession session = this.repository.new HazelcastSession(delegate, false);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.repository.save(session);
|
||||
ArgumentCaptor<SessionUpdateEntryProcessor> captor = ArgumentCaptor.forClass(SessionUpdateEntryProcessor.class);
|
||||
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
||||
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(2);
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void saveWithSaveModeAlways() {
|
||||
verify(this.sessions).addEntryListener(any(MapListener.class), anyBoolean());
|
||||
this.repository.setSaveMode(SaveMode.ALWAYS);
|
||||
MapSession delegate = new MapSession();
|
||||
delegate.setAttribute("attribute1", "value1");
|
||||
delegate.setAttribute("attribute2", "value2");
|
||||
delegate.setAttribute("attribute3", "value3");
|
||||
HazelcastSession session = this.repository.new HazelcastSession(delegate, false);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.repository.save(session);
|
||||
ArgumentCaptor<SessionUpdateEntryProcessor> captor = ArgumentCaptor.forClass(SessionUpdateEntryProcessor.class);
|
||||
verify(this.sessions).executeOnKey(eq(session.getId()), captor.capture());
|
||||
assertThat((Map<String, Object>) ReflectionTestUtils.getField(captor.getValue(), "delta")).hasSize(3);
|
||||
verifyZeroInteractions(this.sessions);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.session.FlushMode;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.hazelcast.HazelcastFlushMode;
|
||||
import org.springframework.session.hazelcast.HazelcastSessionRepository;
|
||||
import org.springframework.session.hazelcast.config.annotation.SpringSessionHazelcastInstance;
|
||||
@@ -149,6 +150,20 @@ class HazelcastHttpSessionConfigurationTests {
|
||||
assertThat(ReflectionTestUtils.getField(repository, "flushMode")).isEqualTo(FlushMode.IMMEDIATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeAnnotation() {
|
||||
registerAndRefresh(BaseConfiguration.class, CustomSaveModeExpressionAnnotationConfiguration.class);
|
||||
assertThat(this.context.getBean(HazelcastSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeSetter() {
|
||||
registerAndRefresh(BaseConfiguration.class, CustomSaveModeExpressionSetterConfiguration.class);
|
||||
assertThat(this.context.getBean(HazelcastSessionRepository.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void qualifiedHazelcastInstanceConfiguration() {
|
||||
registerAndRefresh(QualifiedHazelcastInstanceConfiguration.class);
|
||||
@@ -285,6 +300,20 @@ class HazelcastHttpSessionConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableHazelcastHttpSession(saveMode = SaveMode.ALWAYS)
|
||||
static class CustomSaveModeExpressionAnnotationConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomSaveModeExpressionSetterConfiguration extends HazelcastHttpSessionConfiguration {
|
||||
|
||||
CustomSaveModeExpressionSetterConfiguration() {
|
||||
setSaveMode(SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableHazelcastHttpSession(flushMode = FlushMode.IMMEDIATE)
|
||||
static class CustomFlushImmediatelyConfiguration extends BaseConfiguration {
|
||||
|
||||
@@ -51,6 +51,7 @@ import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
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.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
@@ -237,6 +238,8 @@ public class JdbcOperationsSessionRepository
|
||||
|
||||
private LobHandler lobHandler = new DefaultLobHandler();
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
/**
|
||||
* Create a new {@link JdbcOperationsSessionRepository} instance which uses the
|
||||
* provided {@link JdbcOperations} to manage sessions.
|
||||
@@ -383,13 +386,22 @@ public class JdbcOperationsSessionRepository
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
JdbcSession session = new JdbcSession();
|
||||
MapSession delegate = new MapSession();
|
||||
if (this.defaultMaxInactiveInterval != null) {
|
||||
session.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
|
||||
delegate.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval));
|
||||
}
|
||||
return session;
|
||||
return new JdbcSession(delegate, UUID.randomUUID().toString(), true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -708,17 +720,13 @@ public class JdbcOperationsSessionRepository
|
||||
|
||||
private Map<String, DeltaValue> delta = new HashMap<>();
|
||||
|
||||
JdbcSession() {
|
||||
this.delegate = new MapSession();
|
||||
this.isNew = true;
|
||||
this.primaryKey = UUID.randomUUID().toString();
|
||||
}
|
||||
|
||||
JdbcSession(String primaryKey, Session delegate) {
|
||||
Assert.notNull(primaryKey, "primaryKey cannot be null");
|
||||
Assert.notNull(delegate, "Session cannot be null");
|
||||
this.primaryKey = primaryKey;
|
||||
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() {
|
||||
@@ -757,7 +765,15 @@ public class JdbcOperationsSessionRepository
|
||||
@Override
|
||||
public <T> T getAttribute(String attributeName) {
|
||||
Supplier<T> supplier = this.delegate.getAttribute(attributeName);
|
||||
return (supplier != null) ? supplier.get() : null;
|
||||
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
|
||||
@@ -848,7 +864,7 @@ public class JdbcOperationsSessionRepository
|
||||
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(primaryKey, delegate);
|
||||
session = new JdbcSession(delegate, primaryKey, false);
|
||||
}
|
||||
String attributeName = rs.getString("ATTRIBUTE_NAME");
|
||||
if (attributeName != null) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2017 the original author or authors.
|
||||
* 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.
|
||||
@@ -27,6 +27,7 @@ import javax.sql.DataSource;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
|
||||
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
|
||||
import org.springframework.session.web.http.SessionRepositoryFilter;
|
||||
@@ -96,4 +97,12 @@ public @interface EnableJdbcHttpSession {
|
||||
*/
|
||||
String cleanupCron() default JdbcHttpSessionConfiguration.DEFAULT_CLEANUP_CRON;
|
||||
|
||||
/**
|
||||
* Save mode for the session. The default is {@link SaveMode#ON_SET_ATTRIBUTE}, which
|
||||
* only saves changes made to session.
|
||||
* @return the save mode
|
||||
* @since 2.2.0
|
||||
*/
|
||||
SaveMode saveMode() default SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.scheduling.annotation.SchedulingConfigurer;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration;
|
||||
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
|
||||
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
|
||||
@@ -77,6 +78,8 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
|
||||
private String cleanupCron = DEFAULT_CLEANUP_CRON;
|
||||
|
||||
private SaveMode saveMode = SaveMode.ON_SET_ATTRIBUTE;
|
||||
|
||||
private DataSource dataSource;
|
||||
|
||||
private PlatformTransactionManager transactionManager;
|
||||
@@ -100,6 +103,7 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
sessionRepository.setTableName(this.tableName);
|
||||
}
|
||||
sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds);
|
||||
sessionRepository.setSaveMode(this.saveMode);
|
||||
if (this.lobHandler != null) {
|
||||
sessionRepository.setLobHandler(this.lobHandler);
|
||||
}
|
||||
@@ -142,6 +146,10 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
this.cleanupCron = cleanupCron;
|
||||
}
|
||||
|
||||
public void setSaveMode(SaveMode saveMode) {
|
||||
this.saveMode = saveMode;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setDataSource(@SpringSessionDataSource ObjectProvider<DataSource> springSessionDataSource,
|
||||
ObjectProvider<DataSource> dataSource) {
|
||||
@@ -199,6 +207,7 @@ public class JdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration
|
||||
if (StringUtils.hasText(cleanupCron)) {
|
||||
this.cleanupCron = cleanupCron;
|
||||
}
|
||||
this.saveMode = attributes.getEnum("saveMode");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,6 +22,8 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -36,6 +38,7 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.session.FindByIndexNameSessionRepository;
|
||||
import org.springframework.session.MapSession;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.Session;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
@@ -224,6 +227,12 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
.withMessage("conversionService must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void setSaveModeNull() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> this.repository.setSaveMode(null))
|
||||
.withMessage("saveMode must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createSessionDefaultMaxInactiveInterval() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.createSession();
|
||||
@@ -292,8 +301,8 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
void saveUpdatedAddSingleAttribute() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession("primaryKey",
|
||||
new MapSession());
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
|
||||
"primaryKey", false);
|
||||
session.setAttribute("testName", "testValue");
|
||||
|
||||
this.repository.save(session);
|
||||
@@ -307,8 +316,8 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
void saveUpdatedAddMultipleAttributes() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession("primaryKey",
|
||||
new MapSession());
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
|
||||
"primaryKey", false);
|
||||
session.setAttribute("testName1", "testValue1");
|
||||
session.setAttribute("testName2", "testValue2");
|
||||
|
||||
@@ -323,8 +332,8 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
void saveUpdatedModifySingleAttribute() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession("primaryKey",
|
||||
new MapSession());
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
|
||||
"primaryKey", false);
|
||||
session.setAttribute("testName", "testValue");
|
||||
session.clearChangeFlags();
|
||||
session.setAttribute("testName", "testValue");
|
||||
@@ -340,8 +349,8 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
void saveUpdatedModifyMultipleAttributes() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession("primaryKey",
|
||||
new MapSession());
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
|
||||
"primaryKey", false);
|
||||
session.setAttribute("testName1", "testValue1");
|
||||
session.setAttribute("testName2", "testValue2");
|
||||
session.clearChangeFlags();
|
||||
@@ -359,8 +368,8 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
void saveUpdatedRemoveSingleAttribute() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession("primaryKey",
|
||||
new MapSession());
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
|
||||
"primaryKey", false);
|
||||
session.setAttribute("testName", "testValue");
|
||||
session.clearChangeFlags();
|
||||
session.removeAttribute("testName");
|
||||
@@ -376,8 +385,8 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
void saveUpdatedRemoveNonExistingAttribute() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession("primaryKey",
|
||||
new MapSession());
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
|
||||
"primaryKey", false);
|
||||
session.removeAttribute("testName");
|
||||
|
||||
this.repository.save(session);
|
||||
@@ -389,8 +398,8 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
void saveUpdatedRemoveMultipleAttributes() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession("primaryKey",
|
||||
new MapSession());
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
|
||||
"primaryKey", false);
|
||||
session.setAttribute("testName1", "testValue1");
|
||||
session.setAttribute("testName2", "testValue2");
|
||||
session.clearChangeFlags();
|
||||
@@ -408,8 +417,8 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
|
||||
@Test // gh-1070
|
||||
void saveUpdatedAddAndModifyAttribute() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession("primaryKey",
|
||||
new MapSession());
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
|
||||
"primaryKey", false);
|
||||
session.setAttribute("testName", "testValue1");
|
||||
session.setAttribute("testName", "testValue2");
|
||||
|
||||
@@ -424,8 +433,8 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
|
||||
@Test // gh-1070
|
||||
void saveUpdatedAddAndRemoveAttribute() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession("primaryKey",
|
||||
new MapSession());
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
|
||||
"primaryKey", false);
|
||||
session.setAttribute("testName", "testValue");
|
||||
session.removeAttribute("testName");
|
||||
|
||||
@@ -438,8 +447,8 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
|
||||
@Test // gh-1070
|
||||
void saveUpdatedModifyAndRemoveAttribute() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession("primaryKey",
|
||||
new MapSession());
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
|
||||
"primaryKey", false);
|
||||
session.setAttribute("testName", "testValue1");
|
||||
session.clearChangeFlags();
|
||||
session.setAttribute("testName", "testValue2");
|
||||
@@ -456,8 +465,8 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
|
||||
@Test // gh-1070
|
||||
void saveUpdatedRemoveAndAddAttribute() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession("primaryKey",
|
||||
new MapSession());
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
|
||||
"primaryKey", false);
|
||||
session.setAttribute("testName", "testValue1");
|
||||
session.clearChangeFlags();
|
||||
session.removeAttribute("testName");
|
||||
@@ -474,8 +483,8 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
void saveUpdatedLastAccessedTime() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession("primaryKey",
|
||||
new MapSession());
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
|
||||
"primaryKey", false);
|
||||
session.setLastAccessedTime(Instant.now());
|
||||
|
||||
this.repository.save(session);
|
||||
@@ -489,8 +498,8 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
|
||||
@Test
|
||||
void saveUnchanged() {
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession("primaryKey",
|
||||
new MapSession());
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
|
||||
"primaryKey", false);
|
||||
|
||||
this.repository.save(session);
|
||||
|
||||
@@ -516,7 +525,7 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void getSessionExpired() {
|
||||
Session expired = this.repository.new JdbcSession();
|
||||
Session expired = this.repository.createSession();
|
||||
expired.setLastAccessedTime(Instant.now().minusSeconds(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS + 1));
|
||||
given(this.jdbcOperations.query(isA(String.class), isA(PreparedStatementSetter.class),
|
||||
isA(ResultSetExtractor.class))).willReturn(Collections.singletonList(expired));
|
||||
@@ -533,7 +542,7 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void getSessionFound() {
|
||||
Session saved = this.repository.new JdbcSession("primaryKey", new MapSession());
|
||||
Session saved = this.repository.new JdbcSession(new MapSession(), "primaryKey", false);
|
||||
saved.setAttribute("savedName", "savedValue");
|
||||
given(this.jdbcOperations.query(isA(String.class), isA(PreparedStatementSetter.class),
|
||||
isA(ResultSetExtractor.class))).willReturn(Collections.singletonList(saved));
|
||||
@@ -592,10 +601,10 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, "notused",
|
||||
AuthorityUtils.createAuthorityList("ROLE_USER"));
|
||||
List<Session> saved = new ArrayList<>(2);
|
||||
Session saved1 = this.repository.new JdbcSession();
|
||||
Session saved1 = this.repository.createSession();
|
||||
saved1.setAttribute(SPRING_SECURITY_CONTEXT, authentication);
|
||||
saved.add(saved1);
|
||||
Session saved2 = this.repository.new JdbcSession();
|
||||
Session saved2 = this.repository.createSession();
|
||||
saved2.setAttribute(SPRING_SECURITY_CONTEXT, authentication);
|
||||
saved.add(saved2);
|
||||
given(this.jdbcOperations.query(isA(String.class), isA(PreparedStatementSetter.class),
|
||||
@@ -647,8 +656,8 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
@Test
|
||||
void saveUpdatedWithoutTransaction() {
|
||||
this.repository = new JdbcOperationsSessionRepository(this.jdbcOperations);
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession("primaryKey",
|
||||
new MapSession());
|
||||
JdbcOperationsSessionRepository.JdbcSession session = this.repository.new JdbcSession(new MapSession(),
|
||||
"primaryKey", false);
|
||||
session.setLastAccessedTime(Instant.now());
|
||||
|
||||
this.repository.save(session);
|
||||
@@ -709,6 +718,61 @@ class JdbcOperationsSessionRepositoryTests {
|
||||
verifyZeroInteractions(this.transactionManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveWithSaveModeOnSetAttribute() {
|
||||
this.repository.setSaveMode(SaveMode.ON_SET_ATTRIBUTE);
|
||||
MapSession delegate = new MapSession();
|
||||
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);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.repository.save(session);
|
||||
verify(this.jdbcOperations).update(startsWith("UPDATE SPRING_SESSION_ATTRIBUTES SET"),
|
||||
isA(PreparedStatementSetter.class));
|
||||
verifyZeroInteractions(this.jdbcOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveWithSaveModeOnGetAttribute() {
|
||||
this.repository.setSaveMode(SaveMode.ON_GET_ATTRIBUTE);
|
||||
MapSession delegate = new MapSession();
|
||||
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);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.repository.save(session);
|
||||
ArgumentCaptor<BatchPreparedStatementSetter> captor = ArgumentCaptor
|
||||
.forClass(BatchPreparedStatementSetter.class);
|
||||
verify(this.jdbcOperations).batchUpdate(startsWith("UPDATE SPRING_SESSION_ATTRIBUTES SET"), captor.capture());
|
||||
assertThat(captor.getValue().getBatchSize()).isEqualTo(2);
|
||||
verifyZeroInteractions(this.jdbcOperations);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveWithSaveModeAlways() {
|
||||
this.repository.setSaveMode(SaveMode.ALWAYS);
|
||||
MapSession delegate = new MapSession();
|
||||
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);
|
||||
session.getAttribute("attribute2");
|
||||
session.setAttribute("attribute3", "value4");
|
||||
this.repository.save(session);
|
||||
ArgumentCaptor<BatchPreparedStatementSetter> captor = ArgumentCaptor
|
||||
.forClass(BatchPreparedStatementSetter.class);
|
||||
verify(this.jdbcOperations).batchUpdate(startsWith("UPDATE SPRING_SESSION_ATTRIBUTES SET"), captor.capture());
|
||||
assertThat(captor.getValue().getBatchSize()).isEqualTo(3);
|
||||
verifyZeroInteractions(this.jdbcOperations);
|
||||
}
|
||||
|
||||
private void assertPropagationRequiresNew() {
|
||||
ArgumentCaptor<TransactionDefinition> argument = ArgumentCaptor.forClass(TransactionDefinition.class);
|
||||
verify(this.transactionManager, atLeastOnce()).getTransaction(argument.capture());
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.jdbc.core.JdbcOperations;
|
||||
import org.springframework.jdbc.support.lob.LobHandler;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.session.SaveMode;
|
||||
import org.springframework.session.jdbc.JdbcOperationsSessionRepository;
|
||||
import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
@@ -135,6 +136,20 @@ class JdbcHttpSessionConfigurationTests {
|
||||
assertThat(ReflectionTestUtils.getField(configuration, "cleanupCron")).isEqualTo(CLEANUP_CRON_EXPRESSION);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeAnnotation() {
|
||||
registerAndRefresh(DataSourceConfiguration.class, CustomSaveModeExpressionAnnotationConfiguration.class);
|
||||
assertThat(this.context.getBean(JdbcHttpSessionConfiguration.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void customSaveModeSetter() {
|
||||
registerAndRefresh(DataSourceConfiguration.class, CustomSaveModeExpressionSetterConfiguration.class);
|
||||
assertThat(this.context.getBean(JdbcHttpSessionConfiguration.class)).hasFieldOrPropertyWithValue("saveMode",
|
||||
SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
@Test
|
||||
void qualifiedDataSourceConfiguration() {
|
||||
registerAndRefresh(DataSourceConfiguration.class, QualifiedDataSourceConfiguration.class);
|
||||
@@ -300,6 +315,20 @@ class JdbcHttpSessionConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableJdbcHttpSession(saveMode = SaveMode.ALWAYS)
|
||||
static class CustomSaveModeExpressionAnnotationConfiguration {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomSaveModeExpressionSetterConfiguration extends JdbcHttpSessionConfiguration {
|
||||
|
||||
CustomSaveModeExpressionSetterConfiguration() {
|
||||
setSaveMode(SaveMode.ALWAYS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableJdbcHttpSession
|
||||
static class QualifiedDataSourceConfiguration {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user