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
- Entity/
- com/example/demo/
- resources/
- application.properties
- log4jdbc.log4j2.properties
- shema.sql
- data.sql
- ……
- java/
- test/
- java/com/example/demo/Dao/
- JDBCTempDaoTest.java
- java/com/example/demo/Dao/
- main/
- build.gradle
- ……
- src/
サンプルコード
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;
}
}
参考サイト