diff --git a/docs/src/docs/asciidoc/index.adoc b/docs/src/docs/asciidoc/index.adoc index d3fc7f6ac..004a29c3a 100644 --- a/docs/src/docs/asciidoc/index.adoc +++ b/docs/src/docs/asciidoc/index.adoc @@ -1080,6 +1080,7 @@ However, you can override the default `ConversionService` by providing a Bean na By default, this implementation uses `SPRING_SESSION` and `SPRING_SESSION_ATTRIBUTES` tables to store sessions. Note that the table name can be easily customized as already described. In that case the table used to store attributes will be named using the provided table name, suffixed with `_ATTRIBUTES`. +If further customizations are needed, SQL queries used by the repository can be customized using `set*Query` setter methods. In this case you need to manually configure the `sessionRepository` bean. Due to the differences between the various database vendors, especially when it comes to storing binary data, make sure to use SQL script specific to your database. Scripts for most major database vendors are packaged as `org/springframework/session/jdbc/schema-\*.sql`, where `*` is the target database type. diff --git a/spring-session/src/integration-test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryITests.java b/spring-session/src/integration-test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryITests.java index d09e427e0..65a6eae2f 100644 --- a/spring-session/src/integration-test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryITests.java +++ b/spring-session/src/integration-test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryITests.java @@ -18,6 +18,7 @@ import java.util.Map; import java.util.UUID; +import java.util.concurrent.TimeUnit; import javax.sql.DataSource; @@ -505,9 +506,7 @@ public void findByChangedSecurityPrincipalNameReload() throws Exception { } @Test - public void cleanupCleansUpInactiveSessions() { - this.repository.setDefaultMaxInactiveInterval(1800); // 30 minutes - + public void cleanupInactiveSessionsUsingRepositoryDefinedInterval() { JdbcOperationsSessionRepository.JdbcSession session = this.repository .createSession(); @@ -517,32 +516,50 @@ public void cleanupCleansUpInactiveSessions() { this.repository.cleanUpExpiredSessions(); - // verify its not cleaned up immediately after creation assertThat(this.repository.getSession(session.getId())).isNotNull(); long now = System.currentTimeMillis(); - long tenMinutesInMilliseconds = 1000 * 60 * 10; - long thirtyMinutesInMilliseconds = 1000 * 60 * 30; - // set the last accessed time to 10 minutes in the past, session should not get - // cleaned up because it has not been inactive for 30 minutes yet - long tenMinutesInThePast = now - tenMinutesInMilliseconds; - session.setLastAccessedTime(tenMinutesInThePast); + session.setLastAccessedTime(now - TimeUnit.MINUTES.toMillis(10)); this.repository.save(session); + this.repository.cleanUpExpiredSessions(); + + assertThat(this.repository.getSession(session.getId())).isNotNull(); + + session.setLastAccessedTime(now - TimeUnit.MINUTES.toMillis(30)); + this.repository.save(session); + this.repository.cleanUpExpiredSessions(); + + assertThat(this.repository.getSession(session.getId())).isNull(); + } + + // gh-580 + @Test + public void cleanupInactiveSessionsUsingSessionDefinedInterval() { + JdbcOperationsSessionRepository.JdbcSession session = this.repository + .createSession(); + session.setMaxInactiveIntervalInSeconds((int) TimeUnit.MINUTES.toSeconds(45)); + + this.repository.save(session); + + assertThat(this.repository.getSession(session.getId())).isNotNull(); this.repository.cleanUpExpiredSessions(); - // session should still exist assertThat(this.repository.getSession(session.getId())).isNotNull(); - // set the last accessed time to 30 minutes in the past, session should get - // cleaned up as it has been inactive for 30 minutes - long thirtyMinutesInThePast = now - thirtyMinutesInMilliseconds; - session.setLastAccessedTime(thirtyMinutesInThePast); + long now = System.currentTimeMillis(); + + session.setLastAccessedTime(now - TimeUnit.MINUTES.toMillis(40)); this.repository.save(session); + this.repository.cleanUpExpiredSessions(); + + assertThat(this.repository.getSession(session.getId())).isNotNull(); + session.setLastAccessedTime(now - TimeUnit.MINUTES.toMillis(50)); + this.repository.save(session); this.repository.cleanUpExpiredSessions(); - // session should have been cleaned up + assertThat(this.repository.getSession(session.getId())).isNull(); } diff --git a/spring-session/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java b/spring-session/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java index 08dc99cb5..a1d38dc4e 100644 --- a/spring-session/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java +++ b/spring-session/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java @@ -21,7 +21,6 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -177,7 +176,7 @@ public class JdbcOperationsSessionRepository implements private static final String DELETE_SESSIONS_BY_LAST_ACCESS_TIME_QUERY = "DELETE FROM %TABLE_NAME% " + - "WHERE LAST_ACCESS_TIME < ?"; + "WHERE LAST_ACCESS_TIME < ? - MAX_INACTIVE_INTERVAL * 1000"; private static final Log logger = LogFactory .getLog(JdbcOperationsSessionRepository.class); @@ -196,6 +195,24 @@ public class JdbcOperationsSessionRepository implements */ private String tableName = DEFAULT_TABLE_NAME; + private String createSessionQuery; + + private String createSessionAttributeQuery; + + private String getSessionQuery; + + private String updateSessionQuery; + + private String updateSessionAttributeQuery; + + private String deleteSessionAttributeQuery; + + private String deleteSessionQuery; + + private String listSessionsByPrincipalNameQuery; + + private String deleteSessionsByLastAccessTimeQuery; + /** * If non-null, this value is used to override the default value for * {@link JdbcSession#setMaxInactiveIntervalInSeconds(int)}. @@ -229,6 +246,7 @@ public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations, this.jdbcOperations = jdbcOperations; this.transactionOperations = createTransactionTemplate(transactionManager); this.conversionService = createDefaultConversionService(); + prepareQueries(); } /** @@ -238,6 +256,88 @@ public JdbcOperationsSessionRepository(JdbcOperations jdbcOperations, public void setTableName(String tableName) { Assert.hasText(tableName, "Table name must not be empty"); this.tableName = tableName.trim(); + prepareQueries(); + } + + /** + * Set the custom SQL query used to create the session. + * @param createSessionQuery the SQL query string + */ + public void setCreateSessionQuery(String createSessionQuery) { + Assert.hasText(createSessionQuery, "Query must not be empty"); + this.createSessionQuery = createSessionQuery; + } + + /** + * Set the custom SQL query used to create the session attribute. + * @param createSessionAttributeQuery the SQL query string + */ + public void setCreateSessionAttributeQuery(String createSessionAttributeQuery) { + Assert.hasText(createSessionAttributeQuery, "Query must not be empty"); + this.createSessionAttributeQuery = createSessionAttributeQuery; + } + + /** + * Set the custom SQL query used to retrieve the session. + * @param getSessionQuery the SQL query string + */ + public void setGetSessionQuery(String getSessionQuery) { + Assert.hasText(getSessionQuery, "Query must not be empty"); + this.getSessionQuery = getSessionQuery; + } + + /** + * Set the custom SQL query used to update the session. + * @param updateSessionQuery the SQL query string + */ + public void setUpdateSessionQuery(String updateSessionQuery) { + Assert.hasText(updateSessionQuery, "Query must not be empty"); + this.updateSessionQuery = updateSessionQuery; + } + + /** + * Set the custom SQL query used to update the session attribute. + * @param updateSessionAttributeQuery the SQL query string + */ + public void setUpdateSessionAttributeQuery(String updateSessionAttributeQuery) { + Assert.hasText(updateSessionAttributeQuery, "Query must not be empty"); + this.updateSessionAttributeQuery = updateSessionAttributeQuery; + } + + /** + * Set the custom SQL query used to delete the session attribute. + * @param deleteSessionAttributeQuery the SQL query string + */ + public void setDeleteSessionAttributeQuery(String deleteSessionAttributeQuery) { + Assert.hasText(deleteSessionAttributeQuery, "Query must not be empty"); + this.deleteSessionAttributeQuery = deleteSessionAttributeQuery; + } + + /** + * Set the custom SQL query used to delete the session. + * @param deleteSessionQuery the SQL query string + */ + public void setDeleteSessionQuery(String deleteSessionQuery) { + Assert.hasText(deleteSessionQuery, "Query must not be empty"); + this.deleteSessionQuery = deleteSessionQuery; + } + + /** + * Set the custom SQL query used to retrieve the sessions by principal name. + * @param listSessionsByPrincipalNameQuery the SQL query string + */ + public void setListSessionsByPrincipalNameQuery(String listSessionsByPrincipalNameQuery) { + Assert.hasText(listSessionsByPrincipalNameQuery, "Query must not be empty"); + this.listSessionsByPrincipalNameQuery = listSessionsByPrincipalNameQuery; + } + + /** + * Set the custom SQL query used to delete the sessions by last access time. + * @param deleteSessionsByLastAccessTimeQuery the SQL query string + */ + public void setDeleteSessionsByLastAccessTimeQuery(String deleteSessionsByLastAccessTimeQuery) { + Assert.hasText(deleteSessionsByLastAccessTimeQuery, "Query must not be empty"); + this.deleteSessionsByLastAccessTimeQuery = deleteSessionsByLastAccessTimeQuery; } /** @@ -278,7 +378,7 @@ public void save(final JdbcSession session) { protected void doInTransactionWithoutResult(TransactionStatus status) { JdbcOperationsSessionRepository.this.jdbcOperations.update( - getQuery(CREATE_SESSION_QUERY), + JdbcOperationsSessionRepository.this.createSessionQuery, new PreparedStatementSetter() { public void setValues(PreparedStatement ps) throws SQLException { @@ -293,7 +393,7 @@ public void setValues(PreparedStatement ps) throws SQLException { if (!session.getAttributeNames().isEmpty()) { final List attributeNames = new ArrayList(session.getAttributeNames()); JdbcOperationsSessionRepository.this.jdbcOperations.batchUpdate( - getQuery(CREATE_SESSION_ATTRIBUTE_QUERY), + JdbcOperationsSessionRepository.this.createSessionAttributeQuery, new BatchPreparedStatementSetter() { public void setValues(PreparedStatement ps, int i) throws SQLException { @@ -319,7 +419,7 @@ public int getBatchSize() { protected void doInTransactionWithoutResult(TransactionStatus status) { if (session.isChanged()) { JdbcOperationsSessionRepository.this.jdbcOperations.update( - getQuery(UPDATE_SESSION_QUERY), + JdbcOperationsSessionRepository.this.updateSessionQuery, new PreparedStatementSetter() { public void setValues(PreparedStatement ps) @@ -337,7 +437,7 @@ public void setValues(PreparedStatement ps) for (final Map.Entry entry : delta.entrySet()) { if (entry.getValue() == null) { JdbcOperationsSessionRepository.this.jdbcOperations.update( - getQuery(DELETE_SESSION_ATTRIBUTE_QUERY), + JdbcOperationsSessionRepository.this.deleteSessionAttributeQuery, new PreparedStatementSetter() { public void setValues(PreparedStatement ps) throws SQLException { @@ -349,7 +449,7 @@ public void setValues(PreparedStatement ps) throws SQLException { } else { int updatedCount = JdbcOperationsSessionRepository.this.jdbcOperations.update( - getQuery(UPDATE_SESSION_ATTRIBUTE_QUERY), + JdbcOperationsSessionRepository.this.updateSessionAttributeQuery, new PreparedStatementSetter() { public void setValues(PreparedStatement ps) throws SQLException { @@ -361,7 +461,7 @@ public void setValues(PreparedStatement ps) throws SQLException { }); if (updatedCount == 0) { JdbcOperationsSessionRepository.this.jdbcOperations.update( - getQuery(CREATE_SESSION_ATTRIBUTE_QUERY), + JdbcOperationsSessionRepository.this.createSessionAttributeQuery, new PreparedStatementSetter() { public void setValues(PreparedStatement ps) throws SQLException { @@ -387,7 +487,7 @@ public JdbcSession getSession(final String id) { public ExpiringSession doInTransaction(TransactionStatus status) { List sessions = JdbcOperationsSessionRepository.this.jdbcOperations.query( - getQuery(GET_SESSION_QUERY), + JdbcOperationsSessionRepository.this.getSessionQuery, new PreparedStatementSetter() { public void setValues(PreparedStatement ps) throws SQLException { @@ -421,7 +521,7 @@ public void delete(final String id) { protected void doInTransactionWithoutResult(TransactionStatus status) { JdbcOperationsSessionRepository.this.jdbcOperations.update( - getQuery(DELETE_SESSION_QUERY), id); + JdbcOperationsSessionRepository.this.deleteSessionQuery, id); } }); @@ -437,7 +537,7 @@ public Map findByIndexNameAndIndexValue(String indexName, public List doInTransaction(TransactionStatus status) { return JdbcOperationsSessionRepository.this.jdbcOperations.query( - getQuery(LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY), + JdbcOperationsSessionRepository.this.listSessionsByPrincipalNameQuery, new PreparedStatementSetter() { public void setValues(PreparedStatement ps) throws SQLException { @@ -463,24 +563,12 @@ public void setValues(PreparedStatement ps) throws SQLException { @Scheduled(cron = "0 * * * * *") public void cleanUpExpiredSessions() { - long now = System.currentTimeMillis(); - long maxInactiveIntervalSeconds = (this.defaultMaxInactiveInterval != null) - ? this.defaultMaxInactiveInterval - : MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS; - - final long sessionsValidFromTime = now - (maxInactiveIntervalSeconds * 1000); - - if (logger.isDebugEnabled()) { - logger.debug( - "Cleaning up sessions older than " + new Date(sessionsValidFromTime)); - } - int deletedCount = this.transactionOperations.execute(new TransactionCallback() { public Integer doInTransaction(TransactionStatus transactionStatus) { return JdbcOperationsSessionRepository.this.jdbcOperations.update( - getQuery(DELETE_SESSIONS_BY_LAST_ACCESS_TIME_QUERY), - sessionsValidFromTime); + JdbcOperationsSessionRepository.this.deleteSessionsByLastAccessTimeQuery, + System.currentTimeMillis()); } }); @@ -515,10 +603,24 @@ private static GenericConversionService createDefaultConversionService() { return converter; } - protected String getQuery(String base) { + private String getQuery(String base) { return StringUtils.replace(base, "%TABLE_NAME%", this.tableName); } + private void prepareQueries() { + this.createSessionQuery = getQuery(CREATE_SESSION_QUERY); + this.createSessionAttributeQuery = getQuery(CREATE_SESSION_ATTRIBUTE_QUERY); + this.getSessionQuery = getQuery(GET_SESSION_QUERY); + this.updateSessionQuery = getQuery(UPDATE_SESSION_QUERY); + this.updateSessionAttributeQuery = getQuery(UPDATE_SESSION_ATTRIBUTE_QUERY); + this.deleteSessionAttributeQuery = getQuery(DELETE_SESSION_ATTRIBUTE_QUERY); + this.deleteSessionQuery = getQuery(DELETE_SESSION_QUERY); + this.listSessionsByPrincipalNameQuery = + getQuery(LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY); + this.deleteSessionsByLastAccessTimeQuery = + getQuery(DELETE_SESSIONS_BY_LAST_ACCESS_TIME_QUERY); + } + private void serialize(PreparedStatement ps, int paramIndex, Object attributeValue) throws SQLException { this.lobHandler.getLobCreator().setBlobAsBytes(ps, paramIndex, diff --git a/spring-session/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfiguration.java b/spring-session/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfiguration.java index 2eda6dbe5..ccda24425 100644 --- a/spring-session/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfiguration.java +++ b/spring-session/src/main/java/org/springframework/session/jdbc/config/annotation/web/http/JdbcHttpSessionConfiguration.java @@ -15,6 +15,7 @@ */ package org.springframework.session.jdbc.config.annotation.web.http; +import java.util.Arrays; import java.util.Map; import javax.sql.DataSource; @@ -33,6 +34,8 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.JdbcUtils; +import org.springframework.jdbc.support.MetaDataAccessException; import org.springframework.jdbc.support.lob.LobHandler; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration; @@ -80,14 +83,19 @@ public JdbcTemplate springSessionJdbcOperations(DataSource dataSource) { @Bean public JdbcOperationsSessionRepository sessionRepository( - @Qualifier("springSessionJdbcOperations") JdbcOperations jdbcOperations, + @Qualifier("springSessionJdbcOperations") JdbcTemplate jdbcTemplate, PlatformTransactionManager transactionManager) { JdbcOperationsSessionRepository sessionRepository = - new JdbcOperationsSessionRepository(jdbcOperations, transactionManager); + new JdbcOperationsSessionRepository(jdbcTemplate, transactionManager); String tableName = getTableName(); if (StringUtils.hasText(tableName)) { sessionRepository.setTableName(tableName); } + String databaseName = getDatabaseName(jdbcTemplate.getDataSource()); + if (Arrays.asList("Apache Derby", "H2").contains(databaseName)) { + sessionRepository.setDeleteSessionsByLastAccessTimeQuery("DELETE FROM " + tableName + + " WHERE LAST_ACCESS_TIME < ? - CAST(MAX_INACTIVE_INTERVAL AS BIGINT) * 1000"); + } sessionRepository .setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds); if (this.lobHandler != null) { @@ -150,6 +158,17 @@ public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds; } + private String getDatabaseName(DataSource dataSource) { + try { + String databaseProductName = JdbcUtils.extractDatabaseMetaData(dataSource, + "getDatabaseProductName").toString(); + return JdbcUtils.commonDatabaseName(databaseProductName); + } + catch (MetaDataAccessException e) { + return null; + } + } + private String getTableName() { String systemProperty = System.getProperty("spring.session.jdbc.tableName", ""); if (StringUtils.hasText(systemProperty)) { diff --git a/spring-session/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java b/spring-session/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java index f82a79ee5..bfc8ff1df 100644 --- a/spring-session/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java +++ b/spring-session/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java @@ -22,7 +22,6 @@ import javax.sql.DataSource; -import org.assertj.core.data.Offset; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -139,6 +138,150 @@ public void setTableNameEmpty() { this.repository.setTableName(" "); } + @Test + public void setCreateSessionQueryNull() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setCreateSessionQuery(null); + } + + @Test + public void setCreateSessionQueryEmpty() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setCreateSessionQuery(" "); + } + + @Test + public void setCreateSessionAttributeQueryNull() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setCreateSessionAttributeQuery(null); + } + + @Test + public void setCreateSessionAttributeQueryEmpty() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setCreateSessionAttributeQuery(" "); + } + + @Test + public void setGetSessionQueryNull() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setGetSessionQuery(null); + } + + @Test + public void setGetSessionQueryEmpty() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setGetSessionQuery(" "); + } + + @Test + public void setUpdateSessionQueryNull() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setUpdateSessionQuery(null); + } + + @Test + public void setUpdateSessionQueryEmpty() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setUpdateSessionQuery(" "); + } + + @Test + public void setUpdateSessionAttributeQueryNull() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setUpdateSessionAttributeQuery(null); + } + + @Test + public void setUpdateSessionAttributeQueryEmpty() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setUpdateSessionAttributeQuery(" "); + } + + @Test + public void setDeleteSessionAttributeQueryNull() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setDeleteSessionAttributeQuery(null); + } + + @Test + public void setDeleteSessionAttributeQueryEmpty() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setDeleteSessionAttributeQuery(" "); + } + + @Test + public void setDeleteSessionQueryNull() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setDeleteSessionQuery(null); + } + + @Test + public void setDeleteSessionQueryEmpty() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setDeleteSessionQuery(" "); + } + + @Test + public void setListSessionsByPrincipalNameQueryNull() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setListSessionsByPrincipalNameQuery(null); + } + + @Test + public void setListSessionsByPrincipalNameQueryEmpty() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setListSessionsByPrincipalNameQuery(" "); + } + + @Test + public void setDeleteSessionsByLastAccessTimeQueryNull() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setDeleteSessionsByLastAccessTimeQuery(null); + } + + @Test + public void setDeleteSessionsByLastAccessTimeQueryEmpty() { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Query must not be empty"); + + this.repository.setDeleteSessionsByLastAccessTimeQuery(" "); + } + @Test public void setLobHandlerNull() { this.thrown.expect(IllegalArgumentException.class); @@ -380,22 +523,6 @@ public void cleanupExpiredSessions() { verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), anyLong()); } - // gh-564 - @Test - public void cleanupExpiredSessionsNoOverflow() { - long now = System.currentTimeMillis(); - long toDelete = now - (new Long(Integer.MAX_VALUE) * 1000L); - this.repository.setDefaultMaxInactiveInterval(Integer.MAX_VALUE); - - this.repository.cleanUpExpiredSessions(); - - ArgumentCaptor time = ArgumentCaptor.forClass(Long.class); - assertPropagationRequiresNew(); - verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"), - time.capture()); - assertThat(time.getValue()).isCloseTo(toDelete, Offset.offset(5L)); - } - private void assertPropagationRequiresNew() { ArgumentCaptor argument = ArgumentCaptor.forClass(TransactionDefinition.class);