ラベル Spring の投稿を表示しています。 すべての投稿を表示
ラベル Spring の投稿を表示しています。 すべての投稿を表示

2024年12月19日木曜日

Spring Data JPA の JPQL エラー備忘録(LocalDate がエラーになる)

Q: 以下エラーの原因がわかりますでしょうか?

Spring Data REST で、以下のコードを実装しました。

package dev.mikoto2000.study.springboot.web.practice20241215.repository;

import java.time.LocalDate;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

import dev.mikoto2000.study.springboot.web.practice20241215.entity.BookMaster;
import dev.mikoto2000.study.springboot.web.practice20241215.projection.DefaultBookMasterProjection;

/**
 * BookMasterRepository
 */
@RepositoryRestResource(excerptProjection = DefaultBookMasterProjection.class)
public interface BookMasterRepository extends PagingAndSortingRepository<BookMaster, Long>, CrudRepository<BookMaster, Long> {

  @Query(value = """
    select b from BookMaster b
      where
        (b.id = :id or :id is null)
        and
        (b.name = :name or :name is null)
        and
        (b.publicationDate >= :publicationDateBegin or :publicationDateBegin is null)
        and
        (b.publicationDate <= :publicationDateEnd or :publicationDateEnd is null)
  """
  )
  Page<BookMaster> findByComplexConditions(
      Long id,
      String name,
      LocalDate publicationDateBegin,
      LocalDate publicationDateEnd,
      Pageable pageable);
}

しかし、これではクエリパラメーター publicationDateBegin=2024-12-19 を渡したときに以下のインターナルサーバーエラーとなります。何が原因でしょうか?

2024-12-19T12:49:28.873Z DEBUG 13187 --- [nio-8080-exec-8] o.s.web.servlet.DispatcherServlet        : Failed to complete request: org.springframework.dao.InvalidDataAccessResourceUsageException: JDBC ex
ception executing SQL [select bm1_0.id,bm1_0.name,bm1_0.ndc_category_id,bm1_0.publication_date from book_master bm1_0 where (bm1_0.id=? or ? is null) and (bm1_0.name=? or ? is null) and (bm1_0.publicati
on_date=? or ? is null) and (bm1_0.publication_date=? or ? is null) fetch first ? rows only] [ERROR: could not determine data type of parameter $6] [n/a]; SQL [n/a]
2024-12-19T12:49:28.874Z ERROR 13187 --- [nio-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request p
rocessing failed: org.springframework.dao.InvalidDataAccessResourceUsageException: JDBC exception executing SQL [select bm1_0.id,bm1_0.name,bm1_0.ndc_category_id,bm1_0.publication_date from book_master 
bm1_0 where (bm1_0.id=? or ? is null) and (bm1_0.name=? or ? is null) and (bm1_0.publication_date=? or ? is null) and (bm1_0.publication_date=? or ? is null) fetch first ? rows only] [ERROR: could not d
etermine data type of parameter $6] [n/a]; SQL [n/a]] with root cause

org.postgresql.util.PSQLException: ERROR: could not determine data type of parameter $6
        at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2733) ~[postgresql-42.7.4.jar:42.7.4]
        at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2420) ~[postgresql-42.7.4.jar:42.7.4]
        at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:372) ~[postgresql-42.7.4.jar:42.7.4]
        at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:517) ~[postgresql-42.7.4.jar:42.7.4]
        at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:434) ~[postgresql-42.7.4.jar:42.7.4]
        at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:194) ~[postgresql-42.7.4.jar:42.7.4]
        at org.postgresql.jdbc.PgPreparedStatement.executeQuery(PgPreparedStatement.java:137) ~[postgresql-42.7.4.jar:42.7.4]
        at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52) ~[HikariCP-5.1.0.jar:na]
        at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java) ~[HikariCP-5.1.0.jar:na]
        at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.executeQuery(DeferredResultSetAccess.java:250) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
        at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.getResultSet(DeferredResultSetAccess.java:171) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
        at org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl.<init>(JdbcValuesResultSetImpl.java:74) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
        at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.resolveJdbcValuesSource(JdbcSelectExecutorStandardImpl.java:355) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
        at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:137) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
        at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:102) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
        at org.hibernate.sql.exec.spi.JdbcSelectExecutor.executeQuery(JdbcSelectExecutor.java:91) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
        at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:165) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
        at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.lambda$new$1(ConcreteSqmSelectQueryPlan.java:152) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
        at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:442) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
        at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:362) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
        at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:380) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
        at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:143) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
        at org.hibernate.query.Query.getResultList(Query.java:120) ~[hibernate-core-6.6.2.Final.jar:6.6.2.Final]
        at org.springframework.data.jpa.repository.query.JpaQueryExecution$PagedExecution.doExecute(JpaQueryExecution.java:205) ~[spring-data-jpa-3.4.0.jar:3.4.0]
        at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:93) ~[spring-data-jpa-3.4.0.jar:3.4.0]
        at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:152) ~[spring-data-jpa-3.4.0.jar:3.4.0]
        at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:140) ~[spring-data-jpa-3.4.0.jar:3.4.0]
        at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) ~[spring-data-commons-3.4.0.jar:3.4.0]
        at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) ~[spring-data-commons-3.4.0.jar:3.4.0]
        at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:170) ~[spring-data-commons-3.4.0.jar:3.4.0]
        at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:149) ~[spring-data-commons-3.4.0.jar:3.4.0]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.0.jar:6.2.0]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:380) ~[spring-tx-6.2.0.jar:6.2.0]
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.2.0.jar:6.2.0]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.0.jar:6.2.0]
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138) ~[spring-tx-6.2.0.jar:6.2.0]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.0.jar:6.2.0]
        at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:136) ~[spring-data-jp
a-3.4.0.jar:3.4.0]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.0.jar:6.2.0]
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223) ~[spring-aop-6.2.0.jar:6.2.0]
        at jdk.proxy2/jdk.proxy2.$Proxy150.findByComplexConditions(Unknown Source) ~[na:na]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
        at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:281) ~[spring-core-6.2.0.jar:6.2.0]
        at org.springframework.data.repository.support.ReflectionRepositoryInvoker.invoke(ReflectionRepositoryInvoker.java:220) ~[spring-data-commons-3.4.0.jar:3.4.0]
        at org.springframework.data.repository.support.ReflectionRepositoryInvoker.invokeQueryMethod(ReflectionRepositoryInvoker.java:155) ~[spring-data-commons-3.4.0.jar:3.4.0]
        at org.springframework.data.rest.core.support.UnwrappingRepositoryInvokerFactory$UnwrappingRepositoryInvoker.invokeQueryMethod(UnwrappingRepositoryInvokerFactory.java:97) ~[spring-data-rest-core
-4.4.0.jar:4.4.0]
        at org.springframework.data.rest.webmvc.RepositorySearchController.executeQueryMethod(RepositorySearchController.java:339) ~[spring-data-rest-webmvc-4.4.0.jar:4.4.0]
        at org.springframework.data.rest.webmvc.RepositorySearchController.executeSearch(RepositorySearchController.java:170) ~[spring-data-rest-webmvc-4.4.0.jar:4.4.0]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
        at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:255) ~[spring-web-6.2.0.jar:6.2.0]
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188) ~[spring-web-6.2.0.jar:6.2.0]
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.2.0.jar:6.2.0]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) ~[spring-webmvc-6.2.0.jar:6.2.0]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) ~[spring-webmvc-6.2.0.jar:6.2.0]
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.2.0.jar:6.2.0]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1088) ~[spring-webmvc-6.2.0.jar:6.2.0]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:978) ~[spring-webmvc-6.2.0.jar:6.2.0]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.2.0.jar:6.2.0]
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.2.0.jar:6.2.0]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[tomcat-embed-core-10.1.33.jar:6.0]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.2.0.jar:6.2.0]
        at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.33.jar:6.0]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.33.jar:10.1.33]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.2.0.jar:6.2.0]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.0.jar:6.2.0]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.2.0.jar:6.2.0]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.0.jar:6.2.0]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.2.0.jar:6.2.0]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.0.jar:6.2.0]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:397) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:905) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) ~[tomcat-embed-core-10.1.33.jar:10.1.33]
        at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:

A: キャストが足りなかったらしい

null と突き合わせるときは明示的に型指定が必要っぽい (date(:publicationDateBegin) is null とか date(:publicationDateEnd) is null の部分)

package dev.mikoto2000.study.springboot.web.practice20241215.repository;

import java.time.LocalDate;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

import dev.mikoto2000.study.springboot.web.practice20241215.entity.BookMaster;
import dev.mikoto2000.study.springboot.web.practice20241215.projection.DefaultBookMasterProjection;

/**
 * BookMasterRepository
 */
@RepositoryRestResource(excerptProjection = DefaultBookMasterProjection.class)
public interface BookMasterRepository extends PagingAndSortingRepository<BookMaster, Long>, CrudRepository<BookMaster, Long> {

  @Query(value = """
    select b from BookMaster b
      where
        (b.id = :id or :id is null)
        and
        (b.name = :name or :name is null)
        and
        (b.publicationDate >= :publicationDateBegin or date(:publicationDateBegin) is null)
        and
        (b.publicationDate <= :publicationDateEnd or date(:publicationDateEnd) is null)
  """
  )
  Page<BookMaster> findByComplexConditions(
      Long id,
      String name,
      LocalDate publicationDateBegin,
      LocalDate publicationDateEnd,
      Pageable pageable);
}

2018年10月14日日曜日

Java 11 + Spring Boot 2 + Spring Data JPA で PostgreSQL にアクセスする

こんな感じのことをします。(察して...)

DB アクセスのエッセンスに注目したかったので、 CLI アプリとして作ります。

環境

  • OS: Windows 10 Pro
  • Java: openjdk version "11" 2018-09-25
  • Docker: Docker version 18.06.1-ce, build e68fc7a

依存定義

次の jar を依存関係として定義した。

Java9(11?) からの変更の影響により、 jaxb-apijavassist を明示的に追加する必要が出てきたらしい。

dependencies {
    compile("org.springframework.boot:spring-boot-starter:2.0.5.RELEASE")
    compile('org.springframework.boot:spring-boot-starter-data-jpa:2.0.5.RELEASE')
    runtime('org.postgresql:postgresql')

    // https://mvnrepository.com/artifact/javax.xml.bind/jaxb-api
    compile group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1'

    // https://mvnrepository.com/artifact/org.javassist/javassist
    compile group: 'org.javassist', name: 'javassist', version: '3.23.1-GA'
}

PostgreSQL 環境の構築

構成

  • サーバー: PostgreSQL
  • DB: postgres

こんな感じで構築する。

               List of relations
 Schema |      Name      |   Type   |  Owner
--------+----------------+----------+----------
 public | message        | table    | postgres
 public | message_id_seq | sequence | postgres
(2 rows)
                                    Table "public.message"
 Column  |          Type          | Collation | Nullable |               Defaul
t
---------+------------------------+-----------+----------+-------------------------------------
 id      | integer                |           | not null | nextval('message_id_seq'::regclass)
 message | character varying(255) |           | not null |
Indexes:
    "message_pkey" PRIMARY KEY, btree (id)

環境を作る

Docker オフィシャルの PostgreSQL イメージを使って作成する。

Docker オフィシャルの PostgreSQL イメージは、コンテナ起動時に指定した sql を実行する機能があるので、その仕組みを使ってデータを入れる。

初期データ作成 sql

$(pwd)/schema/schema.sql を作成。

DROP TABLE IF EXISTS message;
DROP SEQUENCE IF EXISTS message_id_seq;

CREATE SEQUENCE message_id_seq START WITH 1 INCREMENT BY 1;

CREATE TABLE message (
  id INTEGER DEFAULT nextval('message_id_seq') PRIMARY KEY,
  message VARCHAR(255) NOT NULL
);

INSERT INTO message(message)
    VALUES
        ('テスト1'),
        ('テスト2');

コンテナ起動 + データ作成

次のコマンドで、 PostgreSQL サーバー起動と、前述の初期データの作成が完了する。

docker run -it --rm --name postgres -v "$(pwd)/schema:/docker-entrypoint-initdb.d" -p 5432:5432 postgres

プログラミング

エンティティの作成

まず、 DB から取得するレコードを表す Bean を作成する。

  • 今回は、 idmessage を取得したいので、それらをフィールドに持つ Bean を作る
  • アノテーション @Entity を付ける
  • プライマリキーに @Id アノテーションを付ける
  • id はシーケンスによる自動採番なので、それ関係のアノテーションを付ける
    • @SequenceGenerator
    • @GeneratedValue
package gs.postgres;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;

/**
 * Message
 */
@Entity
@SequenceGenerator(name = "message_id_seq", sequenceName = "message_id_seq", allocationSize = 1, initialValue = 1)
public class Message {
    @Id
    @GeneratedValue(generator="message_id_seq")
    private Long id;

    private String message;

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getMessage() {
        return this.message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return String.format("id:%d, message=%s", this.id, this.message);
    }
}

リポジトリインターフェースを作成

DB への操作を抽象化してくれるやつを作る。

JpaRepository を継承したインターフェース定義だけ定義すれば、 Spring が良しなに実装を作ってくれるらしい。

継承する JpaRepository は、 JpaRepository<エンティティの型, エンティティのプライマリキーの型> とする。

package gs.postgres;

import org.springframework.data.jpa.repository.JpaRepository;

/**
 * MessageRepository
 */
public interface MessageRepository extends JpaRepository<Message, Long> {
}

DB にアクセスするプログラムを作る

情報取得と挿入、標準出力するプログラムを作成する。

  • repo フィールドに @Autowired アノテーションを付けているため、 Spring によって自動で MessageRepository のインスタンス化と代入が行われる。
    • MessageRepository は、SpringApplication.run(App.class, args) をした時点で、よしなに実装が作られるみたい
  • repoJpaRepository (Spring Data JPA 2.1.0.RELEASE API) のインターフェースが実装されているので、これを使って DB 操作ができる
package gs.postgres;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.domain.Sort;

@SpringBootApplication
public class App implements CommandLineRunner {

    // `SpringApplication.run` で実行した時点で、
    // よしなに `MessageRepository` クラスが注入されるらしい。
    @Autowired
    private MessageRepository repo;

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

    /**
     * App 主処理
     *
     * 1. メッセージを全件取得して標準出力
     * 2. メッセージインサート
     * 3. メッセージを全件取得して標準出力
     *
     */
    @Override
    public void run(String... args) {

        // DB(postgres/message) から全件取得する
        List<Message> messages1 = repo.findAll(new Sort(Sort.Direction.ASC, "id"));

        // 取得した全件標準出力
        for (Message message : messages1) {
            System.out.println(message);
        }

        // 新規メッセージのインサート
        // ID はシーケンスからの自動採番なので null のままで良い
        Message insertMessage = new Message();
        insertMessage.setMessage("インサートメッセージ");
        repo.save(insertMessage);

        System.out.println("Message added.");

        // もう一度全件取得する
        List<Message> messages2 = repo.findAll(new Sort(Sort.Direction.ASC, "id"));

        // 取得した全件標準出力
        for (Message message : messages2) {
            System.out.println(message);
        }
    }
}

アプリの設定

src/main/resourcesapplication.propertieshibernate.properties を作成する。

application.properties

PostgreSQL に接続するための情報を記述。

spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=

hibernate.properties

hibernate.properties はこのエラー対策

hibernate.jdbc.lob.non_contextual_creation = true

動作確認

ここまで作ったらあとは gradle run するだけ。

gradle run

以下のような感じで取得とインサートが出来ていることが確認できる。

> gradle run

> Task :run

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.5.RELEASE)

...(略)...

2018-10-14 14:36:00.877  INFO 25552 --- [           main] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
id:1, message=テスト1
id:2, message=テスト2
Message added.
id:1, message=テスト1
id:2, message=テスト2
id:3, message=インサートメッセージ

...(略)...

2018-10-14 14:36:01.047  INFO 25552 --- [       Thread-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

BUILD SUCCESSFUL in 5s
3 actionable tasks: 1 executed, 2 up-to-date

完成品はこちら

以上。

参考資料