JdbcTemplateの使用方法(サンプルコード付き)

この記事は約69分で読めます。
JdbcTemplateアイキャッチ画像

JavaのSpring bootで、DBと連携する方法は、
Spring Data JDBC、Spring Data JPA、JdbcTemplate、NamedParamaterJdbcTemplateなどがあります。

今回は、その中のJdbcTemplateの使用方法をサンプルコードを用いて
ご紹介します。

JdbcTemplateは、単純なSQL文から複雑なSQL文までを直書きできるので、
プロジェクトによっては、JdbcTemplateのみを使用するプロジェクトも
あります。(自分の入っている現場はそうです。)

なお、使用例についてはJUnitを用い、詳細説明はコード内の
コメントを用いてしていきます。
使用したコードについては、以下のGitHubにも載せています。

GitHub - tkmttkm/SQL
Contribute to tkmttkm/SQL development by creating an account on GitHub.

GitHubよりソースをダウンロードし、
エクリプスで見た方が見やすいかもしれません。


また、JdbcTemplateとよく似ているNamedParamaterJdbcTemplate
についても以下の記事で紹介しています。

個人的には、NamedParamaterJdbcTemplateの方が使いやすいと思います。
興味があるかたはぜひご覧ください。

環境・定義

build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.2.0'
	id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '17'
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	runtimeOnly 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4:1.16'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.junit.jupiter:junit-jupiter:5.5.2'
}

tasks.named('test') {
	useJUnitPlatform()
}

DBはh2データベースを使用し、
GetterやSetterの生成には、lombokを用います。

application.properties

spring.datasource.driver-class-name=net.sf.log4jdbc.sql.jdbcapi.DriverSpy
spring.datasource.url=jdbc:log4jdbc:h2:mem:hogedb
spring.datasource.username=sa
spring.datasource.password=

spring.jpa.hibernate.ddl-auto=validate
spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:schema.sql
spring.sql.init.data-locations=classpath:data.sql

log4jdbc.log4j2.properties

log4jdbc.spylogdelegator.name=net.sf.log4jdbc.log.slf4j.Slf4jSpyLogDelegator

schema.sql

CREATE TABLE test_table
(
  id INT NOT NULL AUTO_INCREMENT,
  first_name VARCHAR(10) NOT NULL,
  last_name VARCHAR(10) NOT NULL,
  birth_day INT NULL,
  PRIMARY KEY(id)
);

data.sql

INSERT INTO test_table(id,first_name, last_name, birth_day)
VALUES(1,'テスト', '太郎', 20240101);
INSERT INTO test_table(id,first_name, last_name, birth_day)
VALUES(2,'テスト', '二郎', 20240101);
INSERT INTO test_table(id,first_name, last_name, birth_day)
VALUES(3,'テスト', '三郎', 20240101);
INSERT INTO test_table(id,first_name, last_name, birth_day)
VALUES(4,'テスト', '花子', 20250101);

ファイル階層

  • /
    • src/
      • main/
        • java/
          • com/example/demo/
            • Entity/
              • JDBCEntity.java
            • Dao/
              • JDBCTempDao.java
              • RowMapper/
                • JDBCEntityRowMapper.java
        • resources/
          • application.properties
          • log4jdbc.log4j2.properties
          • shema.sql
          • data.sql
          • ……
      • test/
        • java/com/example/demo/Dao/
          • JDBCTempDaoTest.java
    • build.gradle
    • ……

サンプルコード

JDBCEntity.java

package com.example.demo.Entity;

import java.util.ArrayList;
import java.util.List;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * <pre>
 * テーブルtest_tableのマッピングクラス
 * </pre>
 * @author Takumi
 */
@Getter
@AllArgsConstructor
public class JDBCEntity {
 
	//カラム名
	/** プライマリキー */
	private int id;
	private String first_name;
	private String last_name;
	private int birth_day;

	//カラム名の文字列
	public static final String TEST = "test_table";
	public static final String ID = "id";
	public static final String FIRST_NAME = "first_name";
	public static final String LAST_NAME = "last_name";
	public static final String BIRTHDAY = "birth_day";

	/**
	 * <pre>
	 * バッチアップデートなどで、カラム名と挿入したい値の順番を連携するために使用
	 * 同じリストを用いることで順番を対応づける
	 * </pre>
	 * @return
	 */
	public static List<String> GetSetQueryList_forBatchUpdate() {
		List<String> setQueryList = new ArrayList<String>();

		setQueryList.add(JDBCEntity.ID);
		setQueryList.add(JDBCEntity.FIRST_NAME);
		setQueryList.add(JDBCEntity.LAST_NAME);
		setQueryList.add(JDBCEntity.BIRTHDAY);

		return setQueryList;
	}
}

JDBCEntityRowMapper.java

package com.example.demo.Dao.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

import org.springframework.jdbc.core.RowMapper;

import com.example.demo.Entity.JDBCEntity;

/**
 * @author Takumi
 * <pre>
 * {@link JDBCEntity}のフィールドとマッピングをするためのクラス
 * {@link #mapRow(ResultSet, int)}で対応するカラムと値を設定する({@code override}使用
 * </pre>
 */
public class JDBCEntityRowMapper implements RowMapper<JDBCEntity> {

	/**
	 * <pre>
	 * 引数にテーブルのカラム名を渡し、型を合わせることで
	 * マッピングされる
	 * </pre>
	 */
	@Override
	public JDBCEntity mapRow(ResultSet rs, int rowNum) throws SQLException {
		return new JDBCEntity(
				rs.getInt(JDBCEntity.ID),
				rs.getString(JDBCEntity.FIRST_NAME),
				rs.getString(JDBCEntity.LAST_NAME),
				rs.getInt(JDBCEntity.BIRTHDAY)
				);
	}

}

JDBCTempDao.java

package com.example.demo.Dao;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;

import com.example.demo.Dao.RowMapper.JDBCEntityRowMapper;
import com.example.demo.Entity.JDBCEntity;

import io.micrometer.common.util.StringUtils;
import lombok.RequiredArgsConstructor;

/**
 * @author Takumi
 * <pre>
 * {@link JdbcTemplate}を使用するクラス
 * パラメータは、?と何番目の?に何を入れるかで指定できる
 * </pre>
 */
@Repository
@RequiredArgsConstructor
public class JDBCTempDao {

	private final JdbcTemplate jdbcTemp;

	/**
	 * <pre>
	 * {@link JDBCEntity#TEST TEST}テーブルのデータを全てを
	 * {@link JdbcTemplate#queryForList(String)}を使用して
	 * keyカラム名、valueその値(型Object)で取得
	 * </pre>
	 * @return key:カラム名 value:取得データ の{@code Map<String, Object>}をつめた{@code List} 
	 * @throws DataAccessException
	 */
	public List<Map<String, Object>> findAll() throws DataAccessException {
		try {
			List<String> sqlList = new ArrayList<String>();

			sqlList.add("SELECT");
			sqlList.add("*");
			sqlList.add("FROM");
			sqlList.add(JDBCEntity.TEST);

			String sql = String.join(" ", sqlList);

			return jdbcTemp.queryForList(sql);
		} catch (DataAccessException e) {
			System.err.println(e.getMessage() + "\r\n"
					+ e.getStackTrace());
			throw e;
		}
	}

	/**
	 * <pre>
	 * プライマリキーをWHERE句に入れることでデータを1つ取得
	 * {@link JdbcTemplate#queryForMap(String)}を使用
	 * </pre>
	 * @param id 取得したいデータのid(プライマリキー)を渡す
	 * @return
	 * @throws DataAccessException
	 */
	public Map<String, Object> findById(int id) throws DataAccessException {
		try {
			List<String> sqlList = new ArrayList<String>();

			sqlList.add("SELECT");
			sqlList.add("*");
			sqlList.add("FROM");
			sqlList.add(JDBCEntity.TEST);
			sqlList.add("WHERE");
			sqlList.add(JDBCEntity.ID + " = ?");

			String sql = String.join(" ", sqlList);

			return jdbcTemp.queryForMap(sql, id);
		} catch (DataAccessException e) {
			System.err.println(e.getMessage() + "\r\n"
					+ e.getStackTrace());
			return new HashMap<String, Object>();
		}
	}

	/**
	 * <pre>
	 * 更新したいデータのidをWHERE句に渡すことで、1つのデータを更新する
	 * {@link JdbcTemplate#update(String, PreparedStatementSetter)}使用
	 * </pre>
	 * @param id 更新したいデータのid(プライマリキー)
	 * @param updateDataMap 更新したいデータ(key:カラム名 value:更新したい値)
	 * @return データを更新した件数
	 * @throws DataAccessException
	 */
	public int updateById(int id, Map<String, String> updateDataMap) throws DataAccessException {
		try {

			Map<String, String> otherMap = new LinkedHashMap<String, String>();
			Integer idInte = Integer.valueOf(id);
			otherMap.put(JDBCEntity.ID, idInte.toString());
			PreparedStatementSetter pss = GetPreparedStatementSetter(updateDataMap, otherMap);

			List<String> sqlList = new ArrayList<String>();
			
			sqlList.add("UPDATE");
			sqlList.add(JDBCEntity.TEST);
			sqlList.add("SET");
			sqlList.add(updateSet(updateDataMap));
			sqlList.add("WHERE");
			sqlList.add(JDBCEntity.ID + " = ?");
			
			String sql = String.join(" ", sqlList);

			return jdbcTemp.update(sql, pss);
		} catch (DataAccessException e) {
			System.err.println(e.getMessage() + "\r\n"
					+ e.getStackTrace());
			throw e;
		}
	}
	
	/**
	 * <pre>
	 * データのインサートに使用
	 * {@link JdbcTemplate#update(String, PreparedStatementSetter)}使用
	 * </pre>
	 * @param insertDataMap key:カラム名 value:インサートしたい値
	 * @return インサートされた数(基本1)
	 * @throws DataAccessException
	 */
	public int insert(Map<String, String> insertDataMap) throws DataAccessException {
		try {
			PreparedStatementSetter pss = GetPreparedStatementSetter(insertDataMap, null);

			List<String> sqlList = new ArrayList<String>();
			
			sqlList.add("INSERT INTO");
			sqlList.add(JDBCEntity.TEST);
			sqlList.add(insertSet(insertDataMap));
			
			String sql = String.join(" ", sqlList);

			return jdbcTemp.update(sql, pss);
		} catch (DataAccessException e) {
			System.err.println(e.getMessage() + "\r\n"
					+ e.getStackTrace());
			throw e;
		}
	}

	/**
	 * <pre>
	 * 削除したいデータのid(プライマリキー)を引数に渡すことでデータを削除する
	 * {@link JdbcTemplate#update(String, Object...)}使用
	 * </pre>
	 * @param id s削除したいデータのid
	 * @return 削除したデータの数(基本1)
	 * @throws DataAccessException
	 */
	public int deleteById(int id) throws DataAccessException {
		try {
			List<String> sqlList = new ArrayList<String>();

			sqlList.add("DELETE");
			sqlList.add(JDBCEntity.TEST);
			sqlList.add("WHERE");
			sqlList.add(JDBCEntity.ID + " = ?");

			String sql = String.join(" ", sqlList);

			return jdbcTemp.update(sql, id);
		} catch (DataAccessException e) {
			System.err.println(e.getMessage() + "\r\n"
					+ e.getStackTrace());
			throw e;
		}
	}

	/**
	 * <pre>
	 * データの一括アップデートをする
	 * {@link JDCEntity}クラスにプライマリキーと更新したい値を正しくセットし、それを{@code List}煮詰めえることで
	 * そのデータが更新される
	 * {@link JdbcTemplate#batchUpdate(String, BatchPreparedStatementSetter)}使用
	 * </pre>
	 * @param updateList 更新したいデータのリスト
	 * @return 更新した数
	 * @throws DataAccessException
	 */
	public int batchUpdate(List<JDBCEntity> updateList) throws DataAccessException {
		try {
			//WHERE句のid = ?の部分に値を入れるためにotherMapを作成する。
			List<Map<String, String>> otherMapList = new ArrayList<>();
			for (JDBCEntity updateData : updateList) {
				Map<String, String> otherMap = new LinkedHashMap<>();
				var id = Integer.valueOf(updateData.getId());
				otherMap.put(JDBCEntity.ID, id.toString());
				otherMapList.add(otherMap);
			}

			BatchPreparedStatementSetter batchPs = GetBatchPreparedStatementSetter(updateList, otherMapList);

			List<String> sqlList = new ArrayList<String>();

			sqlList.add("UPDATE");
			sqlList.add(JDBCEntity.TEST);
			sqlList.add("SET");
			sqlList.add(batchUpdateSet());
			sqlList.add("WHERE");
			sqlList.add(JDBCEntity.ID + " = ?");

			String sql = String.join(" ", sqlList);

			int[] batchUpdate = jdbcTemp.batchUpdate(sql, batchPs);
			int returnCount = 0;
			for (int count : batchUpdate) {
				returnCount += count;
			}

			return returnCount;
		} catch (DataAccessException e) {
			System.err.println(e.getMessage() + "\r\n" + e.getStackTrace());
			throw e;
		}
	}
	
	/**
	 * <pre>
	 * データの一括インサート
	 * {@link JdbcTemplate#batchUpdate(String, BatchPreparedStatementSetter)}使用
	 * </pre>
	 * @param insertList インサートしたいデータのリスト
	 * @return インサート件数
	 * @throws DataAccessException
	 */
	public int batchInsert(List<JDBCEntity> insertList) throws DataAccessException {
		try {
			BatchPreparedStatementSetter batchPs = GetBatchPreparedStatementSetter(insertList, null);

			List<String> sqlList = new ArrayList<String>();

			sqlList.add("INSERT INTO");
			sqlList.add(JDBCEntity.TEST);
			sqlList.add(batchInsertSet());

			String sql = String.join(" ", sqlList);

			int[] batchUpdate = jdbcTemp.batchUpdate(sql, batchPs);
			int returnCount = 0;
			for (int count : batchUpdate) {
				returnCount += count;
			}

			return returnCount;
		} catch (DataAccessException e) {
			System.err.println(e.getMessage() + "\r\n" + e.getStackTrace());
			throw e;
		}
	}

	/**
	 * <pre>
	 * データの一括削除
	 * {@link JdbcTemplate#batchUpdate(String, BatchPreparedStatementSetter)}使用
	 * </pre>
	 * @param deleteList
	 * @return
	 * @throws DataAccessException
	 */
	public int batchDelete(List<JDBCEntity> deleteList) throws DataAccessException {
		try {
			List<Map<String, String>> whereMapList = new ArrayList<>();
			Map<String, Integer> sortMap = new LinkedHashMap<String, Integer>();
			sortMap.put(JDBCEntity.ID, 1);
			for (JDBCEntity updateData : deleteList) {
				Map<String, String> otherMap = new LinkedHashMap<>();
				var id = Integer.valueOf(updateData.getId());
				otherMap.put(JDBCEntity.ID, id.toString());
				whereMapList.add(otherMap);
			}

			BatchPreparedStatementSetter batchPs = GetBatchPreparedStatementSetter_forDelete(whereMapList, sortMap);

			List<String> sqlList = new ArrayList<String>();
			sqlList.add("DELETE");
			sqlList.add(JDBCEntity.TEST);
			sqlList.add("WHERE");
			sqlList.add(JDBCEntity.ID + " = ?");
			String sql = String.join(" ", sqlList);

			int[] batchUpdate = jdbcTemp.batchUpdate(sql, batchPs);
			int returnCount = 0;
			for (int count : batchUpdate) {
				returnCount += count;
			}

			return returnCount;
		} catch (DataAccessException e) {
			System.err.println(e.getMessage() + "\r\n" + e.getStackTrace());
			throw e;
		}
	}

	/**
	 * <pre>
	 * テーブルのすべてのデータを取得する
	 * かつ、取得データを{@link JDBCEntity}の対応するフィールドにセットする
	 * {@link JdbcTemplate#query(String, org.springframework.jdbc.core.RowMapper)}使用
	 * </pre>
	 * @return 取得データを{@link JDBCEntity}のフィールドにセットしたリスト
	 * @throws DataAccessException
	 * すべてのデータを取得する
	 * 
	 */
	public List<JDBCEntity> getAllJDBCEntity() throws DataAccessException {
		try {
			List<String> sqlList = new ArrayList<String>();

			sqlList.add("SELECT");
			sqlList.add("*");
			sqlList.add("FROM");
			sqlList.add(JDBCEntity.TEST);

			String sql = String.join(" ", sqlList);

			return jdbcTemp.query(sql, new JDBCEntityRowMapper());
		} catch (DataAccessException e) {
			System.err.println(e.getMessage() + "\r\n" + e.getStackTrace());
			throw e;
		}
	}

	/**
	 * <pre>
	 * 取得したいデータのid(プライマリキー)を引数に渡すことで、データを1件取得する
	 * かつ取得データを{@link JABCEntity}の対応するフィールドにセットする
	 * {@link JdbcTemplate#query(String, org.springframework.jdbc.core.RowMapper, Object...)}使用
	 * </pre>
	 * @param id 取得したいデータのid(プライマリキー)
	 * @return 取得したデータを{@link JDBCEntity}の対応するフィールドにセットしたリスト
	 * @throws DataAccessException
	 */
	public JDBCEntity getJDBCEntityById(int id) throws DataAccessException {
		try {
			List<String> sqlList = new ArrayList<String>();

			sqlList.add("SELECT");
			sqlList.add("*");
			sqlList.add("FROM");
			sqlList.add(JDBCEntity.TEST);
			sqlList.add("WHERE");
			sqlList.add(JDBCEntity.ID + " = ?");

			String sql = String.join(" ", sqlList);

			List<JDBCEntity> data = jdbcTemp.query(sql, new JDBCEntityRowMapper(), id);
			return data.get(0);
		} catch (DataAccessException e) {
			System.err.println(e.getMessage() + "\r\n" + e.getStackTrace());
			throw e;
		}
	}

	/**
	 * <pre>
	 * テーブルの作成で使用
	 * {@link JdbcTemplate#execute(String)}の使用例紹介のために作成したメソッド
	 * ※SQLインジェクション対策ができないため、あまりお勧めできない
	 * {@link JdbcTemplate#execute(String)}使用
	 * </pre>
	 * @param tableName 作成したいテーブルの名前
	 * @param column_columnInfo key:カラム名 value:型やNULL許容など(例 INT NOT NULL)
	 * @param primaryKeyList プライマリキーのカラム名を詰めたリスト
	 * @throws DataAccessException
	 */
	public void executeCreate(String tableName, Map<String, String> column_columnInfo, List<String> primaryKeyList) throws DataAccessException {
		try {
			List<String> sqlList = new ArrayList<>();
			
			sqlList.add("CREATE TABLE");
			sqlList.add(tableName);
			sqlList.add("(");
			
			List<String> columnList = new ArrayList<String>();
			for(var keyValue : column_columnInfo.entrySet()) {
				columnList.add(keyValue.getKey() + " " + keyValue.getValue());
			}
			sqlList.add(String.join(", ", columnList));
			
			if(!CollectionUtils.isEmpty(primaryKeyList)) {
				sqlList.add(", PRIMARY KEY (");
				sqlList.add(String.join(", ", primaryKeyList));
				sqlList.add(")");
			}
			
			sqlList.add(")");
			
			jdbcTemp.execute(String.join(" ", sqlList));
		} catch (DataAccessException e) {
			System.err.println(e.getMessage() + "r\n" + e.getStackTrace());
			throw e;
		}
	}
	
	/**
	 * <pre>
	 * テーブルの削除で使用
	 * {@link JdbcTemplate#execute(String)}の使用例紹介のために作成したメソッド
	 * ※SQLインジェクション対策ができないため、あまりお勧めできない
	 * {@link JdbcTemplate#execute(String)}使用
	 * </pre>
	 * @param tableName 削除したいテーブルの名前
	 * @throws DataAccessException
	 */
	public void executeDrop(String tableName) throws DataAccessException {
		try {
			List<String> sqlList = new ArrayList<>();
			
			sqlList.add("DROP TABLE");
			sqlList.add(tableName);
			
			jdbcTemp.execute(String.join(" ", sqlList));
		} catch (DataAccessException e) {
			System.err.println(e.getMessage() + "r\n" + e.getStackTrace());
			throw e;
		}
	}

	/**
	 * <pre>
	 * カラム名 = ? の形を作る
	 * </pre>
	 * @param map key:カラム名 value:その値
	 * @return カラム名 = ? が詰められたリスト
	 */
	private List<String> joinEqual(Map<String, String> map) {
		List<String> list = new ArrayList<String>();

		for (var entry : map.entrySet()) {
			list.add(entry.getKey() + " = ?");
		}

		return list;
	}

	/**
	 * <pre>
	 * リストにつめた文字列をカンマでつなぐ
	 * アップデートなどで使用
	 * </pre>
	 * @param list カラム名 = ? が詰められたリスト
	 * @return カラム名 = ?, カラム名 = ?, ....
	 */
	private String joinComma(List<String> list) {
		return String.join(", ", list);
	}

	/**
	 * <pre>
	 * UPDATEのSET句を作成する
	 * </pre>
	 * @param map
	 * @return
	 */
	private String updateSet(Map<String, String> map) {
		return joinComma(joinEqual(map));
	}
	
	/**
	 * <pre>
	 * () VALUES ()
	 * の部分を作成する
	 * </pre>
	 * @param column_valueMap key:カラム名 value:インサートしたい値
	 * @return () VALUES ()
	 */
	private String insertSet(Map<String, String> column_valueMap) {
		//挿入する場所を確実に指定するためにあえて配列にする
		String[] columnArray = new String[column_valueMap.size()];
		String[] valueArray = new String[column_valueMap.size()];
		
		//マップの数分ループし、keyのみの配列と、マップの数分の?のみの配列を作成する
		int index = 0;
		for(var column_value : column_valueMap.entrySet()) {
			columnArray[index] = column_value.getKey();
			valueArray[index] = "?";
			index++;
		}
		
		List<String> insertSqlList = new ArrayList<>();
		
		insertSqlList.add("(");
		insertSqlList.add(String.join(", ", columnArray)); //作成した配列を, で繋ぐ
		insertSqlList.add(") VALUES (");
		insertSqlList.add(String.join(", ", valueArray)); //作成した配列を, で繋ぐ
		insertSqlList.add(")");
		
		return String.join(" ", insertSqlList);
	}

	/**
	 * <pre>
	 * updateのset句を作成する
	 * 主にバッチ処理で使用
	 * </pre>
	 * @return カラム名 = ?, カラム名 = ?, ........
	 */
	private String batchUpdateSet() {
		List<String> batchUpdateSetList = new ArrayList<>();

		for (String columnName : JDBCEntity.GetSetQueryList_forBatchUpdate()) {
			batchUpdateSetList.add(columnName + " = ?");
		}

		return String.join(", ", batchUpdateSetList);
	}
	
	/**
	 * <pre>
	 * インサートようのクエリを作成
	 * バッチ処理で使用
	 * </pre>
	 * @return (....) VALUES (.....)
	 */
	private String batchInsertSet() {
		List<String> batchInsertColumnSetList = new ArrayList<>();
		List<String> batchInsertValueSetList = new ArrayList<>();

		for (String columnName : JDBCEntity.GetSetQueryList_forBatchUpdate()) {
			batchInsertColumnSetList.add(columnName);
			batchInsertValueSetList.add("?");
		}
		
		List<String> sql = new ArrayList<>();
		sql.add("(");
		sql.add(String.join(", ", batchInsertColumnSetList));
		sql.add(") VALUES (");
		sql.add(String.join(", ", batchInsertValueSetList));
		sql.add(")");

		return String.join(" ", sql);
	}



	/**
	 * <pre>
	 * ?の何番目になんの値を入れるかをセットする
	 * </pre>
	 * @param ps
	 * @param column_valueMap key:カラム value:代入する値
	 * @param column_sortNoMap key;カラム value:?の何番目に対応するかの数({@link #GetColumn_sortNoMap(Map)}から取得
	 * @throws NumberFormatException
	 * @throws SQLException
	 */
	private void SetPreparedStatement(PreparedStatement ps, Map<String, String> column_valueMap,
			Map<String, Integer> column_sortNoMap) throws NumberFormatException, SQLException {
		try {
			if (existKey(column_valueMap, JDBCEntity.ID)) {
				ps.setInt(column_sortNoMap.get(JDBCEntity.ID), Integer.parseInt(column_valueMap.get(JDBCEntity.ID)));
			}
			if (existKey(column_valueMap, JDBCEntity.BIRTHDAY)) {
				ps.setInt(column_sortNoMap.get(JDBCEntity.BIRTHDAY), Integer.parseInt(column_valueMap.get(JDBCEntity.BIRTHDAY)));
			}
			if (existKey(column_valueMap, JDBCEntity.FIRST_NAME)) {
				ps.setString(column_sortNoMap.get(JDBCEntity.FIRST_NAME), column_valueMap.get(JDBCEntity.FIRST_NAME));
			}
			if (existKey(column_valueMap, JDBCEntity.LAST_NAME)) {
				ps.setString(column_sortNoMap.get(JDBCEntity.LAST_NAME), column_valueMap.get(JDBCEntity.LAST_NAME));
			}
		} catch (NumberFormatException e) {
			System.out.println(e.getMessage() + "\r\n" + e.getStackTrace());
		} catch (SQLException e) {
			System.out.println(e.getMessage() + "\r\n" + e.getStackTrace());
		}
	}

	/**
	 * <pre>
	 * 何番目の?になんの値をセットする指定するメソッド
	 * batch系で使用
	 * </pre>
	 * @param ps
	 * @param data インサートしたいデータ
	 * @param column_sortNoMap key:カラム名 value:?の何番目にそのデータを入れるか
	 */
	private void SetPreparedStatement_forBatchUpdate(PreparedStatement ps, JDBCEntity data,
			Map<String, Integer> column_sortNoMap) {
		try {
			for (String columnName : JDBCEntity.GetSetQueryList_forBatchUpdate()) {
				if (columnName.equals(JDBCEntity.ID)) {
					if (data.getId() != 0) {
						ps.setInt(column_sortNoMap.get(JDBCEntity.ID), data.getId());
					}
					continue;
				}
				if (columnName.equals(JDBCEntity.BIRTHDAY)) {
					if (data.getBirth_day() != 0) {
						ps.setInt(column_sortNoMap.get(JDBCEntity.BIRTHDAY), data.getBirth_day());
					}
					continue;
				}
				if (columnName.equals(JDBCEntity.FIRST_NAME)) {
					if (StringUtils.isNotBlank(data.getFirst_name())) {
						ps.setString(column_sortNoMap.get(JDBCEntity.FIRST_NAME), data.getFirst_name());
					}
					continue;
				}
				if (columnName.equals(JDBCEntity.LAST_NAME)) {
					if (StringUtils.isNotBlank(data.getLast_name())) {
						ps.setString(column_sortNoMap.get(JDBCEntity.LAST_NAME), data.getLast_name());
					}
					continue;
				}
			}
		} catch (NumberFormatException e) {
			System.out.println(e.getMessage() + "\r\n" + e.getStackTrace());
		} catch (SQLException e) {
			System.out.println(e.getMessage() + "\r\n" + e.getStackTrace());
		}
	}

	/**
	 * <pre>
	 * 追加で?に値をセットする際に使用(where句の値のセットなど)
	 * </pre>
	 * @param ps
	 * @param otherMap 追加で値をセットする
	 * @param index 続きの番号
	 * @throws NumberFormatException
	 * @throws SQLException
	 */
	private void setPreparedStatementOther(PreparedStatement ps, Map<String, String> otherMap, Integer index)
			throws NumberFormatException, SQLException {
		try {
			//ここで採番して始める
			index++;
			//マップの数分回し、keyの存在確認をしながらセットする
			for (var columnName_Value : otherMap.entrySet()) {
				if (columnName_Value.getKey().equals(JDBCEntity.ID)) {
					Integer idInte = Integer.parseInt(otherMap.get(JDBCEntity.ID));
					if (idInte.intValue() == 0) {
						continue;
					}
					ps.setInt(index, idInte.intValue());
				}
				if (columnName_Value.getKey().equals(JDBCEntity.BIRTHDAY)) {
					Integer birthDayInte = Integer.parseInt(otherMap.get(JDBCEntity.BIRTHDAY));
					if (birthDayInte.intValue() == 0) {
						continue;
					}
					ps.setInt(index, birthDayInte.intValue());
				}
				if (columnName_Value.getKey().equals(JDBCEntity.FIRST_NAME)) {
					if (StringUtils.isBlank(otherMap.get(JDBCEntity.FIRST_NAME))) {
						continue;
					}
					ps.setString(index, otherMap.get(JDBCEntity.FIRST_NAME));
				}
				if (columnName_Value.getKey().equals(JDBCEntity.LAST_NAME)) {
					if (StringUtils.isBlank(otherMap.get(JDBCEntity.LAST_NAME))) {
						continue;
					}
					ps.setString(index, otherMap.get(JDBCEntity.LAST_NAME));
				}
			}
		} catch (NumberFormatException e) {
			System.out.println(e.getMessage() + "\r\n" + e.getStackTrace());
		} catch (SQLException e) {
			System.out.println(e.getMessage() + "\r\n" + e.getStackTrace());
		}
	}

	/**
	 * <pre>
	 * {@code Map<String, String>}のkeyの存在確認をする
	 * </pre>
	 * @param map keuの存在確認がしたいMap
	 * @param key 存在確認したいkey
	 * @return keyが存在したらtrue
	 */
	private boolean existKey(Map<String, String> map, String key) {
		return map.containsKey(key);
	}

	/**
	 * <pre>
	 * ?の何番目に何を入れるかをセットする際に、
	 * カラム名と何番目かを対応させておく
	 * </pre>
	 * @param column_valueMap key:カラム名 value:その値
	 * @return key:カラム名 value:?の何番目にセットするかの数
	 */
	private Map<String, Integer> GetColumn_sortNoMap(Map<String, String> column_valueMap) {
		Map<String, Integer> returnMap = new LinkedHashMap<>();
		Integer index = 1;
		for (var map : column_valueMap.entrySet()) {
			returnMap.put(map.getKey(), index);
			index++;
		}
		return returnMap;
	}

	/**
	 * <pre>
	 * カラム名と?との対応づけを確実にするために、奈良み順を指定しているメソッドを使用して
	 * key:カラム名 value:何番目の?に値を入れるか
	 * の{@code Map}を作成する
	 * 並び順は{@link JDBCEntity#GetSetQueryList_forBatchUpdate()}で取得
	 * </pre>
	 * @return
	 */
	private Map<String, Integer> GetColumn_sortNoMap_forBatchUpdate() {
		Map<String, Integer> returnMap = new LinkedHashMap<>();
		Integer index = 1;
		for (String columnName : JDBCEntity.GetSetQueryList_forBatchUpdate()) {
			returnMap.put(columnName, index);
			index++;
		}
		return returnMap;
	}

	/**
	 * <pre>
	 * SQLインジェクション対策
	 * ?のところの何番目になんの値を入れるかをセットしていく
	 * </pre>
	 * @param updateDataMap 更新したいデータ(key:カラム名 value:更新したい値)
	 * @return
	 */
	private PreparedStatementSetter GetPreparedStatementSetter(Map<String, String> updateDataMap,
			Map<String, String> otherMap) {
		return new PreparedStatementSetter() {

			@Override
			public void setValues(PreparedStatement ps) throws SQLException {
				Map<String, Integer> sortMap = GetColumn_sortNoMap(updateDataMap);
				SetPreparedStatement(ps, updateDataMap, sortMap);
				if (otherMap != null) {
					if (otherMap.size() != 0) {
						setPreparedStatementOther(ps, otherMap, updateDataMap.size());
					}
				}
			}
		};
	}

	/**
	 * <pre>
	 * バッチアップデートなどで使用
	 * 引数に渡されたデータが入ったリストを元に、?の何番目に何を入れるかをセットする
	 * </pre>
	 * @param updateList
	 * @param otherList where句などに使う値を格納する
	 * @return
	 */
	private BatchPreparedStatementSetter GetBatchPreparedStatementSetter(List<JDBCEntity> updateList,
			List<Map<String, String>> otherMapList) {
		return new BatchPreparedStatementSetter() {
			@Override
			public void setValues(PreparedStatement ps, int i) throws SQLException {
				Map<String, Integer> sortMap = GetColumn_sortNoMap_forBatchUpdate();
				SetPreparedStatement_forBatchUpdate(ps, updateList.get(i), sortMap);
				if (otherMapList != null) {
					if (otherMapList.size() != 0) {
						if (otherMapList.size() == updateList.size()) {
							setPreparedStatementOther(ps, otherMapList.get(i), sortMap.size());
						} else {
							throw new SQLException("otherMapListが正しく設定されていません。");
						}
					}
				}
			}

			@Override
			public int getBatchSize() {
				// バッチのサイズを返す
				return updateList.size();
			}
		};
	}

	/**
	 * <pre>
	 * 一括削除で使用
	 * where句の何番目の?になんの値を入れるかを設定する
	 * </pre>
	 * @param whereMapList key:カラム名 value:where句に入れる値
	 * @param sortMap key:カラム名 value:?の何番目に入れるかの数
	 * @return
	 */
	private BatchPreparedStatementSetter GetBatchPreparedStatementSetter_forDelete(
			List<Map<String, String>> whereMapList, Map<String, Integer> sortMap) {
		return new BatchPreparedStatementSetter() {
			@Override
			public void setValues(PreparedStatement ps, int i) throws SQLException {
				SetPreparedStatement(ps, whereMapList.get(i), sortMap);
			}

			@Override
			public int getBatchSize() {
				return whereMapList.size();
			}
		};
	}

}

JDBCTempDaoTest.java

package com.example.demo.Dao;

import static org.junit.jupiter.api.Assertions.*;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Transactional;

import com.example.demo.Entity.JDBCEntity;

@SpringBootTest
@Transactional
class JDBCTempDaoTest {

	@Autowired
	private JDBCTempDao dao;

	/**
	 * <pre>
	 * {@link JdbcTemplate#queryForMap(String)}i
	 * にてテーブルデータすべて取得
	 * </pre>
	 */
	@Test
	void testFindAll() {
		List<Map<String, Object>> dataList = dao.findAll();
		assertTrue(dataList.size() == 4);
	}

	/**
	 * <pre>
	 * {@link JdbcTemplate#queryForMap(String, Object...)}
	 * にてプライマリキー(id)を渡し取得したいデータを1つ取得
	 * </pre>
	 */
	@Test
	void testFindById() {
		Map<String, Object> data = dao.findById(1);

		assertEquals(data.get(JDBCEntity.BIRTHDAY), 20240101);
		assertEquals(data.get(JDBCEntity.FIRST_NAME).toString().strip(), "テスト");
		assertEquals(data.get(JDBCEntity.LAST_NAME).toString().strip(), "太郎");
	}

	/**
	 * <pre>
	 * {@link JdbcTemplate#update(String, org.springframework.jdbc.core.PreparedStatementSetter)}
	 * にて更新したいデータのプライマリキー(id)を渡すことで、データを更新
	 * </pre>
	 */
	@Test
	void testUpdateById() {
		//更新前のデータ取得
		Map<String, Object> beforeData = dao.findById(1);
		assertEquals(beforeData.get(JDBCEntity.FIRST_NAME).toString().strip(), "テスト");
		assertEquals(beforeData.get(JDBCEntity.LAST_NAME).toString().strip(), "太郎");

		//データ更新
		Map<String, String> updateMap = new TreeMap<>();
		updateMap.put(JDBCEntity.FIRST_NAME, "更新した");
		updateMap.put(JDBCEntity.LAST_NAME, "太郎くん");
		int updateCount = dao.updateById(1, updateMap);
		assertTrue(updateCount == 1);

		//更新後のデータ確認
		Map<String, Object> updateData = dao.findById(1);
		assertEquals(updateData.get(JDBCEntity.FIRST_NAME).toString().strip(), "更新した");
		assertEquals(updateData.get(JDBCEntity.LAST_NAME).toString().strip(), "太郎くん");
	}

	/**
	 * <pre>
	 * {@link JdbcTemplate#update(String, org.springframework.jdbc.core.PreparedStatementSetter)}
	 * を用いてデータを挿入
	 * {@code Map<Strng, String>}にkey:カラム名 value:挿入したい値
	 * をセットし、引数に渡すことで挿入
	 * </pre>
	 */
	@Test
	void testInsert() {
		//素運輸データの準備
		Map<String, String> insertData = new LinkedHashMap<>();
		insertData.put(JDBCEntity.ID, "10");
		insertData.put(JDBCEntity.FIRST_NAME, "インサート");
		insertData.put(JDBCEntity.LAST_NAME, "太郎");
		insertData.put(JDBCEntity.BIRTHDAY, "20200202");
		//データの挿入
		dao.insert(insertData);

		//データの挿入確認
		Map<String, Object> data = dao.findById(10);
		assertEquals(data.get(JDBCEntity.FIRST_NAME), "インサート");
		assertEquals(data.get(JDBCEntity.LAST_NAME), "太郎");
		assertEquals(data.get(JDBCEntity.BIRTHDAY), 20200202);
	}

	/**
	 * <pre>
	 * {@link JdbcTemplate#update(String, Object...)}
	 * を用いてデータの削除
	 * 引数に削除したデータのid(プライマリキー)を渡すことで
	 * データを削除する
	 * </pre>
	 */
	@Test
	void testDeleteById() {
		//削除前のデータの存在確認
		Map<String, Object> beforeData = dao.findById(1);
		assertEquals(beforeData.get(JDBCEntity.FIRST_NAME).toString().strip(), "テスト");
		assertEquals(beforeData.get(JDBCEntity.LAST_NAME).toString().strip(), "太郎");
		assertEquals(beforeData.get(JDBCEntity.BIRTHDAY), 20240101);

		//データの削除
		int deleteCount = dao.deleteById(1);
		assertTrue(deleteCount == 1);

		//データの削除確認
		List<Map<String, Object>> deleteData = dao.findAll();
		assertFalse(deleteData.contains(beforeData));
	}

	/**
	 * <pre>
	 * {@link JdbcTemplate#batchUpdate(String, org.springframework.jdbc.core.BatchPreparedStatementSetter)}
	 * を用いてデータの一括更新
	 * {@code List<JDBCEntity>}に更新したいデータをセットすることで
	 * データを一括更新する
	 * </pre>
	 */
	@Test
	void testBatchUpdate() {
		//更新データのセット
		List<JDBCEntity> entityList = new ArrayList<>();
		entityList.add(new JDBCEntity(1, "Junit", "たのすいーーーー", 20240202));
		entityList.add(new JDBCEntity(2, "Junit", "楽しいねえ", 20240203));

		//データの一括更新
		int updateCount = dao.batchUpdate(entityList);
		
		//データの更新確認
		assertEquals(updateCount, 2);
		
		Map<String, Object> afterId1 = dao.findById(1);
		assertEquals(afterId1.get(JDBCEntity.FIRST_NAME.toString().strip()), "Junit");
		assertEquals(afterId1.get(JDBCEntity.LAST_NAME.toString().strip()), "たのすいーーーー");
		assertEquals(Integer.parseInt(afterId1.get(JDBCEntity.BIRTHDAY).toString()), 20240202);
		
		Map<String, Object> afterId2 = dao.findById(2);
		assertEquals(afterId2.get(JDBCEntity.FIRST_NAME.toString().strip()), "Junit");
		assertEquals(afterId2.get(JDBCEntity.LAST_NAME.toString().strip()), "楽しいねえ");
		assertEquals(Integer.parseInt(afterId2.get(JDBCEntity.BIRTHDAY).toString()), 20240203);
	}

	/**
	 * <pre>
	 * {@link JdbcTemplate#batchUpdate(String, org.springframework.jdbc.core.BatchPreparedStatementSetter)}
	 * を用いることでデータを一括更新する
	 * {@code List<JDBCEntity>}に挿入したいデータをセットすることで
	 * データを一括挿入する
	 * </pre>
	 */
	@Test
	void testBatchInsert() {
		//挿入データのセット
		List<JDBCEntity> entityList = new ArrayList<>();
		entityList.add(new JDBCEntity(10, "Junit", "たのすいーーーー", 20240202));
		entityList.add(new JDBCEntity(20, "Junit", "楽しいねえ", 20240203));
		//データの一括挿入
		int insertCount = dao.batchInsert(entityList);
		
		//データの挿入確認
		assertEquals(insertCount, 2);
		
		var insertData1 = dao.findById(10);
		assertEquals(insertData1.get(JDBCEntity.FIRST_NAME.toString().strip()), "Junit");
		assertEquals(insertData1.get(JDBCEntity.LAST_NAME.toString().strip()), "たのすいーーーー");
		assertEquals(Integer.parseInt(insertData1.get(JDBCEntity.BIRTHDAY).toString()), 20240202);
		
		var insertData2 = dao.findById(20);
		assertEquals(insertData2.get(JDBCEntity.FIRST_NAME.toString().strip()), "Junit");
		assertEquals(insertData2.get(JDBCEntity.LAST_NAME.toString().strip()), "楽しいねえ");
		assertEquals(Integer.parseInt(insertData2.get(JDBCEntity.BIRTHDAY).toString()), 20240203);
	}

	/**
	 * {@link JdbcTemplate#batchUpdate(String, org.springframework.jdbc.core.BatchPreparedStatementSetter)}
	 * を用いることでデータを一括削除する
	 * {@code List<JDBCEntity>}に削除したいデータをセットすることで
	 * データを一括削除する
	 * セットする値はid(プライマリキー)のみでOK
	 */
	@Test
	void testBatchDelete() {
		//削除したいデータのidをセット
		List<JDBCEntity> entityList = new ArrayList<>();
		entityList.add(new JDBCEntity(1, null, null, 0));
		entityList.add(new JDBCEntity(2, null, null, 0));
		//データの一括削除
		int deleteCount = dao.batchDelete(entityList);
		
		//データの削除確認
		assertEquals(deleteCount, 2);
		
		Map<String, Object> afterId1 = dao.findById(1);
		assertTrue(afterId1.size() == 0);
		
		Map<String, Object> afterId2 = dao.findById(2);
		assertTrue(afterId2.size() == 0);
	}

	/**
	 * <pre>
	 * {@link JdbcTemplate#query(String, org.springframework.jdbc.core.RowMapper)}
	 * を用いることで、テーブル内のデータを{@link JDBCEntity}のフィールでのセットした状態で取得
	 * </pre>
	 */
	@Test
	void testGetAllJDBCEEntity() {
		//データの取得
		List<JDBCEntity> dataList = dao.getAllJDBCEntity();
		
		//取得データの確認
		assertTrue(dataList.size() == 4);

		//本来は全データ確認した方が良いが、ここではidが2のデータのみ確認
		List<JDBCEntity> IdTwoData = dataList.stream().filter(data -> data.getId() == 2).collect(Collectors.toList());
		assertTrue(IdTwoData.size() == 1);
		//確実に取得データは1つなのでget(0)する
		JDBCEntity twoData = IdTwoData.get(0);

		assertEquals(twoData.getId(), 2);
		assertEquals(twoData.getBirth_day(), 20240101);
		assertEquals(twoData.getFirst_name(), "テスト");
		assertEquals(twoData.getLast_name(), "二郎");
	}

	/**
	 * <pre>
	 * {@link JdbcTemplate#query(String, org.springframework.jdbc.core.RowMapper)}
	 * を用いて、引数に取得したいデータのid(プライマリキー)を渡すことで、
	 * テーブル内のデータを{@link JDBCEntity}のフィールでのセットした状態で取得
	 * </pre>
	 */
	@Test
	void testGetJDBCEntityById() {
		//データの取得
		JDBCEntity data = dao.getJDBCEntityById(2);
		//取得データの確認
		assertEquals(data.getId(), 2);
		assertEquals(data.getBirth_day(), 20240101);
		assertEquals(data.getFirst_name(), "テスト");
		assertEquals(data.getLast_name(), "二郎");
	}

	/**
	 * <pre>
	 * {@link JdbcTemplate#execute(String)}
	 * を用いることでテーブルを削除
	 * 引数に削除したいテーブル名を渡すことでテーブルを削除する
	 * 
	 * テストは通っているが
	 * テーブルを消してしまうので、Disabled
	 * </pre>
	 */
	@Test
	@Disabled
	void testDrop() {
		//テーブルの削除
		dao.executeDrop(JDBCEntity.TEST);
		
		//削除したテーブルを取得しようとすることで、テーブルの削除確認
		assertThrows(DataAccessException.class, () -> {
			dao.getAllJDBCEntity();
		});
	}

	/**
	 * <pre>
	 * {@link JdbcTemplate#execute(String)}
	 * を用いてテーブルを作成する
	 * 第一引数に作成したいテーブル名、第二引数にカラム、第三引数にプライマリキーに設定したいカラム
	 * を渡す
	 * 第二引数のカラムは、key:カラム名、value:型やNULL許容など
	 * をセットする
	 * </pre>
	 */
	@Test
	void testExecuteCreate() {
		//カラムのセット
		Map<String, String> columnInfo = new HashMap<>();
		columnInfo.put("id", "INT");
		columnInfo.put("name", "char(50)");
		columnInfo.put("comment", "char(50)");
		columnInfo.put("message", "char(100)");
		//プライマリキーのセット
		List<String> primaryList = new ArrayList<>();
		primaryList.add("id");
		primaryList.add("name");
		//テーブル作成
		dao.executeCreate("CREATE_TABLE", columnInfo, primaryList);
		//テーブルの作成確認
		List<String> tableNameList = new createTable().GetTableNames();
		assertTrue(tableNameList.contains("CREATE_TABLE"));
	}
}

/**
 * @author Takumi
 * {@link JDBCTempDaoTest#testExecuteCreate()}のテーブル作成確認用クラス
 *
 */
class createTable {

	private final JdbcTemplate jdbc;

	/**
	 * h2データベースの設定
	 */
	public createTable() {
		jdbc = new JdbcTemplate();
		jdbc.setDataSource(DataSourceBuilder.create()
				.driverClassName("net.sf.log4jdbc.sql.jdbcapi.DriverSpy")
				.url("jdbc:log4jdbc:h2:mem:hogedb")
				.username("sa")
				.password("")
				.build());
	}

	/**
	 * @return 存在するテーブル名のリスト
	 */
	public List<String> GetTableNames() {
		List<Map<String, Object>> tableData = jdbc.queryForList("SELECT"
				+ " TBL.TABLE_NAME AS TABLE_NAME "
				+ "FROM"
				+ " INFORMATION_SCHEMA.TABLES AS TBL "
				+ "WHERE"
				+ " TBL.TABLE_SCHEMA =  SCHEMA()");

		List<String> tableNameList = new ArrayList<>();
		for (Map<String, Object> data : tableData) {
			tableNameList.add(data.get("TABLE_NAME").toString());
		}

		return tableNameList;

	}

}