Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GETP-324 feat: 프로젝트 테이블 인덱스 설계를 위한 배치 프로그램 작성 #181

Merged
merged 5 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions get-p-batch/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
dependencies {
implementation(project(":get-p-domain"))
implementation(project(":get-p-persistence"))
implementation(testFixtures(project(":get-p-domain")))
testImplementation(testFixtures(project(':get-p-domain')))

// Spring Data
implementation 'org.springframework.boot:spring-boot-starter-data-jpa:3.3.5'

// Flyway
implementation 'org.flywaydb:flyway-core:9.16.3'
implementation 'org.flywaydb:flyway-mysql:9.16.3'

// JDBC MySQL 드라이버
runtimeOnly 'com.mysql:mysql-connector-j:9.0.0'
}

bootJar {
enabled = true
}

jar {
enabled = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package es.princip.getp.batch;

public class BatchInsertionException extends RuntimeException {

public BatchInsertionException(final String message) {
super(message);
}

public BatchInsertionException(final String message, final Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package es.princip.getp.batch;

import es.princip.getp.batch.project.commission.BatchDeleteProjectService;
import es.princip.getp.batch.project.commission.ParallelBatchInsertProjectService;
import es.princip.getp.batch.project.apply.BatchDeleteProjectApplicationService;
import es.princip.getp.batch.project.apply.BatchInsertProjectApplicationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GetpBatchApplication implements CommandLineRunner {

@Autowired private BatchDeleteProjectApplicationService batchDeleteProjectApplicationService;
@Autowired private BatchDeleteProjectService batchDeleteProjectService;

@Autowired private BatchInsertProjectApplicationService batchInsertProjectApplicationService;
@Autowired private ParallelBatchInsertProjectService batchInsertProjectService;

public static void main(String[] args) {
SpringApplication.run(GetpBatchApplication.class, args);
}

private static final int PROJECT_SIZE = 100_000;
@Override
public void run(final String... args) {
batchDeleteProjectApplicationService.delete();
batchDeleteProjectService.delete();

batchInsertProjectService.insert(PROJECT_SIZE);
batchInsertProjectApplicationService.insert(PROJECT_SIZE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package es.princip.getp.batch;

import java.util.concurrent.atomic.AtomicLong;

public class UniqueLongGenerator {
private static final AtomicLong counter = new AtomicLong();

public static long generateUniqueLong() {
return counter.incrementAndGet();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package es.princip.getp.batch.config;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Slf4j
@Aspect
@Component
public class ExecutionTimer {

@Pointcut("@annotation(es.princip.getp.batch.config.ExtendsWithExecutionTimer)")
private void timer() {}

@Around("timer()")
public Object AssumeExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
final long start = System.currentTimeMillis();
try {
return joinPoint.proceed();
} finally {
final long finish = System.currentTimeMillis();
final long executionTime = finish - start;
final String signature = joinPoint.getSignature().toShortString();
log.info("execution time of {}: {}ms", signature, executionTime);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package es.princip.getp.batch.config;

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

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtendsWithExecutionTimer {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package es.princip.getp.batch.parallel;

import es.princip.getp.batch.BatchInsertionException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
@Service
public class ParallelBatchInsertService {

@Getter
private final int numThreads = Runtime.getRuntime().availableProcessors();

public void insert(final int size, final ParallelBatchInserter batchInserter) {
final ExecutorService executorService = Executors.newFixedThreadPool(numThreads);
final List<CompletableFuture<Void>> futures = new ArrayList<>();
final int batchSize = size / numThreads;
for (int i = 0; i < numThreads; i++) {
final int start = i * batchSize + 1;
final int end = (i == numThreads - 1) ? size : start + batchSize - 1;
final CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
final String threadName = Thread.currentThread().getName();
log.info("Thread {} is inserting projects from {} to {}", threadName, start, end);
try {
batchInserter.insert(start, end);
} catch (final Exception exception) {
throw new BatchInsertionException(
String.format(
"Thread %s encountered an error during batch insert for range %d to %d: ",
threadName,
start,
end
),
exception
);
}
}, executorService);
futures.add(future);
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
executorService.shutdown();
log.info("All threads completed. Executor service shutdown.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package es.princip.getp.batch.parallel;

@FunctionalInterface
public interface ParallelBatchInserter {
void insert(int start, int end);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package es.princip.getp.batch.project.apply;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class BatchDeleteProjectApplicationService {

private final JdbcTemplate jdbcTemplate;

public void delete() {
jdbcTemplate.execute("delete from team_project_application_teammate");
jdbcTemplate.execute("delete from team_project_application");
jdbcTemplate.execute("delete from individual_project_application");
jdbcTemplate.execute("delete from project_application_attachment_file");
jdbcTemplate.execute("delete from project_application");
log.info("Table \"team_project_application_teammate\" is dropped");
log.info("Table \"team_project_application\" is dropped");
log.info("Table \"individual_project_application\" is dropped");
log.info("Table \"project_application_attachment_file\" is dropped");
log.info("Table \"project_application\" is dropped");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package es.princip.getp.batch.project.apply;

import es.princip.getp.domain.project.apply.model.IndividualProjectApplication;
import es.princip.getp.domain.project.apply.model.ProjectApplication;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

@Service
@RequiredArgsConstructor
class BatchInsertIndividualProjectApplicationJdbcService {

private final JdbcTemplate jdbcTemplate;
private static final String sql =
"""
insert into individual_project_application (
project_application_id
) values (?);
""";

public void batchUpdate(final List<ProjectApplication> applications) {
final List<IndividualProjectApplication> individuals = applications.stream()
.filter(IndividualProjectApplication.class::isInstance)
.map(IndividualProjectApplication.class::cast)
.toList();

jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(final PreparedStatement ps, final int i) throws SQLException {
final IndividualProjectApplication application = individuals.get(i);
ps.setLong(1, application.getId().getValue());
}

@Override
public int getBatchSize() {
return individuals.size();
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package es.princip.getp.batch.project.apply;

import es.princip.getp.domain.common.model.AttachmentFile;
import es.princip.getp.domain.project.apply.model.ProjectApplication;
import es.princip.getp.domain.project.apply.model.ProjectApplicationId;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

@Service
@RequiredArgsConstructor
class BatchInsertProjectApplicationAttachmentFileJdbcService {

private record ProjectApplicationIdAttachmentFile(
ProjectApplicationId applicationId,
AttachmentFile attachmentFile
) {
}

private final JdbcTemplate jdbcTemplate;
private static final String sql =
"""
insert into project_application_attachment_file(
project_application_id,
attachment_files
) values (?, ?);
""";

public void batchUpdate(final List<ProjectApplication> applications) {
final List<ProjectApplicationIdAttachmentFile> attachmentFiles = applications.stream()
.flatMap(application -> application.getAttachmentFiles().stream()
.map(attachmentFile -> new ProjectApplicationIdAttachmentFile(
application.getId(),
attachmentFile
)))
.toList();

jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(final PreparedStatement ps, final int i) throws SQLException {
final ProjectApplicationId applicationId = attachmentFiles.get(i).applicationId();
final AttachmentFile attachmentFile = attachmentFiles.get(i).attachmentFile();
ps.setLong(1, applicationId.getValue());
ps.setString(2, attachmentFile.getUrl().getValue());
}

@Override
public int getBatchSize() {
return attachmentFiles.size();
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package es.princip.getp.batch.project.apply;

import es.princip.getp.domain.project.apply.model.IndividualProjectApplication;
import es.princip.getp.domain.project.apply.model.ProjectApplication;
import es.princip.getp.domain.project.apply.model.TeamProjectApplication;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

@Service
@RequiredArgsConstructor
class BatchInsertProjectApplicationJdbcService {

private final BatchInsertProjectApplicationAttachmentFileJdbcService attachmentFileJdbcService;
private final BatchInsertTeamProjectApplicationJdbcService teamJdbcService;
private final BatchInsertIndividualProjectApplicationJdbcService individualJdbcService;
private final JdbcTemplate jdbcTemplate;
private static final String sql =
"""
insert into project_application (
project_application_id,
expected_end_date,
expected_start_date,
description,
status,
people_id,
project_id,
dtype
) values (?, ?, ?, ?, ?, ?, ?, ?);
""";

public void batchUpdate(final List<ProjectApplication> applications) {
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(final PreparedStatement ps, final int i) throws SQLException {
final ProjectApplication application = applications.get(i);
final String dtype = (application instanceof TeamProjectApplication) ?
TeamProjectApplication.TYPE : IndividualProjectApplication.TYPE;
ps.setLong(1, application.getId().getValue());
ps.setDate(2, Date.valueOf(application.getExpectedDuration().getEndDate()));
ps.setDate(3, Date.valueOf(application.getExpectedDuration().getStartDate()));
ps.setString(4, application.getDescription());
ps.setString(5, application.getStatus().toString());
ps.setLong(6, application.getApplicantId().getValue());
ps.setLong(7, application.getProjectId().getValue());
ps.setString(8,dtype);
}

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

attachmentFileJdbcService.batchUpdate(applications);
individualJdbcService.batchUpdate(applications);
teamJdbcService.batchUpdate(applications);
}
}
Loading