Improve result set extraction in JdbcOperationsSessionRepository (#577)

Previously, result set extraction was performed by a `RowMapper` implementation that required scrollable result sets support which caused compatibility issues with some database vendors.

This commit improves result set extraction by using a `ResultSetExtractor` which does not require scrollable result sets.
This commit is contained in:
Vedran Pavić
2016-08-15 20:34:12 +02:00
committed by Rob Winch
parent 9133e337e6
commit 5b70d55a21
2 changed files with 63 additions and 49 deletions

View File

@@ -16,7 +16,6 @@
package org.springframework.session.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -38,14 +37,14 @@ import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.serializer.support.DeserializingConverter;
import org.springframework.core.serializer.support.SerializingConverter;
import org.springframework.dao.DataAccessException;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.scheduling.annotation.Scheduled;
@@ -186,7 +185,8 @@ public class JdbcOperationsSessionRepository implements
private final TransactionOperations transactionOperations;
private final RowMapper<ExpiringSession> mapper = new ExpiringSessionMapper();
private final ResultSetExtractor<List<ExpiringSession>> extractor =
new ExpiringSessionResultSetExtractor();
/**
* The name of database table used by Spring Session to store sessions.
@@ -382,17 +382,15 @@ public class JdbcOperationsSessionRepository implements
public ExpiringSession doInTransaction(TransactionStatus status) {
List<ExpiringSession> sessions = JdbcOperationsSessionRepository.this.jdbcOperations.query(
new PreparedStatementCreator() {
getQuery(GET_SESSION_QUERY),
new PreparedStatementSetter() {
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
PreparedStatement ps = con.prepareStatement(getQuery(GET_SESSION_QUERY),
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
public void setValues(PreparedStatement ps) throws SQLException {
ps.setString(1, id);
return ps;
}
},
JdbcOperationsSessionRepository.this.mapper
JdbcOperationsSessionRepository.this.extractor
);
if (sessions.isEmpty()) {
return null;
@@ -434,18 +432,15 @@ public class JdbcOperationsSessionRepository implements
public List<ExpiringSession> doInTransaction(TransactionStatus status) {
return JdbcOperationsSessionRepository.this.jdbcOperations.query(
new PreparedStatementCreator() {
getQuery(LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY),
new PreparedStatementSetter() {
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
PreparedStatement ps = con.prepareStatement(
getQuery(LIST_SESSIONS_BY_PRINCIPAL_NAME_QUERY),
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
public void setValues(PreparedStatement ps) throws SQLException {
ps.setString(1, indexValue);
return ps;
}
},
JdbcOperationsSessionRepository.this.mapper
JdbcOperationsSessionRepository.this.extractor
);
}
@@ -661,23 +656,34 @@ public class JdbcOperationsSessionRepository implements
}
private class ExpiringSessionMapper implements RowMapper<ExpiringSession> {
private class ExpiringSessionResultSetExtractor
implements ResultSetExtractor<List<ExpiringSession>> {
public ExpiringSession mapRow(ResultSet rs, int rowNum) throws SQLException {
MapSession session = new MapSession(rs.getString("SESSION_ID"));
session.setCreationTime(rs.getLong("CREATION_TIME"));
session.setLastAccessedTime(rs.getLong("LAST_ACCESS_TIME"));
session.setMaxInactiveIntervalInSeconds(rs.getInt("MAX_INACTIVE_INTERVAL"));
String attributeName = rs.getString("ATTRIBUTE_NAME");
if (attributeName != null) {
session.setAttribute(attributeName, deserialize(rs, "ATTRIBUTE_BYTES"));
while (rs.next() && session.getId().equals(rs.getString("SESSION_ID"))) {
session.setAttribute(rs.getString("ATTRIBUTE_NAME"),
deserialize(rs, "ATTRIBUTE_BYTES"));
public List<ExpiringSession> extractData(ResultSet rs) throws SQLException, DataAccessException {
List<ExpiringSession> sessions = new ArrayList<ExpiringSession>();
while (rs.next()) {
String id = rs.getString("SESSION_ID");
MapSession session;
if (sessions.size() > 0 && getLast(sessions).getId().equals(id)) {
session = (MapSession) getLast(sessions);
}
rs.previous();
else {
session = new MapSession(id);
session.setCreationTime(rs.getLong("CREATION_TIME"));
session.setLastAccessedTime(rs.getLong("LAST_ACCESS_TIME"));
session.setMaxInactiveIntervalInSeconds(rs.getInt("MAX_INACTIVE_INTERVAL"));
}
String attributeName = rs.getString("ATTRIBUTE_NAME");
if (attributeName != null) {
session.setAttribute(attributeName, deserialize(rs, "ATTRIBUTE_BYTES"));
}
sessions.add(session);
}
return session;
return sessions;
}
private ExpiringSession getLast(List<ExpiringSession> sessions) {
return sessions.get(sessions.size() - 1);
}
}

View File

@@ -33,9 +33,8 @@ import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
@@ -235,14 +234,17 @@ public class JdbcOperationsSessionRepositoryTests {
@Test
public void getSessionNotFound() {
String sessionId = "testSessionId";
given(this.jdbcOperations.query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class)))
.willReturn(Collections.emptyList());
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.getSession(sessionId);
assertThat(session).isNull();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(
isA(PreparedStatementCreator.class), isA(RowMapper.class));
verify(this.jdbcOperations, times(1)).query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
}
@Test
@@ -250,16 +252,17 @@ public class JdbcOperationsSessionRepositoryTests {
MapSession expired = new MapSession();
expired.setLastAccessedTime(System.currentTimeMillis() -
(MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS * 1000 + 1000));
given(this.jdbcOperations.query(isA(PreparedStatementCreator.class),
isA(RowMapper.class))).willReturn(Collections.singletonList(expired));
given(this.jdbcOperations.query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class)))
.willReturn(Collections.singletonList(expired));
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.getSession(expired.getId());
assertThat(session).isNull();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(
isA(PreparedStatementCreator.class), isA(RowMapper.class));
verify(this.jdbcOperations, times(1)).query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
verify(this.jdbcOperations, times(1)).update(startsWith("DELETE"),
eq(expired.getId()));
}
@@ -268,8 +271,9 @@ public class JdbcOperationsSessionRepositoryTests {
public void getSessionFound() {
MapSession saved = new MapSession();
saved.setAttribute("savedName", "savedValue");
given(this.jdbcOperations.query(isA(PreparedStatementCreator.class),
isA(RowMapper.class))).willReturn(Collections.singletonList(saved));
given(this.jdbcOperations.query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class)))
.willReturn(Collections.singletonList(saved));
JdbcOperationsSessionRepository.JdbcSession session = this.repository
.getSession(saved.getId());
@@ -278,8 +282,8 @@ public class JdbcOperationsSessionRepositoryTests {
assertThat(session.isNew()).isFalse();
assertThat(session.getAttribute("savedName")).isEqualTo("savedValue");
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(
isA(PreparedStatementCreator.class), isA(RowMapper.class));
verify(this.jdbcOperations, times(1)).query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
}
@Test
@@ -306,6 +310,9 @@ public class JdbcOperationsSessionRepositoryTests {
@Test
public void findByIndexNameAndIndexValuePrincipalIndexNameNotFound() {
String principal = "username";
given(this.jdbcOperations.query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class)))
.willReturn(Collections.emptyList());
Map<String, JdbcOperationsSessionRepository.JdbcSession> sessions = this.repository
.findByIndexNameAndIndexValue(
@@ -314,8 +321,8 @@ public class JdbcOperationsSessionRepositoryTests {
assertThat(sessions).isEmpty();
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(
isA(PreparedStatementCreator.class), isA(RowMapper.class));
verify(this.jdbcOperations, times(1)).query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
}
@Test
@@ -330,8 +337,9 @@ public class JdbcOperationsSessionRepositoryTests {
MapSession saved2 = new MapSession();
saved2.setAttribute(SPRING_SECURITY_CONTEXT, authentication);
saved.add(saved2);
given(this.jdbcOperations.query(isA(PreparedStatementCreator.class),
isA(RowMapper.class))).willReturn(saved);
given(this.jdbcOperations.query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class)))
.willReturn(saved);
Map<String, JdbcOperationsSessionRepository.JdbcSession> sessions = this.repository
.findByIndexNameAndIndexValue(
@@ -340,8 +348,8 @@ public class JdbcOperationsSessionRepositoryTests {
assertThat(sessions).hasSize(2);
assertPropagationRequiresNew();
verify(this.jdbcOperations, times(1)).query(
isA(PreparedStatementCreator.class), isA(RowMapper.class));
verify(this.jdbcOperations, times(1)).query(isA(String.class),
isA(PreparedStatementSetter.class), isA(ResultSetExtractor.class));
}
@Test