springboot2.1.3+spring-session2.1.4分库处理
作者:互联网
使用spring session框架来统一管理session,该框架支持jdbc、redis存储,使用非常简单,可以去官网查看文档一步步接入即可,
官网文档如下:https://docs.spring.io/spring-session/docs/current/reference/html5/,
不过,我使用的场景官网没有提供方法给予解决,最后,本人只能重写了它的部分源码,来实现分库管理session,好了,上代码。
pom.xml
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-core</artifactId> <version>2.1.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-jdbc</artifactId> <version>2.1.4.RELEASE</version> </dependency> <!-- <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-redis</artifactId> <version>2.1.4.RELEASE</version> </dependency> --> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.2.0</version> </dependency>
application.properties
# spring-session setting, timeout: 2 years spring.session.timeout.setting=63072000 # sharding number, CONFIG_SHARDING_NUM 环境变量名 mydb.server.sharding.num=${CONFIG_SHARDING_NUM:4} # global setting dbs parameter mysql.global.pools.MinimumIdle=1 mysql.global.pools.MaximumPoolSize=20 mysql.global.pools.IdleTimeout=600000 mysql.global.pools.MaxLifetime=1800000 # 这里配置分库信息,这里只是demo,本人配置了4个主库,至于分库分表不在本博客中体现。 # master0 sharding.jdbc.datasource.mainshard0.type=com.zaxxer.hikari.HikariDataSource sharding.jdbc.datasource.mainshard0.driver-class-name=org.mariadb.jdbc.Driver sharding.jdbc.datasource.mainshard0.jdbc-url=jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding-utf8&allowMutiQueries=true sharding.jdbc.datasource.mainshard0.username=xxxx sharding.jdbc.datasource.mainshard0.password=xxxxxx # master1 sharding.jdbc.datasource.mainshard1.type=com.zaxxer.hikari.HikariDataSource sharding.jdbc.datasource.mainshard1.driver-class-name=org.mariadb.jdbc.Driver sharding.jdbc.datasource.mainshard1.jdbc-url=jdbc:mysql://127.0.0.1:3307/demo?useUnicode=true&characterEncoding-utf8&allowMutiQueries=true sharding.jdbc.datasource.mainshard1.username=xxxx sharding.jdbc.datasource.mainshard1.password=xxxxxx # master2 sharding.jdbc.datasource.mainshard2.type=com.zaxxer.hikari.HikariDataSource sharding.jdbc.datasource.mainshard2.driver-class-name=org.mariadb.jdbc.Driver sharding.jdbc.datasource.mainshard2.jdbc-url=jdbc:mysql://127.0.0.1:3308/demo?useUnicode=true&characterEncoding-utf8&allowMutiQueries=true sharding.jdbc.datasource.mainshard2.username=xxxx sharding.jdbc.datasource.mainshard2.password=xxxxxx # master3 sharding.jdbc.datasource.mainshard3.type=com.zaxxer.hikari.HikariDataSource sharding.jdbc.datasource.mainshard3.driver-class-name=org.mariadb.jdbc.Driver sharding.jdbc.datasource.mainshard3.jdbc-url=jdbc:mysql://127.0.0.1:3309/demo?useUnicode=true&characterEncoding-utf8&allowMutiQueries=true sharding.jdbc.datasource.mainshard3.username=xxxx sharding.jdbc.datasource.mainshard3.password=xxxxxx
重写第一个类(源码文件是JdbcHttpSessionConfiguration.java,可以去官网下载),本人重写如下:
package com.szl.demo.spring.session.common.datasource.sessionConfig; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportAware; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.serializer.support.DeserializingConverter; import org.springframework.core.serializer.support.SerializingConverter; import org.springframework.core.type.AnnotationMetadata; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.JdbcUtils; import org.springframework.jdbc.support.MetaDataAccessException; import org.springframework.jdbc.support.lob.DefaultLobHandler; import org.springframework.jdbc.support.lob.LobHandler; 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.config.annotation.web.http.SpringHttpSessionConfiguration; import org.springframework.session.jdbc.JdbcOperationsSessionRepository; import org.springframework.session.jdbc.config.annotation.SpringSessionDataSource; import org.springframework.session.web.http.SessionRepositoryFilter; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.util.StringUtils; import org.springframework.util.StringValueResolver; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; /** * @author Jimmy Shan * @date 2019-06-25 * @desc 重写jdbc session配置类 */ @Configuration @EnableScheduling public class CustomizedJdbcHttpSessionConfiguration extends SpringHttpSessionConfiguration implements BeanClassLoaderAware, EmbeddedValueResolverAware, ImportAware, SchedulingConfigurer { static final String DEFAULT_CLEANUP_CRON = "0 0 0/1 * * *"; private String tableName = JdbcOperationsSessionRepository.DEFAULT_TABLE_NAME; private String cleanupCron = DEFAULT_CLEANUP_CRON; private LobHandler lobHandler; private ConversionService springSessionConversionService; private ConversionService conversionService; private ClassLoader classLoader; private StringValueResolver embeddedValueResolver; //--------------Modify by Jimmy Shan, the date is 2019-06-25 start------// @Value("${mydb.server.sharding.num}") private String shardingNum; @Autowired private Environment env; private Map<Integer, JdbcTemplate> myJdbcTemplateMap = new HashMap<>(); private Map<Integer, PlatformTransactionManager> myDataSourceTransactionMap = new HashMap<>(); private DataSource dataSource; // 时间设置,间隔时间为30秒 private Integer maxInactiveIntervalInSeconds; //--------------Modify by Jimmy Shan, the date is 2019-06-25 end-------// /** * @desc 创建数据源,手动创建,为了后面的分库分表 */ private DataSource convertDataSource(int num) { HikariConfig hkConfig = new HikariConfig(); hkConfig.setDriverClassName("org.mariadb,jdbc.Driver"); hkConfig.setMinimumIdle(env.getProperty("mysql.global.pools.MinimumIdle") == null ? 5 : env.getProperty("mysql.global.pools.MinimumIdle"))); hkConfig.setMaximumPoolSize(env.getProperty("mysql.global.pools.MaximumPoolSize") == null ? 20 : env.getProperty("mysql.global.pools.MaximumPoolSize"))); hkConfig.setIdleTimeout(env.getProperty("mysql.global.pools.IdleTimeout") == null ? 600000 : Integer.parseInt(env.getProperty("mysql.global.pools.IdleTimeout"))); hkConfig.setMaxLifetime(env.getProperty("mysql.global.pools.MaxLifetime") == null ? 1800000 : Integer.parseInt(env.getProperty("mysql.global.pools.MaxLifetime"))); hkConfig.setJdbcUrl(env.getProperty("sharding.jdbc.datasource.mainshard" + num + ".jdbc-url")); hkConfig.setUsername(env.getProperty("sharding.jdbc.datasource.mainshard" + num + ".username")); hkConfig.setPassword(env.getProperty("sharding.jdbc.datasource.mainshard" + num + ".password")); HikariDataSource ds = new HikariDataSource(hkConfig); return ds; } /** * @desc 重写了部分逻辑 */ @Bean public CustomizedJdbcOperationsSessionRepository sessionRepository() { for (int i = 0; i < Integer.parseInt(shardingNum); i++) { DataSource ds = convertDataSource(i); myJdbcTemplateMap.put(i, new JdbcTemplate(ds)); myDataSourceTransactionMap.put(i, new DataSourceTransactionManager(ds)); } this.dataSource = myJdbcTemplateMap.get(0).getDataSource(); CustomizedJdbcOperationsSessionRepository sessionRepository = new JdbcOperationsSessionRepository(myJdbcTemplateMap, myDataSourceTransactionMap); if (StringUtils.hasText(this.tableName)) { sessionRepository.setTableName(this.tableName); } this.setMaxInactiveIntervalInSeconds(Integer.parseInt(env.getProperty("spring.session.timeout.setting"))); sessionRepository.setDefaultMaxInactiveInterval(this.maxInactiveIntervalInSeconds); if (this.lobHandler != null) { sessionRepository.setLobHandler(this.lobHandler); } else if (requiresTemporaryLob(this.dataSource)) { DefaultLobHandler lobHandler = new DefaultLobHandler(); lobHandler.setCreateTemporaryLob(true); sessionRepository.setLobHandler(lobHandler); } if (this.springSessionConversionService != null) { sessionRepository.setConversionService(this.springSessionConversionService); } else if (this.conversionService != null) { sessionRepository.setConversionService(this.conversionService); } else { sessionRepository.setConversionService(createConversionServiceWithBeanClassLoader()); } return sessionRepository; } private static boolean requiresTemporaryLob(DataSource dataSource) { try { String productName = JdbcUtils.extractDatabaseMetaData(dataSource, "getDatabaseProductName"); return "Oracle".equalsIgnoreCase(JdbcUtils.commonDatabaseName(productName)); } catch (MetaDataAccessException ex) { return false; } } public void setMaxInactiveIntervalInSeconds(Integer maxInactiveIntervalInSeconds) { this.maxInactiveIntervalInSeconds = maxInactiveIntervalInSeconds; } public void setTableName(String tableName) { this.tableName = tableName; } public void setCleanupCron(String cleanupCron) { this.cleanupCron = cleanupCron; } @Autowired(required = false) @Qualifier("springSessionLobHandler") public void setLobHandler(LobHandler lobHandler) { this.lobHandler = lobHandler; } @Autowired(required = false) @Qualifier("springSessionConversionService") public void setSpringSessionConversionService(ConversionService conversionService) { this.springSessionConversionService = conversionService; } @Autowired(required = false) @Qualifier("conversionService") public void setConversionService(ConversionService conversionService) { this.conversionService = conversionService; } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { this.embeddedValueResolver = resolver; } @Override public void setImportMetadata(AnnotationMetadata importMetadata) { Map<String, Object> attributeMap = importMetadata .getAnnotationAttributes(EnableJdbcHttpSession.class.getName()); AnnotationAttributes attributes = AnnotationAttributes.fromMap(attributeMap); this.maxInactiveIntervalInSeconds = attributes .getNumber("maxInactiveIntervalInSeconds"); String tableNameValue = attributes.getString("tableName"); if (StringUtils.hasText(tableNameValue)) { this.tableName = this.embeddedValueResolver .resolveStringValue(tableNameValue); } String cleanupCron = attributes.getString("cleanupCron"); if (StringUtils.hasText(cleanupCron)) { this.cleanupCron = cleanupCron; } } @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.addCronTask(() -> sessionRepository().cleanUpExpiredSessions(), this.cleanupCron); } private GenericConversionService createConversionServiceWithBeanClassLoader() { GenericConversionService conversionService = new GenericConversionService(); conversionService.addConverter(Object.class, byte[].class, new SerializingConverter()); conversionService.addConverter(byte[].class, Object.class, new DeserializingConverter(this.classLoader)); return conversionService; } }
重写第二个类(源码文件是JdbcOperationsSessionRepository.java,可以去官网下载),本人重写如下:
package com.szl.demo.spring.session.common.datasource.sessionConfig; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.function.Supplier; import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.convert.ConversionService; 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.ResultSetExtractor; import org.springframework.jdbc.support.lob.DefaultLobHandler; import org.springframework.jdbc.support.lob.LobHandler; import org.springframework.session.FindByIndexNameSessionRepository; import org.springframework.session.MapSession; import org.springframework.session.Session; import org.springframework.session.jdbc.JdbcOperationsSessionRepository; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionException; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionOperations; import org.springframework.transaction.support.TransactionTemplate; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * @author Jimmy Shan * @date 2019-06-25 * @desc 重写jdbc session类 */ public class CustomizedJdbcOperationsSessionRepository implements FindByIndexNameSessionRepository<JdbcOperationsSessionRepository.JdbcSession> { public static final String DEFAULT_TABLE_NAME = "SPRING_SESSION"; private static final String SPRING_SECURITY_CONTEXT = "SPRING_SECURITY_CONTEXT"; private static final String CREATE_SESSION_QUERY = "INSERT INTO %TABLE_NAME%(PRIMARY_ID, SESSION_ID, CREATION_TIME, LAST_ACCESS_TIME, MAX_INACTIVE_INTERVAL, EXPIRY_TIME, PRINCIPAL_NAME) " + "VALUES (?, ?, ?, ?, ?, ?, ?)"; private static final String CREATE_SESSION_ATTRIBUTE_QUERY = "INSERT INTO %TABLE_NAME%_ATTRIBUTES(SESSION_PRIMARY_ID, ATTRIBUTE_NAME, ATTRIBUTE_BYTES) " + "SELECT PRIMARY_ID, ?, ? " + "FROM %TABLE_NAME% " + "WHERE SESSION_ID = ?"; private static final String GET_SESSION_QUERY = "SELECT S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES " + "FROM %TABLE_NAME% S " + "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID " + "WHERE S.SESSION_ID = ?"; private static final String UPDATE_SESSION_QUERY = "UPDATE %TABLE_NAME% SET SESSION_ID = ?, LAST_ACCESS_TIME = ?, MAX_INACTIVE_INTERVAL = ?, EXPIRY_TIME = ?, PRINCIPAL_NAME = ? " + "WHERE PRIMARY_ID = ?"; private static final String UPDATE_SESSION_ATTRIBUTE_QUERY = "UPDATE %TABLE_NAME%_ATTRIBUTES SET ATTRIBUTE_BYTES = ? " + "WHERE SESSION_PRIMARY_ID = ? " + "AND ATTRIBUTE_NAME = ?"; private static final String DELETE_SESSION_ATTRIBUTE_QUERY = "DELETE FROM %TABLE_NAME%_ATTRIBUTES " + "WHERE SESSION_PRIMARY_ID = ? " + "AND ATTRIBUTE_NAME = ?"; 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 S.PRIMARY_ID, S.SESSION_ID, S.CREATION_TIME, S.LAST_ACCESS_TIME, S.MAX_INACTIVE_INTERVAL, SA.ATTRIBUTE_NAME, SA.ATTRIBUTE_BYTES " + "FROM %TABLE_NAME% S " + "LEFT OUTER JOIN %TABLE_NAME%_ATTRIBUTES SA ON S.PRIMARY_ID = SA.SESSION_PRIMARY_ID " + "WHERE S.PRINCIPAL_NAME = ?"; private static final String DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY = "DELETE FROM %TABLE_NAME% " + "WHERE EXPIRY_TIME < ?"; private static final Log logger = LogFactory.getLog(JdbcOperationsSessionRepository.class); private static final PrincipalNameResolver PRINCIPAL_NAME_RESOLVER = new PrincipalNameResolver(); private final ResultSetExtractor<List<JdbcSession>> extractor = new SessionResultSetExtractor(); 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 deleteSessionsByExpiryTimeQuery; private Integer defaultMaxInactiveInterval; private ConversionService conversionService; private LobHandler lobHandler = new DefaultLobHandler(); //--------------Modify by Jimmy Shan, the date is 2019-06-25 start--------------// private Map<Integer, JdbcOperations> jdbcOperationMaps = new Hash<>(); private Map<Integer, TransactionOperations> transOperationMaps = new Hash<>(); @Value("{mydb.server.sharding.num}") private String shardingNum; //--------------Modify by Jimmy Shan, the date is 2019-06-25 end----------------// /** * @desc 重写这个方法 */ public CustomizedJdbcOperationsSessionRepository(Map<Integer, JdbcTemplate> myJdbcTemplateMap, Map<Integer, PlatformTransactionManager> myDataSourceTransactionMap) { if (myJdbcTemplateMap == null || myJdbcTemplateMap.isEmpty()) { Assert.notNull(myJdbcTemplateMap, "myJdbcTemplateMap must not be null"); } if (myDataSourceTransactionMap == null || myDataSourceTransactionMap.isEmpty()) { Assert.notNull(myDataSourceTransactionMap, "myDataSourceTransactionMap must not be null"); } jdbcOperationMaps.putAll(myJdbcTemplateMap); this.conversionService = createDefaultConversionService(); prepareQueries(); Set<Integer> setKey = myDataSourceTransactionMap.keySet(); for (Iterator ir = setKey.iterator(); ir.hasNext(); ) { Integer key = (Integer) ir.next(); TransactionOperations transOperations = createTransactionTemplate(myDataSourceTransactionMap.get(key)); transOperationMaps.put(key, transOperations); } } public void setTableName(String tableName) { Assert.hasText(tableName, "Table name must not be empty"); this.tableName = tableName.trim(); prepareQueries(); } public void setCreateSessionQuery(String createSessionQuery) { Assert.hasText(createSessionQuery, "Query must not be empty"); this.createSessionQuery = createSessionQuery; } public void setCreateSessionAttributeQuery(String createSessionAttributeQuery) { Assert.hasText(createSessionAttributeQuery, "Query must not be empty"); this.createSessionAttributeQuery = createSessionAttributeQuery; } public void setGetSessionQuery(String getSessionQuery) { Assert.hasText(getSessionQuery, "Query must not be empty"); this.getSessionQuery = getSessionQuery; } public void setUpdateSessionQuery(String updateSessionQuery) { Assert.hasText(updateSessionQuery, "Query must not be empty"); this.updateSessionQuery = updateSessionQuery; } public void setUpdateSessionAttributeQuery(String updateSessionAttributeQuery) { Assert.hasText(updateSessionAttributeQuery, "Query must not be empty"); this.updateSessionAttributeQuery = updateSessionAttributeQuery; } public void setDeleteSessionAttributeQuery(String deleteSessionAttributeQuery) { Assert.hasText(deleteSessionAttributeQuery, "Query must not be empty"); this.deleteSessionAttributeQuery = deleteSessionAttributeQuery; } public void setDeleteSessionQuery(String deleteSessionQuery) { Assert.hasText(deleteSessionQuery, "Query must not be empty"); this.deleteSessionQuery = deleteSessionQuery; } public void setListSessionsByPrincipalNameQuery(String listSessionsByPrincipalNameQuery) { Assert.hasText(listSessionsByPrincipalNameQuery, "Query must not be empty"); this.listSessionsByPrincipalNameQuery = listSessionsByPrincipalNameQuery; } public void setDeleteSessionsByExpiryTimeQuery(String deleteSessionsByExpiryTimeQuery) { Assert.hasText(deleteSessionsByExpiryTimeQuery, "Query must not be empty"); this.deleteSessionsByExpiryTimeQuery = deleteSessionsByExpiryTimeQuery; } public void setDefaultMaxInactiveInterval(Integer defaultMaxInactiveInterval) { this.defaultMaxInactiveInterval = defaultMaxInactiveInterval; } public void setLobHandler(LobHandler lobHandler) { Assert.notNull(lobHandler, "LobHandler must not be null"); this.lobHandler = lobHandler; } public void setConversionService(ConversionService conversionService) { Assert.notNull(conversionService, "conversionService must not be null"); this.conversionService = conversionService; } @Override public JdbcSession createSession() { JdbcSession session = new JdbcSession(); if (this.defaultMaxInactiveInterval != null) { session.setMaxInactiveInterval(Duration.ofSeconds(this.defaultMaxInactiveInterval)); } return session; } /** * @desc Modify by Jimmy Shan, the date is 2019-06-25 * Implementing Routing Function */ @Override public void save(final JdbcSession session) { String sessionId = session.getId(); int hashCode = Math.abs(sessionId.hashCode()); int mod = hashCode % Integer.parseInt(shardingNum); if (session.isNew()) { this.transOperationMaps.get(mod).execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { CustomizedJdbcOperationsSessionRepository.this.jdbcOperationMaps.get(mod).update( CustomizedJdbcOperationsSessionRepository.this.createSessionQuery, (ps) -> { ps.setString(1, session.primaryKey); ps.setString(2, session.getId()); ps.setLong(3, session.getCreationTime().toEpochMilli()); ps.setLong(4, session.getLastAccessedTime().toEpochMilli()); ps.setInt(5, (int) session.getMaxInactiveInterval().getSeconds()); ps.setLong(6, session.getExpiryTime().toEpochMilli()); ps.setString(7, session.getPrincipalName()); }); Set<String> attributeNames = session.getAttributeNames(); if (!attributeNames.isEmpty()) { insertSessionAttributes(session, new ArrayList<>(attributeNames)); } } }); } else { this.transOperationMaps.get(mod).execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { if (session.isChanged()) { CustomizedJdbcOperationsSessionRepository.this.jdbcOperationMaps.get(mod).update( CustomizedJdbcOperationsSessionRepository.this.updateSessionQuery, (ps) -> { ps.setString(1, session.getId()); ps.setLong(2, session.getLastAccessedTime().toEpochMilli()); ps.setInt(3, (int) session.getMaxInactiveInterval().getSeconds()); ps.setLong(4, session.getExpiryTime().toEpochMilli()); ps.setString(5, session.getPrincipalName()); ps.setString(6, session.primaryKey); }); } List<String> addedAttributeNames = session.delta.entrySet().stream() .filter((entry) -> entry.getValue() == DeltaValue.ADDED) .map(Map.Entry::getKey) .collect(Collectors.toList()); if (!addedAttributeNames.isEmpty()) { insertSessionAttributes(session, addedAttributeNames); } List<String> updatedAttributeNames = session.delta.entrySet().stream() .filter((entry) -> entry.getValue() == DeltaValue.UPDATED) .map(Map.Entry::getKey) .collect(Collectors.toList()); if (!updatedAttributeNames.isEmpty()) { updateSessionAttributes(session, updatedAttributeNames); } List<String> removedAttributeNames = session.delta.entrySet().stream() .filter((entry) -> entry.getValue() == DeltaValue.REMOVED) .map(Map.Entry::getKey) .collect(Collectors.toList()); if (!removedAttributeNames.isEmpty()) { deleteSessionAttributes(session, removedAttributeNames); } } }); } session.clearChangeFlags(); } /** * @desc Modify by Jimmy Shan, the date is 2019-06-25 * Implementing Routing Function */ @Override public JdbcSession findById(final String id) { String sessionId = id; int hashCode = Math.abs(sessionId.hashCode()); int mod = hashCode % Integer.parseInt(shardingNum); final JdbcSession session = this.transOperationMaps.get(mod).execute((status) -> { List<JdbcSession> sessions = CustomizedJdbcOperationsSessionRepository .this.jdbcOperationMaps.get(mod).query( CustomizedJdbcOperationsSessionRepository.this.getSessionQuery, (ps) -> ps.setString(1, id), CustomizedJdbcOperationsSessionRepository.this.extractor ); if (sessions.isEmpty()) { return null; } return sessions.get(0); }); if (session != null) { if (session.isExpired()) { deleteById(id); } else { return session; } } return null; } /** * @desc Modify by Jimmy Shan, the date is 2019-06-25 * Implementing Routing Function */ @Override public void deleteById(final String id) { String sessionId = id; int hashCode = Math.abs(sessionId.hashCode()); int mod = hashCode % Integer.parseInt(shardingNum); this.transOperationMaps.get(mod).execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { CustomizedJdbcOperationsSessionRepository.this.jdbcOperationMaps.get(mod).update( CustomizedJdbcOperationsSessionRepository.this.deleteSessionQuery, id); } }); } /** * @desc Modify by Jimmy Shan, the date is 2019-06-25 * Implementing Routing Function */ @Override public Map<String, JdbcSession> findByIndexNameAndIndexValue(String indexName, final String indexValue) { if (!PRINCIPAL_NAME_INDEX_NAME.equals(indexName)) { return Collections.emptyMap(); } List<JdbcSession> sessions = new ArrayList<>(); Set<Integer> setKey = transOperationMaps.keySet(); for (Iterator ir = setKey.iterator(); ir.hasNext(); ) { Integer key = (Integer) ir.next(); TransactionOperations transOperation = (TransactionOperations) transOperationMaps.get(key); List<JdbcSession> tempSession = transOperation.execute(status) -> CustomizedJdbcOperationsSessionRepository.this.jdbcOperationMaps.get(key).query( CustomizedJdbcOperationsSessionRepository.this.listSessionsByPrincipalNameQuery, (ps) -> ps.setString(1, indexValue), CustomizedJdbcOperationsSessionRepository.this.extractor)); if (tempSession != null && !tempSession.isEmpty()) { sessions.addAll(tempSession); } } Map<String, JdbcSession> sessionMap = new HashMap<>(sessions.size()); for (JdbcSession session : sessions) { sessionMap.put(session.getId(), session); } return sessionMap; } /** * @desc Modify by Jimmy Shan, the date is 2019-06-25 * Implementing Routing Function */ private void insertSessionAttributes(JdbcSession session, List<String> attributeNames) { Assert.notEmpty(attributeNames, "attributeNames must not be null or empty"); String sessionId = session.getId(); int hashCode = Math.abs(sessionId.hashCode()); int mod = hashCode % Integer.parseInt(shardingNum); if (attributeNames.size() > 1) { this.jdbcOperationMaps.get(mod).batchUpdate(this.createSessionAttributeQuery, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { String attributeName = attributeNames.get(i); ps.setString(1, attributeName); setObjectAsBlob(ps, 2, session.getAttribute(attributeName)); ps.setString(3, session.getId()); } @Override public int getBatchSize() { return attributeNames.size(); } }); } else { this.jdbcOperationMaps.get(mod).update(this.createSessionAttributeQuery, (ps) -> { String attributeName = attributeNames.get(0); ps.setString(1, attributeName); setObjectAsBlob(ps, 2, session.getAttribute(attributeName)); ps.setString(3, session.getId()); }); } } /** * @desc Modify by Jimmy Shan, the date is 2019-06-25 * Implementing Routing Function */ private void updateSessionAttributes(JdbcSession session, List<String> attributeNames) { Assert.notEmpty(attributeNames, "attributeNames must not be null or empty"); String sessionId = session.getId(); int hashCode = Math.abs(sessionId.hashCode()); int mod = hashCode % Integer.parseInt(shardingNum); if (attributeNames.size() > 1) { this.jdbcOperationMaps.get(mod).batchUpdate(this.updateSessionAttributeQuery, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { String attributeName = attributeNames.get(i); setObjectAsBlob(ps, 1, session.getAttribute(attributeName)); ps.setString(2, session.primaryKey); ps.setString(3, attributeName); } @Override public int getBatchSize() { return attributeNames.size(); } }); } else { this.jdbcOperationMaps.get(mod).update(this.updateSessionAttributeQuery, (ps) -> { String attributeName = attributeNames.get(0); setObjectAsBlob(ps, 1, session.getAttribute(attributeName)); ps.setString(2, session.primaryKey); ps.setString(3, attributeName); }); } } /** * @desc Modify by Jimmy Shan, the date is 2019-06-25 * Implementing Routing Function */ private void deleteSessionAttributes(JdbcSession session, List<String> attributeNames) { Assert.notEmpty(attributeNames, "attributeNames must not be null or empty"); String sessionId = session.getId(); int hashCode = Math.abs(sessionId.hashCode()); int mod = hashCode % Integer.parseInt(shardingNum); if (attributeNames.size() > 1) { this.jdbcOperationMaps.get(mod).batchUpdate(this.deleteSessionAttributeQuery, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { String attributeName = attributeNames.get(i); ps.setString(1, session.primaryKey); ps.setString(2, attributeName); } @Override public int getBatchSize() { return attributeNames.size(); } }); } else { this.jdbcOperationMaps.get(mod).update(this.deleteSessionAttributeQuery, (ps) -> { String attributeName = attributeNames.get(0); ps.setString(1, session.primaryKey); ps.setString(2, attributeName); }); } } /** * @desc Modify by Jimmy Shan, the date is 2019-06-25 * Implementing Routing Function */ public void cleanUpExpiredSessions() { Set<Integer> setKey = transOperationMaps.keySet(); for (Iterator ir = setKey.iterator(); ir.hasNext(); ) { Integer key = (Integer) ir.next(); TransactionOperations transOperation = (TransactionOperations) transOperationMaps.get(key); Integer deletedCount = transOperation.execute((status) -> CustomizedJdbcOperationsSessionRepository.this.jdbcOperationMaps.get(key).update( CustomizedJdbcOperationsSessionRepository.this.deleteSessionsByExpiryTimeQuery, System.currentTimeMillis())); if (logger.isDebugEnabled()) { logger.debug("Cleaned up " + deletedCount + " expired sessions"); } } } private static TransactionTemplate createTransactionTemplate( PlatformTransactionManager transactionManager) { TransactionTemplate transactionTemplate = new TransactionTemplate( transactionManager); transactionTemplate.setPropagationBehavior( TransactionDefinition.PROPAGATION_REQUIRES_NEW); transactionTemplate.afterPropertiesSet(); return transactionTemplate; } private static GenericConversionService createDefaultConversionService() { GenericConversionService converter = new GenericConversionService(); converter.addConverter(Object.class, byte[].class, new SerializingConverter()); converter.addConverter(byte[].class, Object.class, new DeserializingConverter()); return converter; } 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.deleteSessionsByExpiryTimeQuery = getQuery(DELETE_SESSIONS_BY_EXPIRY_TIME_QUERY); } private void setObjectAsBlob(PreparedStatement ps, int paramIndex, Object object) throws SQLException { byte[] bytes = (byte[]) this.conversionService.convert(object, TypeDescriptor.valueOf(Object.class), TypeDescriptor.valueOf(byte[].class)); this.lobHandler.getLobCreator().setBlobAsBytes(ps, paramIndex, bytes); } private Object getBlobAsObject(ResultSet rs, String columnName) throws SQLException { byte[] bytes = this.lobHandler.getBlobAsBytes(rs, columnName); return this.conversionService.convert(bytes, TypeDescriptor.valueOf(byte[].class), TypeDescriptor.valueOf(Object.class)); } private enum DeltaValue { ADDED, UPDATED, REMOVED } private static <T> Supplier<T> value(T value) { return (value != null) ? () -> value : null; } private static <T> Supplier<T> lazily(Supplier<T> supplier) { Supplier<T> lazySupplier = new Supplier<T>() { private T value; @Override public T get() { if (this.value == null) { this.value = supplier.get(); } return this.value; } }; return (supplier != null) ? lazySupplier : null; } final class JdbcSession implements Session { private final Session delegate; private final String primaryKey; private boolean isNew; private boolean changed; 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; this.delegate = delegate; } boolean isNew() { return this.isNew; } boolean isChanged() { return this.changed; } Map<String, DeltaValue> getDelta() { return this.delta; } void clearChangeFlags() { this.isNew = false; this.changed = false; this.delta.clear(); } String getPrincipalName() { return PRINCIPAL_NAME_RESOLVER.resolvePrincipal(this); } Instant getExpiryTime() { return getLastAccessedTime().plus(getMaxInactiveInterval()); } @Override public String getId() { return this.delegate.getId(); } @Override public String changeSessionId() { this.changed = true; return this.delegate.changeSessionId(); } @Override public <T> T getAttribute(String attributeName) { Supplier<T> supplier = this.delegate.getAttribute(attributeName); return (supplier != null) ? supplier.get() : null; } @Override public Set<String> getAttributeNames() { return this.delegate.getAttributeNames(); } @Override public void setAttribute(String attributeName, Object attributeValue) { boolean attributeExists = (this.delegate.getAttribute(attributeName) != null); boolean attributeRemoved = (attributeValue == null); if (!attributeExists && attributeRemoved) { return; } if (attributeExists) { if (attributeRemoved) { this.delta.merge(attributeName, DeltaValue.REMOVED, (oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? null : deltaValue); } else { this.delta.merge(attributeName, DeltaValue.UPDATED, (oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? oldDeltaValue : deltaValue); } } else { this.delta.merge(attributeName, DeltaValue.ADDED, (oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? oldDeltaValue : DeltaValue.UPDATED); } this.delegate.setAttribute(attributeName, value(attributeValue)); if (PRINCIPAL_NAME_INDEX_NAME.equals(attributeName) || SPRING_SECURITY_CONTEXT.equals(attributeName)) { this.changed = true; } } @Override public void removeAttribute(String attributeName) { setAttribute(attributeName, null); } @Override public Instant getCreationTime() { return this.delegate.getCreationTime(); } @Override public void setLastAccessedTime(Instant lastAccessedTime) { this.delegate.setLastAccessedTime(lastAccessedTime); this.changed = true; } @Override public Instant getLastAccessedTime() { return this.delegate.getLastAccessedTime(); } @Override public void setMaxInactiveInterval(Duration interval) { this.delegate.setMaxInactiveInterval(interval); this.changed = true; } @Override public Duration getMaxInactiveInterval() { return this.delegate.getMaxInactiveInterval(); } @Override public boolean isExpired() { return this.delegate.isExpired(); } } static class PrincipalNameResolver { private SpelExpressionParser parser = new SpelExpressionParser(); public String resolvePrincipal(Session session) { String principalName = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME); if (principalName != null) { return principalName; } Object authentication = session.getAttribute(SPRING_SECURITY_CONTEXT); if (authentication != null) { Expression expression = this.parser .parseExpression("authentication?.name"); return expression.getValue(authentication, String.class); } return null; } } private class SessionResultSetExtractor implements ResultSetExtractor<List<JdbcSession>> { @Override public List<JdbcSession> extractData(ResultSet rs) throws SQLException, DataAccessException { List<JdbcSession> sessions = new ArrayList<>(); while (rs.next()) { String id = rs.getString("SESSION_ID"); JdbcSession session; if (sessions.size() > 0 && getLast(sessions).getId().equals(id)) { session = getLast(sessions); } else { MapSession delegate = new MapSession(id); String primaryKey = rs.getString("PRIMARY_ID"); 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); } String attributeName = rs.getString("ATTRIBUTE_NAME"); if (attributeName != null) { Object attributeValue = getBlobAsObject(rs, "ATTRIBUTE_BYTES"); session.delegate.setAttribute(attributeName, lazily(() -> attributeValue)); } sessions.add(session); } return sessions; } private JdbcSession getLast(List<JdbcSession> sessions) { return sessions.get(sessions.size() - 1); } } }
以上工作都完成后,让我们来看看如何使用。
代码如下:
package com.szl.demo.spring.session.controller; import java.io.PrintWriter; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class TestSessionController { @RequestMapping("/testGetSession") public void testGetSession(HttpServletRequest request, HttpServletResponse response, ModelMap model) { PrintWriter out = null; try { out = response.getWriter(); HttpSession session = request.getSession(); String content = (String) session.getAttribute("userId"); System.out.println("session content is : " + content); out.println("session content is : " + content); out.flush(); out.close(); } catch (Exception e) { e.printStackTrace(); } } @RequestMapping("/testSetSession") public void testSetSession(HttpServletRequest request, HttpServletResponse response, ModelMap model) { PrintWriter out = null; try { out = response.getWriter(); UUID uuid = UUID.randomUUID(); String uid = uuid.toString().replaceAll("-", ""); HttpSession session = request.getSession(); String content = (String) session.getAttribute("userId"); System.out.println("old session content is : " + content); // 设置session内容 session.setAttribute("userId", uid); out.println("new session content is : " + uid); out.flush(); out.close(); } catch (Exception e) { e.printStackTrace(); } } }
是不是很简单,和平时使用session方式一样,好了,备忘录记录到此,还是那句,
仅供有需要的朋友参考,也欢迎转载,但请注明原著,谢谢。
标签:分库,springboot2.1,spring,private,session,springframework,org,import,String 来源: https://www.cnblogs.com/jimmyshan-study/p/11087223.html