Skip to content

Commit

Permalink
feat(dbrecovery): dbrecovery properties bean
Browse files Browse the repository at this point in the history
  • Loading branch information
gmoon92 committed Jul 26, 2024
1 parent 85d05fe commit 2347aa9
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 107 deletions.
5 changes: 5 additions & 0 deletions testing/dbrecovery/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
<artifactId>java-core</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.gmoon.dbrecovery.global;

import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationPropertiesScan(basePackages = "com.gmoon")
public class PropertiesConfig {
}
7 changes: 1 addition & 6 deletions testing/dbrecovery/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ service:
db-schema: dbrecovery
dbrecovery:
schema: ${service.db-schema}
backup-schema: ${service.dbrecovery.schema}_back
recovery-schema: ${service.dbrecovery.schema}_recovery

spring:
datasource:
Expand All @@ -11,11 +11,6 @@ spring:
# url: jdbc:mysql://localhost:3306/${service.db-schema}?createDatabaseIfNotExist=true&serverTimezone=UTC&useLegacyDatetimeCode=false&characterEncoding=UTF-8
username: root
password: 111111
hikari:
pool-name: gmoon-pool
maximum-pool-size: 200
idle-timeout: 20000
keepalive-time: 20000

jpa:
open-in-view: false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package com.gmoon.dbrecovery.global.recovery;

import com.gmoon.dbrecovery.global.recovery.datasource.DataSourceProxy;
import com.gmoon.dbrecovery.global.recovery.properties.RecoveryDatabaseProperties;
import com.gmoon.dbrecovery.global.recovery.vo.TableMetaData;
import com.gmoon.javacore.util.CollectionUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
import org.springframework.boot.autoconfigure.sql.init.SqlDataSourceScriptDatabaseInitializer;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
Expand All @@ -21,40 +19,14 @@
import java.util.Set;
import java.util.stream.Collectors;

/**
* <pre>
* 빈 순서 정의
* @DependsOn(value = {"dataSourceScriptDatabaseInitializer", "entityManagerFactory"})
* </pre>
*
* @link https://discourse.hibernate.org/t/get-table-name-in-hibernate-6-2/8601
* @see SqlDataSourceScriptDatabaseInitializer
* @see JpaBaseConfiguration
*/
@Slf4j
//@DependsOn(value = { "dataSourceScriptDatabaseInitializer", "entityManagerFactory" })
@Component
@RequiredArgsConstructor
public class DataRecoveryHelper {

private final DataSource dataSource;
private final RecoveryDatabaseInitialization recoveryDatabase;

@Value("${service.db-schema}")
private String schema;

private String getBackupSchema() {
return schema + "_test";
}

private String obtainBackupTableName(String tableName) {
String backupSchema = getBackupSchema();
return backupSchema + "." + tableName;
}

private String obtainOriginTableName(String tableName) {
return schema + "." + tableName;
}
private final RecoveryDatabaseProperties properties;
private final DataSource dataSource;

private void executeQuery(String queryString) {
try (Connection connection = dataSource.getConnection();
Expand All @@ -79,7 +51,7 @@ public void recovery() {
}

private Set<String> obtainRecoveryTables() {
Set<String> rollbackTables = new HashSet<>();
Set<String> recoveryTables = new HashSet<>();
Map<Class<? extends Statement>, Set<String>> modifiedTables = DataSourceProxy.modifiedTables;
Set<String> result = modifiedTables.values()
.stream()
Expand All @@ -90,11 +62,11 @@ private Set<String> obtainRecoveryTables() {
// todo refactoring Inject into delete table after db initialization.
if (CollectionUtils.isNotEmpty(deleteTables)) {
Set<String> deleteTablesRecursively = getDeleteTablesRecursively(deleteTables);
rollbackTables.addAll(deleteTablesRecursively);
recoveryTables.addAll(deleteTablesRecursively);
}

rollbackTables.addAll(result);
return rollbackTables;
recoveryTables.addAll(result);
return recoveryTables;
}

public Set<String> getDeleteTablesRecursively(Set<String> deleteTables) {
Expand All @@ -111,15 +83,15 @@ public Set<String> getDeleteTablesRecursively(Set<String> deleteTables) {
}

private void truncateTable(String tableName) {
String originTable = obtainOriginTableName(tableName);
String originTable = properties.schema + "." + tableName;
log.debug("[TRUNCATE] START {}", originTable);
executeQuery("TRUNCATE TABLE " + originTable);
log.debug("[TRUNCATE] DONE {}", originTable);
}

private void recoveryTable(String tableName) {
String originTable = obtainOriginTableName(tableName);
String backupTable = obtainBackupTableName(tableName);
String originTable = properties.schema + "." + tableName;
String backupTable = properties.recoverySchema + "." + tableName;
log.debug("[RECOVERY] START {}", originTable);
executeQuery(String.format("INSERT INTO %s SELECT * FROM %s", originTable, backupTable));
log.debug("[RECOVERY] DONE {}", originTable);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,99 +1,79 @@
package com.gmoon.dbrecovery.global.recovery;

import com.gmoon.dbrecovery.global.recovery.properties.RecoveryDatabaseProperties;
import com.gmoon.dbrecovery.global.recovery.vo.Table;
import com.gmoon.dbrecovery.global.recovery.vo.TableMetaData;
import jakarta.persistence.EntityManager;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
import org.springframework.boot.autoconfigure.sql.init.SqlDataSourceScriptDatabaseInitializer;
import org.springframework.boot.sql.init.dependency.DependsOnDatabaseInitialization;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
* <pre>
* 빈 순서 정의
* @DependsOn(value = {"dataSourceScriptDatabaseInitializer", "entityManagerFactory"})
*
* DB 생성 이후 초기화 하기 위해, @DependsOnDatabaseInitialization 사용
* DB 초기화 이후 빈 주입을 위해, @DependsOnDatabaseInitialization 사용
* @DependsOn 는 type safe 하지 않음.
* </pre>
*
* @link https://discourse.hibernate.org/t/get-table-name-in-hibernate-6-2/8601
* @see SqlDataSourceScriptDatabaseInitializer
* @see JpaBaseConfiguration
*/
@Slf4j
// @DependsOn(value = {"dataSourceScriptDatabaseInitializer", "entityManagerFactory"})
@DependsOnDatabaseInitialization
@Component
@RequiredArgsConstructor
public class RecoveryDatabaseInitialization implements InitializingBean, DisposableBean {
public class RecoveryDatabaseInitialization implements InitializingBean {

private final EntityManager entityManager;
private final DataSource dataSource;
private final RecoveryDatabaseProperties properties;

@Getter
private TableMetaData metadata;

@Value("${service.dbrecovery.schema}")
private String schema;

@Value("${service.dbrecovery.backup-schema}")
private String backupSchema;

@Override
public void afterPropertiesSet() throws Exception {
log.debug("===========Initializing recovery database===============");
String recoverySchema = properties.recoverySchema;
log.debug("===========Initializing recovery {} database===============", recoverySchema);
metadata = obtainTableMetaData();
createTargetSchema(metadata);
log.debug("===========Initializing recovery database===============");
}

@Override
public void destroy() throws Exception {
log.debug("===========Destroying recovery database===============");
executeQuery("DROP SCHEMA IF EXISTS " + backupSchema);
log.debug("===========Destroying recovery database===============");
}

public Connection getConnection() throws SQLException {
return dataSource.getConnection();
createRecoverySchema(metadata);
log.debug("===========Initializing recovery {} database===============", recoverySchema);
}

private TableMetaData obtainTableMetaData() {
try (Connection connection = dataSource.getConnection()) {
log.debug("[Start] table metadata info.");
String queryString = "SELECT kcu.TABLE_NAME AS table_name, " +
" kcu.COLUMN_NAME AS table_pk_column_name, " +
" kcu.REFERENCED_TABLE_NAME AS ref_table_name, " +
" kcu.REFERENCED_COLUMN_NAME AS ref_table_pk_column_name, " +
" CASE WHEN rc.DELETE_RULE = 'CASCADE' " +
" THEN 1 " +
" ELSE 0 " +
" END AS on_delete " +
"FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu " +
" LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc " +
" ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME " +
" AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA " +
"WHERE kcu.TABLE_SCHEMA = ? ";
String queryString =
"SELECT kcu.TABLE_NAME AS table_name, " +
" kcu.COLUMN_NAME AS table_pk_column_name, " +
" kcu.REFERENCED_TABLE_NAME AS ref_table_name, " +
" kcu.REFERENCED_COLUMN_NAME AS ref_table_pk_column_name, " +
" CASE WHEN rc.DELETE_RULE = 'CASCADE' " +
" THEN 1 " +
" ELSE 0 " +
" END AS on_delete " +
"FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu " +
" LEFT JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc " +
" ON kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME " +
" AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA " +
"WHERE kcu.TABLE_SCHEMA = ? ";

PreparedStatement preparedStatement = connection.prepareStatement(queryString);
preparedStatement.setString(1, schema);
preparedStatement.execute();
try (Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement(queryString)) {
log.debug("[Start] table metadata info.");
statement.setString(1, properties.schema);
statement.execute();

ResultSet resultSet = preparedStatement.getResultSet();
ResultSet resultSet = statement.getResultSet();
List<Table> tables = new ArrayList<>();
while (resultSet.next()) {
String tableName = resultSet.getString("table_name");
Expand Down Expand Up @@ -121,30 +101,27 @@ private TableMetaData obtainTableMetaData() {

private void executeQuery(String queryString) {
try (Connection connection = dataSource.getConnection();
CallableStatement callableStatement = connection.prepareCall(queryString)) {
PreparedStatement statement = connection.prepareStatement(queryString)) {

log.debug("[START] execute query: {}", queryString);
int result = callableStatement.executeUpdate();
log.debug("[END{}] execute query: {}", result, queryString);
int result = statement.executeUpdate();
log.debug("[END] execute query {}: {}", result, queryString);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

private void createTargetSchema(TableMetaData metadata) {
String sourceSchema = schema;
String targetSchema = backupSchema;
private void createRecoverySchema(TableMetaData metadata) {
executeQuery("SET FOREIGN_KEY_CHECKS = 0");
executeQuery(String.format("CREATE DATABASE IF NOT EXISTS %s", targetSchema));
executeQuery(String.format("CREATE DATABASE IF NOT EXISTS %s", properties.recoverySchema));
for (Table table : metadata.getValue().keySet()) {
String tableName = table.getTableName();
String sourceTable = sourceSchema + "." + tableName;
String targetTable = targetSchema + "." + tableName;
log.debug("Copy table {} to {}", sourceTable, targetTable);
String sourceTable = properties.schema + "." + tableName;
String targetTable = properties.recoverySchema + "." + tableName;
executeQuery(String.format("DROP TABLE IF EXISTS %s", targetTable));
executeQuery(String.format("CREATE TABLE %s AS SELECT * FROM %s", targetTable, sourceTable));
log.debug("Copy table {} to {}", sourceTable, targetTable);
}
executeQuery("SET FOREIGN_KEY_CHECKS = 1");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,17 @@
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@RequiredArgsConstructor
public class DataSourceProxy implements DataSource, RedefineDataSource {

public static ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<>();
public static final Map<Class<? extends Statement>, Set<String>> modifiedTables = new HashMap<>();
public static final Map<Class<? extends Statement>, Set<String>> modifiedTables = new ConcurrentHashMap<>();

@Delegate(excludes = RedefineDataSource.class)
private final DataSource dataSource;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.gmoon.dbrecovery.global.recovery.properties;

import lombok.RequiredArgsConstructor;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "service.dbrecovery")
//@ConstructorBinding
@RequiredArgsConstructor
@ToString
public class RecoveryDatabaseProperties {

public final String schema;
public final String recoverySchema;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.gmoon.dbrecovery.global.recovery.properties;

import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.assertj.core.api.Assertions.assertThat;

@Slf4j
@SpringBootTest
class RecoveryDatabasePropertiesTest {

@Autowired
private RecoveryDatabaseProperties properties;

@Test
void test() {
log.info("properties: {}", properties);
assertThat(properties.schema).isNotBlank();
assertThat(properties.recoverySchema).isNotBlank();
}
}

0 comments on commit 2347aa9

Please sign in to comment.