JPAのEntityクラスファイルを自動生成するソースを作ってみた

##はじめに
タイトルにもある通り、今回はEntityクラスのJavaファイルを生成するアプリを作ってみました！
OracleDB11g2R,MySQLでは動作確認済みです。
※下図の通り、結構簡単な物しか生成できませんが。。
（諸事情で@columnと@Id、@TableはJavaEEのではなく、自作したものを使用しました）

```java:Test.java
import java.util.ArrayList;

import annotation.Entity;
import annotation.Table;
import annotation.column;
import annotation.id;

@Entity
@Table("test")//DBに登録したテーブル名が表示されます
public class Test {

	@id//主キー
	@column//カラム
	private String num;

	@column//カラム
	private String namename;

	public void setNum(String num) {
		this.num = num;
 	}

	public String getNum() {
		return this.num;
 	}

	public void setNamename(String namename) {
		this.namename = namename;
 	}

	public String getNamename() {
		return this.namename;
 	}

}
```

##環境(適当)
* 実行環境: macOS
* IDE: Eclipse

##構成(超概要)

| クラス名         | 説明                                                           |
|:---------------|:---------------------------------------------------------------|
|EntityGenerator |下記クラスを呼び出しながら、JavaFileの生成及びファイルの中身をWriteしていくクラス|
|DBReader        |DBに接続し、メタデータを取得してくるクラス（テーブル一覧、カラム名、主キーetc..）   |
|DataTypeMapper  |カラムのデータ型をDBのデータ型　→　javaのデータ型に変換するクラス            |
|EntityInfo      |DBReaderで取得してきた情報を格納するクラス                             |
|annotation群    |@Table, @Id, @column                                            |
　
##ソース

###EntityGenerator

interface

```java:EntityGenerator.java

package entityCreater.generator;

public interface EntityGenerator {

	void generateEntity();

}
```

実装クラス

```java:EntityGeneratorImplements.java

package entityCreater.generator;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import entityCreater.info.EntityInfo;
import entityCreater.reader.DBReaderImplements;

public class EntityGeneratorImplements implements EntityGenerator{

	//Fileを保存する場所
	String filePlace;

	/**
	 * コンストラクタ
	 */
	public EntityGeneratorImplements(String filePlace) {
		this.filePlace = filePlace;
	}

	/**
	 * Entity作成メソッド
	 */
	@Override
	public void generateEntity() {
		System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
		System.out.println("EntityFile作成メソッドを開始します");

		ArrayList<EntityInfo> entityInfos = new DBReaderImplements().read();
		for(EntityInfo ei : entityInfos) {
			createFile(ei);
		}
	}


	/**
	 * EntityFile作成メソッド
	 * @param entityInfo
	 */
	private void createFile(EntityInfo entityInfo) {
    	//DBのテーブル情報を取得
    	String tableName = entityInfo.getTableName();
    	String id = entityInfo.getId();
    	List<String[]> columns = entityInfo.getColumns();

    	//クラス名を生成
    	String className = tableName.substring(0, 1).toUpperCase() + tableName.substring(1).toLowerCase();

		//EntityFileクラス
		File entityFile = new File(filePlace + "/" + className + ".java");

		try{
			if (entityFile.createNewFile()){
				System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
		    	System.out.println("ファイルの作成に成功しました。");
		    }else{
		    	System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
		    	System.out.println("ファイルの作成に失敗しました");
		    }
		}catch(IOException e){
		    System.out.println(e);
		}

		//Fileの中身を作成する
		System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
    	System.out.println("ファイルの中身を作成します。");

    	try(PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(entityFile, true)));) {

    		//Annotationのインポート文
    		pw.println("import annotation.Entity;");
    		pw.println("import annotation.Table;");
    		pw.println("import annotation.id;");
    		pw.println("import annotation.column;");
    		pw.println("");

    		//Class宣言(開始)
    		pw.println("@Entity");
    		pw.println("@Table(\"" + tableName + "\")");
    		pw.println("public class " + className + " {");
    		pw.println("");

    		//カラム宣言
    		Iterator<String[]> it = columns.iterator();

    		while(it.hasNext()) {
    			String[] colum = it.next();

    			//Columnの宣言
    			if(colum[1].equals(id)) {
    	    		pw.println("	@id");
    			}
    			pw.println("	@column");
        		pw.println("	private " + colum[0] + " " + colum[1] + ";");
        		pw.println("");

    		}

    		//Setter.Getter宣言
    		//Iteratorを先頭に戻す
    		it = columns.iterator();

    		while(it.hasNext()) {
    			String[] colum = it.next();

    			//Setter宣言
    			pw.println("	public void set" + colum[1].substring(0, 1).toUpperCase() + colum[1].substring(1).toLowerCase() + "(" + colum[0] + " " + colum[1] + ") {");
    			pw.println("		this." + colum[1] + " = " + colum[1] + ";");
    			pw.println(" 	}");
    			pw.println("");

    			//Getter宣言
    			pw.println("	public " + colum[0] + " get" + colum[1].substring(0, 1).toUpperCase() + colum[1].substring(1).toLowerCase() + "() {");
    			pw.println("		return this." + colum[1] + ";");
    			pw.println(" 	}");
    			pw.println("");
    		}

    		//Class宣言(終了)
    		pw.println("}");

			//終了表示
			System.out.println(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
	    	System.out.println("*************************************************************");
	    	System.out.println("***    SUCCESS   パッケージ宣言、多重度の宣言を追記してください。 ***");
	    	System.out.println("*************************************************************");

		} catch (IOException e) {
			e.printStackTrace();
		}finally {

		}
	}


}
```



###DBReader
interface

```java:DBReader.java
package entityCreater.reader;

import java.util.ArrayList;

import entityCreater.info.EntityInfo;

public interface DBReader {

	ArrayList<EntityInfo> read();
}
```

実装クラス

```java:DBReaderImplements.java
package entityCreater.reader;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Iterator;

import container.DBConfig;
import container.EnvironmentConfigReader;
import db_access.DBAccess;
import entityCreater.dataTypeMapper.DataTypeMapper;
import entityCreater.info.EntityInfo;

public class DBReaderImplements implements DBReader{

	@Override
	public ArrayList<EntityInfo> read() {

		//metadata格納用
		ResultSet rs = null;

		//Table情報格納用
		ArrayList<String> tableNames = new ArrayList<>();

		//Entity情報格納用
		ArrayList<EntityInfo> entityInfos = new ArrayList<>();

		//DB設定の取得
                /*
　　　　　　　　　　* 以下の範囲はあまり気にしないでください。
　　　　　　　　　　* 自作のyamlファイルからDBの接続情報（URLとかPASSWORDとか）を取得してきて、
　　　　　　　　　　* DBAccessクラス内部でDBとのConnectionを取得しているだけです。
　　　　　　　　　　* EnvironmentConfigReaderとDBAccessのソースは参考に記載しておきます
　　　　　　　　　　*/

               /*
               *ここから
               */
		EnvironmentConfigReader ecr = new EnvironmentConfigReader();
		DBConfig dbc = ecr.read();

		String dbName = dbc.getDbName();

		//コネクションの確立
		DBAccess dba = new DBAccess(dbc);

               /*
               * ここまで
               */

		try {

			// データベースとの接続
			Connection con = dba.getConnection();

			//データベースメタデータの取得
			DatabaseMetaData dbmd = con.getMetaData();

			//Table名の取得
			System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
			System.out.println("Table一覧を取得します");
			String[] types = { "TABLE", "VIEW", "SYSTEM TABLE" };
			rs = dbmd.getTables(dbName, null, "%", types);
			while (rs.next()) {
				String tableName = rs.getString("TABLE_NAME");

				//Table名の格納
				tableNames.add(tableName);
			}

			//Table情報の格納用クラス群
			Iterator<String> tableNameList = tableNames.iterator();
			String tableName;
			String id = null;
			ArrayList<String[]> columns;

			while (tableNameList.hasNext()) {

				//TableNameを取得
				tableName = tableNameList.next();

				System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
				System.out.println("Table名：" + tableName + "のEntityクラスを生成します");

				//Tableの全column情報の取得
				System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
				System.out.println("Table名：" + tableName + "の全column情報を取得します");
				rs = dbmd.getColumns(dbName, null, tableName, "%");

				//column格納用
				columns = new ArrayList<>();
				while (rs.next()) {
					//SQLのデータタイプからJavaのデータタイプに変換
					String dataType = DataTypeMapper.dataTypeChange(rs.getString("TYPE_NAME"));

					//変数名を小文字に変換
					String columnName = rs.getString("COLUMN_NAME").toLowerCase();

					String[] column = { dataType, columnName };
					columns.add(column);
				}

				//PrimaryKeyを取得
				System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
				System.out.println("Table名：" + tableName + "のPrimaryKeyを取得します");
				ResultSet primaryKeys = dbmd.getPrimaryKeys(dbName, null, tableName);
				if (primaryKeys.next()) {
					System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
					System.out.println("Table名：" + tableName + "のPrimaryKeyは " + primaryKeys.getString("COLUMN_NAME") + "です");
					id = primaryKeys.getString("COLUMN_NAME").toLowerCase();
				}

				System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
				System.out.println("EntityInfoクラスにTable情報を格納しています");
				//Table情報格納用クラス
				EntityInfo ei = new EntityInfo();

				//Table情報格納用クラスにTableNameを設定
				ei.setTableName(tableName);
				//Table情報格納用クラスにidを設定
				ei.setId(id);
				//Table情報格納クラスにカラム情報を設定
				ei.setColumns(columns);

				entityInfos.add(ei);
			}

			rs.close();

			// データベースのクローズ
			System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
			System.out.println("DBとのコネクションを破棄します");
			con.close();

		} catch (Exception e) {
			System.out.println("Exception発生");
			e.printStackTrace();
		}
		return entityInfos;
	}

}
```

###DataTypeMapper

```java:DataTypeMapper.java

package entityCreater.dataTypeMapper;

public class DataTypeMapper {

	/**
	 * SQLのデータタイプからJavaのデータタイプに変換するクラス
	 * @param sqlDataType
	 * @return
	 */
	public static String dataTypeChange(String sqlDataType) {

		String javaDataType = null;

		switch (sqlDataType) {
		case "CHAR":
		case "VARCHAR":
		case "LONGVARCHAR":
			javaDataType = "String";
			break;
		case "NUMERIC":
		case "DECIMAL":
			javaDataType = "java.math.BigDecimal";
			break;
		case "BIT":
			javaDataType = "boolean";
			break;
		case "TINYINT":
			javaDataType = "byte";
			break;
		case "SMALLINT":
			javaDataType = "short";
			break;
		case "INTEGER":
		case "INT":
			javaDataType = "Integer";
			break;
		case "BIGINT":
			javaDataType = "long";
			break;
		case "REAL":
			javaDataType = "float";
			break;
		case "FLOAT":
		case "DOUBLE":
			javaDataType = "double";
			break;
		case "BINARY":
		case "VARBINARY":
		case "LONGVARBINARY":
			javaDataType = "byte[]";
			break;
		case "DATE":
			javaDataType = "java.sql.Date";
			break;
		case "TIME":
			javaDataType = "java.sql.Time";
			break;
		case "TIMESTAMP":
			javaDataType = "java.sql.Timestamp";
			break;
		default:
			break;
		}

		return javaDataType;

	}

}
```

###EntityInfo

```java:EntityInfo.java

package entityCreater.info;

import java.util.List;

/**
 *
 * Entity情報格納クラス
 * @author okamotoyuuma
 *
 */
public class EntityInfo {

	//テーブル名
	private String tableName;

	//Primary Key名
	private String id;

	//すべてのカラム名と型
	private List<String[]> columns;

	/**
	 * (non-javadoc)
	 * getter setter
	 * @return
	 */

	public String getTableName() {
		return tableName;
	}

	public void setTableName(String tableName) {
		this.tableName = tableName;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public List<String[]> getColumns() {
		return columns;
	}

	public void setColumns(List<String[]> columns) {
		this.columns = columns;
	}

}
```

###アノテーション
@Tableアノテーション（属性にテーブル名を格納するクラス）

```java:Table.java
package annotation;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Target(TYPE)
@Retention(RUNTIME)
public @interface Table {

	String value();

}
```

@idアノテーション（主キーのフィールドに付与する）

```java:id.java
package annotation;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(FIELD)
public @interface id {

}
```

@columnnアノテーション（テーブルのカラム値に付与する）

```java:column.java
package annotation;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(FIELD)
public @interface column {

}
```


##使い方
適当にMainメソッドを作って、下記のように生成するEntityクラスを格納する場所を指定すればOKです！

```java:main.java

public class main {

	public static void main(String[] args) {
               EntityGenerator eg = new EntityGeneratorImplements("保存先のパス");
               eg.generateEntity();
	}

}
```

##最後に
今回はEntityクラスの自動生成をしてみました。
ソースやロジック等はまだまだ未熟で稚拙ですが、File生成はしたことがなかったのでとても良い経験になりました！
考慮できていない点や、ソースの改善点などご指摘あればコメントでいただけますと幸いです。

以下の参考項に上記ソースに載せていないDB設定ファイルや読み込むクラス等を載せていますので
興味ある方はみていただけたらと思います。

##参考（DB設定YAMLファイルと設定ファイルを読み込むクラスなど）

DB設定ファイル

```yaml:DBProfile.yaml

#DBの設定
!!container.DBConfig #DB定義設定クラスのパス
driver: com.mysql.cj.jdbc.Driver #driver名
url: jdbc:mysql://localhost:3306/DB名 #DBのURL
user: DBのユーザ名 #DBのユーザ
password: password #DBのパスワード
numberOfAccess: 10 #コネクションの数
dbName: データベース名 #DB名
dbType: MySQL #DBの種類（今回対応するのはMySQLとOracleDBのみ）
schema: スキーマ名
```
※DB設定ファイルは別のアプリでも使っているため、余計な情報も入ってます。。

設定情報を格納するためのクラス

```java:DBConfig.java

package container;

//データベースの設定ファイル
public class DBConfig {

	//ドライバ名
	String driver;

	//DBのURL
	String url;

	//DBのユーザ
	String user;

	//パスワード
	String password;

	//スキーマ名
	String schema;

	//コネクションの確立数
	int numberOfAccess;

	//DBName
	String dbName;

	//DBType
	String dbType;

	public String getDriver() {
		return driver;
	}

	public void setDriver(String driver) {
		this.driver = driver;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	public String getUser() {
		return user;
	}

	public void setUser(String user) {
		this.user = user;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getSchema() {
		return schema;
	}

	public void setSchema(String schema) {
		this.schema = schema;
	}

	public int getNumberOfAccess() {
		return numberOfAccess;
	}

	public void setNumberOfAccess(int numberOfAccess) {
		this.numberOfAccess = numberOfAccess;
	}

	public String getDbName() {
		return dbName;
	}

	public void setDbName(String dbName) {
		this.dbName = dbName;
	}

	public String getDbType() {
		return dbType;
	}

	public void setDbType(String dbType) {
		this.dbType = dbType;
	}

}


```

上記設定ファイルを読み込むEnvironmentConfigReaderクラス

```java:EnvironmentConfigReader.java

package container;

import org.yaml.snakeyaml.Yaml;

public class EnvironmentConfigReader implements Reader<DBConfig>{

	//DBの設定ファイル
	static String configFileName = "DBProfile.yaml";

	//yamlファイルからDB設定を取得するメソッド(引数なし)
	@Override
	public DBConfig read() {
		//ログ発生箇所
		System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
		//処理内容
		System.out.println(configFileName + "の読み込みを開始します。");

		Yaml yaml = new Yaml();
		DBConfig dbc = (DBConfig) yaml.load(getClass().getResourceAsStream(configFileName));

		//ログ発生箇所
		System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
		//処理内容
		System.out.println(configFileName + "の読み込みが完了しました。");

		return dbc;
	}

	//yamlファイルからDB設定を取得するメソッド(引数あり)
	@Override
	public DBConfig read(String setConfigFileName) {
		//ログ発生箇所
		System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
		//処理内容
		System.out.println(configFileName + "の読み込みを開始します。");

		//指定されたファイル名をセット
		if (configFileName != null) {
			configFileName = setConfigFileName;
		}

		Yaml yaml = new Yaml();
		DBConfig dbc = (DBConfig) yaml.load(getClass().getResourceAsStream(configFileName));

		//ログ発生箇所
		System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
		//処理内容
		System.out.println(configFileName + "の読み込みが完了しました。");

		return dbc;
	}

}
```

DBとのコネクションを格納するクラス

```java:DBAccess.java

package db_access;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import container.DBConfig;

public class DBAccess {

	//メンバ
	private Connection conn = null;

	public DBAccess(DBConfig dbc) {
		try {
			Class.forName(dbc.getDriver());
			conn = DriverManager.getConnection(dbc.getUrl(), dbc.getUser(), dbc.getPassword());
			System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
			System.out.println("DBに接続しました");
			//自動コミットOFF
			conn.setAutoCommit(false);
		} catch (SQLException | ClassNotFoundException e) {//コネクションの確立に失敗
			System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
			System.out.println("DBとの接続に失敗しました");
			e.printStackTrace();
		}
	}

	//コネクションを配布する
	public Connection getConnection() {
		return conn;
	}

	//コネクションの破棄
	public void closeConnection() {
		try {
			conn.close();
			System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
			System.out.println("DBから切断しました");
		} catch (SQLException e) {
			// TODO 自動生成された catch ブロック
			System.out.print(Thread.currentThread().getStackTrace()[1].getClassName() + ":");
			System.out.println("DBから切断できませんでした");
			e.printStackTrace();
		}
	}
}
```