Spring BootでのDB操作 ~JDBC編~

Spring BootでDBアクセスの色々なパターンを試してみようと思います。 今回はSpring Frameworkが提供しているJDBC data sourceを利用して、JdbcTemplateを使った実装をしてみます。

DB準備

ローカルのMySQL(5.7系)を利用します。インストール等の手順は省略。 今回はサンプルーデータとして、下記のようなデータを作成しました。

CREATE DATABASE mydb  CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;
USE mydb;

CREATE TABLE users (
    userid BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(32)
);
CREATE TABLE stores (
    storeid BIGINT AUTO_INCREMENT PRIMARY KEY,
    storename VARCHAR(32)
);
CREATE TABLE reserves (
    reserve_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    userid BIGINT NOT NULL,
    storeid BIGINT NOT NULL, 
    reservetime TIMESTAMP DEFAULT current_timestamp(),
    FOREIGN KEY (userid) REFERENCES users (userid),
    FOREIGN KEY (storeid) REFERENCES stores (storeid)
);
  
INSERT INTO users (username) VALUES ('tanaka'), ('katou'), ('yoshida');
INSERT INTO stores (storename) VALUES ('a_store'), ('b_store'), ('c_store');
INSERT INTO reserves (userid, storeid) VALUES (1, 1), (2, 1), (3, 2);

目指すところ

@RestController アノテーションを付けたコントローラーで、以下の全予約情報をjson形式で返却するところまで実装します。

  • username : ユーザー名
  • storename : 店舗名
  • reservetime : 予約情報

実装

1. pom.xmlに以下の依存関係を追加

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

2. JDBC datasourceの設定
resources配下に以下のようにapplication.ymlを追加。

spring:
  datasource:
    url:  jdbc:mysql://127.0.0.1:3306/mydb
    username: root
    password: 794Uguisu
    driverClassName: com.mysql.jdbc.Driver

3. SpringBootのメイン処理

@SpringBootApplication
public class MybootApplication {
    public static void main(String[] args) {
        SpringApplication.run(MybootappApplication.class, args);
    }
}

4. DAOの作成

public class ReserveDao {
    private String username;
    private String storename;
    private String reservetime;

    // getter, setter省略
}

5. Controller作成
Modelに当たる実装もControllerに含めてしまっています。 上記設定をするだけで、JdbcTemlateがAutowiredできるので手軽です。

@RestController
public class SampleController {

    @Autowired
    JdbcTemplate jdbcTemplate;

    @Transactional
    public List<Reserve> findAllReserve() {

        RowMapper<Reserve> mapper = new BeanPropertyRowMapper<>(Reserve.class);
        String sql = "SELECT u.username, s.storename, r.reservetime " +
                "FROM ( reserves AS r LEFT JOIN users as u ON r.userid = u.userid ) " +
                "LEFT JOIN stores AS s ON r.storeid = s.storeid;";

        List<Reserve> rs = jdbcTemplate.query(sql, mapper);

        // 代わりに`queryForList`を利用して以下のようにも書けるらしいが不便
        // List<Reserve> rs = new ArrayList<>();
        // List<Map<String, Object>> sqlResults = jdbcTemplate.queryForList(sql);
        // for( Map<String, Object> map : sqlResults ) {
        //    Reserve reserve = new Reserve();
        //    reserve.setUsername(map.get("username").toString());
        //    reserve.setStorename(map.get("storename").toString());
        //    reserve.setReservetime((Timestamp) map.get("reservetime"));
        //    reserveList.add(reserve);
        //}

        return rs;
    }

    @RequestMapping("/")
    public List<Reserve> index() {
        return findAllReserve();
    }
}

ES6のクラス記法を使ってテストを書いてみた

表題の通り、JavaScriptのテストをES6のクラス記法を使って書いてみたのでメモ。
今回テストを実装しようとしたときに、各テストケースごとに決まった前・後処理があったので、継承を利用して決まった処理の部分は隠蔽できないかなと思ったのがきっかけです。

準備

テストのライブラリはavaを使いました。
ES6の記法を使うので、合わせてbabelもインストールします。

$ npm init
$ npm install ava babel babel-core babel-preset-es2015 --save-dev 

avaとbabelの設定をします。

// package.jsonに以下の記述を追記
{
  ...

  "ava": {
    "require": "babel-register",
    "babel": "inherit"
  },
  "scripts": {
    "test": "node_modules/ava/cli.js -v "
  },

  ...
}
// .babelrcに作成してbabelの設定を入れる
{
  "presets": ["es2015"]
}

テストサンプル

// testBase.js
// 各テストケースの抽象クラス

class TestBase {
  constructor(test) {
    this.test = test;
    this.beforeEach();
    this.afterEach();
  }
  beforeEach() {
   this.test.beforeEach(t => {
     // テストケースごとの先行処理
   });
  }
  afterEach() {
    this.test.afterEach(t => {
      // テストケースごとの後続処理
    });
  }
  runTests() {
    // メソッド名がtestで始まるメソッドを取得
    const methodNames = Object.getOwnPropertyNames(this.__proto__).filter(methodName => {
        return methodName.match(/^test.+/);
    });
    // 取得したメソッドを実行
    methodNames.forEach(m => {
      this[m]();
    });
  }
}

export default TestBase;
// testCase.js
// 個別のテストケース

import test from 'ava';
import TestBase from './testBase';

class TestCase extends TestBase {
  constructor(test){
    super(test);
  }
  testSample() {
    this.test('sample test', t => {
      // テストケース処理
    });
  }
}

const testCase = new TestCase(test);
testCase.runTests();

実行

以下のような感じで実行できる。

$ npm test testCase.js

Bridgeパターンを使ったクラス設計の具体例を考えてみる

オブジェクト指向のこころ」という本を読んでいて、 第10章のBridgeパターンは使いどころが多そうだな、と思ったので簡単にメモ。
いつか使ってみたい。

前提

今自分が関わっているWEBサービスを模して、以下の3つの要件を持つサービスを考える。

1. 検索導線が複数存在する

あるコンテンツを検索するために、異なる軸で検索できる複数の導線が用意されている。
ここでは例として、以下の2つの導線が存在すると仮定する。

  • 地域から検索(areaSearch)
  • コンテンツの特徴から検索(featureSearch)

2. それぞれの検索導線に2種類のパスが存在する。

それぞれの検索導線は、どの画面から遷移したかによって2種類のパスに分けられる。
例えば、以下のような要件を持っている。

  • A画面から地域検索に遷移したときには「/areaSearch/A/」のパスに遷移
  • B画面から地域検索に遷移したときには「/areaSearch/B/」のパスに遷移

3. それぞれの検索導線で2種類の表示方法(リスト表示 or 地図表示)を選ぶことができる。

画面内にある切替ボタンからリスト表示か地図表示を切り替えることができるというもの。 地図表示への切替は別の画面に遷移するが、遷移したときに、パスに含まれるAもしくはBを引き継ぐものとする。 例として地域検索の場合は以下が要件だとする。

  • A画面から地域検索に遷移したとき
    • リスト表示は「/areaSearch/A/」, 地図表示は「/areaSearch/A/map/」
  • B画面から地域検索に遷移したとき
    • リスト表示は「/areaSearch/B/」, 地図表示は「/areaSearch/B/map/」

Bridgeパターンを使った設計

クラス図

f:id:yuki-ogawa:20170311161309p:plain

何をしているのか

「検索導線」と「パス生成」をクラスに切り分け、継承関係を整理している。
検索導線ごとにifやswitchでパスを切り替える実装の仕方と比べると、パス生成クラスのインターフェースをActionクラスに集約させることで、パスの違いを考慮した実装を1箇所に集めカプセル化できているので、凝集度が高い実装になる。