Spring Data JDBC Preview


## (2019/01/02追記) この記事は更新されていません

**この記事に書いてあるコードの大半はすでに動きません。一応記録用に残しておきますが、更新の予定はありません。**
確実な情報が必要な場合は[公式のドキュメント](https://docs.spring.io/spring-data/jdbc/docs/1.0.3.RELEASE/reference/html/)をご参照ください。

@kazuki43zooさんが素晴らしい解説を書かれているので、そちらもご一読をおすすめします。
[Spring Data JDBC 1.0.0.BUILD-SNAPSHOT(-> 1.0.0.RELEASE)を試してみた](https://qiita.com/kazuki43zoo/items/bd63d28dc2348aa21719)

また、Spring Data JDBCはまだまだ成長途中のプロジェクトです。publishされたドキュメントだけでは辿れない情報もあるため、以下のリンクが役に立つかもしれません。

* GitHub: https://github.com/spring-projects/spring-data-jdbc
* JIRA: https://jira.spring.io/browse/DATAJDBC
* Stack Overflow: https://stackoverflow.com/questions/tagged/spring-data-jdbc

----

[Spring Data JDBC](https://github.com/spring-projects/spring-data-jdbc)というプロジェクトができていたので、試してみます。

SpringのRDBアクセスといえば[JdbcTemplate](https://spring.io/guides/gs/relational-data-access/)や[Spring Data JPA](https://projects.spring.io/spring-data-jpa/)などがあります。
まだCRUDサポートレベルですが、将来的にSpring Data JDBCもDBアクセスの有力な選択肢となるかもしれません。

今回試したソースは[こちら](https://github.com/rshindo/spring-data-jdbc-demo)です。

>!!!2018年1月現在、Spring Data JDBCはBUILD-SNAPSHOTです。将来的にこの記事中のコードが動かなくなる可能性が十分あります。!!!

## 環境

* jdk 1.8
* Spring Boot 2.0.0.M7
* Maven 3

## pom.xml

Spring Bootプロジェクトを作成したら、pom.xmlにdependencyを追加します。DBはH2 Databaseにします。

```xml:pom.xml
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-jdbc</artifactId>
			<version>1.0.0.BUILD-SNAPSHOT</version>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>
```

## テーブル定義

schema.sqlにテーブル定義を書きます。

```sql:schema.sql
create table employee (
    employee_number bigint primary key auto_increment,
    firstname varchar NOT NULL,
    lastname varchar NOT NULL,
    age int NOT NULL,
    hired_at date
);
```

## Repositoryの作成

テーブルに対応したエンティティクラスを作成します。IDとなるフィールドには`@Id`アノテーションをつけます。
(Getter/Setter書くのが面倒だったのでLombokを使っていますw)

```java:Employee.java
@Data
public class Employee {

    @Id
    private Long employeeNumber;
    private String firstname;
    private String lastname;
    private Integer age;
    private LocalDate hiredAt;

}
```

エンティティを作成したら、`CrudRepository`を継承したRepositoryを作ります。

```java:EmployeeRepository.java
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
}
```

## Bean定義

Spring Data JDBCを動かすには

* `@EnableJdbcRepositories`
* `DataAccessStrategy`のBean定義

が必要になります。
またカラム名をスネークケースにしたい場合は、エンティティのフィールド名<=>カラム名の変換を行うNamingStrategyを定義します。

```java:SpringDataJdbcDemoApplication.java
@SpringBootApplication
@EnableJdbcRepositories
public class SpringDataJdbcDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringDataJdbcDemoApplication.class, args);
	}

	@Autowired
	JdbcMappingContext context;
	@Autowired
	DataSource datasource;

	@Bean
	DataAccessStrategy dataAccessStrategy() {
	    return new DefaultDataAccessStrategy(
	            new SqlGeneratorSource(context),
	            new NamedParameterJdbcTemplate(datasource),
	            context);
	}

	@Bean
	NamingStrategy namingStrategy() {
	    return new CustomNamingStrategy();
	}
}
```

```java:CustomNamingStrategy.java
public class CustomNamingStrategy extends DefaultNamingStrategy {

    @Override
    public String getColumnName(JdbcPersistentProperty property) {
        String propertyName = property.getName();
        return camelToSnake(propertyName);
    }

    @Override
    public String getTableName(Class<?> type) {
        return super.getTableName(type);
    }

    public String camelToSnake(String original) {
        char[] chars = original.toCharArray();
        char[] buff = new char[chars.length + 10];

        int j = 0;
        for(int i = 0; i < chars.length; i++) {
            char c = chars[i];
            if(buff[buff.length - 1] != ' ') {
                buff = Arrays.copyOf(buff, buff.length + 10);
            }
            if (Character.isUpperCase(c)) {
                buff[j++] = '_';
                buff[j++] = Character.toLowerCase(c);
            } else {
                buff[j++] = c;
            }
        }
        return new String(buff).trim();
    }

}
```

## 動かしてみる

さっそく動かしてみます。

```java
@ExtendWith(SpringExtension.class)
@SpringBootTest
public class SpringDataJdbcDemoApplicationTests {

    @Autowired
    EmployeeRepository repo;

    @Test
    public void contextLoads() {
    }

    @Test
    public void test1() {
        Employee employee = new Employee();
        employee.setFirstname("John");
        employee.setLastname("Do");
        employee.setAge(30);
        employee.setHiredAt(LocalDate.of(2012, 4, 1));

        // Insert
        employee = repo.save(employee);
        Long employeeNumber = employee.getEmployeeNumber();

        Optional<Employee> insertedOpt = repo.findById(employeeNumber);
        assertTrue(insertedOpt.isPresent());
        Employee inserted = insertedOpt.get();
        assertAll("insert",
                () -> assertEquals(inserted.getEmployeeNumber(), employeeNumber),
                () -> assertEquals(inserted.getFirstname(), "John"),
                () -> assertEquals(inserted.getLastname(), "Do"),
                () -> assertEquals(inserted.getAge(), Integer.valueOf(30)),
                () -> assertEquals(inserted.getHiredAt(), LocalDate.of(2012, 4, 1)));

        // Update
        employee.setAge(31);
        repo.save(employee);

        Optional<Employee> updatedOpt = repo.findById(employeeNumber);
        assertTrue(updatedOpt.isPresent());
        Employee updated = updatedOpt.get();
        assertEquals(updated.getAge(), Integer.valueOf(31));

        // Delete
        repo.delete(employee);

        Optional<Employee> deleted = repo.findById(employeeNumber);
        assertTrue(!deleted.isPresent());
    }
}
```

## MyBatis連携

試してないですが、MyBatisと連携させることも可能らしいです。
Repositoryのメソッドが呼ばれると、対応した名前のMapperのメソッドが実行されるようです。