【Java】enumを使ってDBのコードをコード値に変換する

DBに登録されたステータスコードをJava側でコード値に変換するサンプルです。
これにより、ステータスコードの意味を定義するマスタを作成する必要がなくなります。
フレームワークはSpring Boot。

## 環境
- OS：Windows10
- IDE：Eclipse2020-03
- Java：8
- MySQL：5.7
- Hibernate ORM：5.4
- Spring Boot：2.3.1

## 概要
生徒テーブルに登録された生徒ステータスコード（整数値）をJava側でenumを用いて対応するコード値に変換します。

### ER図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/d974ef0d-edb8-d49f-f1ec-8290e85d1db8.png)

## ステータスコードとコード値
|ステータスコード|コード値|
|---|---|
|1|在学中|
|2|休学中|
|3|卒業|


### パッケージ構成図
```
enum-sample
     |
    src/main/java
     |----jp.co.high_school
     |               |---- app
     |               |     |---- controller
     |               |     |---- response
     |               |
     |               |---- domain
     |               |      |---- service
     |               |      |---- repository
     |               |      |---- model
     |               |  
     |               |---- constant
     |
    src/main/resources
     |----application.yml
```

## ソースコード

### 1. DDL
```sql:生徒テーブル
CREATE TABLE `students` (
  `student_id` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT '生徒ID',
  `student_name` varchar(300) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '生徒名',
  `sex` tinyint(4) DEFAULT NULL COMMENT '性別',
  `school_year` tinyint(4) DEFAULT NULL COMMENT '学年',
  `class` tinyint(4) DEFAULT NULL COMMENT 'クラス',
  `student_status` tinyint(4) DEFAULT NULL COMMENT '生徒ステータス',
  PRIMARY KEY (`student_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='生徒';
```


### 2. 定数クラス
定数クラスにenumを定義して使用します。
クラスとしてenumを作成してもよいのですが、数が多くなることを想定してクラス内に定義しました。
こうすることで1クラスファイルで複数のenumを管理することができます。

```java:Constant.java
package jp.co.high_school.constant;

import java.util.stream.Collectors;
import java.util.stream.Stream;

import lombok.Getter;

/**
 * 定数クラス
 * @author CHI-3
 * @Version 1.0
 */
public class Constant {

        /**
         * StudentStatus
         * 生徒ステータス
         */
        @Getter
        public static enum StudentStatus {

            In(1, "在学中"),
            Rest(2, "休学中"),
            Out(3, "卒業");

            private StudentStatus(int key, String value) {
                this.key = key;
                this.value = value;
            }


            private int key;

            private String value;

            // コード値を全て返す
            public static Stream<StudentStatus> stream(){
                return Stream.of(StudentStatus.values());
            }

            // 取得したコードに一致するコード値を返す
            public static String getStatusValue(Byte code) {
            	return Stream.of(StudentStatus.values()).filter(v -> v.getKey() == code).map(v -> v.getValue()).collect(Collectors.joining());
            }

        }


        // 今回は使用しない：1クラス内に複数enum定義できることの証明として記述
        /**
         * Grade
         * 成績
         */
        @Getter
        public static enum Grade{

            // 優、良、可は最低条件-sam、不可は最高条件
            Excellent(1, "優", 90),
            Good(2, "良", 75),
            Passing(3, "可", 60),
            Failing(4, "不可", 59);


            private Grade(int key, String value, int order) {
                this.key = key;
                this.value = value;
                this.order = order;
            }

            private int key;

            private String value;

            private int order;

        }

}
```
### 3. Entityクラス
今回は、EclipseのHibernate Toolsを用いて、DBより自動生成しました。

```java:Student.java
package jp.co.high_school.domain.model;

import java.io.Serializable;
import javax.persistence.*;


/**
 * The persistent class for the students database table.
 *
 */
@Entity
@Table(name="students")
@NamedQuery(name="Student.findAll", query="SELECT s FROM Student s")
public class Student implements Serializable {
	private static final long serialVersionUID = 1L;

	@Id
	@Column(name="student_id")
	private Integer studentId;

	@Column(name="class")
	private Byte class_;

	@Column(name="school_year")
	private Byte schoolYear;

	private Byte sex;

	@Column(name="student_name")
	private String studentName;

	@Column(name="student_status")
	private Byte studentStatus;

	//bi-directional one-to-one association to Grade
	@OneToOne(mappedBy="student")
	private Grade grade;

	public Student() {
	}

	public Integer getStudentId() {
		return this.studentId;
	}

	public void setStudentId(Integer studentId) {
		this.studentId = studentId;
	}

	public Byte getClass_() {
		return this.class_;
	}

	public void setClass_(Byte class_) {
		this.class_ = class_;
	}

	public Byte getSchoolYear() {
		return this.schoolYear;
	}

	public void setSchoolYear(Byte schoolYear) {
		this.schoolYear = schoolYear;
	}

	public Byte getSex() {
		return this.sex;
	}

	public void setSex(Byte sex) {
		this.sex = sex;
	}

	public String getStudentName() {
		return this.studentName;
	}

	public void setStudentName(String studentName) {
		this.studentName = studentName;
	}

	public Byte getStudentStatus() {
		return this.studentStatus;
	}

	public void setStudentStatus(Byte studentStatus) {
		this.studentStatus = studentStatus;
	}

	public Grade getGrade() {
		return this.grade;
	}

	public void setGrade(Grade grade) {
		this.grade = grade;
	}

}
```

### 4. Repositoryクラス
生徒IDの指定により、該当する生徒の情報を取得します。

```java:StudentRepository.java
package jp.co.high_school.domain.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import jp.co.test_shop.domain.model.Student;

/**
 * 生徒（students）テーブル操作用リポジトリクラス
 * @author CHI-3
 * @Version 1.0
 */
@Repository
public interface StudentRepository extends JpaRepository<Student, Integer>{

	/**
	 * findByStudentIdメソッド
	 * 生徒IDに対応する生徒を取得する
	 * @param studentId
	 * @return 生徒
	 */
	public Student findByStudentId(Integer studentId);

}

```

### 5. Serviceクラス
取得した生徒の、生徒ステータスのコードを対応するステータスコードに変換して返します。

```java:StudentInformationService.java
package jp.co.high_school.domain.service;

import java.util.Optional;

import org.springframework.stereotype.Service;

import jp.co.test_shop.constant.Constant.StudentStatus;
import jp.co.test_shop.domain.model.Student;
import jp.co.test_shop.domain.repository.StudentRepository;
import lombok.RequiredArgsConstructor;

/**
 * 生徒情報取得用サービスクラス
 * @author CHI-3
 * @version 1.0
 */
@Service
@RequiredArgsConstructor
public class StudentInformationService {

	private final StudentRepository studentRepository;

	/**
	 * getStatusメソッド
	 * 対象の生徒の生徒ステータス（コード値）を取得する
	 * @param studentId 生徒ID
	 * @return 生徒ステータス（コード値）
	 */
	public String getStatus(Integer studentId) throws Exception{

		Student student = studentRepository.findByStudentId(studentId);
		
		// 生徒IDに紐づく生徒が存在しない場合、例外を発生させる
		Optional.ofNullable(student).orElseThrow(Exception::new);
		
		String status = null;
		/* ステータスコード →　対応するコード値：streamによる記述に変更のためコメントアウト
		for(StudentStatus ss:StudentStatus.values()){
			if(ss.getKey() == student.getStudentStatus()) {
				status = ss.getValue();
				break;
			}
		}
		*/
		
		// ステータスコード →　対応するコード値
		status = StudentStatus.getStatusValue(student.getStudentStatus());

		return status;

	}

}


```

### 6. Controllerクラス
Serviceクラスで取得したコード値を整形して返します。

```java:StudentInformationController.java
package jp.co.high_school.app.controller;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import jp.co.test_shop.app.response.StudentStatusResponse;
import jp.co.test_shop.domain.service.StudentInformationService;
import lombok.RequiredArgsConstructor;

/**
 * 生徒情報取得用コントローラークラス
 * @author CHI-3
 * @Version 1.0
 */
@RestController
@RequiredArgsConstructor
public class StudentInformationController {

	private final StudentInformationService studenInformationService;

	/**
	 * getStatusメソッド
	 * 生徒ステータスを取得する
	 * @param studentId 生徒ID
	 * @return レスポンスエンティティ（ステータスのコード値＋HTTPステータス）
	 */
	@GetMapping("/student-information/{studentId}")
	public ResponseEntity<StudentStatusResponse> getStatus(@PathVariable("studentId") Integer studentId){
		String status = studenInformationService.getStatus(studentId);
		StudentStatusResponse studentStatusReponse = StudentStatusResponse.builder().status(status).build();
		return new ResponseEntity<>(studentStatusReponse, HttpStatus.OK);
	}

}
```

### 7. Responseクラス
```java:StudentStatusResponse.java
package jp.co.high_school.app.response;

import lombok.Builder;
import lombok.Getter;

/**
 * 生徒ステータスレスポンスクラス
 * 生徒ステータスの成形に使用
 * @author CHI-3
 * @Version 1.0
 */
@Getter
@Builder
public class StudentStatusResponse {

	private String status;

}
```

## 動作確認
今回はローカル環境で動作確認を行います。

### 生徒（students）テーブル
|student_id|student_name|sex|school_year|class|student_status|
|---|---|---|---|---|---|
|1|在学中|1|1|2|1|

生徒テーブルが上記の内容の場合、API（Advanced Rest Clientを使用）を叩くと以下のような結果が返ります。
きちんと、ステータスコード（1）に対応するコード値（在学中）がとれていますね。

メソッド|GET
---|---
URL|http://localhost:8080/student-information/1
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/356958/f24af0a6-f0e9-1f0e-a8af-0fe6fe3e25ca.png)

## 変更履歴
- 2020/07/13：ステータスコード →　対応するコード値を取得する処理の記述、定数クラスのパッケージ構成を変更

## 終わりに
enumを用いれば、マスタ（DB）や定数（Java）の管理がかなり楽になりそう。
うまく使っていきたいところです。

## 参考

- [あなたの知らないJava enumの使い方 | SEチャンネル](https://tkmtys.hatenablog.com/entry/2015/10/11/182918)
- [列挙型（enum）の基本的な使い方とコード例 | Qiita](https://qiita.com/igm50/items/8c9788d4ba5868642c69)
- [Javaでenum(列挙型)の使い方 | なりきりアーキテクト](https://architect.slowcat.net/java/enum/)
- [Java 8 - マップのサンプルをフィルタリングする | codeflow](https://www.codeflow.site/ja/article/java8__java-8-filter-a-map-examples)