Spring Bootでデータベース(PostgreSQL)にアクセスする方法をご紹介します。
条件
- Spring Boot 2.1.4
- PostgreSQL 11.2
- maven
事前準備1
データベース作成
まずは、PostgreSQLにデータベースおよびテーブルを作成します。
以下のコマンドを実行します。
# create database testdb;
作成したデータベースへ切り替え
以下のコマンドで、作成したデータベースに切り替えます。
# \c testdb
テーブル作成
例として以下のようなテーブルを作成します。
create table weather ( id serial primary key, -- ID location_id int, -- 地名ID name varchar(20), -- 地名 temperature int, -- 気温 humidity int, -- 湿度 date_time timestamp -- 日時 );
データ追加
テーブルにデータを追加します。
insert into weather (location_id, name, temperature, humidity, date_time) values (1, '東京', 15, 55, '2019-04-10 09:00:00'), (1, '東京', 16, 53, '2019-04-10 10:00:00'), (1, '東京', 17, 40, '2019-04-10 11:00:00'), (2, '那覇', 20, 65, '2019-04-10 09:00:00'), (2, '那覇', 22, 67, '2019-04-10 10:00:00'), (2, '那覇', 25, 69, '2019-04-10 11:00:00');
事前準備2
Spring Bootで適当なプロジェクトを作成します。
ここでは、単純な「Hello Worldプロジェクト」を使用します。
Hello Worldプロジェクトの作成については、以下の記事をご参照ください。
DB接続
DB接続情報の設定
application.propertiesに以下のような記述を追記します。(環境に応じて適宜変更してください。)
spring.jpa.database=POSTGRESQL spring.datasource.url=jdbc:postgresql://localhost:5432/testdb spring.datasource.username=postgres spring.datasource.password=postgrespassword
依存関係
pom.xmlに以下の記述を追記します。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency>
エンティティ作成
modelフォルダ配下に「Weather.java」を作成します。
package com.example.demo.model; import java.sql.Timestamp; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="weather") public class Weather { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private Integer id; private Integer location_id; private String name; private Integer temperature; private Integer humidity; private Timestamp date_time; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Integer getLocation_id() { return location_id; } public void setLocation_id(Integer location_id) { this.location_id = location_id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getTemperature() { return temperature; } public void setTemperature(Integer temperature) { this.temperature = temperature; } public Integer getHumidity() { return humidity; } public void setHumidity(Integer humidity) { this.humidity = humidity; } public Timestamp getDate_time() { return date_time; } public void setDate_time(Timestamp date_time) { this.date_time = date_time; } }
リポジトリクラス作成
repositoryフォルダ配下に「WeatherRepository.java」を作成します。
package com.example.demo.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.example.demo.model.Weather; @Repository public interface WeatherRepository extends JpaRepository<Weather, Integer> {}
サービスクラス作成
serviceフォルダ配下に「WeatherService.java」を作成します。
package com.example.demo.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.demo.model.Weather; import com.example.demo.repository.WeatherRepository; @Service @Transactional public class WeatherService{ @Autowired WeatherRepository weatherRepository; /** * レコードを全件取得する。 * @return */ public List<Weather> findAllWeatherData(){ return weatherRepository.findAll(); } }
コントローラの編集
「HelloController.java」を以下のように編集します。
package com.example.demo; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import com.example.demo.model.Weather; import com.example.demo.service.WeatherService; @Controller public class HelloController { @Autowired WeatherService weatherService; @RequestMapping("/hello") public String hello(Model model) { model.addAttribute("hello", "Hello World!"); // Hello World!の表示 // 気象データの取得 List<Weather> weatherDataList = weatherService.findAllWeatherData(); model.addAttribute("weatherDataList", weatherDataList); return "hello"; } }
テンプレートの編集
「hello.html」を以下のように編集します。
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <title></title> </head> <body> <p> <span th:text="${hello}">Hello World!</span> </p> <table> <tr th:each="data : ${weatherDataList}" th:object="${data}"> <td th:text="*{id}"></td> <td th:text="*{location_id}"></td> <td th:text="*{name}"></td> <td th:text="*{temperature}"></td> <td th:text="*{humidity}"></td> <td th:text="*{date_time}"></td> </tr> </table> </body> </html>
エラー抑制
サーバー起動時に以下のようなエラーが出力されます。
java.lang.reflect.InvocationTargetException: null at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na] at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na] at org.hibernate.engine.jdbc.env.internal.LobCreatorBuilderImpl.useContextualLobCreation(LobCreatorBuilderImpl.java:113) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.engine.jdbc.env.internal.LobCreatorBuilderImpl.makeLobCreatorBuilder(LobCreatorBuilderImpl.java:54) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentImpl.<init>(JdbcEnvironmentImpl.java:271) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:114) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:35) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:94) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:237) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.id.factory.internal.DefaultIdentifierGeneratorFactory.injectServices(DefaultIdentifierGeneratorFactory.java:152) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.service.internal.AbstractServiceRegistryImpl.injectDependencies(AbstractServiceRegistryImpl.java:286) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:243) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:214) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.<init>(InFlightMetadataCollectorImpl.java:179) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:119) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:904) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:935) ~[hibernate-core-5.3.9.Final.jar:5.3.9.Final] at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:57) ~[spring-orm-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) ~[spring-orm-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:390) ~[spring-orm-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:377) ~[spring-orm-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341) ~[spring-orm-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1837) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1774) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1105) ~[spring-context-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) ~[spring-context-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.1.6.RELEASE.jar:5.1.6.RELEASE] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142) ~[spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) ~[spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) ~[spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) ~[spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) ~[spring-boot-2.1.4.RELEASE.jar:2.1.4.RELEASE] at com.example.demo.HelloApplication.main(HelloApplication.java:10) ~[classes/:na] Caused by: java.sql.SQLFeatureNotSupportedException: org.postgresql.jdbc.PgConnection.createClob() メソッドはまだ実装されていません。 at org.postgresql.Driver.notImplemented(Driver.java:688) ~[postgresql-42.2.5.jar:42.2.5] at org.postgresql.jdbc.PgConnection.createClob(PgConnection.java:1269) ~[postgresql-42.2.5.jar:42.2.5] ... 44 common frames omitted
動作上、特に問題はなさそうですが、気になるので抑制します。
「hibernate.properties」を作成し、以下のように記述します。
(application.propertiesと同じフォルダに作成します。)
hibernate.jdbc.lob.non_contextual_creation = true
構成
ここまでの対応でソースは以下のような構成になりました。
実行結果
以下のようにレコード一覧が表示されます。
改造
すこし改造してみます。
application.properties
以下を追記します。
spring.jpa.hibernate.ddl-auto=validate spring.jpa.properties.hibernate.format_sql=true logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
それぞれ以下のような設定です。
1. エンティティの設計が実際のカラムと矛盾していないかチェックする。
2. SQLがログにフォーマットされた形で(見やすい形)で出力される。
3,4. SQLおよびSQLのバインドパラメータがログに出力されるよう設定する。
リポジトリ
「WeatherRepository.java」を以下のように修正します。
package com.example.demo.repository; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.example.demo.model.Weather; @Repository public interface WeatherRepository extends JpaRepository<Weather, Integer> { List<Weather> findByName(String name); }
サービス
「WeatherService.java」を以下のように修正します。
package com.example.demo.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.demo.model.Weather; import com.example.demo.repository.WeatherRepository; @Service @Transactional public class WeatherService{ @Autowired WeatherRepository weatherRepository; /** * レコードを全件取得する。 * @return */ public List<Weather> findAllWeatherData(){ return weatherRepository.findAll(); } /** * 指定した都市のレコードを取得する。 * @param name * @return */ public List<Weather> findWetherDataListByName(String name){ return weatherRepository.findByName(name); } }
コントローラ
「HelloController.java」を以下のように修正します。
package com.example.demo; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import com.example.demo.model.Weather; import com.example.demo.service.WeatherService; @Controller public class HelloController { @Autowired WeatherService weatherService; @RequestMapping("/hello") public String hello(Model model) { model.addAttribute("hello", "Hello World!"); // Hello World!の表示 // 気象データの取得 List<Weather> weatherDataList = weatherService.findAllWeatherData(); model.addAttribute("weatherDataList", weatherDataList); List<Weather> weatherDataTokyo = weatherService.findWetherDataListByName("東京"); model.addAttribute("weatherDataTokyo", weatherDataTokyo); List<Weather> weatherDataNaha = weatherService.findWetherDataListByName("那覇"); model.addAttribute("weatherDataNaha", weatherDataNaha); return "hello"; } }
テンプレート
「Hello.html」を以下のように修正します。
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8" /> <title></title> </head> <body> <p> <span th:text="${hello}">Hello World!</span> </p> <table> <tr th:each="data : ${weatherDataList}" th:object="${data}"> <td th:text="*{id}"></td> <td th:text="*{location_id}"></td> <td th:text="*{name}"></td> <td th:text="*{temperature}"></td> <td th:text="*{humidity}"></td> <td th:text="*{date_time}"></td> </tr> </table> <p>東京のデータ</p> <table> <tr th:each="data : ${weatherDataTokyo}" th:object="${data}"> <td th:text="*{id}"></td> <td th:text="*{location_id}"></td> <td th:text="*{name}"></td> <td th:text="*{temperature}"></td> <td th:text="*{humidity}"></td> <td th:text="*{date_time}"></td> </tr> </table> <p>那覇のデータ</p> <table> <tr th:each="data : ${weatherDataNaha}" th:object="${data}"> <td th:text="*{id}"></td> <td th:text="*{location_id}"></td> <td th:text="*{name}"></td> <td th:text="*{temperature}"></td> <td th:text="*{humidity}"></td> <td th:text="*{date_time}"></td> </tr> </table> </body> </html>
改造後の実行結果
以下のように表示されます。
改造後のログ出力
以下のようなSQLログが出力されます。
2019-04-20 20:59:44.542 DEBUG 2816 --- [nio-8080-exec-1] org.hibernate.SQL : select weather0_.id as id1_0_, weather0_.date_time as date_tim2_0_, weather0_.humidity as humidity3_0_, weather0_.location_id as location4_0_, weather0_.name as name5_0_, weather0_.temperature as temperat6_0_ from weather weather0_ 2019-04-20 20:59:44.581 DEBUG 2816 --- [nio-8080-exec-1] org.hibernate.SQL : select weather0_.id as id1_0_, weather0_.date_time as date_tim2_0_, weather0_.humidity as humidity3_0_, weather0_.location_id as location4_0_, weather0_.name as name5_0_, weather0_.temperature as temperat6_0_ from weather weather0_ where weather0_.name=? 2019-04-20 20:59:44.584 TRACE 2816 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [東京] 2019-04-20 20:59:44.589 DEBUG 2816 --- [nio-8080-exec-1] org.hibernate.SQL : select weather0_.id as id1_0_, weather0_.date_time as date_tim2_0_, weather0_.humidity as humidity3_0_, weather0_.location_id as location4_0_, weather0_.name as name5_0_, weather0_.temperature as temperat6_0_ from weather weather0_ where weather0_.name=? 2019-04-20 20:59:44.589 TRACE 2816 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [那覇]
JDBC
もちろん、JDBCも使用できます。
pom.xml
以下の記述を追記します。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
コントローラ
「HelloController.java」にJDBC関連処理を記述します。
package com.example.demo; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import com.example.demo.model.Weather; import com.example.demo.service.WeatherService; @Controller public class HelloController { @Autowired WeatherService weatherService; @Autowired JdbcTemplate jdbcTemplate; @RequestMapping("/hello") public String hello(Model model) { model.addAttribute("hello", "Hello World!"); // Hello World!の表示 // 気象データの取得 List<Weather> weatherDataList = weatherService.findAllWeatherData(); model.addAttribute("weatherDataList", weatherDataList); List<Weather> weatherDataTokyo = weatherService.findWetherDataListByName("東京"); model.addAttribute("weatherDataTokyo", weatherDataTokyo); List<Weather> weatherDataNaha = weatherService.findWetherDataListByName("那覇"); model.addAttribute("weatherDataNaha", weatherDataNaha); // JDBC System.out.println("*** JDBC Start. ***"); String sql = "select * from weather"; List<Map<String, Object>> sqlResultList = jdbcTemplate.queryForList(sql); sqlResultList.forEach(s -> { System.out.println(s); }); System.out.println("*** JDBC End. ***"); return "hello"; } }
実行結果
以下のように出力されます。
*** JDBC Start. *** {id=1, location_id=1, name=東京, temperature=15, humidity=55, date_time=2019-04-10 09:00:00.0} {id=2, location_id=1, name=東京, temperature=16, humidity=53, date_time=2019-04-10 10:00:00.0} {id=3, location_id=1, name=東京, temperature=17, humidity=40, date_time=2019-04-10 11:00:00.0} {id=4, location_id=2, name=那覇, temperature=20, humidity=65, date_time=2019-04-10 09:00:00.0} {id=5, location_id=2, name=那覇, temperature=22, humidity=67, date_time=2019-04-10 10:00:00.0} {id=6, location_id=2, name=那覇, temperature=25, humidity=69, date_time=2019-04-10 11:00:00.0} *** JDBC End. ***
参考
Qiita:SpringBoot + Spring JPAでデータベースに接続する
https://qiita.com/t-iguchi/items/685c0a1bb9b0e8ec68d2
Qiita:HibernateでPostgreSQLに接続する際の初期化時エラー
https://qiita.com/bwtakacy/items/be3509e1765546f92184