
⇧ Oh, my gosh...
Oh~ Oh~ ともだち ずっと ともだち
ずっと ともだち いない
⇧ 友人のいない人にとっては、幸福感をどう高めたら良いのかしらね...
DBUnitでSQLファイルを実行したかったけど
⇧ 残念ながら、DBUnitはSQLファイルの実行をサポートしていないらしい...
DBUnit以外で実行するしかないのですが、Spring Frameworkを使っている場合は、spring-jdbcの依存関係を追加すれば、SQLファイルを実行できるAPIが利用できるみたいなので、試してみます。
試してみたのですが、
⇧ SQL文の中のスラッシュに対応してないっぽい...
しかも、Spring Frameworkの中の人の説明がかなり苦しい。
バグじゃないかもしれないけど、結局、スラッシュに対応してないんなら、対応してませんの一言で済むと思うんだけど、認めたくないのかね?
話が脱線しましたが、Oracle Database 19cのPDBにテーブルを作成しておきます。
そして、ビルドツールにMavenを選択し、Spring Bootプロジェクトを作成して、以下のようなファイルを配置。

ファイルの内容など。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.8</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>dbunit-example</artifactId> <version>0.0.1-SNAPSHOT</version> <name>dbunit-example</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc8</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <!-- <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <scope>test</scope> </dependency> --> <!-- https://mvnrepository.com/artifact/org.dbunit/dbunit --> <dependency> <groupId>org.dbunit</groupId> <artifactId>dbunit</artifactId> <version>2.7.3</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.poi/poi --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>5.2.3</version> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.3</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
⇧ JUnitはDBUnitの依存関係に含まれるらしいので、DBUnitを依存関係に追加してれば自動的にJUnitが使えるっぽい。
あと、Spring Bootのバージョンが、3.0とかにしちゃうと、pom.xmlでJavaのバージョンを1.8(Java 8)とかにしても、実行時にはJava 17が利用されるっぽいので、実行時に、コンパイル時のJavaのバージョンと実行時のJavaのバージョン違うよってエラーになるんで要注意ですかね。
■/dbunit-example/src/test/resources/application.properties
# DB接続情報 spring.datasource.url=jdbc:oracle:thin:@//localhost:1519/orcldb_19c spring.datasource.username=ts0818 spring.datasource.password=password spring.datasource.driverClassName=oracle.jdbc.driver.OracleDriver server.address=localhost database.port=1519 pdb.service.name=orcldb_19c
■/dbunit-example/src/test/resources/db/backup_db.sql
-- PDBに接続する
ALTER session SET container=ORCLDB_19C;
/
-- バックアップテーブルが存在する場合は削除
--SET SERVEROUTPUT ON;
DECLARE
-- 削除対象のテーブル
CURSOR table_list IS
SELECT
a.table_name, a.owner
FROM
dba_tables a
WHERE
a.owner = 'TS0818' -- OWNERを指定
AND a.table_name IN ( -- テーブルを指定
'BK_ADDRESS_DETAIL'
,'BK_AUTH_INFOMATION'
,'BK_CATEGORY'
,'BK_CATEGORY_DETAIL'
,'BK_ORDER_DETAIL'
,'BK_ORDERS'
,'BK_ORDER_STATUS'
,'BK_PRODUCT'
,'BK_PRODUCT_DETAIL'
,'BK_PURCHASEORDER'
,'BK_USER_DETAIL'
,'BK_USER_LEAVE'
,'BK_USERS'
)
ORDER BY
a.table_name;
version VARCHAR2(2);
BEGIN
-- Oracleのバージョンを取得
DBMS_OUTPUT.PUT_LINE('Oracleのバージョンを確認...');
SELECT
REPLACE(SUBSTR(version ,0,2) ,'.' ,'') AS major_version
INTO version
FROM
product_component_version
WHERE
LOWER(product) LIKE 'oracle%';
DBMS_OUTPUT.PUT_LINE('バージョンは ' || version);
-- 削除処理開始
DBMS_OUTPUT.PUT_LINE('テーブル削除 ----> START');
FOR vRec IN table_list LOOP
DBMS_OUTPUT.PUT_LINE('削除対象テーブル:' || vRec.owner || '.' || vRec.table_name);
-- DDL確認用:DROP
-- Oracleのバージョンが10g以降⇒PURGEでテーブルを完全削除
IF TO_NUMBER(version) >= 10 THEN
DBMS_OUTPUT.PUT_LINE('PURGE TABLE ' || vRec.owner || '.' || vRec.table_name);
-- DDL確認用:PURGE
EXECUTE IMMEDIATE 'DROP TABLE ' || vRec.owner || '.' || vRec.table_name || ' CASCADE CONSTRAINTS';
EXECUTE IMMEDIATE 'PURGE TABLE ' || vRec.owner || '.' || vRec.table_name;
ELSE
DBMS_OUTPUT.PUT_LINE('DROP TABLE ' || vRec.owner || '.' || vRec.table_name);
EXECUTE IMMEDIATE 'DROP TABLE ' || vRec.owner || '.' || vRec.table_name || ' CASCADE CONSTRAINTS';
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE('テーブル削除 ----> END');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('----エラー発生!----------------');
DBMS_OUTPUT.PUT_LINE('--SQLERRM:'||SQLERRM);
DBMS_OUTPUT.PUT_LINE('--SQLCODE:'||SQLCODE);
END;
/
-- バックアップテーブルの作成
CREATE TABLE BK_ADDRESS_DETAIL
AS
SELECT *
FROM ADDRESS_DETAIL
/
CREATE TABLE BK_AUTH_INFOMATION
AS
SELECT *
FROM AUTH_INFOMATION
/
CREATE TABLE BK_CATEGORY
AS
SELECT *
FROM CATEGORY
/
CREATE TABLE BK_CATEGORY_DETAIL
AS
SELECT *
FROM CATEGORY_DETAIL
/
CREATE TABLE BK_ORDER_DETAIL
AS
SELECT *
FROM ORDER_DETAIL
/
CREATE TABLE BK_ORDERS
AS
SELECT *
FROM ORDERS
/
CREATE TABLE BK_ORDER_STATUS
AS
SELECT *
FROM ORDER_STATUS
/
CREATE TABLE BK_PRODUCT
AS
SELECT *
FROM PRODUCT
/
CREATE TABLE BK_PRODUCT_DETAIL
AS
SELECT *
FROM PRODUCT_DETAIL
/
CREATE TABLE BK_PURCHASEORDER
AS
SELECT *
FROM PURCHASEORDER
/
CREATE TABLE BK_USER_DETAIL
AS
SELECT *
FROM USER_DETAIL
/
CREATE TABLE BK_USER_LEAVE
AS
SELECT *
FROM USER_LEAVE
/
CREATE TABLE BK_USERS
AS
SELECT *
FROM USERS
/
EXIT;
/
■/dbunit-example/src/test/java/com/example/demo/DbunitExampleApplicationTests.java
package com.example.demo;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Properties;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.dbunit.JdbcDatabaseTester;
import org.dbunit.database.AmbiguousTableNameException;
import org.dbunit.database.QueryDataSet;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.excel.XlsDataSetWriter;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.springframework.jdbc.datasource.init.ScriptException;
class DbunitExampleApplicationTests {
private static Properties prop;
private static String url;
private static String user;
private static String pass;
private static String jdbcDriver;
private static Connection conn;
private static String host;
private static String dbPort;
private static String dbServiceName;
private TestInfo testInfo;
private static String EVIDENCE_DIR = Paths.get(System.getProperty("user.dir"), "evidence").toString();
private static String beforeOutputFileName = "before_output.xlsx";
private static String afterOutputFileName = "after_output.xlsx";
static {
InputStream is = null;
try {
prop = new Properties();
is = ClassLoader.class.getResourceAsStream("/application.properties");
prop.load(is);
url = getPropertyValue("spring.datasource.url");
user = getPropertyValue("spring.datasource.username");
pass = getPropertyValue("spring.datasource.password");
jdbcDriver = getPropertyValue("spring.datasource.driverClassName");
host = getPropertyValue("server.address");
dbPort = getPropertyValue("database.port");
dbServiceName = getPropertyValue("pdb.service.name");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@BeforeEach
void init(TestInfo testInfo) {
this.testInfo = testInfo;
Path evidenceDirPath = Paths.get(EVIDENCE_DIR, this.testInfo.getDisplayName());
if (Files.notExists(evidenceDirPath)) {
try {
Files.createDirectories(evidenceDirPath);
} catch (IOException e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
}
}
EVIDENCE_DIR = evidenceDirPath.toString();
}
@BeforeEach
void backup () {
Process process = null;
try {
conn = DriverManager.getConnection(url, user, pass);
// ScriptUtils.executeSqlScript(conn, new ClassPathResource("/db/backup_db.sql"));
String sqlFilePath = ClassLoader.class.getResource("/db/backup_db.sql").getFile();
File file = new File(sqlFilePath);
StringBuilder sb = new StringBuilder();
sb.append("sqlplus ")
.append(user)
.append("/")
.append(pass)
.append("@")
.append(host)
.append(":")
.append(dbPort)
.append("/")
.append(dbServiceName)
.append(" ")
.append(" @")
.append(file.getAbsolutePath());
process = Runtime.getRuntime().exec(sb.toString());
int exitVal = process.waitFor();
if (exitVal == 0) {
System.out.println("正常終了");
}
} catch (ScriptException | SQLException | IOException | InterruptedException e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
}
}
@Test
void test() {
try {
// チェックしたいテーブル名のリスト
List<String> tableNameList = Arrays.asList(
"ADDRESS_DETAIL"
,"USERS"
);
// AmbiguousTableNameException対策
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_SCHEMA, user );
// DBUnitでデータベース接続
JdbcDatabaseTester jdbcDatabaseTester = new JdbcDatabaseTester(jdbcDriver, url, user, pass);
QueryDataSet dataSet = new QueryDataSet(jdbcDatabaseTester.getConnection());
setUpDataSet(dataSet, tableNameList);
// 処理実施前のテーブル
writeExcelFromDataSet(dataSet, Paths.get(EVIDENCE_DIR, beforeOutputFileName).toString());
// TODO: 何らかの処理実施
dataSet = new QueryDataSet(jdbcDatabaseTester.getConnection());
setUpDataSet(dataSet, tableNameList);
// 処理実施後のテーブル
writeExcelFromDataSet(dataSet, Paths.get(EVIDENCE_DIR, afterOutputFileName).toString());
} catch (Exception e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
}
}
public static String getPropertyValue(String key){
return prop.getProperty(key);
}
/**
* DBのテーブルのレコードを取得する
* @param dataSet DBのテーブルのデータ
* @param tableNameList テーブル名のリスト
* @return DBのテーブルのデータ
*/
private static QueryDataSet setUpDataSet(QueryDataSet dataSet, List<String> tableNameList) {
// 取得するレコード数の上限
// 取得するレコード数が多過ぎると、Excelの書き込みでOutOfMemoryErrorが起こる
// 実行するJVMの引数でメモリー増やせばOutOfMemoryError回避できるが、メモリー増やすにも限界はあるので、要注意
Integer rownum = 100;
try {
conn = DriverManager.getConnection(url, user, pass);
PreparedStatement preparedStatement = null;
StringBuilder sbSelectSql = new StringBuilder();
StringBuilder sbPrimaryKeySql = new StringBuilder();
StringBuilder sbPrimaryKey = new StringBuilder();
for (String tableName: tableNameList) {
// テーブルの主キーを取得するSQL
sbPrimaryKeySql.append("SELECT ")
.append("column_name ")
.append("FROM ")
.append("all_cons_columns ")
.append("WHERE ")
.append("constraint_name = (")
.append("SELECT ")
.append("constraint_name ")
.append("FROM ")
.append("user_constraints ")
.append("WHERE ")
.append("UPPER(table_name) = UPPER('")
.append(tableName)
.append("') ")
.append("AND ")
.append("CONSTRAINT_TYPE = 'P'")
.append(")");
preparedStatement = conn.prepareStatement(sbPrimaryKeySql.toString());
ResultSet resultSet = preparedStatement.executeQuery();
int index = 0;
while (resultSet.next()) {
if (index != 0) {
sbPrimaryKey.append(", ");
}
sbPrimaryKey.append(String.valueOf(resultSet.getObject(1)))
.append(" DESC"); // 降順
index++;
}
// テーブルからレコード取得するSQL
sbSelectSql.append("SELECT ")
.append("* ")
.append("FROM (")
.append("SELECT ")
.append("* ")
.append("FROM ")
.append(tableName)
.append(" ORDER BY ")
.append(sbPrimaryKey.toString())
.append(") WHERE ")
.append("rownum <= ")
.append(rownum);
dataSet.addTable(tableName, sbSelectSql.toString());
sbSelectSql.setLength(0);
sbPrimaryKeySql.setLength(0);
sbPrimaryKey.setLength(0);
}
} catch (SQLException | AmbiguousTableNameException e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
} finally {
//try {
// if (Objects.nonNull(conn)) {
// conn.close();
// }
//} catch (SQLException e) {
// // TODO 自動生成された catch ブロック
// e.printStackTrace();
//}
}
return dataSet;
}
/**
* DBのテーブルのレコードをExcelに書き込み
* @param dataSet DBのテーブルのレコード
* @param outputFilePath Excel出力先
*/
private static void writeExcelFromDataSet(QueryDataSet dataSet, String outputFilePath) {
try {
XlsDataSetWriter writer = new XlsDataSetWriter() {
@Override
public Workbook createWorkbook() {
return new XSSFWorkbook();
}
};
// Excelに書き込み
writer.write(dataSet, new FileOutputStream(outputFilePath));
} catch (DataSetException | IOException e) {
// TODO 自動生成された catch ブロック
e.printStackTrace();
}
}
}
で、実行すると、

SQLファイルが実行されてるのを確認。

DBUnitでデータベースの選択したテーブルのレコードがLibreOfficeのCalc(Microsoft Excelの代替として使ってます)に出力されてます。
SQLファイルの実行については、Spring Frameworkの対応がイケてないので、標準のJavaのAPIで頑張る感じになるんかね...
毎度モヤモヤ感が半端ない...
今回はこのへんで。