はじめに
Spring JPAとSpring JDBCを使ったアプリ開発をしていて、DBコネクションプーリングの作成タイミングに違いがあるのではという疑問を持ちました。
JPAは、アプリ起動時に、接続情報に不備があったりDBサーバにアクセスできないと、例外が発生することから、先にコネクションを確保しているようです。
一方、JDBCは、DBに接続できなくてもアプリは起動します。DBアクセス時に例外が発生して問題に気がつきます。
コネクションプールは、アプリ起動時に作成されるものだとばかり思い込んでいますので、JDBCの振る舞いには納得できませんでした。実は、設定不備などで、JDBCでコネクションプールが作成されてないのではないかと心配になってきたので、確認することにしました。
検証方法
次のタイミングに、DBコネクション数を確認して、コネクションプールが作成されているかを判断します。
- アプリ起動前
- アプリ起動後
- DBアクセス後
DBにpostgreSQLを使ったので、次のコマンドで接続数を確認します。
sample=# select count(*) from pg_stat_activity;
検証結果
アプリ起動前後のコネクション数は同じで、DBアクセスした後に、コネクション数が10増えました。「コネクションプールはアプリ起動時に作成される」という、私の思い込みは間違いでした。
-- アプリ起動前
sample=# select count(*) from pg_stat_activity;
count
-------
2
(1 row)
-- アプリ起動後
sample=# select count(*) from pg_stat_activity;
count
-------
2
(1 row)
-- DBアクセス後
sample=# select count(*) from pg_stat_activity;
count
-------
12
(1 row)
検証プログラム
Spring Initializer 使ってWebアプリを作成。
Controllerで、JDBCでDBを参照しています。
pom.xml(dependency抜粋)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>
</dependencies>
application.properties
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/sample
spring.datasource.username=admin
spring.datasource.password=admin
spring.datasource.sql-script-encoding=UTF-8
Entityクラス
package com.example.jdbc.entity;
import lombok.Data;
@Data
public class Users {
private String username;
private String encodedPassword;
private Boolean systemUserFlag;
private Boolean adminUserFlag;
}
DAOクラス
package com.example.jdbc.repository;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.stereotype.Component;
import com.example.jdbc.entity.Users;
@Component
public class UsersRepository {
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Autowired
public UsersRepository(DataSource dataSource) {
namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
}
public List<Users> searchAll() {
List<String> sqlLines = new ArrayList<>();
sqlLines.add("SELECT username, encoded_password, system_user_flag, admin_user_flag FROM universe.users");
String sql = String.join(" ", sqlLines);
MapSqlParameterSource paramMap = new MapSqlParameterSource();
namedParameterJdbcTemplate.queryForRowSet(sql, paramMap);
SqlRowSet ret = namedParameterJdbcTemplate.queryForRowSet(sql, paramMap);
List<Users> list = new ArrayList<>();
while (ret.next()) {
Users users = new Users();
users.setUsername(ret.getString("username"));
users.setEncodedPassword(ret.getString("encoded_password"));
users.setSystemUserFlag(ret.getBoolean("system_user_flag"));
users.setAdminUserFlag(ret.getBoolean("admin_user_flag"));
list.add(users);
}
return list;
}
}
Controllerクラス
package com.example.jdbc.controller;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import com.example.jdbc.entity.Users;
import com.example.jdbc.repository.UsersRepository;
@Controller
@RequestMapping("sample")
public class SampleController {
private Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
private UsersRepository userRepository;
@RequestMapping(value = "")
public ModelAndView index(@RequestParam String name, ModelAndView mov) {
List<Users> usersList = userRepository.searchAll();
logger.info(usersList.toString());
mov.addObject("name", name);
mov.setViewName("/sample/index");
return mov;
}
}
src/main/resources/templates/sample/index.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Sample</title>
</head>
<body>
<div th:text="${'Hello,' + name}"></div>
</body>
</html>