Spring+mybatisを試してみた

# mybatisを動かしてみた話

## CRUD最小構成に適用してみる

- お決まりのPlayerのCRUDサンプルを作ってみる

![players.gif](https://qiita-image-store.s3.amazonaws.com/0/175213/4c262976-feb3-768c-6e54-e508e2cecbdd.gif)

### 共通部分

- mybatisと関連の少ない部分
- templateは省略

#### controller

- 典型的なCRUDのエンドポイント

```java:PlayerController.java
@Controller
@RequestMapping("/players")
public class PlayerController {
    @Autowired
    private PlayerService playerService;

    @GetMapping
    public String index(Model model) {
        model.addAttribute("players", playerService.findAll());
        return "players/index";
    }

    @GetMapping("new")
    public String newPlayer() {
        return "players/new";
    }

    @GetMapping("{id}/edit")
    public String edit(@PathVariable Long id, Model model) {
        model.addAttribute("player", playerService.findOne(id));
        return "players/edit";
    }

    @GetMapping("{id}")
    public String show(@PathVariable Long id, Model model) {
        model.addAttribute("player", playerService.findOne(id));
        return "players/show";
    }

    @PostMapping
    public String create(@ModelAttribute Player player) {
        playerService.save(player);
        return "redirect:/players";
    }

    @PutMapping("{id}")
    public String update(@PathVariable Long id, @ModelAttribute Player player) {
        player.setId(id);
        playerService.update(player);
        return "redirect:/players";
    }

    @DeleteMapping("{id}")
    public String destroy(@PathVariable Long id) {
        playerService.delete(id);
        return "redirect:/players";
    }
}
```

#### service

- ここからmybatisの処理を呼んでいる
- PlayerMapperはこの後作るmybatisの処理

```java:PlayerService.java
@Service
public class PlayerService {
    @Autowired
    private PlayerMapper playerMapper;

    @Transactional
    public List<Player> findAll() {
        return playerMapper.findAll();
    }

    @Transactional
    public Player findOne(Long id) {
        return playerMapper.findOne(id);
    }

    @Transactional
    public void save(Player player) {
        playerMapper.save(player);
    }

    @Transactional
    public void update(Player player) {
        playerMapper.update(player);
    }

    @Transactional
    public void delete(Long id) {
        playerMapper.delete(id);
    }
}
```

#### domain

- playerテーブルのentity

```java:Player.java
public class Player {
    private Long id;
    private String name;
    private String team;
    private String position;

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getTeam() {
        return team;
    }
    public void setTeam(String team) {
        this.team = team;
    }
    public String getPosition() {
        return position;
    }
    public void setPosition(String position) {
        this.position = position;
    }
}
```

### ベーシックなパターン

### 依存関係

- 他のパターンでも共通
- pom.xmlにmybatisの依存関係を追加

```xml:pom.xml
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.3.1</version>
    </dependency>
```

#### mapper

- `@Mapper`のついたinterfaceを作成する
- アノテーションでSQLを記述する

```java:PlayerMapper.java
@Mapper
public interface PlayerMapper {
    @Select("select * from player")
    List<Player> findAll();

    @Select("select * from player where id = #{id}")
    Player findOne(Long id);

    @Insert("insert into player (name, team, position) values (#{name}, #{team}, #{position})")
    @Options(useGeneratedKeys = true)
    void save(Player player);

    @Update("update player set name = #{name}, team = #{team}, position = #{position} where id = #{id}")
    void update(Player player);

    @Delete("delete from player where id = #{id}")
    void delete(Long id);
}
```
- これだけで動く
- この状態の全量はこちら
    - https://github.com/ozaki25/SpringMybatisSample/tree/mybatis1
- もしくはこちら
    - `git clone -b mybatis1 https://github.com/ozaki25/SpringMybatisSample.git`

### SQLをxmlに書くパターン

- 上の書き方でもいいが、もっと複雑なことをしたかったり、SQLが長くなった時複数行で書けないし、JavaとSQLは分けて書きたいと思うかもしれない
- そんな人のためのxmlにSQLを書くパターン

#### mapper

- SQLが消えた

```java:PlayerMapper.java
@Mapper
public interface PlayerMapper {
    List<Player> findAll();

    Player findOne(Long id);

    void save(Player player);

    void update(Player player);

    void delete(Long id);
}
```

#### xml

- SQLがこっちに移った

```xml:PlayerMapper.xml
<?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.mapper.PlayerMapper">
   <select id="findAll" resultType="com.example.domain.Player">
      select * from player
   </select>
   <select id="findOne" resultType="com.example.domain.Player">
      select * from player where id = #{id}
   </select>
   <insert id="save" useGeneratedKeys="true" keyProperty="id">
     insert into player (name, team, position) values (#{name}, #{team}, #{position})
   </insert>
   <update id="update">
     update player set name = #{name}, team = #{team}, position = #{position} where id = #{id}
   </update>
   <delete id="delete">
     delete from player where id = #{id}
   </delete>
</mapper>
```

- 同じフォルダ内に置いておけば設定ファイルに何も書かなくて動く
- 実行結果はベーシックなパターンと全く同じ
- ただxmlにSQLを切り出しただけ

- この状態の全量はこちら
    - https://github.com/ozaki25/SpringMybatisSample/tree/mybatis2
- もしくはこちら
    - `git clone -b mybatis2 https://github.com/ozaki25/SpringMybatisSample.git`
        - or
    - `git checkout -b mybatis2 mybatis2`

- しかし、これだけだとJPAで書いたほうが簡単なんじゃない？となってしまう

### JPAで同じものを作ったパターン

- 変更点は以下の3ファイル

#### domain

- JPA用のアノテーションを追加

```java:Player.java
@Entity
public class Player {
    @Id
    @GeneratedValue
    private Long id;
    private String name;
    private String team;
    private String position;

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getTeam() {
        return team;
    }
    public void setTeam(String team) {
        this.team = team;
    }
    public String getPosition() {
        return position;
    }
    public void setPosition(String position) {
        this.position = position;
    }
}
```

#### service

- 各メソッドからの呼び出し先をrepositoryに変更

```java:PlayerService.java
@Service
public class PlayerService {
    @Autowired
    private PlayerRepository playerRepository;

    @Transactional
    public List<Player> findAll() {
        return playerRepository.findAll();
    }

    @Transactional
    public Player findOne(Long id) {
        return playerRepository.findOne(id);
    }

    @Transactional
    public void save(Player player) {
        playerRepository.save(player);
    }

    @Transactional
    public void update(Player player) {
        playerRepository.save(player);
    }

    @Transactional
    public void delete(Long id) {
        playerRepository.delete(id);
    }
}
```

#### repository

- maybatisで書いているMapperの代わりになるようなもの
- 典型的なものはextendsしている先ですでに用意されているので空っぽでも動く

```java:PlayerRepository.java
@Repository
public interface PlayerRepository extends JpaRepository<Player, Long> {

}
```

- JPA版のコードはこちら
    - https://github.com/ozaki25/SpringMybatisSample/tree/jpa
- cloneはこちら
    - `git clone -b jpa https://github.com/ozaki25/SpringMybatisSample.git`
        - or
    - `git checkout -b jpa jpa`

- JPAとmybatisの差分としてはこんな感じ

https://github.com/ozaki25/SpringMybatisSample/compare/jpa...mybatis1

## mybatisが活きるところ

- 肝心なところの作成が間に合わなかった
- 以下のページにあるようにSQLにロジックを埋め込めちゃうところがウリっぽい
    - http://www.mybatis.org/mybatis-3/ja/dynamic-sql.html