MyBatis
SpringBoot

Spring+mybatisを試してみた

More than 1 year has passed since last update.

mybatisを動かしてみた話

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

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

players.gif

共通部分

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

controller

  • 典型的なCRUDのエンドポイント
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の処理
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
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の依存関係を追加
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を記述する
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);
}

SQLをxmlに書くパターン

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

mapper

  • SQLが消えた
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がこっちに移った
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を切り出しただけ

  • この状態の全量はこちら

  • もしくはこちら

    • git clone -b mybatis2 https://github.com/ozaki25/SpringMybatisSample.git
      • or
    • git checkout -b mybatis2 mybatis2
  • しかし、これだけだとJPAで書いたほうが簡単なんじゃない?となってしまう

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

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

domain

  • JPA用のアノテーションを追加
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に変更
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している先ですでに用意されているので空っぽでも動く
PlayerRepository.java
@Repository
public interface PlayerRepository extends JpaRepository<Player, Long> {

}

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

mybatisが活きるところ