6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Spring Boot+MyBatisでHello World

Last updated at Posted at 2022-04-30

What's?

Spring Boot環境下で、MyBatisのHello World的なことをやってみよう、ということで。

環境

今回の環境。

$ java --version
openjdk 11.0.15 2022-04-19
OpenJDK Runtime Environment (build 11.0.15+10-Ubuntu-0ubuntu0.20.04.1)
OpenJDK 64-Bit Server VM (build 11.0.15+10-Ubuntu-0ubuntu0.20.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)
Maven home: /home/charon/.sdkman/candidates/maven/current
Java version: 11.0.15, vendor: Private Build, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-109-generic", arch: "amd64", family: "unix"

データベースは、PostgreSQL 14.2を使うことにします。Dockerで用意。

$ docker container run \
  -it --rm --name postgres \
  -p 5432:5432 \
  -e POSTGRES_DB=example \
  -e POSTGRES_USER=charon \
  -e POSTGRES_PASSWORD=password \
  postgres:14.2-bullseye

Spring Boot+MyBatisプロジェクトを作成する

Spring Initializrを使って、MyBatisが使えるようにしたSpring Bootプロジェクトを作成します。

$ curl -s https://start.spring.io/starter.tgz \
  -d bootVersion=2.6.7 \
  -d javaVersion=11 \
  -d name=spring-hello-mybatis \
  -d groupId=com.example \
  -d artifactId=hello-mybatis \
  -d version=0.0.1-SNAPSHOT \
  -d packageName=com.example.spring.mybatis \
  -d dependencies=mybatis,postgresql \
  -d baseDir=hello-mybatis | tar zxvf -

ディレクトリ内に移動。

$ cd hello-mybatis

自動生成されたソースコードは、今回は使わないので削除。

$ rm src/main/java/com/example/spring/mybatis/SpringHelloMybatisApplication.java src/test/java/com/example/spring/mybatis/SpringHelloMybatisApplicationTests.java

依存関係やプラグインの設定なども見ておきます。

	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.2.2</version>
		</dependency>

		<dependency>
			<groupId>org.postgresql</groupId>
			<artifactId>postgresql</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

mvn dependency:treeで見ると、spring-boot-starterspring-boot-starter-jdbcも含まれているようなので、最低限であればmybatis-spring-boot-starterだけで良さそうですね。

[INFO] +- org.mybatis.spring.boot:mybatis-spring-boot-starter:jar:2.2.2:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:2.6.7:compile
[INFO] |  |  +- org.springframework.boot:spring-boot:jar:2.6.7:compile
[INFO] |  |  |  \- org.springframework:spring-context:jar:5.3.19:compile
[INFO] |  |  |     +- org.springframework:spring-aop:jar:5.3.19:compile
[INFO] |  |  |     \- org.springframework:spring-expression:jar:5.3.19:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:2.6.7:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.6.7:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.2.11:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.11:compile
[INFO] |  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.2:compile
[INFO] |  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.17.2:compile
[INFO] |  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.36:compile
[INFO] |  |  +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
[INFO] |  |  \- org.yaml:snakeyaml:jar:1.29:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.6.7:compile
[INFO] |  |  +- com.zaxxer:HikariCP:jar:4.0.3:compile
[INFO] |  |  \- org.springframework:spring-jdbc:jar:5.3.19:compile
[INFO] |  |     +- org.springframework:spring-beans:jar:5.3.19:compile
[INFO] |  |     \- org.springframework:spring-tx:jar:5.3.19:compile
[INFO] |  +- org.mybatis.spring.boot:mybatis-spring-boot-autoconfigure:jar:2.2.2:compile
[INFO] |  +- org.mybatis:mybatis:jar:3.5.9:compile
[INFO] |  \- org.mybatis:mybatis-spring:jar:2.0.7:compile

ソースコードを作成する

最初に、テーブルを決めましょう。今回は、こちらをお題にします。

src/main/resources/schema.sql
drop table if exists person;

create table person (
  id serial,
  first_name varchar(20),
  last_name varchar(20),
  age integer,
  primary key(id)
);

次に、Javaのソースコードを書いていきます。

MyBatisに関しては、このあたりを参考にしていきます。

テーブルにマッピングするモデル。

src/main/java/com/example/spring/mybatis/Person.java
package com.example.spring.mybatis;

public class Person {
    Long id;
    String firstName;
    String lastName;
    Integer age;

    public static Person create(String firstName, String lastName, Integer age) {
        Person person = new Person();

        person.setFirstName(firstName);
        person.setLastName(lastName);
        person.setAge(age);

        return person;
    }

    // getter、setterは省略
}

次に、Mapperインターフェースを作成。

src/main/java/com/example/spring/mybatis/PersonMapper.java
package com.example.spring.mybatis;

import java.util.List;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface PersonMapper {
    @Insert("insert into person(first_name, last_name, age) values(#{firstName}, #{lastName}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(Person person);

    @Select("select id, first_name, last_name, age from person order by id")
    List<Person> findAll();

    @Select("select id, first_name, last_name, age from person where last_name = #{lastName}")
    List<Person> findByLastName(String lastName);

    @Select("select id, first_name, last_name, age from person where last_name = #{lastName} and age > #{age}")
    List<Person> findByLastNameAndAge(Person person);

    @Select("select id, first_name, last_name, age from person where last_name = #{lastName} and age > #{age}")
    List<Person> findByLastNameAndAgePrimitive(String lastName, int age);
}

今回は、MapperインターフェースはXMLファイルではなくアノテーションを使って作成します。

There's one more trick to Mapper classes like BlogMapper. Their mapped statements don't need to be mapped with XML at all. Instead they can use Java Annotations.

@Mapperアノテーションを付与しておけば、MyBatis Spring Boot Starterが自動検出してくれます。

@Mapper
public interface PersonMapper {

The MyBatis-Spring-Boot-Starter will search, by default, for mappers marked with the @Mapper annotation.

insert文。

    @Insert("insert into person(first_name, last_name, age) values(#{firstName}, #{lastName}, #{age})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(Person person);

SQLにパラメーターを埋め込むには、#{変数名}表記を使います。

また、今回idはPostgreSQLのserial型を使用しているので自動採番です。

こちらを使うにはuseGeneratedKeystrueにして、さらにモデルに反映するにはkeyPropertyで採番結果を反映するプロパティを指定します。

Mapper XML Files / insert, update and delete

この時、アノテーションとしては@Optionsを使用します。

あとは、いくつかのselect文のバリエーションです。

    @Select("select id, first_name, last_name, age from person order by id")
    List<Person> findAll();

    @Select("select id, first_name, last_name, age from person where last_name = #{lastName}")
    List<Person> findByLastName(String lastName);

    @Select("select id, first_name, last_name, age from person where last_name = #{lastName} and age > #{age}")
    List<Person> findByLastNameAndAge(Person person);

    @Select("select id, first_name, last_name, age from person where last_name = #{lastName} and age > #{age}")
    List<Person> findByLastNameAndAgePrimitive(String lastName, int age);

最後の複数パラメーターの例は確認のために入れているのですが、引数が複数ある場合は@Paramで明示的に名前を指定するか、コンパイル時に-parametersオプションを指定しつつuseActualParamNametrue(デフォルト)とする必要があるようです。

Since 3.4.3, by specifying the name of each parameter, you can write arg elements in any order. To reference constructor parameters by their names, you can either add @Param annotation to them or compile the project with '-parameters' compiler option and enable useActualParamName (this option is enabled by default).

Mapper XML Files / Result Maps

今回はプロジェクトをSpring Initializrで作成したのでspring-boot-starter-parentが親pomとなっており、-parametersが指定されている状態になっているので、この前提のまま進みます。

このMapperインターフェースを使うクラスを作成。

src/main/java/com/example/spring/mybatis/Runner.java
package com.example.spring.mybatis;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class Runner implements ApplicationRunner {
    Logger logger = LoggerFactory.getLogger(Runner.class);
    PersonMapper personMapper;

    public Runner(PersonMapper personMapper) {
        this.personMapper = personMapper;
    }

    @Transactional
    @Override
    public void run(ApplicationArguments args) throws Exception {
        // insert
        Person maruko = Person.create("ももこ", "さくら", 9);
        Person sakiko = Person.create("さきこ", "さくら", 11);
        Person tamae = Person.create("たまえ", "穂波", 9);

        personMapper.insert(maruko);
        personMapper.insert(sakiko);
        personMapper.insert(tamae);

        List
                .of(maruko, sakiko, tamae)
                .forEach(p -> logger.info("inserted: id = {}, first_name = {}, last_name = {}, age = {}", p.getId(), p.getFirstName(), p.getLastName(), p.getAge()));

        // select all
        List<Person> allPeople = personMapper.findAll();
        allPeople.forEach(p -> logger.info("find all: id = {}, first_name = {}, last_name = {}, age = {}", p.getId(), p.getFirstName(), p.getLastName(), p.getAge()));

        // select by last_name
        List<Person> sakuraFamily = personMapper.findByLastName("さくら");
        sakuraFamily.forEach(p -> logger.info("find by last_name: id = {}, first_name = {}, last_name = {}, age = {}", p.getId(), p.getFirstName(), p.getLastName(), p.getAge()));

        // select by object
        List<Person> results = personMapper.findByLastNameAndAge(Person.create(null, "さくら", 10));
        results.forEach(p -> logger.info("find by object: id = {}, first_name = {}, last_name = {}, age = {}", p.getId(), p.getFirstName(), p.getLastName(), p.getAge()));

        // select by primitives
        List<Person> results2 = personMapper.findByLastNameAndAgePrimitive("さくら", 10);
        results2.forEach(p -> logger.info("find by primitives: id = {}, first_name = {}, last_name = {}, age = {}", p.getId(), p.getFirstName(), p.getLastName(), p.getAge()));
    }
}

データをinsertした後に、用意したselect文をそれぞれ実行しています。

最後にmainクラス。

src/main/java/com/example/spring/mybatis/App.java
package com.example.spring.mybatis;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
    public static void main(String... args) {
        SpringApplication.run(App.class);
    }
}

設定をする

設定もしておきましょう。

spring.datasource.*プロパティで、用意したPostgreSQLへの接続情報を設定。

src/main/resources/application.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/example
spring.datasource.username=charon
spring.datasource.password=password

spring.sql.init.mode=always

mybatis.configuration.map-underscore-to-camel-case=true

spring.sql.init.modealwaysにしているのは、schema.sqlを常に実行するためです。

mybatis.configuration.map-underscore-to-camel-casetrueにしているのは、

Mapperインターフェースで作成していたクエリーの結果のカラム名を、キャメルケースのJavaのプロパティにマッピングするためのものです。

    @Select("select id, first_name, last_name, age from person order by id")
    List<Person> findAll();

こういう書き方の場合、mybatis.configuration.map-underscore-to-camel-casetrueにしておかないとクエリーの結果を取得する際に、このようなアンダースコア入りの列の値が設定されません。

Mapper XML Files / Auto-mapping

他にもresultMapを設定するといった方法もありますが、今回はこちらの方針でいきます。

実行する

では、実行してみます。

$ mvn spring-boot:run

結果のログ。

2022-04-30 21:14:34.597  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : inserted: id = 1, first_name = ももこ, last_name = さくら, age = 9
2022-04-30 21:14:34.598  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : inserted: id = 2, first_name = さきこ, last_name = さくら, age = 11
2022-04-30 21:14:34.598  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : inserted: id = 3, first_name = たまえ, last_name = 穂波, age = 9
2022-04-30 21:14:34.605  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : find all: id = 1, first_name = ももこ, last_name = さくら, age = 9
2022-04-30 21:14:34.606  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : find all: id = 2, first_name = さきこ, last_name = さくら, age = 11
2022-04-30 21:14:34.606  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : find all: id = 3, first_name = たまえ, last_name = 穂波, age = 9
2022-04-30 21:14:34.608  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : find by last_name: id = 1, first_name = ももこ, last_name = さくら, age = 9
2022-04-30 21:14:34.608  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : find by last_name: id = 2, first_name = さきこ, last_name = さくら, age = 11
2022-04-30 21:14:34.610  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : find by object: id = 2, first_name = さきこ, last_name = さくら, age = 11
2022-04-30 21:14:34.612  INFO 30274 --- [           main] com.example.spring.mybatis.Runner        : find by primitives: id = 2, first_name = さきこ, last_name = さくら, age = 11

insert文の実行後に、serialで採番されたidがモデルに反映されています。

inserted: id = 1, first_name = ももこ, last_name = さくら, age = 9
inserted: id = 2, first_name = さきこ, last_name = さくら, age = 11
inserted: id = 3, first_name = たまえ, last_name = 穂波, age = 9

select文もOKそうですね。

find all: id = 1, first_name = ももこ, last_name = さくら, age = 9
find all: id = 2, first_name = さきこ, last_name = さくら, age = 11
find all: id = 3, first_name = たまえ, last_name = 穂波, age = 9


find by last_name: id = 1, first_name = ももこ, last_name = さくら, age = 9
find by last_name: id = 2, first_name = さきこ, last_name = さくら, age = 11


find by object: id = 2, first_name = さきこ, last_name = さくら, age = 11

find by primitives: id = 2, first_name = さきこ, last_name = さくら, age = 11

今回はこんなところでしょうか。

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?