Skip to content

Commit e08f739

Browse files
committed
Optimize JDBC session cleanup SQL statement
This commit improves session cleanup handling in `JdbcOperationsSessionRepository#cleanUpExpiredSessions` by optimizing the used SQL statement. This is done by calculating the session expiry time when persisting the session, which in turn allows the cleanup SQL statement to be more index-friendly. Closes gh-847
1 parent 274aec1 commit e08f739

File tree

12 files changed

+50
-32
lines changed

12 files changed

+50
-32
lines changed

spring-session-jdbc/src/main/java/org/springframework/session/jdbc/JdbcOperationsSessionRepository.java

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -97,20 +97,22 @@
9797
* <pre class="code">
9898
* CREATE TABLE SPRING_SESSION (
9999
* PRIMARY_ID CHAR(36) NOT NULL,
100-
* SESSION_ID CHAR(36),
100+
* SESSION_ID CHAR(36) NOT NULL,
101101
* CREATION_TIME BIGINT NOT NULL,
102102
* LAST_ACCESS_TIME BIGINT NOT NULL,
103103
* MAX_INACTIVE_INTERVAL INT NOT NULL,
104+
* EXPIRY_TIME BIGINT NOT NULL,
104105
* PRINCIPAL_NAME VARCHAR(100),
105106
* CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
106107
* );
107108
*
108-
* CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (LAST_ACCESS_TIME);
109+
* CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
110+
* CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (EXPIRY_TIME);
109111
*
110112
* CREATE TABLE SPRING_SESSION_ATTRIBUTES (
111113
* SESSION_PRIMARY_ID CHAR(36) NOT NULL,
112-
* ATTRIBUTE_NAME VARCHAR(200),
113-
* ATTRIBUTE_BYTES BYTEA,
114+
* ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
115+
* ATTRIBUTE_BYTES BYTEA NOT NULL,
114116
* CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
115117
* CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
116118
* );
@@ -138,8 +140,8 @@ public class JdbcOperationsSessionRepository implements
138140
private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT";
139141

140142
private static final String CREATE_SESSION_QUERY =
141-
"INSERT INTO %TABLE_NAME%(PRIMARY_ID, SESSION_ID, CREATION_TIME, LAST_ACCESS_TIME, MAX_INACTIVE_INTERVAL, PRINCIPAL_NAME) " +
142-
"VALUES (?, ?, ?, ?, ?, ?)";
143+
"INSERT INTO %TABLE_NAME%(PRIMARY_ID, SESSION_ID, CREATION_TIME, LAST_ACCESS_TIME, MAX_INACTIVE_INTERVAL, EXPIRY_TIME, PRINCIPAL_NAME) " +
144+
"VALUES (?, ?, ?, ?, ?, ?, ?)";
143145

144146
private static final String CREATE_SESSION_ATTRIBUTE_QUERY =
145147
"INSERT INTO %TABLE_NAME%_ATTRIBUTES(SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) " +
@@ -152,7 +154,7 @@ public class JdbcOperationsSessionRepository implements
152154
"WHERE S.SESSION_ID = ?";
153155

154156
private static final String UPDATE_SESSION_QUERY =
155-
"UPDATE %TABLE_NAME% SET SESSION_ID = ?, LAST_ACCESS_TIME = ?, MAX_INACTIVE_INTERVAL = ?, PRINCIPAL_NAME = ? " +
157+
"UPDATE %TABLE_NAME% SET SESSION_ID = ?, LAST_ACCESS_TIME = ?, MAX_INACTIVE_INTERVAL = ?, EXPIRY_TIME = ?, PRINCIPAL_NAME = ? " +
156158
"WHERE PRIMARY_ID = ?";
157159

158160
private static final String UPDATE_SESSION_ATTRIBUTE_QUERY =
@@ -175,9 +177,9 @@ public class JdbcOperationsSessionRepository implements
175177
"LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID " +
176178
"WHERE S.PRINCIPAL_NAME = ?";
177179

178-
private static final String DELETE_SESSIONS_BY_LAST_ACCESS_TIME_QUERY =
180+
private static final String DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY =
179181
"DELETE FROM %TABLE_NAME% " +
180-
"WHERE MAX_INACTIVE_INTERVAL < (? - LAST_ACCESS_TIME) / 1000";
182+
"WHERE EXPIRY_TIME < ?";
181183

182184
private static final Log logger = LogFactory
183185
.getLog(JdbcOperationsSessionRepository.class);
@@ -211,7 +213,7 @@ public class JdbcOperationsSessionRepository implements
211213

212214
private String listSessionsByPrincipalNameQuery;
213215

214-
private String deleteSessionsByLastAccessTimeQuery;
216+
private String deleteSessionsByExpiryTimeQuery;
215217

216218
/**
217219
* If non-null, this value is used to override the default value for
@@ -333,11 +335,11 @@ public void setListSessionsByPrincipalNameQuery(String listSessionsByPrincipalNa
333335

334336
/**
335337
* Set the custom SQL query used to delete the sessions by last access time.
336-
* @param deleteSessionsByLastAccessTimeQuery the SQL query string
338+
* @param deleteSessionsByExpiryTimeQuery the SQL query string
337339
*/
338-
public void setDeleteSessionsByLastAccessTimeQuery(String deleteSessionsByLastAccessTimeQuery) {
339-
Assert.hasText(deleteSessionsByLastAccessTimeQuery, "Query must not be empty");
340-
this.deleteSessionsByLastAccessTimeQuery = deleteSessionsByLastAccessTimeQuery;
340+
public void setDeleteSessionsByExpiryTimeQuery(String deleteSessionsByExpiryTimeQuery) {
341+
Assert.hasText(deleteSessionsByExpiryTimeQuery, "Query must not be empty");
342+
this.deleteSessionsByExpiryTimeQuery = deleteSessionsByExpiryTimeQuery;
341343
}
342344

343345
/**
@@ -385,7 +387,8 @@ protected void doInTransactionWithoutResult(TransactionStatus status) {
385387
ps.setLong(3, session.getCreationTime().toEpochMilli());
386388
ps.setLong(4, session.getLastAccessedTime().toEpochMilli());
387389
ps.setInt(5, (int) session.getMaxInactiveInterval().getSeconds());
388-
ps.setString(6, session.getPrincipalName());
390+
ps.setLong(6, session.getExpiryTime().toEpochMilli());
391+
ps.setString(7, session.getPrincipalName());
389392
});
390393
if (!session.getAttributeNames().isEmpty()) {
391394
final List<String> attributeNames = new ArrayList<>(session.getAttributeNames());
@@ -421,8 +424,9 @@ protected void doInTransactionWithoutResult(TransactionStatus status) {
421424
ps.setString(1, session.getId());
422425
ps.setLong(2, session.getLastAccessedTime().toEpochMilli());
423426
ps.setInt(3, (int) session.getMaxInactiveInterval().getSeconds());
424-
ps.setString(4, session.getPrincipalName());
425-
ps.setString(5, session.primaryKey);
427+
ps.setLong(4, session.getExpiryTime().toEpochMilli());
428+
ps.setString(5, session.getPrincipalName());
429+
ps.setString(6, session.primaryKey);
426430
});
427431
}
428432
Map<String, Object> delta = session.getDelta();
@@ -524,7 +528,7 @@ public Map<String, JdbcSession> findByIndexNameAndIndexValue(String indexName,
524528
public void cleanUpExpiredSessions() {
525529
int deletedCount = this.transactionOperations.execute(transactionStatus ->
526530
JdbcOperationsSessionRepository.this.jdbcOperations.update(
527-
JdbcOperationsSessionRepository.this.deleteSessionsByLastAccessTimeQuery,
531+
JdbcOperationsSessionRepository.this.deleteSessionsByExpiryTimeQuery,
528532
System.currentTimeMillis()));
529533

530534
if (logger.isDebugEnabled()) {
@@ -571,8 +575,8 @@ private void prepareQueries() {
571575
this.deleteSessionQuery = getQuery(DELETE_SESSION_QUERY);
572576
this.listSessionsByPrincipalNameQuery =
573577
getQuery(LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY);
574-
this.deleteSessionsByLastAccessTimeQuery =
575-
getQuery(DELETE_SESSIONS_BY_LAST_ACCESS_TIME_QUERY);
578+
this.deleteSessionsByExpiryTimeQuery =
579+
getQuery(DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY);
576580
}
577581

578582
private void serialize(PreparedStatement ps, int paramIndex, Object attributeValue)
@@ -643,6 +647,10 @@ String getPrincipalName() {
643647
return PRINCIPAL_NAME_RESOLVER.resolvePrincipal(this);
644648
}
645649

650+
Instant getExpiryTime() {
651+
return getLastAccessedTime().plus(getMaxInactiveInterval());
652+
}
653+
646654
public String getId() {
647655
return this.delegate.getId();
648656
}

spring-session-jdbc/src/main/resources/org/springframework/session/jdbc/schema-db2.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ CREATE TABLE SPRING_SESSION (
44
CREATION_TIME BIGINT NOT NULL,
55
LAST_ACCESS_TIME BIGINT NOT NULL,
66
MAX_INACTIVE_INTERVAL INT NOT NULL,
7+
EXPIRY_TIME BIGINT NOT NULL,
78
PRINCIPAL_NAME VARCHAR(100),
89
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
910
);
1011

1112
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
12-
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (LAST_ACCESS_TIME);
13+
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
1314

1415
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
1516
SESSION_PRIMARY_ID CHAR(36) NOT NULL,

spring-session-jdbc/src/main/resources/org/springframework/session/jdbc/schema-derby.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ CREATE TABLE SPRING_SESSION (
44
CREATION_TIME BIGINT NOT NULL,
55
LAST_ACCESS_TIME BIGINT NOT NULL,
66
MAX_INACTIVE_INTERVAL INT NOT NULL,
7+
EXPIRY_TIME BIGINT NOT NULL,
78
PRINCIPAL_NAME VARCHAR(100),
89
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
910
);
1011

1112
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
12-
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (LAST_ACCESS_TIME);
13+
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
1314

1415
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
1516
SESSION_PRIMARY_ID CHAR(36) NOT NULL,

spring-session-jdbc/src/main/resources/org/springframework/session/jdbc/schema-h2.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ CREATE TABLE SPRING_SESSION (
44
CREATION_TIME BIGINT NOT NULL,
55
LAST_ACCESS_TIME BIGINT NOT NULL,
66
MAX_INACTIVE_INTERVAL INT NOT NULL,
7+
EXPIRY_TIME BIGINT NOT NULL,
78
PRINCIPAL_NAME VARCHAR(100),
89
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
910
);
1011

1112
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
12-
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (LAST_ACCESS_TIME);
13+
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
1314

1415
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
1516
SESSION_PRIMARY_ID CHAR(36) NOT NULL,

spring-session-jdbc/src/main/resources/org/springframework/session/jdbc/schema-hsqldb.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ CREATE TABLE SPRING_SESSION (
44
CREATION_TIME BIGINT NOT NULL,
55
LAST_ACCESS_TIME BIGINT NOT NULL,
66
MAX_INACTIVE_INTERVAL INT NOT NULL,
7+
EXPIRY_TIME BIGINT NOT NULL,
78
PRINCIPAL_NAME VARCHAR(100),
89
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
910
);
1011

1112
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
12-
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (LAST_ACCESS_TIME);
13+
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
1314

1415
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
1516
SESSION_PRIMARY_ID CHAR(36) NOT NULL,

spring-session-jdbc/src/main/resources/org/springframework/session/jdbc/schema-mysql.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ CREATE TABLE SPRING_SESSION (
44
CREATION_TIME BIGINT NOT NULL,
55
LAST_ACCESS_TIME BIGINT NOT NULL,
66
MAX_INACTIVE_INTERVAL INT NOT NULL,
7+
EXPIRY_TIME BIGINT NOT NULL,
78
PRINCIPAL_NAME VARCHAR(100),
89
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
910
) ENGINE=InnoDB;
1011

1112
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
12-
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (LAST_ACCESS_TIME);
13+
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
1314

1415
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
1516
SESSION_PRIMARY_ID CHAR(36) NOT NULL,

spring-session-jdbc/src/main/resources/org/springframework/session/jdbc/schema-oracle.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ CREATE TABLE SPRING_SESSION (
44
CREATION_TIME NUMBER(19,0) NOT NULL,
55
LAST_ACCESS_TIME NUMBER(19,0) NOT NULL,
66
MAX_INACTIVE_INTERVAL NUMBER(10,0) NOT NULL,
7+
EXPIRY_TIME NUMBER(19,0) NOT NULL,
78
PRINCIPAL_NAME VARCHAR2(100 CHAR),
89
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
910
);
1011

1112
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
12-
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (LAST_ACCESS_TIME);
13+
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
1314

1415
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
1516
SESSION_PRIMARY_ID CHAR(36) NOT NULL,

spring-session-jdbc/src/main/resources/org/springframework/session/jdbc/schema-postgresql.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ CREATE TABLE SPRING_SESSION (
44
CREATION_TIME BIGINT NOT NULL,
55
LAST_ACCESS_TIME BIGINT NOT NULL,
66
MAX_INACTIVE_INTERVAL INT NOT NULL,
7+
EXPIRY_TIME BIGINT NOT NULL,
78
PRINCIPAL_NAME VARCHAR(100),
89
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
910
);
1011

1112
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
12-
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (LAST_ACCESS_TIME);
13+
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
1314

1415
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
1516
SESSION_PRIMARY_ID CHAR(36) NOT NULL,

spring-session-jdbc/src/main/resources/org/springframework/session/jdbc/schema-sqlite.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ CREATE TABLE SPRING_SESSION (
44
CREATION_TIME INTEGER NOT NULL,
55
LAST_ACCESS_TIME INTEGER NOT NULL,
66
MAX_INACTIVE_INTERVAL INTEGER NOT NULL,
7+
EXPIRY_TIME INTEGER NOT NULL,
78
PRINCIPAL_NAME VARCHAR(100),
89
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
910
);
1011

1112
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
12-
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (LAST_ACCESS_TIME);
13+
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
1314

1415
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
1516
SESSION_PRIMARY_ID CHAR(36) NOT NULL,

spring-session-jdbc/src/main/resources/org/springframework/session/jdbc/schema-sqlserver.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ CREATE TABLE SPRING_SESSION (
44
CREATION_TIME BIGINT NOT NULL,
55
LAST_ACCESS_TIME BIGINT NOT NULL,
66
MAX_INACTIVE_INTERVAL INT NOT NULL,
7+
EXPIRY_TIME BIGINT NOT NULL,
78
PRINCIPAL_NAME VARCHAR(100),
89
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
910
);
1011

1112
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
12-
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (LAST_ACCESS_TIME);
13+
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
1314

1415
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
1516
SESSION_PRIMARY_ID CHAR(36) NOT NULL,

spring-session-jdbc/src/main/resources/org/springframework/session/jdbc/schema-sybase.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ CREATE TABLE SPRING_SESSION (
44
CREATION_TIME BIGINT NOT NULL,
55
LAST_ACCESS_TIME BIGINT NOT NULL,
66
MAX_INACTIVE_INTERVAL INT NOT NULL,
7+
EXPIRY_TIME BIGINT NOT NULL,
78
PRINCIPAL_NAME VARCHAR(100),
89
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
910
) LOCK DATAROWS;
1011

1112
CREATE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
12-
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (LAST_ACCESS_TIME);
13+
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
1314

1415
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
1516
SESSION_PRIMARY_ID CHAR(36) NOT NULL,

spring-session-jdbc/src/test/java/org/springframework/session/jdbc/JdbcOperationsSessionRepositoryTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,15 +274,15 @@ public void setDeleteSessionsByLastAccessTimeQueryNull() {
274274
this.thrown.expect(IllegalArgumentException.class);
275275
this.thrown.expectMessage("Query must not be empty");
276276

277-
this.repository.setDeleteSessionsByLastAccessTimeQuery(null);
277+
this.repository.setDeleteSessionsByExpiryTimeQuery(null);
278278
}
279279

280280
@Test
281281
public void setDeleteSessionsByLastAccessTimeQueryEmpty() {
282282
this.thrown.expect(IllegalArgumentException.class);
283283
this.thrown.expectMessage("Query must not be empty");
284284

285-
this.repository.setDeleteSessionsByLastAccessTimeQuery(" ");
285+
this.repository.setDeleteSessionsByExpiryTimeQuery(" ");
286286
}
287287

288288
@Test

0 commit comments

Comments
 (0)