SpringBootでwebAPIサーバーを作るハンズオン(Java, Maven)
概要
SpringBoot を学んだので CRUD ができる Web API サーバを作ります。
repository と service はインタフェースと実行クラスを分けています。
最終的なソースコードの URL です。
ディレクトリ構成は以下のようになりました。
.
├── HELP.md
├── demo.iml
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── demo
│ │ │ ├── DemoApplication.java
│ │ │ ├── configuration
│ │ │ │ ├── DataSourceConfiguration.java
│ │ │ │ └── DataSourceConfigurationProperties.java
│ │ │ ├── controller
│ │ │ │ └── MovieRestController.java
│ │ │ ├── domain
│ │ │ │ ├── Director.java
│ │ │ │ ├── Movie.java
│ │ │ │ └── MovieList.java
│ │ │ ├── repository
│ │ │ │ ├── MovieRepository.java
│ │ │ │ ├── MovieRepositoryImpl.java
│ │ │ │ └── mybatis
│ │ │ │ ├── MovieMapper.java
│ │ │ │ └── MovieMapper.xml
│ │ │ └── service
│ │ │ ├── MovieService.java
│ │ │ └── MovieServiceImpl.java
│ │ └── resources
│ │ ├── application.yml
│ │ ├── static
│ │ └── templates
│ └── test
│ └── java
│ └── com
│ └── example
│ └── demo
│ └── DemoApplicationTests.java
└── target
├── classes
│ ├── application.yml
│ └── com
│ └── example
│ └── demo
│ ├── DemoApplication.class
│ ├── configuration
│ │ ├── DataSourceConfiguration.class
│ │ └── DataSourceConfigurationProperties.class
│ ├── controller
│ │ └── MovieRestController.class
│ ├── domain
│ │ ├── Director.class
│ │ ├── Movie.class
│ │ └── MovieList.class
│ ├── repository
│ │ ├── MovieRepository.class
│ │ ├── MovieRepositoryImpl.class
│ │ └── mybatis
│ │ ├── MovieMapper.class
│ │ └── MovieMapper.xml
│ └── service
│ ├── MovieService.class
│ └── MovieServiceImpl.class
├── generated-sources
│ └── annotations
├── generated-test-sources
│ └── test-annotations
└── test-classes
└── com
└── example
└── demo
└── DemoApplicationTests.class
39 directories, 35 files
下準備
環境
項目 | 環境 |
---|---|
OS | macOS |
IDE | Intellij IDEA |
DB | MySQL |
フレームワーク作成
Spring Initializr を使用して demo.zip をダウンロードして適当な場所で解凍します。
設定は以下のようにしました。
項目 | 設定 |
---|---|
Project | Maven |
Language | Java |
Spring Boot | 2.4.5 |
Project Metadata | Java 8(それ以外はデフォルト) |
Dependencies | Spring Web MySQL Driver MyBatis Framework |
MySQL の準備
以下の構成で作成します。
認証関係
項目 | 設定 |
---|---|
ユーザー | root |
パスワード | password |
DB 関係
新しい DB を作成
CREATE DATABASE moviedb;
moviedb を選択
USE moviedb;
外部キーを持った テーブル を作成
director テーブルを作成。
CREATE TABLE director
(director_id char(10) PRIMARY KEY ,
director_name varchar(20));
movie テーブルを作成。
CREATE TABLE movie
(movie_id CHAR(10) PRIMARY KEY,
movie_name VARCHAR(50),
director_id CHAR(10),
CONSTRAINT director_id_fk FOREIGN KEY (director_id) REFERENCES director(director_id));
tabel に値を挿入
director に挿入。
INSERT INTO director VALUES('D01', '新海誠');
INSERT INTO director VALUES('D02', '藤井道人');
INSERT INTO director VALUES('D03', 'クリストファー・ノーラン');
movie に挿入。
INSERT INTO movie VALUES('M01', '君の名は。', 'D01');
INSERT INTO movie VALUES('M02', '天気の子', 'D01');
INSERT INTO movie VALUES('M03', '言の葉の庭', 'D01');
INSERT INTO movie VALUES('M04', '新聞記者', 'D02');
INSERT INTO movie VALUES('M05', 'デイアンドナイト', 'D02');
INSERT INTO movie VALUES('M06', 'ダークナイト', 'D03');
INSERT INTO movie VALUES('M07', 'インセプション', 'D03');
手順
1. プロジェクトの新規作成
- Intellij -> ファイル -> 新規 -> 既存のソースからプロジェクト -> demo を選択して Open をクリックします
- "既存プロジェクトから作成する"をチェックして完了を押下します
2. pom.xml を編集
dependencies タグに以下を追加。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency>
また build タグに resources タグを追加。
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
追加後、pom.xml を右クリック->Maven->"プロジェクトの再ロード"を選択。
3. application.yml を作成
まず、demo/src/main/java/resources/application.properties
の名前を変更します。右クリックして"リファクタリング"を選択し、application.yml にします。
記述する設定は以下になります。
- JDBC の接続設定
- ドライバ
- MySQL
- password, username、文字コード
- コネクションプール
- 初期コネクション数、アイドル時間
- MyBatis の設定
- mapper-locations
- MapperXML の場所を指定
- mapper-locations
# JDBCの接続設定
dbcp2.jdbc:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/moviedb?characterEncoding=UTF-8
username: root
password: password
initial-size: 1
max-idle: 3
min-idle: 1
# mybatisの設定
mybatis:
mapper-locations: classpath:com/example/demo/repository/mybatis/*.xml
4. domain を作成
demo/src/main/java/com/example/demo/に domain パッケージを作成します。
domain パッケージ配下に Director クラス, Movie クラス, MovieList クラスを作成します。
Director クラス
package com.example.demo.domain;
public class Director {
private String directorId;
private String directorName;
public Director() {
}
public Director(String directorId, String directorName) {
this.directorId = directorId;
this.directorName = directorName;
}
public String getDirectorId() {
return directorId;
}
public void setDirectorId(String directorId) {
this.directorId = directorId;
}
public String getDirectorName() {
return directorName;
}
public void setDirectorName(String directorName) {
this.directorName = directorName;
}
}
Movie クラス
package com.example.demo.domain;
public class Movie {
private String movieId;
private String movieName;
private Director director;
public Movie() {
}
public Movie(String movieId, String movieName) {
this.movieId = movieId;
this.movieName = movieName;
}
public String getMovieId() {
return movieId;
}
public void setMovieId(String movieId) {
this.movieId = movieId;
}
public String getMovieName() {
return movieName;
}
public void setMovieName(String movieName) {
this.movieName = movieName;
}
public Director getDirector() {
return director;
}
public void setDirector(Director director) {
this.director = director;
}
}
MovieList
package com.example.demo.domain;
import java.util.List;
public class MovieList {
private List<Movie> movieList;
public List<Movie> getMovieList() {
return movieList;
}
public void setMovieList(List<Movie> movieList) {
this.movieList = movieList;
}
}
5. Repository を作成
demo/src/main/java/com/example/demo/に repository パッケージを作成します。
repository と mybatis によって、MySQL と java の接続ができます。
5-1 Mapper.xml を作成
repository パッケージ配下に mybatis パッケージを作成します。
mybatis パッケージ配下に MyBatis の設定ファイルである、MovieMapper.xml を作成します。
#{}は Java から渡された変数を表示します。
タグの解説。
- mapper タグ
- namespace="Mapper のパス"を指定
- resultMap タグ
- MySQL のテーブルと Java クラスのフィールドを対応付ける
- type=""で Java のオブジェクトとの関連付け
- id タグの property で Java のフィールドを記述し、column で MySQL の列名を記述
- association タグで複数のテーブルとの関連付けをする
- MySQL のテーブルと Java クラスのフィールドを対応付ける
- SQL を記述するタグ(id の意味は後述)
- select タグ
- CRUD の READE に該当
- insert タグ
- CRUD の CREATE に該当
- update タグ
- CRUD の UPDATE に該当
- delete タグ - CRUD の DELETE に該当
- select タグ
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC
"-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.repository.mybatis.MovieMapper">
<resultMap id="Director" type="com.example.demo.domain.Director">
<id property="directorId" column="DIRECTOR_ID"/>
<result property="directorName" column="DIRECTOR_NAME"/>
</resultMap>
<resultMap id="Movie" type="com.example.demo.domain.Movie">
<id property="movieId" column="MOVIE_ID"/>
<result property="movieName" column="MOVIE_NAME"/>
<association property="director" resultMap="Director"/>
</resultMap>
<select id="find" resultMap="Movie">
SELECT M.MOVIE_ID, M.MOVIE_NAME, D.DIRECTOR_ID, D.DIRECTOR_NAME
FROM MOVIE M INNER JOIN DIRECTOR D USING (DIRECTOR_ID)
<where>
<if test="movieName != null">
M.MOVIE_NAME LIKE CONCAT('%', #{movieName}, '%')
</if>
<if test="directorName != null">
AND D.DIRECTOR_NAME LIKE CONCAT('%', #{directorName}, '%')
</if>
</where>
ORDER BY M.MOVIE_ID ASC
</select>
<select id="get" resultMap="Movie">
SELECT M.MOVIE_ID, M.MOVIE_NAME, D.DIRECTOR_ID, D.DIRECTOR_NAME
FROM MOVIE M INNER JOIN DIRECTOR D USING (DIRECTOR_ID)
WHERE MOVIE_ID = #{movieId}
</select>
<select id="lock" resultMap="Movie">
SELECT M.MOVIE_ID, M.MOVIE_NAME, D.DIRECTOR_ID, D.DIRECTOR_NAME
FROM MOVIE M INNER JOIN DIRECTOR D USING (DIRECTOR_ID)
WHERE MOVIE_ID = #{movieId}
FOR UPDATE
</select>
<insert id="add" parameterType="com.example.demo.domain.Movie" keyProperty="movieId">
<!-- selectKeyによってmovieIdを新しく設定する -->
<selectKey keyProperty="movieId" resultType="string" order="BEFORE">
<!-- MOVIE_IDのAUTO INCREMENTを実装 -->
SELECT COALESCE(CONCAT('M', LPAD(RIGHT(MAX(MOVIE_ID), 2) + 1, 2, '0')), 'M01') FROM MOVIE
</selectKey>
INSERT INTO MOVIE (MOVIE_ID , MOVIE_NAME, DIRECTOR_ID) VALUES (#{movieId}, #{movieName}, #{director.directorId});
</insert>
<update id="set" parameterType="com.example.demo.domain.Movie">
UPDATE MOVIE
<set>
<if test="movieName != null">
MOVIE_NAME = #{movieName},
</if>
<if test="director.directorId != null">
DIRECTOR_ID = #{director.directorId},
</if>
</set>
WHERE MOVIE_ID = #{movieId}
</update>
<delete id="remove" parameterType="com.example.demo.domain.Movie">
DELETE FROM MOVIE
WHERE MOVIE_ID = #{movieId}
</delete>
</mapper>
5-2 Mapper インタフェースを作成
mybatis パッケージ配下に MovieMapper インタフェースを作成します。
抽象メソッドを定義します。
メソッド名は MovieMapper.xml で SQL を記述するタグの id="" で定義した名前になっています。
@Param が MovieMapper.xml の引数との関連付けをします。
package com.example.demo.repository.mybatis;
import com.example.demo.domain.Movie;
import java.util.List;
import org.apache.ibatis.annotations.Param;
public interface MovieMapper {
List<Movie> find(@Param("movieName") String movieName, @Param("directorName") String directorName);
Movie get(@Param("movieId") String movieId);
Movie lock(@Param("movieId") String movieId);
int add(Movie movie);
int set(Movie movie);
int remove(Movie movie);
}
5-3 Repository インタフェースを作成
repository パッケージ配下に MovieRepository インタフェースを作成します。
抽象メソッドを定義します。
package com.example.demo.repository;
import com.example.demo.domain.Movie;
import java.util.List;
public interface MovieRepository {
List<Movie> findList(String movieName, String directorName);
Movie findOne(String movieId);
Movie lock(String movieId);
void insert(Movie movie);
void update(Movie movie);
void delete(Movie movie);
}
5-4 RepositoryImpl クラスを作成
repository パッケージ配下に MovieRepository インタフェースを実装した、実行クラスである RepositoryImpl クラスを作成します。
@Repository アノテーションによって、repository に DI をします。
SqlSessionTemplate クラスによって、Mapper インタフェースを読み込み、MySQL を操作するメソッドを利用できます。
package com.example.demo.repository;
import com.example.demo.domain.Movie;
import com.example.demo.repository.mybatis.MovieMapper;
import java.util.List;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class MovieRepositoryImpl implements MovieRepository {
private final SqlSessionTemplate sqlSessionTemplate;
public MovieRepositoryImpl(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
@Override
public List<Movie> findList(String movieName, String directorName) {
List<Movie> movie = this.sqlSessionTemplate.getMapper(MovieMapper.class).find(movieName, directorName);
return movie;
}
@Override
public Movie findOne(String movieId) {
Movie movie = this.sqlSessionTemplate.getMapper(MovieMapper.class).get(movieId);
if (movie == null){
throw new RuntimeException("movie not found");
}
return movie;
}
@Override
public Movie lock(String movieId) {
Movie movie = this.sqlSessionTemplate.getMapper(MovieMapper.class).lock(movieId);
if (movie == null){
throw new RuntimeException("movie not found");
}
return movie;
}
@Override
public void insert(Movie movie) {
this.sqlSessionTemplate.getMapper(MovieMapper.class).add(movie);
}
@Override
public void update(Movie movie) {
int affected = this.sqlSessionTemplate.getMapper(MovieMapper.class).set(movie);
if (affected != 1){
throw new RuntimeException("failed");
}
}
@Override
public void delete(Movie movie) {
int affected = this.sqlSessionTemplate.getMapper(MovieMapper.class).remove(movie);
if (affected != 1){
throw new RuntimeException("failed");
}
}
}
6 Service を作成
demo/src/main/java/com/example/demo/配下に、service パッケージを作成します。
ビジネスロジックとトランザクション管理をします。
6-1 Service インタフェースを作成
service パッケージ配下に MovieService インタフェースを作成する。
package com.example.demo.service;
import com.example.demo.domain.Movie;
import com.example.demo.domain.MovieList;
public interface MovieService {
MovieList find(String movieName, String directorName);
Movie get(String movieId);
void add(Movie movie);
void set(Movie movie);
void remove(String movieId);
}
6-2 ServiceImpl クラスを作成
Service インタフェース(6-1)を実装したクラスを作成します。
@Service アノテーションによって DI します。
@Transactional アノテーションによって、DB 操作にエラーが発生したときにロールバックします。
package com.example.demo.service;
import com.example.demo.domain.Movie;
import com.example.demo.domain.MovieList;
import com.example.demo.repository.MovieRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class MovieServiceImpl implements MovieService{
private final MovieRepository repository;
public MovieServiceImpl(MovieRepository repository) {
this.repository = repository;
}
@Override
public MovieList find(String movieName, String directorName) {
MovieList movieList = new MovieList();
movieList.setMovieList(this.repository.findList(movieName, directorName));
return movieList;
}
@Override
public Movie get(String movieId) {
Movie movie = this.repository.findOne(movieId);
return movie;
}
@Override
@Transactional(rollbackFor = Throwable.class)
public void add(Movie movie) {
this.repository.insert(movie);
}
@Override
@Transactional(rollbackFor = Throwable.class)
public void set(Movie movie) {
this.repository.lock(movie.getMovieId());
this.repository.update(movie);
}
@Override
@Transactional(rollbackFor = Throwable.class)
public void remove(String movieId) {
this.repository.delete(this.repository.findOne(movieId));
}
}
7 RestController を作成
demo/src/main/java/com/example/demo/配下に、controller パッケージ作成します。
RestController を作成
controller パッケージ配下に MovieRestController クラスを作成します。
アノテーションについて。
Controller クラスに付与するアノテーション。
- @RestController
- RestController の DI する
- フロントエンドの@Controller とは異なり、戻り値をテキストコンテンツで返すために使用する
- @RequestMapping
- localhost:8080 以下に、/api/movie のパスを割り当てています。
メソッドに付与するアノテーション。
- @GetMapping
- get メソッド時の動作を指定する
- @PostMapping
- post メソッド時の動作を指定する
- @PatchMapping
- patch メソッド時の動作を指定する
- @DeleteMapping
- delete メソッド時の動作を指定する
メソッド内の引数に付与するアノテーション。
- @RequestParam
- HTTP リクエストパラメータを取得する
- @RequestBody
- HTTP リクエストボディのデータを引数のクラスにマッピングする
- @PathVariable
- URI パスのパラメータを取得する
package com.example.demo.controller;
import com.example.demo.domain.Movie;
import com.example.demo.domain.MovieList;
import com.example.demo.service.MovieService;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("api/movie")
public class MovieRestController {
private final MovieService service;
public MovieRestController(MovieService service) {
this.service = service;
}
@GetMapping(path = "", produces = "application/json")
public MovieList find(@RequestParam(name = "movieName", required = false) String movieName,
@RequestParam(name = "directorName", required = false) String directorName){
return this.service.find(movieName, directorName);
}
@GetMapping(path = "/{movieId}", produces = "application/json")
public Movie get(@PathVariable String movieId){
return this.service.get(movieId);
}
@PostMapping(path = "", produces = "application/json")
public void add(@RequestBody Movie movie){
this.service.add(movie);
}
@PatchMapping(path = "/{movieId}", produces = "application/json")
public void update(@PathVariable String movieId, @RequestBody Movie movie){
movie.setMovieId(movieId);
this.service.set(movie);
}
@DeleteMapping(path = "/{movieId}", produces = "application/json")
public void remove(@PathVariable String movieId){
this.service.remove(movieId);
}
}
8 configuration を作成
MySQL との接続設定を作成します。
demo/src/main/java/com/example/demo/配下に、configuration パッケージ作成します。
DataSourceConfigurationProperties を作成
configuration パッケージ配下に DataSourceConfigurationProperties を作成します。
application.yml の読み込みをすることで、独自プロパティの設定をします。
package com.example.demo.configuration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
@ConstructorBinding
@ConfigurationProperties(prefix = "dbcp2.jdbc")
public class DataSourceConfigurationProperties {
private final String url;
private final String driverClassName;
private final String username;
private final String password;
private final int initialSize;
private final int maxIdle;
private final int minIdle;
public DataSourceConfigurationProperties(String url, String driverClassName, String username, String password,
int initialSize, int maxIdle, int minIdle) {
this.url = url;
this.driverClassName = driverClassName;
this.username = username;
this.password = password;
this.initialSize = initialSize;
this.maxIdle = maxIdle;
this.minIdle = minIdle;
}
public String getUrl() {
return url;
}
public String getDriverClassName() {
return driverClassName;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public int getInitialSize() {
return initialSize;
}
public int getMaxIdle() {
return maxIdle;
}
public int getMinIdle() {
return minIdle;
}
}
DataSourceConfiguration を作成
DataSourceConfigurationProperties の読み込むことで、設定の反映します。
package com.example.demo.configuration;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(DataSourceConfigurationProperties.class)
public class DataSourceConfiguration {
private final DataSourceConfigurationProperties properties;
public DataSourceConfiguration(DataSourceConfigurationProperties properties) {
this.properties = properties;
}
@Bean
public DataSource dataSource(){
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(this.properties.getDriverClassName());
dataSource.setUrl(this.properties.getUrl());
dataSource.setUsername(this.properties.getUsername());
dataSource.setPassword(this.properties.getPassword());
dataSource.setInitialSize(this.properties.getInitialSize());
dataSource.setMaxIdle(this.properties.getMaxIdle());
dataSource.setMinIdle(this.properties.getMinIdle());
return dataSource;
}
}
以上で完成です。
動作確認
起動
demo/src/main/java/com/example/demo/配下の実行クラス、DemoApplication クラスを Intellij を使って実行します。
http://localhost:8080/api/movie にアクセスして MySQL に格納されているデータが表示されたら成功です。
CRUD
curl コマンドを用いて動作確認をします。
READ(get)
単一検索
入力。
curl -H "Content-Type: application/json" "localhost:8080/api/movie/M01"
出力。
{
"movieId": "M01",
"movieName": "君の名は。",
"director": { "directorId": "D01", "directorName": "新海誠" }
}
全検索
入力。
curl -H "Content-Type: application/json" "localhost:8080/api/movie"
出力。
{
"movieList": [
{
"movieId": "M01",
"movieName": "君の名は。",
"director": { "directorId": "D01", "directorName": "新海誠" }
},
{
"movieId": "M02",
"movieName": "天気の子",
"director": { "directorId": "D01", "directorName": "新海誠" }
},
{
"movieId": "M03",
"movieName": "言の葉の庭",
"director": { "directorId": "D01", "directorName": "新海誠" }
},
{
"movieId": "M04",
"movieName": "新聞記 者",
"director": { "directorId": "D02", "directorName": "藤井道人" }
},
{
"movieId": "M05",
"movieName": "デイアンドナイト",
"director": { "directorId": "D02", "directorName": "藤井道人" }
},
{
"movieId": "M06",
"movieName": "ダークナイト",
"director": {
"directorId": "D03",
"directorName": "クリストファー・ノーラン"
}
},
{
"movieId": "M07",
"movieName": "インセプション",
"director": {
"directorId": "D03",
"directorName": "クリストファー・ノーラン"
}
}
]
}
CREATE(post)
映画を追加する
入力。
curl -X POST \
-H "Content-Type: application/json" "localhost:8080/api/movie" \
-d '{"movieName":"テネット", "director": {"directorId":"D03"}}'
確認。
curl -H "Content-Type: application/json" "localhost:8080/api/movie/M08"
結果。
{
"movieId": "M08",
"movieName": "テネット",
"director": {
"directorId": "D03",
"directorName": "クリストファー・ノーラン"
}
}
UPDATE(patch)
映画を更新する
入力。
curl -X PATCH \
-H "Content-Type: application/json" "localhost:8080/api/movie/M08" \
-d '{"movieName":"秒速5センチメートル", "director": {"directorId":"D01"}}'
確認。
curl -H "Content-Type: application/json" "localhost:8080/api/movie/M08"
結果。
{
"movieId": "M08",
"movieName": "秒速5センチメートル",
"director": { "directorId": "D01", "directorName": "新海誠" }
}
DELETE(delete)
映画を削除する
入力。
curl -X DELETE -H "Content-Type: application/json" "localhost:8080/api/movie/M08"
確認。
curl -H "Content-Type: application/json" "localhost:8080/api/movie/M08"
出力。
{
"timestamp": "2021-05-02T22:17:32.490+00:00",
"status": 500,
"error": "Internal Server Error",
"message": "",
"path": "/api/movie/M08"
}
Discussion
こんにちは。記事ありがとうございます!
movie に挿入。のレコードに重複レコードがあるためPKエラーになってしまいます。
INSERT INTO movies VALUES('M07', 'インセプション', 'D03');
お手すきの際に修正いただければと思います。
@yukimaru さん
ご指摘のほどありがとうございます。
こちら、SQL の修正と合わせて movies を movie に統一する対応を実施しました。
ありがとうございます!!
たびたびすみません、
DataSourceConfiguration.javaの
@CoustructorBinding
ですが、現在のTargetがCONSTRUCTOR
のため、コンパイルエラーになってしまいます。@ConstructorBinding
の記述をコンストラクタの上に修正をお願いしたいです。MoviesRepository とMovieRepository の名称ブレがありました。
DDLではテーブル名が
movies
ですが、 xmlのSELECT文ではmovie
になっております