(2019/01/02追記) この記事は更新されていません
この記事に書いてあるコードの大半はすでに動きません。一応記録用に残しておきますが、更新の予定はありません。
確実な情報が必要な場合は公式のドキュメントをご参照ください。
@kazuki43zooさんが素晴らしい解説を書かれているので、そちらもご一読をおすすめします。
Spring Data JDBC 1.0.0.BUILD-SNAPSHOT(-> 1.0.0.RELEASE)を試してみた
また、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というプロジェクトができていたので、試してみます。
SpringのRDBアクセスといえばJdbcTemplateやSpring Data JPAなどがあります。
まだCRUDサポートレベルですが、将来的にSpring Data JDBCもDBアクセスの有力な選択肢となるかもしれません。
今回試したソースはこちらです。
!!!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にします。
<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にテーブル定義を書きます。
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)
@Data
public class Employee {
@Id
private Long employeeNumber;
private String firstname;
private String lastname;
private Integer age;
private LocalDate hiredAt;
}
エンティティを作成したら、CrudRepository
を継承したRepositoryを作ります。
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
}
Bean定義
Spring Data JDBCを動かすには
@EnableJdbcRepositories
-
DataAccessStrategy
のBean定義
が必要になります。
またカラム名をスネークケースにしたい場合は、エンティティのフィールド名<=>カラム名の変換を行うNamingStrategyを定義します。
@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();
}
}
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();
}
}
動かしてみる
さっそく動かしてみます。
@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のメソッドが実行されるようです。