Spring BootでのDBアクセス方法として、下記の3パターンを試してみました。
- JDBC(spring-boot-starter-jdbc)
- JPA(spring-boot-starter-data-jpa)
- MyBatis(mybatis-spring-boot-starter)
なお、それぞれの全体のコードは、下記に配置してあります。
テーブル構成
DBはH2を使います。DDLは下記の通りです。
CREATE TABLE customers (
id INT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(30) NOT NULL,
last_name VARCHAR(30) NOT NULL,
address VARCHAR(100)
);
実装する処理
基本的なCRUDにプラスして、first_name
カラムを部分一致で検索するものを実装します。
Entityは下記のように定義します。(JPAだけ追加でアノテーションが必要なので後述します)
package com.example.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Customer {
private Integer id;
private String firstName;
private String lastName;
private String address;
}
JDBC
NamedParameterJdbcTemplate
を利用します。
SQL自体は書きますが、Entityとのマッピングが簡単になります。
package com.example.repository;
import java.util.HashMap;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
import com.example.domain.Customer;
@Repository
public class CustomerRepository {
@Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
public List<Customer> findAll() {
return jdbcTemplate.query(
"SELECT * FROM customers ORDER BY id",
new BeanPropertyRowMapper<Customer>(Customer.class));
}
public Customer findOne(Integer id) {
SqlParameterSource param = new MapSqlParameterSource().addValue("id", id);
try {
return jdbcTemplate.queryForObject(
"SELECT * FROM customers WHERE id = :id",
param,
new BeanPropertyRowMapper<Customer>(Customer.class));
} catch (EmptyResultDataAccessException e) {
return null;
}
}
public Customer save(Customer customer) {
SqlParameterSource param = new BeanPropertySqlParameterSource(customer);
if (customer.getId() == null) {
SimpleJdbcInsert insert =
new SimpleJdbcInsert((JdbcTemplate) jdbcTemplate.getJdbcOperations())
.withTableName("customers")
.usingGeneratedKeyColumns("id");
Number key = insert.executeAndReturnKey(param);
customer.setId(key.intValue());
} else {
jdbcTemplate.update(
"UPDATE customers SET first_name = :firstName, last_name = :lastName, address = :address WHERE id = :id",
param);
}
return customer;
}
public void delete(Integer id) {
SqlParameterSource param = new MapSqlParameterSource().addValue("id", id);
jdbcTemplate.update(
"DELETE FROM customers WHERE id = :id",
param);
}
public List<Customer> findByFirstName(String firstName) {
SqlParameterSource param = new MapSqlParameterSource().addValue("firstName", "%" + firstName + "%");
return jdbcTemplate.query(
"SELECT * FROM customers WHERE first_name LIKE :firstName ORDER BY id",
param,
new BeanPropertyRowMapper<Customer>(Customer.class));
}
public void deleteAll() {
jdbcTemplate.update("DELETE FROM customers", new HashMap<>());
}
}
JPA
EntityにもJPA用のアノテーションが必要となります。
package com.example.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "customers")
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Customer {
@Id
@GeneratedValue
private Integer id;
private String firstName;
private String lastName;
private String address;
}
JpaRepository
を継承したinterface
を定義するだけで、基本的なCRUD操作用のメソッド(find
, findOne
, save
, delete
等)が提供されます。
また、メソッド名からクエリを作り出してくれる機能もあり、findByFirstNameContains
とすると、first_nameに対してLIKEで部分一致とするクエリを実行するメソッドとなります。
EntityManagerを使うことも無く、とても簡単に書けます。
package com.example.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.example.domain.Customer;
@Repository
public interface CustomerRepository extends JpaRepository<Customer, Integer> {
public List<Customer> findByFirstNameContains(String firstName);
}
MyBatis
インタフェースを定義し、アノテーションでSQLを指定します。
JDBCの時のようにSQLを書くことになりますが、アノテーションで済ませられるので、より完結に書けます。
package com.example.repository;
import java.util.List;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectKey;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;
import com.example.domain.Customer;
@Repository
@Mapper
public interface CustomerRepository {
@Select("SELECT * FROM customers ORDER BY id")
public List<Customer> findAll();
@Select("SELECT * FROM customers WHERE id = #{id}")
public Customer findOne(@Param("id") Integer id);
@Insert("INSERT INTO customers(first_name, last_name, address) VALUES(#{firstName}, #{lastName}, #{address})")
@SelectKey(statement = "call identity()", keyProperty = "id", before = false, resultType = int.class)
public void insert(Customer customer);
@Update("UPDATE customers SET first_name = #{firstName}, last_name = #{lastName}, address = #{address} WHERE id = #{id}")
public void update(Customer customer);
@Delete("DELETE FROM customers WHERE id = #{id}")
public void delete(@Param("id") Integer id);
@Select("SELECT * FROM customers WHERE first_name LIKE '%' || #{firstName} || '%' ORDER BY id")
public List<Customer> findByFirstName(@Param("firstName") String firstName);
@Delete("DELETE FROM customers")
public void deleteAll();
}
MyBatis(Kotlin)
Javaだとヒアドキュメントが書けないので、複数行にわたるようなSQLを書くのに不便です。
ということで、ヒアドキュメントが使えるKotlinで書いてみます。
package com.example.repository
import com.example.domain.Customer
import org.apache.ibatis.annotations.Delete
import org.apache.ibatis.annotations.Insert
import org.apache.ibatis.annotations.Mapper
import org.apache.ibatis.annotations.Param
import org.apache.ibatis.annotations.Select
import org.apache.ibatis.annotations.SelectKey
import org.apache.ibatis.annotations.Update
@Mapper
interface CustomerRepository {
@Select("SELECT * FROM customers ORDER BY id")
fun findAll(): List<Customer>
@Select("SELECT * FROM customers WHERE id = #{id}")
fun findOne(@Param("id") id: Int): Customer
@Insert("INSERT INTO customers(first_name, last_name, address) VALUES(#{firstName}, #{lastName}, #{address})")
@SelectKey(statement = arrayOf("call identity()"), keyProperty = "id", before = false, resultType = Int::class)
fun insert(customer: Customer)
@Update("UPDATE customers SET first_name = #{firstName}, last_name = #{lastName}, address = #{address} WHERE id = #{id}")
fun update(customer: Customer)
@Delete("DELETE FROM customers WHERE id = #{id}")
fun delete(@Param("id") id: Int);
@Select("""
SELECT
*
FROM
customers
WHERE
first_name LIKE '%' || #{firstName} || '%'
ORDER BY id
""")
fun findByFirstName(@Param("firstName") firstName: String): List<Customer>
@Delete("DELETE FROM customers")
fun deleteAll()
}
まとめ
シンプルな操作だけならば、JPAがインタフェースを定義するだけなのでとてもらくちんです。
ただ、JPAは多機能で嵌りどころも多いイメージ(理解して使わないと危険)なので、今の自分の知識だとMyBatisがあっているかなと考えています。
MyBatisを使う場合に、ヒアドキュメントを使えるKotlinでMapper部分だけでも書こうと考えていましたが、EclipseのKotlinプラグインだと、importの補完をしてくれないので、結構面倒に感じました(IntelliJ IDEA使えれば、、)。
ここはGroovyも試してみようと考えています。