Docker + SQLServer  + Javaで開発環境を構築する

# はじめに
この記事は　[ZOZO #2 Advent Calendar 2022 21日目](https://qiita.com/advent-calendar/2022/zozo)の記事になります。
今回はDockerを用いてSQLServerの環境をローカルに構築し、それをJavaのアプリケーションから接続しようと思います。
また、テストデータの作成も合わせて行います。

# ディレクトリ構成
ディレクトリ構成は以下になります。
今回の記事に関係ないディレクトリやファイルは省略してます。
```
docker-sqlserver-service
├─script
│  ├─ DDL
│  │   └─ employee.sql(employeeテーブル作成用のファイル)
│  ├─ DML
│  │   └─ insert.sql(employeeテーブルにデータを登録するファイル)
│  └ init.sh(テストデータ作成用のシェルスクリプト)
├── src
│   └─ main
│        ├─ java
│        │    └─ com
│        │        └─ example
│        │             └─ employee
│        │                  ├─ EmployeeApplication.java
│        │                  └─ EmployeeRepository.java
│        └── resources
│               └─application.properties
├── build.gradle
├── gradlew
├── docker-compose.yml
・
・
・
```

# docker-compose.ymlの設定
設定内容は以下になります。
| 項目名 | 説明 | 設定値 | 備考|
|--|--|--|--| 
|image| コンテナのイメージ |2022-latest|現状の最新版 
|conainer_name| コンテナ名 | mssql-test | 
|ports| ポート | "11433:1433"|ホスト : 11433 <br>コンテナ : 1433 
|ACCEPT_EULA|使用許諾契約書の承諾|Y| 
|MSSQL_SA_PASSWORD| SA ユーザーのパスワード | h0geHugaPiyo | [パスワードポリシー](https://learn.microsoft.com/ja-jp/sql/relational-databases/security/password-policy?view=sql-server-ver16)
|MSSQL_PID|SQLServerのエディションまたはプロダクトキー| Express| 
|MSSQL_LCID| SQLServer に使用する言語ID |1041 | 日本語
|MSSQL_COLLATION| SQLServerの既定の照合順序|Japanese_CI_AS | 

yamlファイルのサンプルは以下です。
``` yaml
version: "3"
services:
  mssql:
    image: mcr.microsoft.com/mssql/server:2022-latest 
    container_name: mssql-test
    ports:
      - "11433:1433"
    environment:
      - ACCEPT_EULA=Y
      - SA_PASSWORD=h0geHugaPiyo
      - MSSQL_PID=Express
      - MSSQL_LCID=1041
      - MSSQL_COLLATION=Japanese_CI_AS
```
# dockerの起動
以下のコマンドを実行してコンテナを起動します。
```
$ docker-compose up -d
```
コンテナが起動しているかは以下のコマンドで確認します。
```
$ docker-compose ps
```
コンテナの停止・削除は以下のコマンドになります。
```
$ docker-compose down
```

# テストデータの作成
## Microsoft ODBC Driver 18 for SQL Server をインストール(MacOS)
テストデータを作成するシェルスクリプトに必要なドライバーをインストールします。
公式からインストールの部分だけ抜粋してます。
下記のコマンドを1行ずつ実行してください。
詳しく知りたい方は[公式ドキュメント](https://learn.microsoft.com/ja-jp/sql/connect/odbc/linux-mac/install-microsoft-odbc-driver-sql-server-macos?view=sql-server-ver16)をご参考ください。

```
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
brew tap microsoft/mssql-release https://github.com/Microsoft/homebrew-mssql-release
brew update
HOMEBREW_ACCEPT_EULA=Y brew install msodbcsql18 mssql-tools18
```

## SQLファイルの作成
シェルスクリプトで実行するSQLファイルを作成します。
### テーブルの作成をするファイル（employee.sql）
```
CREATE TABLE employee(
    id INT IDENTITY(1,1)  not null,
    name VARCHAR(20) not null,
    created_at datetime,
    updated_at datetime,
    primary key(id)
);
```
### データを挿入するファイル（insert.sql）
```
INSERT INTO employee VALUES ('佐藤',getdate(),getdate());
INSERT INTO employee VALUES ('鈴木',getdate(),getdate());
INSERT INTO employee VALUES ('田中',getdate(),getdate());
```

## シェルスクリプトの作成
下記のシェルスクリプトを作成し、実行します。
シェルスクリプトではテーブルの作成とテストデータの挿入を行います。
また、何度でも実行できるように、実行前に作成されているテーブルをDROPしています。
```bash
#!/bin/bash

ddl_dir_path="./DDL/*" # DDLのsqlファイルが有るディレクトリを指定
ddl_files=`find $ddl_dir_path -maxdepth 0 -type f -name *.sql` # ディレクトリ内のファイルを取得
user_id="sa"
password="h0geHugaPiyo"
ip_address="127.0.0.1" #localhostでは動かないので注意
port="11433"

# すでにテーブルが作成されていた場合はDROPする
tbl_names=`sqlcmd -S $ip_address,$port -d tempdb -U $user_id -P $password -Q "SET NOCOUNT ON; SELECT name FROM sys.tables" -h -1`
for tbl_name in $tbl_names
do
  sqlcmd -S $ip_address,$port -d tempdb -U $user_id -P $password -Q "DROP TABLE ${tbl_name}"
done

# テーブルの作成
for ddl_file in $ddl_files;
do
  sqlcmd -S $ip_address,$port -d tempdb -U $user_id -P $password -i $ddl_file -b

  #実行結果
  res=$?
  # -bの返り値が0より大きい場合はエラーとする
  if [ $res -gt 0 ]; then
      echo $ddl_file "エラー"
      exit 1;
  else
      echo $ddl_file "成功"
  fi
done

# テストデータの挿入
dml_dir_path="./DML/*"
dml_files=`find $dml_dir_path -maxdepth 0 -type f -name *.sql`

for dml_file in $dml_files;
do
  sqlcmd -S $ip_address,$port -d tempdb -U $user_id -P $password -i $dml_file -b

  #実行結果
  res=$?
  # -bの返り値が0より大きい場合はエラーとする
  if [ $res -gt 0 ]; then
      echo $ddl_file "エラー"
      exit 1;
  else
      echo $ddl_file "成功"
  fi
done

# 挿入したデータの確認
sqlcmd -S $ip_address,$port -d tempdb -U $user_id -P $password -Q "SELECT * FROM employee"
```

# build.gradleの設定
dependenciesに以下を追記し、必要なライブラリをインストールします。
```
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-devtools'
    implementation "org.springframework.boot:spring-boot-starter-jdbc"
    implementation 'com.microsoft.sqlserver:mssql-jdbc:11.2.1.jre17'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
```

# application.propertiesの設定
application.propertiesに以下を記載して、アプリケーション側にDBの接続設定を行います。
```
spring.datasource.url=jdbc:sqlserver://localhost:11433;databaseName=tempdb;encrypt=false;
spring.datasource.username=sa
spring.datasource.password=h0geHugaPiyo
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
```

# アプリケーションの構築
SQLServerのテーブルからデータの一覧を取得するAPIを作成します。
## Appication層
```java
package com.example.employee;

import java.util.List;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class EmployeeApplication {
    private final EmployeeRepository employeeRepository;

    public EmployeeApplication(
      EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }

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

    @GetMapping("/employee")
    public List getEmployeeList() {
        return employeeRepository.getAll();
    }
}

```
## Repository層
```java
package com.example.employee;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
@RequiredArgsConstructor
public class EmployeeRepository {
  private final JdbcTemplate jdbcTemplate;

  public List getAll(){
    String sql = "SELECT * FROM employee";
    List<Map<String, Object>> dateList = jdbcTemplate.queryForList(sql);
    List<String> result = new ArrayList<>();
    dateList.forEach(
      x -> result.add(x.get("id") + "," + x.get("name") + "," + x.get("created_at") + "," + x.get("updated_at"))
    );
    return result;
  }
}
```

# アプリケーションの起動と実行方法
アプリケーションを起動したあとに、APIを実行することでデータベースに挿入したテストデータを取得することができます。
## アプリケーションの起動方法
```
./gradlew bootRun
```

## APIの実行方法
```
 curl http://localhost:8080/employee
```

## 実行結果
```
["1,鈴木,2022-12-05 05:05:16.673,2022-12-05 05:05:16.673","2,田中,2022-12-05 05:05:16.673,2022-12-05 05:05:16.673","3,佐藤,2022-12-05 05:05:16.673,2022-12-05 05:05:16.673"]
```

# おわりに
以上で、Dockerで立ち上げたSQLServerにJavaから接続することができました。
少しでも誰かの役に立てるナレッジとなれば幸いです。
ご意見・ご質問・ご指摘等あれば、コメントで教えていただけますようお願いいたします。
読んでいただきありがとうございました。