Перейти к основному содержимому

Весенняя сессия с JDBC

· 5 мин. чтения

1. Обзор

В этом кратком руководстве мы узнаем, как использовать сеанс JDBC Spring для сохранения информации о сеансе в базе данных.

В демонстрационных целях мы будем использовать базу данных H2 в памяти.

2. Параметры конфигурации

Самый простой и быстрый способ создать наш пример проекта — использовать Spring Boot . Однако мы также покажем незагрузочный способ настройки.

Следовательно, вам не нужно заполнять оба раздела 3 и 4. Просто выберите один в зависимости от того, используем ли мы Spring Boot для настройки Spring Session .

3. Конфигурация весенней загрузки

Во-первых, давайте рассмотрим необходимую конфигурацию для Spring Session JDBC.

3.1. Зависимости Maven

Во-первых, нам нужно добавить эти зависимости в наш проект:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.197</version>
<scope>runtime</scope>
</dependency>

Наше приложение работает с Spring Boot , а родительский pom.xml предоставляет версии для каждой записи. Последнюю версию каждой зависимости можно найти здесь: spring-boot-starter-web , spring-boot-starter-test, spring-session-jdbc и h2.

Удивительно, но единственное свойство конфигурации, которое нам нужно для включения Spring Session, поддерживаемого реляционной базой данных , находится в application.properties :

spring.session.store-type=jdbc

4. Стандартная конфигурация Spring (без загрузки Spring)

Давайте также взглянем на интеграцию и настройку spring-session без Spring Boot — просто с помощью простого Spring.

4.1. Зависимости Maven

Во- первых, если мы добавляем spring-session-jdbc в стандартный проект Spring, нам нужно добавить spring-session-jdbc и h2 в наш pom.xml (последние две зависимости из фрагмента в предыдущем разделе).

4.2. Конфигурация весеннего сеанса

Теперь давайте добавим класс конфигурации для Spring Session JDBC :

@Configuration
@EnableJdbcHttpSession
public class Config
extends AbstractHttpSessionApplicationInitializer {

@Bean
public EmbeddedDatabase dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("org/springframework/session/jdbc/schema-h2.sql").build();
}

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}

Как видим, отличия минимальны. Теперь нам нужно явно определить наши bean-компоненты EmbeddedDatabase и PlatformTransactionManager — Spring Boot делает это за нас в предыдущей конфигурации.

Вышеупомянутое гарантирует, что компонент Spring с именем springSessionRepositoryFilter регистрируется в нашем контейнере сервлетов для каждого запроса.

5. Простое приложение

Двигаясь дальше, давайте рассмотрим простой REST API, который сохраняет и демонстрирует постоянство сеанса .

5.1. Контроллер

Во-первых, давайте добавим класс Controller для хранения и отображения информации в HttpSession :

@Controller
public class SpringSessionJdbcController {

@GetMapping("/")
public String index(Model model, HttpSession session) {
List<String> favoriteColors = getFavColors(session);
model.addAttribute("favoriteColors", favoriteColors);
model.addAttribute("sessionId", session.getId());
return "index";
}

@PostMapping("/saveColor")
public String saveMessage
(@RequestParam("color") String color,
HttpServletRequest request) {

List<String> favoriteColors
= getFavColors(request.getSession());
if (!StringUtils.isEmpty(color)) {
favoriteColors.add(color);
request.getSession().
setAttribute("favoriteColors", favoriteColors);
}
return "redirect:/";
}

private List<String> getFavColors(HttpSession session) {
List<String> favoriteColors = (List<String>) session
.getAttribute("favoriteColors");

if (favoriteColors == null) {
favoriteColors = new ArrayList<>();
}
return favoriteColors;
}
}

6. Тестирование нашей реализации

Теперь, когда у нас есть API с методами GET и POST, давайте напишем тесты для вызова обоих методов.

В каждом случае мы должны иметь возможность утверждать, что информация о сеансе сохраняется в базе данных. Чтобы убедиться в этом, мы запросим базу данных сеанса напрямую.

Давайте сначала настроим вещи:

@RunWith(SpringRunner.class)
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SpringSessionJdbcApplicationTests {

@LocalServerPort
private int port;

@Autowired
private TestRestTemplate testRestTemplate;

private List<String> getSessionIdsFromDatabase()
throws SQLException {

List<String> result = new ArrayList<>();
ResultSet rs = getResultSet(
"SELECT * FROM SPRING_SESSION");

while (rs.next()) {
result.add(rs.getString("SESSION_ID"));
}
return result;
}

private List<byte[]> getSessionAttributeBytesFromDb()
throws SQLException {

List<byte[]> result = new ArrayList<>();
ResultSet rs = getResultSet(
"SELECT * FROM SPRING_SESSION_ATTRIBUTES");

while (rs.next()) {
result.add(rs.getBytes("ATTRIBUTE_BYTES"));
}
return result;
}

private ResultSet getResultSet(String sql)
throws SQLException {

Connection conn = DriverManager
.getConnection("jdbc:h2:mem:testdb", "sa", "");
Statement stat = conn.createStatement();
return stat.executeQuery(sql);
}
}

Обратите внимание на использование @FixMethodOrder(MethodSorters.NAME_ASCENDING) для управления порядком выполнения тестового примера . Подробнее об этом читайте здесь .

Начнем с утверждения, что таблицы сеансов в базе данных пусты:

@Test
public void whenH2DbIsQueried_thenSessionInfoIsEmpty()
throws SQLException {

assertEquals(
0, getSessionIdsFromDatabase().size());
assertEquals(
0, getSessionAttributeBytesFromDatabase().size());
}

Далее мы тестируем конечную точку GET:

@Test
public void whenH2DbIsQueried_thenOneSessionIsCreated()
throws SQLException {

assertThat(this.testRestTemplate.getForObject(
"http://localhost:" + port + "/", String.class))
.isNotEmpty();
assertEquals(1, getSessionIdsFromDatabase().size());
}

Когда API вызывается в первый раз, сеанс создается и сохраняется в базе данных. Как мы видим, на данный момент в таблице SPRING_SESSION есть только одна строка .

Наконец, мы тестируем конечную точку POST, указав любимый цвет:

@Test
public void whenH2DbIsQueried_thenSessionAttributeIsRetrieved()
throws Exception {

MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("color", "red");
this.testRestTemplate.postForObject(
"http://localhost:" + port + "/saveColor", map, String.class);
List<byte[]> queryResponse = getSessionAttributeBytesFromDatabase();

assertEquals(1, queryResponse.size());
ObjectInput in = new ObjectInputStream(
new ByteArrayInputStream(queryResponse.get(0)));
List<String> obj = (List<String>) in.readObject();
assertEquals("red", obj.get(0));
}

Как и ожидалось, таблица SPRING_SESSION_ATTRIBUTES сохраняет любимый цвет. Обратите внимание, что мы должны десериализовать содержимое ATTRIBUTE_BYTES в список объектов String , поскольку Spring выполняет сериализацию объектов при сохранении атрибутов сеанса в базе данных.

7. Как это работает?

Глядя на контроллер, нет никаких признаков того, что база данных сохраняет информацию о сеансе. Вся магия происходит в одной строке, которую мы добавили в application.properties .

То есть, когда мы указываем spring.session.store-type=jdbc, за кулисами Spring Boot применит конфигурацию, эквивалентную ручному добавлению аннотации @EnableJdbcHttpSession .

Это создает Spring Bean с именем s pringSessionRepositoryFilter , который реализует SessionRepositoryFilter .

Еще одним ключевым моментом является то, что фильтр перехватывает каждый HttpServletRequest и упаковывает его в SessionRepositoryRequestWrapper .

Он также вызывает метод commitSession для сохранения информации о сеансе.

8. Информация о сеансе хранится в базе данных H2

Добавив следующие свойства, мы могли бы взглянуть на таблицы, в которых информация о сеансе хранится из URL-адреса — http://localhost:8080/h2-console/ :

spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

./c9c3eec3983b5b16b6154b8d56ef4ca2.png

./ef16060d1bfa1c78c20df76ebc357ae7.png

9. Заключение

Spring Session — это мощный инструмент для управления HTTP-сессиями в архитектуре распределенной системы. Spring берет на себя тяжелую работу для простых случаев использования, предоставляя предопределенную схему с минимальной конфигурацией. В то же время он предлагает гибкость, позволяющую придумать наш дизайн того, как мы хотим хранить информацию о сеансе.

Наконец, для управления информацией об аутентификации с помощью Spring Session вы можете обратиться к этой статье — Guide to Spring Session .

Как всегда, вы можете найти исходный код на Github .