Repository: spring-projects/spring-session
Issue: JdbcOperationsSessionRepository ignores lastAccessTime

Session always expires after maxInactiveInterval since creation, because lastAccessTime is not loaded from database column.

Session inactivity is checked using de-serialized object, which is **not** updated using UPDATE_SESSION_LAST_ACCESS_TIME_QUERY executed when there are no changes to session attributes.

Session in deleted by the check session.isExpired() in JdbcOperationsSessionRepository.getSession(String id)


Changed files:
spring-session/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java
spring-session/src/integration-test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryITests.java
spring-session/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java

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 0c0b7221..07588690 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
@@ -37,6 +37,7 @@ import org.springframework.security.core.Authentication;
 import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.session.ExpiringSession;
 import org.springframework.session.FindByIndexNameSessionRepository;
 import org.springframework.session.MapSession;
 import org.springframework.session.Session;
@@ -135,6 +136,26 @@ public class JdbcOperationsSessionRepositoryITests {
 		this.repository.delete(toSave.getId());
 	}
 
+	@Test
+	public void updateLastAccessedTime() {
+		JdbcOperationsSessionRepository.JdbcSession toSave = this.repository
+				.createSession();
+		toSave.setLastAccessedTime(System.currentTimeMillis()
+				- (MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS * 1000 + 1000));
+
+		this.repository.save(toSave);
+
+		long lastAccessedTime = System.currentTimeMillis();
+		toSave.setLastAccessedTime(lastAccessedTime);
+		this.repository.save(toSave);
+
+		ExpiringSession session = this.repository.getSession(toSave.getId());
+
+		assertThat(session).isNotNull();
+		assertThat(session.isExpired()).isFalse();
+		assertThat(session.getLastAccessedTime()).isEqualTo(lastAccessedTime);
+	}
+
 	@Test
 	public void findByPrincipalName() throws Exception {
 		String principalName = "findByPrincipalName" + UUID.randomUUID();
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 cd7b3866..a515cdbf 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
@@ -102,7 +102,7 @@ public class JdbcOperationsSessionRepository implements
 
 	private static final String CREATE_SESSION_QUERY = "INSERT INTO %TABLE_NAME%(SESSION_ID, LAST_ACCESS_TIME, PRINCIPAL_NAME, SESSION_BYTES) VALUES (?, ?, ?, ?)";
 
-	private static final String GET_SESSION_QUERY = "SELECT SESSION_BYTES FROM %TABLE_NAME% WHERE SESSION_ID = ?";
+	private static final String GET_SESSION_QUERY = "SELECT LAST_ACCESS_TIME, SESSION_BYTES FROM %TABLE_NAME% WHERE SESSION_ID = ?";
 
 	private static final String UPDATE_SESSION_QUERY = "UPDATE %TABLE_NAME% SET LAST_ACCESS_TIME = ?, PRINCIPAL_NAME = ?, SESSION_BYTES = ? WHERE SESSION_ID = ?";
 
@@ -110,7 +110,7 @@ public class JdbcOperationsSessionRepository implements
 
 	private static final String DELETE_SESSION_QUERY = "DELETE FROM %TABLE_NAME% WHERE SESSION_ID = ?";
 
-	private static final String LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY = "SELECT SESSION_BYTES FROM %TABLE_NAME% WHERE PRINCIPAL_NAME = ?";
+	private static final String LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY = "SELECT LAST_ACCESS_TIME, SESSION_BYTES FROM %TABLE_NAME% WHERE PRINCIPAL_NAME = ?";
 
 	private static final String DELETE_SESSIONS_BY_LAST_ACCESS_TIME_QUERY = "DELETE FROM %TABLE_NAME% WHERE LAST_ACCESS_TIME < ?";
 
@@ -473,12 +473,14 @@ public class JdbcOperationsSessionRepository implements
 	private class ExpiringSessionMapper implements RowMapper<ExpiringSession> {
 
 		public ExpiringSession mapRow(ResultSet rs, int rowNum) throws SQLException {
-			return (ExpiringSession) JdbcOperationsSessionRepository.this.conversionService
-					.convert(
+			ExpiringSession session = (ExpiringSession) JdbcOperationsSessionRepository
+					.this.conversionService.convert(
 							JdbcOperationsSessionRepository.this.lobHandler
 									.getBlobAsBytes(rs, "SESSION_BYTES"),
 							TypeDescriptor.valueOf(byte[].class),
 							TypeDescriptor.valueOf(ExpiringSession.class));
+			session.setLastAccessedTime(rs.getLong("LAST_ACCESS_TIME"));
+			return session;
 		}
 
 	}
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 d4714b01..b2e5da1f 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
@@ -212,7 +212,6 @@ public class JdbcOperationsSessionRepositoryTests {
 	}
 
 	@Test
-	@SuppressWarnings("unchecked")
 	public void getSessionNotFound() {
 		String sessionId = "testSessionId";
 
@@ -225,10 +224,10 @@ public class JdbcOperationsSessionRepositoryTests {
 	}
 
 	@Test
-	@SuppressWarnings("unchecked")
 	public void getSessionExpired() {
 		MapSession expired = new MapSession();
-		expired.setMaxInactiveIntervalInSeconds(0);
+		expired.setLastAccessedTime(System.currentTimeMillis() -
+				(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS * 1000 + 1000));
 		given(this.jdbcOperations.queryForObject(startsWith("SELECT"),
 				eq(new Object[] { expired.getId() }), isA(RowMapper.class)))
 						.willReturn(expired);
@@ -244,7 +243,6 @@ public class JdbcOperationsSessionRepositoryTests {
 	}
 
 	@Test
-	@SuppressWarnings("unchecked")
 	public void getSessionFound() {
 		MapSession saved = new MapSession();
 		saved.setAttribute("savedName", "savedValue");
@@ -283,7 +281,6 @@ public class JdbcOperationsSessionRepositoryTests {
 	}
 
 	@Test
-	@SuppressWarnings("unchecked")
 	public void findByIndexNameAndIndexValuePrincipalIndexNameNotFound() {
 		String principal = "username";
 
@@ -298,7 +295,6 @@ public class JdbcOperationsSessionRepositoryTests {
 	}
 
 	@Test
-	@SuppressWarnings("unchecked")
 	public void findByIndexNameAndIndexValuePrincipalIndexNameFound() {
 		String principal = "username";
 		Authentication authentication = new UsernamePasswordAuthenticationToken(principal,