概述
session的基礎知識就不再多說。
通常,我們會把一個項目部署到多個tomcat上,通過nginx進行負載均衡,提高系統的並發性。此時,就會存在一個問題。假如用戶第一次訪問tomcat1,並登陸保存了用戶信息,但是下一次訪問的時候,nginx讓用戶訪問tomcat2,此時tomcat2中並沒有用戶的session信息,用戶必須重新進行登錄操作。這樣會極大的破壞用戶的體驗。
對此,我們有兩大類解決方案。一個是將nginx的負載均衡機制設為根據iphash,也就是用戶每次保證能訪問同一台tomcat,但是該方法也存在着弊端,當tomcat掛掉以后,用戶必須重新登陸。第二種方法較為常用,就是通過某些方法進行session共享。常用的方法有使用spring session,如果項目中有用到shiro,可以通過重寫AbstractSessionDAO,即重寫shiro的session存儲機制完成。
本文將先從spring session入手,完成session共享。由於最近接觸的是一個老項目,使用ssm框架,本文基於xml的方式進行配置。熟悉spring boot的朋友可以直接到spring boot官網進行查看,配置較為簡單。由於老項目的並發量並不是很高,因此本文使用spirng-session-jdbc來進行session共享。大型項目可選擇使用非關系型數據庫Redis等
代碼地址
https://github.com/DenchZhou/ssm/tree/master/spring-session-jdbc
如果有用的話不妨給個star,感恩!
配置
Maven依賴
加入maven依賴 以下使用的是2.0以上的版本 如果在沒有網的環境下記得添加其他相關依賴如spring-jdbc、spring-session-core、spring-context
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
數據庫表
我們需要添加兩張數據庫表,spring session通過將httpsession序列化保存至數據庫,需要session的時候再從數據庫中取出並反序列化。因此我們需要保證數據庫中存在保存session的表格。
/* 2.x版本使用的數據庫表*/
CREATE TABLE SPRING_SESSION (
PRIMARY_ID CHAR(36) NOT NULL,
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
EXPIRY_TIME BIGINT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BLOB NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
上面的數據庫表適用於2.0以上的版本,總共有七個字段。如果maven依賴1.x版本使用的數據庫表不一樣。
/* 1.x版本使用的數據庫表*/
CREATE TABLE `SPRING_SESSION` (
`SESSION_ID` char(36) NOT NULL DEFAULT '',
`CREATION_TIME` bigint(20) NOT NULL,
`LAST_ACCESS_TIME` bigint(20) NOT NULL,
`MAX_INACTIVE_INTERVAL` int(11) NOT NULL,
`PRINCIPAL_NAME` varchar(100) DEFAULT NULL,
PRIMARY KEY (`SESSION_ID`) USING BTREE,
KEY `SPRING_SESSION_IX1` (`LAST_ACCESS_TIME`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `SPRING_SESSION_ATTRIBUTES` (
`SESSION_ID` char(36) NOT NULL DEFAULT '',
`ATTRIBUTE_NAME` varchar(100) NOT NULL DEFAULT '',
`ATTRIBUTE_BYTES` blob,
PRIMARY KEY (`SESSION_ID`,`ATTRIBUTE_NAME`),
KEY `SPRING_SESSION_ATTRIBUTES_IX1` (`SESSION_ID`) USING BTREE,
CONSTRAINT `SPRING_SESSION_ATTRIBUTES_ibfk_1` FOREIGN KEY (`SESSION_ID`) REFERENCES `SPRING_SESSION` (`SESSION_ID`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
spring xml相關配置
<context:annotation-config/>
<bean class="org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration">
<property name="tableName" value="spring_session"/>
<property name="maxInactiveIntervalInSeconds" value="1800"/>
</bean>
<!-- 配置事務管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入數據庫連接池 -->
<property name="dataSource" ref="dataSource" />
</bean>
使用context:annotation-config和JdbcHttpSessionConfiguration是因為Spring Session沒有提供XML命名空間的支持。我們可以在配置相關的屬性注入,如數據庫表名,session保持的時間等等。然后就是事務管理器的配置,數據源的配置不再詳細說明了。可以看最上面的項目源碼。
web.xml配置過濾器
spring session通過自定義一個filter,通過filter職責鏈將用自己定義的request替換httpservletrequest,從而使用自己httpsession。因此我們必須配置一下Filter,並且最好把他放在最前面,使其優先執行。
<filter>
<filter-name>springSessionRepositoryFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSessionRepositoryFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
spring 配置讀取我們的spring配置文件。
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-*.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<!-- 默認匹配所有的請求 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
測試
我的項目中只寫了一個簡單的查找user並放入session中
@Controller
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/findUser")
@ResponseBody
public String findUserById(HttpServletRequest request){
UserEntity user = userService.findUserById(1);
HttpSession session = request.getSession();
session.setAttribute("user", user);
return user.toString();
}
}
可以通過數據庫管理工具查看


參考
官方文檔 https://docs.spring.io/spring-session/docs/current/reference/html5/#introduction
踩坑
有朋友問我SpringSessionJdbc配置HttpSessionListener不生效,查了一下官方文檔。

想要實現HttpSession的監聽需要三個步驟,在第一步中
- SessionRepository需要實現支持並配置觸發SessionDestroyedEvent和SessionCreatedEvent,在SpringSessionJdbc的JdbcOperationsSessionRepository中並未配置相關Event,實現的接口也沒有包含該Event,所以SpringSessionJdbc並沒有這個功能。而SpringSessionRedis有這個功能。


官方解釋,使用JdbcOperationsSessionReopsitory並不支持事件機制
