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

1. Обзор

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

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

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

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

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

3. Настройка Spring Boot

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

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 предоставляет версии для каждой записи. Последнюю версию каждой зависимости можно найти здесь: https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.springframework.boot%22%20AND%20a%3A%22spring- boot-starter-web% 22[spring-boot-starter-web], https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.springframework.boot%22%20AND% 20a% 3A% 22spring-boot-starter-test% 22[spring-boot-starter-test,] https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.springframework . сессия% 22% 20AND% 20a% 3A% 22spring-session-jdbc% 22[spring-session-jdbc]и h2.

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

spring.session.store-type=jdbc

4. Стандартный Spring Config (без Spring Boot)

Давайте также посмотрим на интеграцию и настройку Spring-сессии без Spring Boot - просто с простым Spring.

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

Во-первых, если мы добавляем spring-session-jdbc в стандартный проект Spring, нам нужно добавить https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.springframework .session% 22% 20AND% 20a% 3A% 22spring-session-jdbc% 22[spring-session-jdbc]и https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22com .h2database% 22% 20AND% 20a% 3A% 22h2% 22[h2]к нашему pom.xml (последние две зависимости из фрагмента в предыдущем разделе).

4.2. Настройка Spring Session

Теперь давайте добавим класс конфигурации для 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);
    }
}

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

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

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) для управления порядком выполнения тестового примера . __Подробнее об этом here .

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

@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 ____table есть только одна строка.

Наконец, мы проверяем конечную точку 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 table сохраняет любимый цвет. Обратите внимание, что мы должны десериализовать содержимое ATTRIBUTE BYTES _ в список объектов String__, поскольку Spring выполняет сериализацию объектов при сохранении атрибутов сеанса в базе данных.

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

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

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

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

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

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

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

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

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

9. Вывод

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

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

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