From d4eea3b5272f36fb8f08de80f1e3758595c771a3 Mon Sep 17 00:00:00 2001 From: miiiinju1 <111269144+miiiinju1@users.noreply.github.com> Date: Fri, 29 Mar 2024 16:27:41 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=EB=AC=B8=EC=A0=9C=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8=20Job=20=EA=B0=9C=EB=B0=9C=20(#2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :sparkles: 문제 업데이트 Job 개발 * :sparkles: DataSource 분리 및 ECR 배포코드 작성 --- .github/workflows/cicd.yml | 61 + .gitignore | 2 + Dockerfile | 4 + build.gradle | 2 + .../MorandiBatchApplication.java | 2 + .../batch/config/DataSourceConfig.java | 64 +- .../batch/config/NewProblemJobConfig.java | 8 +- ... => UpdateBaekjoonProblemBatchConfig.java} | 23 +- .../batch/dto/response/BojAlgorithmTag.java | 11 - .../batch/dto/response/BojProblemInfo.java | 21 - .../dto/response/BojProblemInfoList.java | 12 - .../batch/dto/response/TagDTO.java | 12 - .../batch/dto/response/TitleDTO.java | 10 - .../PagingCollectionsItemReader.java | 4 +- .../batch/processor/NewProblemProcessor.java | 2 +- .../batch/processor/ProblemProcessor.java | 47 - .../GetProblemUpdateInfoProcessor.java | 55 + .../problemupdate/ProblemUpdateProcessor.java | 70 ++ .../ProblemUpdateProcessorConfig.java | 26 + .../dto/ProblemInfoProcessorDTO.java | 22 + .../dto/response/BojAlgorithmTag.java | 17 + .../dto/response/BojProblemInfo.java | 29 + .../dto/response/BojProblemInfoList.java | 17 + .../problemupdate/dto/response/TagDTO.java | 18 + .../problemupdate/dto/response/TitleDTO.java | 20 + .../batch/reader/dto/ProblemDTO.java | 3 +- .../ProblemUpdateReaderConfig.java} | 13 +- .../batch/writer/ProblemUpdateWriter.java | 26 + .../domain/algorithm/AlgorithmRepository.java | 6 + .../morandi_batch/domain/problem/Problem.java | 45 +- .../domain/problem/ProblemRepository.java | 9 + .../problemalgorithm/ProblemAlgorithm.java | 7 + src/main/resources/Algorithms.json | 1032 +++++++++++++++++ src/main/resources/application.yml | 40 - 34 files changed, 1516 insertions(+), 224 deletions(-) create mode 100644 .github/workflows/cicd.yml create mode 100644 Dockerfile rename src/main/java/kr/co/morandi_batch/batch/config/{BaekjoonProblemBatchConfig.java => UpdateBaekjoonProblemBatchConfig.java} (63%) delete mode 100644 src/main/java/kr/co/morandi_batch/batch/dto/response/BojAlgorithmTag.java delete mode 100644 src/main/java/kr/co/morandi_batch/batch/dto/response/BojProblemInfo.java delete mode 100644 src/main/java/kr/co/morandi_batch/batch/dto/response/BojProblemInfoList.java delete mode 100644 src/main/java/kr/co/morandi_batch/batch/dto/response/TagDTO.java delete mode 100644 src/main/java/kr/co/morandi_batch/batch/dto/response/TitleDTO.java delete mode 100644 src/main/java/kr/co/morandi_batch/batch/processor/ProblemProcessor.java create mode 100644 src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/GetProblemUpdateInfoProcessor.java create mode 100644 src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/ProblemUpdateProcessor.java create mode 100644 src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/ProblemUpdateProcessorConfig.java create mode 100644 src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/ProblemInfoProcessorDTO.java create mode 100644 src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/BojAlgorithmTag.java create mode 100644 src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/BojProblemInfo.java create mode 100644 src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/BojProblemInfoList.java create mode 100644 src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/TagDTO.java create mode 100644 src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/TitleDTO.java rename src/main/java/kr/co/morandi_batch/batch/reader/{ProblemDBBulkReadConfig.java => problemupdate/ProblemUpdateReaderConfig.java} (75%) create mode 100644 src/main/java/kr/co/morandi_batch/batch/writer/ProblemUpdateWriter.java create mode 100644 src/main/java/kr/co/morandi_batch/domain/algorithm/AlgorithmRepository.java create mode 100644 src/main/resources/Algorithms.json delete mode 100644 src/main/resources/application.yml diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml new file mode 100644 index 0000000..ae0223d --- /dev/null +++ b/.github/workflows/cicd.yml @@ -0,0 +1,61 @@ +name: cicd-dev + +on: + push: + branches: [ "main" ] + +env: + AWS_REGION: ap-northeast-2 + ECR_REPOSITORY: new_morandi_batch + ECR_REGISTRY: ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.ap-northeast-2.amazonaws.com/new_morandi_backend + GITHUB_SHA: ${{ github.sha }} + +permissions: + contents: read + +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + # Gradle 빌드를 추가합니다. + - name: Set up JDK 17 + uses: actions/setup-java@v2 + with: + distribution: 'adopt' + java-version: '17' + + - name: Cleanup application.yml + run: rm -f src/main/resources/application.yml + + # GitHub Secret에서 application-prod.yml 내용을 불러와 파일로 저장 + - name: Create application.yml from GitHub Secret + run: | + mkdir -p src/main/resources + echo "${{ secrets.DEV_APPLICATION_YML }}" > src/main/resources/application.yml + + + - name: Build with Gradle + env: + ORG_GRADLE_OPTS: "-Duser.timezone=Asia/Seoul" + run: ./gradlew clean bootJar -x test + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.ECR_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.ECR_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + mask-aws-account-id: true + + - name: Login to Private ECR + run: aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin ${{ env.ECR_REGISTRY }} + + - name: Build Docker Image + run: docker build -t ${{ env.ECR_REGISTRY }}:${{ github.sha }} . + + - name: Push Docker Image to ECR + run: docker push ${{ env.ECR_REGISTRY }}:${{ github.sha }} diff --git a/.gitignore b/.gitignore index c2065bc..9f95022 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +/Users/miiiinju/NewMorandi-Batch/morandi-batch/src/main/resources/application.yml + HELP.md .gradle build/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f255b20 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:17-oracle +WORKDIR /app +COPY build/libs/*.jar app.jar +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/build.gradle b/build.gradle index 852691e..c94d049 100644 --- a/build.gradle +++ b/build.gradle @@ -24,6 +24,8 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-batch' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + //configuration processor + implementation 'org.springframework.boot:spring-boot-configuration-processor' // lombok compileOnly 'org.projectlombok:lombok' diff --git a/src/main/java/kr/co/morandi_batch/MorandiBatchApplication.java b/src/main/java/kr/co/morandi_batch/MorandiBatchApplication.java index 571696f..caad5d4 100644 --- a/src/main/java/kr/co/morandi_batch/MorandiBatchApplication.java +++ b/src/main/java/kr/co/morandi_batch/MorandiBatchApplication.java @@ -2,6 +2,8 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner; +import org.springframework.context.annotation.Bean; @SpringBootApplication public class MorandiBatchApplication { diff --git a/src/main/java/kr/co/morandi_batch/batch/config/DataSourceConfig.java b/src/main/java/kr/co/morandi_batch/batch/config/DataSourceConfig.java index 529e8ce..850d0f7 100644 --- a/src/main/java/kr/co/morandi_batch/batch/config/DataSourceConfig.java +++ b/src/main/java/kr/co/morandi_batch/batch/config/DataSourceConfig.java @@ -1,55 +1,41 @@ package kr.co.morandi_batch.batch.config; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.batch.core.configuration.support.DefaultBatchConfiguration; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.batch.BatchDataSource; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; -import org.springframework.jdbc.datasource.DataSourceTransactionManager; -import org.springframework.orm.jpa.JpaTransactionManager; -import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; -import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; -import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; +import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; import javax.sql.DataSource; @Configuration -@EnableJpaRepositories( - basePackages = "kr.co.morandi_batch.domain", - entityManagerFactoryRef = "businessEntityManager", - transactionManagerRef = "businessTransactionManager" -) public class DataSourceConfig { - @Bean - @Primary - public LocalContainerEntityManagerFactoryBean businessEntityManager() { - LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); - - em.setDataSource(businessDataSource()); - em.setPackagesToScan("kr.co.morandi_batch.domain"); - em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); - - return em; - } - - @Primary - @Bean(name = "businessDataSource") - @ConfigurationProperties(prefix = "spring.datasource.business") - public DataSource businessDataSource() { - return DataSourceBuilder.create().build(); + @Bean(name = "batchDataSource") + @BatchDataSource + public DataSource H2Datasource () { + return new EmbeddedDatabaseBuilder() + .addScript("classpath:org/springframework/batch/core/schema-drop-h2.sql") + .addScript("classpath:org/springframework/batch/core/schema-h2.sql") + .setType(EmbeddedDatabaseType.H2) + .build(); } - - @Primary - @Bean(name = "businessTransactionManager") - public PlatformTransactionManager businessTransactionManager() { - JpaTransactionManager transactionManager = new JpaTransactionManager(); - - transactionManager.setEntityManagerFactory(businessEntityManager().getObject()); - - return transactionManager; + @Bean + @Primary // 이 DataSource를 애플리케이션의 주 데이터 소스로 지정 + public DataSource dataSource(@Value("${spring.datasource.driver-class-name}") String driverClassName, + @Value("${spring.datasource.url}") String url, + @Value("${spring.datasource.username}") String user, + @Value("${spring.datasource.password}") String pass) { + return DataSourceBuilder.create() + .driverClassName(driverClassName) + .url(url) + .username(user) + .password(pass) + .build(); } - } diff --git a/src/main/java/kr/co/morandi_batch/batch/config/NewProblemJobConfig.java b/src/main/java/kr/co/morandi_batch/batch/config/NewProblemJobConfig.java index e05bb17..3e83bba 100644 --- a/src/main/java/kr/co/morandi_batch/batch/config/NewProblemJobConfig.java +++ b/src/main/java/kr/co/morandi_batch/batch/config/NewProblemJobConfig.java @@ -2,10 +2,7 @@ import kr.co.morandi_batch.batch.processor.NewProblemProcessor; import kr.co.morandi_batch.batch.reader.NewProblemPagingReader; -import kr.co.morandi_batch.batch.reader.NewProblemReader; import kr.co.morandi_batch.batch.reader.dto.ProblemDTO; -import kr.co.morandi_batch.batch.reader.dto.ProblemsResponse; -import kr.co.morandi_batch.batch.writer.NewProblemWriter; import kr.co.morandi_batch.domain.problem.Problem; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Job; @@ -18,8 +15,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; -import java.util.List; - @Configuration @RequiredArgsConstructor public class NewProblemJobConfig { @@ -29,6 +24,7 @@ public class NewProblemJobConfig { private final JdbcBatchItemWriter newProblemWriter; + @Bean Job newBaekjoonProblemJob(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new JobBuilder("newBaekjoonProblemJob", jobRepository) @@ -41,7 +37,7 @@ Job newBaekjoonProblemJob(JobRepository jobRepository, PlatformTransactionManage Step newBaekjoonProblemStep(JobRepository jobRepository, PlatformTransactionManager transactionManger) { return new StepBuilder("newBaekjoonProblemStep", jobRepository) .chunk(50, transactionManger) - .allowStartIfComplete(true) +// .allowStartIfComplete(true) .reader(newProblemPagingReader) .processor(newProblemProcessor) .writer(newProblemWriter) diff --git a/src/main/java/kr/co/morandi_batch/batch/config/BaekjoonProblemBatchConfig.java b/src/main/java/kr/co/morandi_batch/batch/config/UpdateBaekjoonProblemBatchConfig.java similarity index 63% rename from src/main/java/kr/co/morandi_batch/batch/config/BaekjoonProblemBatchConfig.java rename to src/main/java/kr/co/morandi_batch/batch/config/UpdateBaekjoonProblemBatchConfig.java index 27d9fc2..a638118 100644 --- a/src/main/java/kr/co/morandi_batch/batch/config/BaekjoonProblemBatchConfig.java +++ b/src/main/java/kr/co/morandi_batch/batch/config/UpdateBaekjoonProblemBatchConfig.java @@ -1,15 +1,15 @@ package kr.co.morandi_batch.batch.config; import kr.co.morandi_batch.batch.pagingCollectionsItemReader.PagingCollectionsItemReader; -import kr.co.morandi_batch.batch.processor.ProblemProcessor; +import kr.co.morandi_batch.batch.writer.ProblemUpdateWriter; import kr.co.morandi_batch.domain.problem.Problem; import lombok.RequiredArgsConstructor; import org.springframework.batch.core.Job; import org.springframework.batch.core.Step; -import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; +import org.springframework.batch.item.support.CompositeItemProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; @@ -19,30 +19,29 @@ @Configuration @RequiredArgsConstructor -public class BaekjoonProblemBatchConfig { +public class UpdateBaekjoonProblemBatchConfig { private final PagingCollectionsItemReader> problemPagingCollectionsItemReader; - private final ProblemProcessor problemProcessor; + private final CompositeItemProcessor, List> compositeItemProcessor; + private final ProblemUpdateWriter problemUpdateWriter; @Bean Job updateBaekjoonProblemJob(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new JobBuilder("updateBaekjoonProblem", jobRepository) - .flow(getProblemStep(jobRepository, transactionManager)) + .flow(updateProblemStep(jobRepository, transactionManager)) .end() .build(); } @Bean - Step getProblemStep(JobRepository jobRepository, PlatformTransactionManager transactionManger) { - return new StepBuilder("getProblemStep", jobRepository) + Step updateProblemStep(JobRepository jobRepository, PlatformTransactionManager transactionManger) { + return new StepBuilder("updateProblemStep", jobRepository) ., List>chunk(10, transactionManger) - .allowStartIfComplete(true) +// .allowStartIfComplete(true) .reader(problemPagingCollectionsItemReader) - .processor(problemProcessor) - .writer(list -> { - System.out.println("dd : "); - }) + .processor(compositeItemProcessor) + .writer(problemUpdateWriter) .build(); } diff --git a/src/main/java/kr/co/morandi_batch/batch/dto/response/BojAlgorithmTag.java b/src/main/java/kr/co/morandi_batch/batch/dto/response/BojAlgorithmTag.java deleted file mode 100644 index 93e4ad1..0000000 --- a/src/main/java/kr/co/morandi_batch/batch/dto/response/BojAlgorithmTag.java +++ /dev/null @@ -1,11 +0,0 @@ -package kr.co.morandi_batch.batch.dto.response; - -import lombok.NoArgsConstructor; - -@NoArgsConstructor -public class BojAlgorithmTag { - String key; - String id; - int bojTagId; - -} diff --git a/src/main/java/kr/co/morandi_batch/batch/dto/response/BojProblemInfo.java b/src/main/java/kr/co/morandi_batch/batch/dto/response/BojProblemInfo.java deleted file mode 100644 index 429dd9e..0000000 --- a/src/main/java/kr/co/morandi_batch/batch/dto/response/BojProblemInfo.java +++ /dev/null @@ -1,21 +0,0 @@ -package kr.co.morandi_batch.batch.dto.response; - -import java.util.List; - -public class BojProblemInfo { - private int problemId; - private String titleKo; - private List titles; - private boolean isSolvable; - private boolean isPartial; - private int acceptedUserCount; - private int level; - private int votedUserCount; - private boolean sprout; - private boolean givesNoRating; - private boolean isLevelLocked; - private double averageTries; - private boolean official; - private List tags; - -} diff --git a/src/main/java/kr/co/morandi_batch/batch/dto/response/BojProblemInfoList.java b/src/main/java/kr/co/morandi_batch/batch/dto/response/BojProblemInfoList.java deleted file mode 100644 index 219f569..0000000 --- a/src/main/java/kr/co/morandi_batch/batch/dto/response/BojProblemInfoList.java +++ /dev/null @@ -1,12 +0,0 @@ -package kr.co.morandi_batch.batch.dto.response; - -import lombok.NoArgsConstructor; - -import java.util.List; - -@NoArgsConstructor -public class BojProblemInfoList { - int count; - List items; - -} diff --git a/src/main/java/kr/co/morandi_batch/batch/dto/response/TagDTO.java b/src/main/java/kr/co/morandi_batch/batch/dto/response/TagDTO.java deleted file mode 100644 index 55a45fe..0000000 --- a/src/main/java/kr/co/morandi_batch/batch/dto/response/TagDTO.java +++ /dev/null @@ -1,12 +0,0 @@ -package kr.co.morandi_batch.batch.dto.response; - -import lombok.NoArgsConstructor; - -@NoArgsConstructor -public class TagDTO { - private String key; - private boolean isMeta; - private int bojTagId; - private int problemCount; - -} diff --git a/src/main/java/kr/co/morandi_batch/batch/dto/response/TitleDTO.java b/src/main/java/kr/co/morandi_batch/batch/dto/response/TitleDTO.java deleted file mode 100644 index 1a5a3f6..0000000 --- a/src/main/java/kr/co/morandi_batch/batch/dto/response/TitleDTO.java +++ /dev/null @@ -1,10 +0,0 @@ -package kr.co.morandi_batch.batch.dto.response; - -public class TitleDTO { - private String language; - private String languageDisplayName; - private String title; - private boolean isOriginal; - - -} diff --git a/src/main/java/kr/co/morandi_batch/batch/pagingCollectionsItemReader/PagingCollectionsItemReader.java b/src/main/java/kr/co/morandi_batch/batch/pagingCollectionsItemReader/PagingCollectionsItemReader.java index 7168970..ca56d3f 100644 --- a/src/main/java/kr/co/morandi_batch/batch/pagingCollectionsItemReader/PagingCollectionsItemReader.java +++ b/src/main/java/kr/co/morandi_batch/batch/pagingCollectionsItemReader/PagingCollectionsItemReader.java @@ -157,7 +157,9 @@ protected void distributeResultsByChunk(List resultList) { } protected void doClose() throws Exception { - this.entityManager.close(); + if(this.entityManager != null) { + this.entityManager.close(); + } super.doClose(); } } diff --git a/src/main/java/kr/co/morandi_batch/batch/processor/NewProblemProcessor.java b/src/main/java/kr/co/morandi_batch/batch/processor/NewProblemProcessor.java index f1a03b9..e762073 100644 --- a/src/main/java/kr/co/morandi_batch/batch/processor/NewProblemProcessor.java +++ b/src/main/java/kr/co/morandi_batch/batch/processor/NewProblemProcessor.java @@ -12,7 +12,7 @@ public class NewProblemProcessor implements ItemProcessor { @Override public Problem process(ProblemDTO problemDTO) throws Exception { - log.info("Processing problem with ID: {}", problemDTO.getProblemId()); + log.debug("Processing problem with ID: {}", problemDTO.getProblemId()); return problemDTO.toEntity(); } } diff --git a/src/main/java/kr/co/morandi_batch/batch/processor/ProblemProcessor.java b/src/main/java/kr/co/morandi_batch/batch/processor/ProblemProcessor.java deleted file mode 100644 index 93e2cff..0000000 --- a/src/main/java/kr/co/morandi_batch/batch/processor/ProblemProcessor.java +++ /dev/null @@ -1,47 +0,0 @@ -package kr.co.morandi_batch.batch.processor; - -import kr.co.morandi_batch.batch.dto.response.BojProblemInfoList; -import kr.co.morandi_batch.domain.problem.Problem; -import org.springframework.batch.item.ItemProcessor; -import org.springframework.stereotype.Component; -import org.springframework.web.reactive.function.client.WebClient; - -import java.util.List; - -@Component -public class ProblemProcessor implements ItemProcessor, List> { - private final String PREFIX = "https://solved.ac/api/v3/search/problem"; - - @Override - public List process(List item) throws Exception { - String apiURI = getAPIURI(item); -// getProblemInfo(apiURI); - - System.out.println("apiURI = " + apiURI); - return null; - } - - public String getAPIURI(List problems) { - StringBuffer sb = new StringBuffer().append(PREFIX).append("?query="); - problems.forEach(problem -> { - sb.append("id:"); - sb.append(problem.getBaekjoonProblemId()); - sb.append("|"); - }); - - return sb.toString(); - } - private List getProblemInfo(String apiURI) { - WebClient webClient = WebClient.builder() - .baseUrl(apiURI) - .build(); - String result = webClient.get() - .retrieve() - .bodyToMono(String.class) - .block(); - return parseProblemInfo(result); - } - private List parseProblemInfo(String result) { - return null; - } -} diff --git a/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/GetProblemUpdateInfoProcessor.java b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/GetProblemUpdateInfoProcessor.java new file mode 100644 index 0000000..bf2525f --- /dev/null +++ b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/GetProblemUpdateInfoProcessor.java @@ -0,0 +1,55 @@ +package kr.co.morandi_batch.batch.processor.problemupdate; + +import kr.co.morandi_batch.batch.processor.problemupdate.dto.ProblemInfoProcessorDTO; +import kr.co.morandi_batch.batch.processor.problemupdate.dto.response.BojProblemInfoList; +import kr.co.morandi_batch.domain.problem.Problem; +import org.springframework.batch.item.ItemProcessor; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; + +import java.util.List; + +@Component +public class GetProblemUpdateInfoProcessor implements ItemProcessor, ProblemInfoProcessorDTO> { + private final WebClient webClient; + public GetProblemUpdateInfoProcessor(WebClient.Builder webClientBuilder) { + String prefix = "https://solved.ac/api/v3/search/problem"; + this.webClient = webClientBuilder.baseUrl(prefix).build(); + } + + @Override + public ProblemInfoProcessorDTO process(List item) throws Exception { +// if(item.get(0).getBaekjoonProblemId()>2500) { +// return null; +// } + BojProblemInfoList problemInfo = getProblemUpdated(item); + + return ProblemInfoProcessorDTO.builder() + .problems(item) + .bojProblemInfoList(problemInfo) + .build(); + + } + + private BojProblemInfoList getProblemUpdated(List problems) { + Mono bojProblemInfoListMono = webClient.get() + .uri(uriBuilder -> uriBuilder + .queryParam("query", getAPIURI(problems)) + .build()) + .retrieve() + .bodyToMono(BojProblemInfoList.class); + + return bojProblemInfoListMono.block(); + } + private String getAPIURI(List problems) { + StringBuffer sb = new StringBuffer(); + problems.forEach(problem -> { + sb.append("id:"); + sb.append(problem.getBaekjoonProblemId()); + sb.append("|"); + }); + + return sb.toString(); + } +} diff --git a/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/ProblemUpdateProcessor.java b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/ProblemUpdateProcessor.java new file mode 100644 index 0000000..df8b4f5 --- /dev/null +++ b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/ProblemUpdateProcessor.java @@ -0,0 +1,70 @@ +package kr.co.morandi_batch.batch.processor.problemupdate; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.PostConstruct; +import kr.co.morandi_batch.batch.processor.problemupdate.dto.ProblemInfoProcessorDTO; +import kr.co.morandi_batch.batch.processor.problemupdate.dto.response.BojProblemInfo; +import kr.co.morandi_batch.domain.algorithm.Algorithm; +import kr.co.morandi_batch.domain.algorithm.AlgorithmRepository; +import kr.co.morandi_batch.domain.problem.Problem; +import kr.co.morandi_batch.domain.problem.ProblemRepository; +import kr.co.morandi_batch.domain.problem.ProblemTier; +import kr.co.morandi_batch.domain.problemalgorithm.ProblemAlgorithm; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.item.ItemProcessor; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +@Slf4j +@RequiredArgsConstructor +public class ProblemUpdateProcessor implements ItemProcessor> { + private Map algorithmMap; + private final ObjectMapper objectMapper; + private final AlgorithmRepository algorithmRepository; + + @PostConstruct + public void init() throws IOException { + List initialAlgorithms = algorithmRepository.findAll(); + + this.algorithmMap = initialAlgorithms.stream() + .collect(Collectors.toMap(Algorithm::getBojTagId, algorithm -> algorithm)); + } + @Override + public List process(ProblemInfoProcessorDTO item) throws Exception { + //Solved AC에서 가져온 정보를 가지고 BaekjoonProblem ID를 Key로 하고, BojProblemInfo를 Value로 하는 Map을 만들어서 반환 + final Map updatedProblemInfo = item.getBojProblemInfoList().getItems().stream() + .collect(Collectors.toMap(BojProblemInfo::getProblemId, problem -> problem)); + + final List problems = item.getProblems(); + + final List problemIds = problems.stream().map(Problem::getProblemId).toList(); + + //TODO N+1 문제 해결하기 +// log.info("start"); +// for(Problem problem : problems) { +// problem.getProblemAlgorithms().size(); +// } +// log.info("end"); + + return problems.stream().peek(problem -> { + final Long problemId = problem.getBaekjoonProblemId(); + + BojProblemInfo bojProblemInfo = updatedProblemInfo.get(problemId); + + problem.updateProblemTier(ProblemTier.of(bojProblemInfo.getLevel())); + problem.updateSolvedCount(bojProblemInfo.getAcceptedUserCount()); + problem.updateProblemAlgorithm(bojProblemInfo.getTags().stream() + .map(tag -> algorithmMap.get(tag.getBojTagId())) + .toList()); + + }).toList(); + } + + +} diff --git a/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/ProblemUpdateProcessorConfig.java b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/ProblemUpdateProcessorConfig.java new file mode 100644 index 0000000..1126cad --- /dev/null +++ b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/ProblemUpdateProcessorConfig.java @@ -0,0 +1,26 @@ +package kr.co.morandi_batch.batch.processor.problemupdate; + +import kr.co.morandi_batch.batch.processor.problemupdate.ProblemUpdateProcessor; +import kr.co.morandi_batch.domain.problem.Problem; +import lombok.RequiredArgsConstructor; +import org.springframework.batch.item.support.CompositeItemProcessor; +import org.springframework.batch.item.support.builder.CompositeItemProcessorBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Configuration +@RequiredArgsConstructor +public class ProblemUpdateProcessorConfig { + + private final GetProblemUpdateInfoProcessor getProblemUpdateInfoProcessor; + private final ProblemUpdateProcessor problemUpdateProcessor; + @Bean + CompositeItemProcessor, List > compositeItemProcessor() { + return new CompositeItemProcessorBuilder, List>() + .delegates(List.of(getProblemUpdateInfoProcessor, problemUpdateProcessor)) + .build(); + } + +} diff --git a/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/ProblemInfoProcessorDTO.java b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/ProblemInfoProcessorDTO.java new file mode 100644 index 0000000..df06b64 --- /dev/null +++ b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/ProblemInfoProcessorDTO.java @@ -0,0 +1,22 @@ +package kr.co.morandi_batch.batch.processor.problemupdate.dto; + +import kr.co.morandi_batch.batch.processor.problemupdate.dto.response.BojProblemInfoList; +import kr.co.morandi_batch.domain.problem.Problem; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ProblemInfoProcessorDTO { + List problems; + BojProblemInfoList bojProblemInfoList; + @Builder + private ProblemInfoProcessorDTO(List problems, BojProblemInfoList bojProblemInfoList) { + this.problems = problems; + this.bojProblemInfoList = bojProblemInfoList; + } +} diff --git a/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/BojAlgorithmTag.java b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/BojAlgorithmTag.java new file mode 100644 index 0000000..8d8d8ee --- /dev/null +++ b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/BojAlgorithmTag.java @@ -0,0 +1,17 @@ +package kr.co.morandi_batch.batch.processor.problemupdate.dto.response; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@ToString + +public class BojAlgorithmTag { + String key; + String id; + int bojTagId; + +} diff --git a/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/BojProblemInfo.java b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/BojProblemInfo.java new file mode 100644 index 0000000..cb77b21 --- /dev/null +++ b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/BojProblemInfo.java @@ -0,0 +1,29 @@ +package kr.co.morandi_batch.batch.processor.problemupdate.dto.response; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@ToString +public class BojProblemInfo { + private long problemId; + private String titleKo; +// private List titles; +// private boolean isSolvable; +// private boolean isPartial; + private long acceptedUserCount; + private int level; +// private int votedUserCount; +// private boolean sprout; +// private boolean givesNoRating; +// private boolean isLevelLocked; +// private double averageTries; +// private boolean official; + private List tags; + +} diff --git a/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/BojProblemInfoList.java b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/BojProblemInfoList.java new file mode 100644 index 0000000..6d1ec7f --- /dev/null +++ b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/BojProblemInfoList.java @@ -0,0 +1,17 @@ +package kr.co.morandi_batch.batch.processor.problemupdate.dto.response; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@ToString +public class BojProblemInfoList { + int count; + List items; + +} diff --git a/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/TagDTO.java b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/TagDTO.java new file mode 100644 index 0000000..376e7b0 --- /dev/null +++ b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/TagDTO.java @@ -0,0 +1,18 @@ +package kr.co.morandi_batch.batch.processor.problemupdate.dto.response; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@ToString + +public class TagDTO { + private String key; +// private boolean isMeta; + private int bojTagId; +// private int problemCount; + +} diff --git a/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/TitleDTO.java b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/TitleDTO.java new file mode 100644 index 0000000..ca3ee5f --- /dev/null +++ b/src/main/java/kr/co/morandi_batch/batch/processor/problemupdate/dto/response/TitleDTO.java @@ -0,0 +1,20 @@ +package kr.co.morandi_batch.batch.processor.problemupdate.dto.response; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@ToString + + +public class TitleDTO { + private String language; + private String languageDisplayName; + private String title; + private boolean isOriginal; + + +} diff --git a/src/main/java/kr/co/morandi_batch/batch/reader/dto/ProblemDTO.java b/src/main/java/kr/co/morandi_batch/batch/reader/dto/ProblemDTO.java index 072c05a..0d422c0 100644 --- a/src/main/java/kr/co/morandi_batch/batch/reader/dto/ProblemDTO.java +++ b/src/main/java/kr/co/morandi_batch/batch/reader/dto/ProblemDTO.java @@ -4,6 +4,7 @@ import kr.co.morandi_batch.domain.problem.ProblemTier; import lombok.*; +import static kr.co.morandi_batch.domain.problem.ProblemStatus.ACTIVE; import static kr.co.morandi_batch.domain.problem.ProblemStatus.INIT; @Getter @@ -27,7 +28,7 @@ public Problem toEntity() { return Problem.builder() .baekjoonProblemId(problemId) // .title(titleKo) - .problemStatus(INIT) + .problemStatus(ACTIVE) .problemTier(ProblemTier.of(level)) .solvedCount(Long.valueOf(acceptedUserCount)) .build(); diff --git a/src/main/java/kr/co/morandi_batch/batch/reader/ProblemDBBulkReadConfig.java b/src/main/java/kr/co/morandi_batch/batch/reader/problemupdate/ProblemUpdateReaderConfig.java similarity index 75% rename from src/main/java/kr/co/morandi_batch/batch/reader/ProblemDBBulkReadConfig.java rename to src/main/java/kr/co/morandi_batch/batch/reader/problemupdate/ProblemUpdateReaderConfig.java index e98dd3c..3794acb 100644 --- a/src/main/java/kr/co/morandi_batch/batch/reader/ProblemDBBulkReadConfig.java +++ b/src/main/java/kr/co/morandi_batch/batch/reader/problemupdate/ProblemUpdateReaderConfig.java @@ -1,4 +1,4 @@ -package kr.co.morandi_batch.batch.reader; +package kr.co.morandi_batch.batch.reader.problemupdate; import jakarta.persistence.EntityManagerFactory; import kr.co.morandi_batch.batch.pagingCollectionsItemReader.PagingCollectionsItemReader; @@ -8,24 +8,25 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Configuration @RequiredArgsConstructor -public class ProblemDBBulkReadConfig { +public class ProblemUpdateReaderConfig { private final EntityManagerFactory emf; - @Bean PagingCollectionsItemReader> problemPagingCollectionsItemReader() { - return new PagingCollectionsItemReaderBuilder>() + return new PagingCollectionsItemReaderBuilder>() .entityManagerFactory(emf) .collectionClass(ArrayList.class) - .chunkAndCollectionSize(10,50) + .chunkAndCollectionSize(10, 50) .queryString("select p from Problem p ") .name("problemPagingCollectionsItemReader") .build(); } - } diff --git a/src/main/java/kr/co/morandi_batch/batch/writer/ProblemUpdateWriter.java b/src/main/java/kr/co/morandi_batch/batch/writer/ProblemUpdateWriter.java new file mode 100644 index 0000000..c425cc4 --- /dev/null +++ b/src/main/java/kr/co/morandi_batch/batch/writer/ProblemUpdateWriter.java @@ -0,0 +1,26 @@ +package kr.co.morandi_batch.batch.writer; + +import jakarta.persistence.EntityManager; +import kr.co.morandi_batch.domain.problem.Problem; +import lombok.RequiredArgsConstructor; +import org.springframework.batch.item.Chunk; +import org.springframework.batch.item.ItemWriter; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class ProblemUpdateWriter implements ItemWriter> { + private final EntityManager em; + @Override + public void write(Chunk> chunk) throws Exception { + final List list = chunk.getItems().stream() + .flatMap(List::stream) + .peek(em::merge) + .toList(); + + em.flush(); + em.clear(); + } +} diff --git a/src/main/java/kr/co/morandi_batch/domain/algorithm/AlgorithmRepository.java b/src/main/java/kr/co/morandi_batch/domain/algorithm/AlgorithmRepository.java new file mode 100644 index 0000000..0846cba --- /dev/null +++ b/src/main/java/kr/co/morandi_batch/domain/algorithm/AlgorithmRepository.java @@ -0,0 +1,6 @@ +package kr.co.morandi_batch.domain.algorithm; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AlgorithmRepository extends JpaRepository { +} diff --git a/src/main/java/kr/co/morandi_batch/domain/problem/Problem.java b/src/main/java/kr/co/morandi_batch/domain/problem/Problem.java index d1d3a8a..6fee9ce 100644 --- a/src/main/java/kr/co/morandi_batch/domain/problem/Problem.java +++ b/src/main/java/kr/co/morandi_batch/domain/problem/Problem.java @@ -1,37 +1,45 @@ package kr.co.morandi_batch.domain.problem; import jakarta.persistence.*; +import kr.co.morandi_batch.domain.algorithm.Algorithm; import kr.co.morandi_batch.domain.common.BaseEntity; +import kr.co.morandi_batch.domain.problemalgorithm.ProblemAlgorithm; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import static kr.co.morandi_batch.domain.problem.ProblemStatus.INIT; @Entity @Getter +@Slf4j @NoArgsConstructor(access = AccessLevel.PROTECTED) public class Problem extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "problem_id") private Long problemId; - @Column(name = "baekjoon_problem_id") private Long baekjoonProblemId; @Enumerated(EnumType.STRING) - @Column(name = "problem_tier") private ProblemTier problemTier; @Enumerated(EnumType.STRING) - @Column(name = "problem_status") private ProblemStatus problemStatus; - @Column(name = "solved_count") private Long solvedCount; + @OneToMany(mappedBy = "problem", cascade = CascadeType.ALL) + private List problemAlgorithms = new ArrayList<>(); + @Builder private Problem(Long baekjoonProblemId, ProblemTier problemTier, ProblemStatus problemStatus, Long solvedCount) { this.baekjoonProblemId = baekjoonProblemId; @@ -39,7 +47,34 @@ private Problem(Long baekjoonProblemId, ProblemTier problemTier, ProblemStatus p this.problemStatus = problemStatus; this.solvedCount = solvedCount; } + public void updateSolvedCount(Long solvedCount) { + this.solvedCount = solvedCount; + } + public void updateProblemTier(ProblemTier problemTier) { + this.problemTier = problemTier; + } + public void updateProblemAlgorithm(List algorithms) { + final Set updatedAlgorithms = algorithms.stream() + .map(Algorithm::getBojTagId) + .collect(Collectors.toSet()); + + // 새로 업데이트된 알고리즘에 포함되지 않는 알고리즘은 삭제 + getProblemAlgorithms().removeIf(pa -> !updatedAlgorithms.contains(pa.getAlgorithm().getBojTagId())); + + // 현재 알고리즘 Tag 목록 + final Set nowAlgorithms = getProblemAlgorithms().stream() + .map(ProblemAlgorithm::getAlgorithm) + .map(Algorithm::getBojTagId) + .collect(Collectors.toSet()); + + // 새로 업데이트된 알고리즘 Tag 중 기존에 없던 Tag의 알고리즘은 추가 + algorithms.stream() + .filter(pa -> !nowAlgorithms.contains(pa.getBojTagId())) + .map(pa -> ProblemAlgorithm.create(pa, this)) + .forEach(pa -> this.problemAlgorithms.add(pa)); + + } public void activate() { this.problemStatus = ProblemStatus.ACTIVE; } diff --git a/src/main/java/kr/co/morandi_batch/domain/problem/ProblemRepository.java b/src/main/java/kr/co/morandi_batch/domain/problem/ProblemRepository.java index 659c371..47400ac 100644 --- a/src/main/java/kr/co/morandi_batch/domain/problem/ProblemRepository.java +++ b/src/main/java/kr/co/morandi_batch/domain/problem/ProblemRepository.java @@ -1,7 +1,11 @@ package kr.co.morandi_batch.domain.problem; +import kr.co.morandi_batch.domain.problemalgorithm.ProblemAlgorithm; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; public interface ProblemRepository extends JpaRepository { @Query(""" @@ -11,4 +15,9 @@ public interface ProblemRepository extends JpaRepository { LIMIT 1 """) Long findLastBaekjoonProblemId(); + + List findAllByProblemIdIn(List problemIds); + @Query("SELECT pa FROM ProblemAlgorithm pa WHERE pa.problem.problemId IN :problemIds") + List findAllByProblemAlgorithmsIdIn(@Param("problemIds") List problemIds); + } diff --git a/src/main/java/kr/co/morandi_batch/domain/problemalgorithm/ProblemAlgorithm.java b/src/main/java/kr/co/morandi_batch/domain/problemalgorithm/ProblemAlgorithm.java index 47d83a2..10ea232 100644 --- a/src/main/java/kr/co/morandi_batch/domain/problemalgorithm/ProblemAlgorithm.java +++ b/src/main/java/kr/co/morandi_batch/domain/problemalgorithm/ProblemAlgorithm.java @@ -24,6 +24,13 @@ public class ProblemAlgorithm extends BaseEntity { @ManyToOne(fetch = FetchType.LAZY) private Problem problem; + public static ProblemAlgorithm create(Algorithm algorithm, Problem problem) { + return ProblemAlgorithm.builder() + .algorithm(algorithm) + .problem(problem) + .build(); + } + @Builder private ProblemAlgorithm(Algorithm algorithm, Problem problem) { this.algorithm = algorithm; diff --git a/src/main/resources/Algorithms.json b/src/main/resources/Algorithms.json new file mode 100644 index 0000000..bd9d102 --- /dev/null +++ b/src/main/resources/Algorithms.json @@ -0,0 +1,1032 @@ +[ + { + "bojTagId": 1, + "algorithmKey": "2_sat", + "algorithmName": "2-sat" + }, + { + "bojTagId": 2, + "algorithmKey": "aho_corasick", + "algorithmName": "아호-코라식" + }, + { + "bojTagId": 3, + "algorithmKey": "polygon_area", + "algorithmName": "다각형의 넓이" + }, + { + "bojTagId": 4, + "algorithmKey": "articulation", + "algorithmName": "단절점과 단절선" + }, + { + "bojTagId": 5, + "algorithmKey": "backtracking", + "algorithmName": "백트래킹" + }, + { + "bojTagId": 6, + "algorithmKey": "combinatorics", + "algorithmName": "조합론" + }, + { + "bojTagId": 7, + "algorithmKey": "graphs", + "algorithmName": "그래프 이론" + }, + { + "bojTagId": 8, + "algorithmKey": "hashing", + "algorithmName": "해싱" + }, + { + "bojTagId": 9, + "algorithmKey": "primality_test", + "algorithmName": "소수 판정" + }, + { + "bojTagId": 10, + "algorithmKey": "bellman_ford", + "algorithmName": "벨만–포드" + }, + { + "bojTagId": 11, + "algorithmKey": "graph_traversal", + "algorithmName": "그래프 탐색" + }, + { + "bojTagId": 12, + "algorithmKey": "binary_search", + "algorithmName": "이분 탐색" + }, + { + "bojTagId": 13, + "algorithmKey": "bipartite_matching", + "algorithmName": "이분 매칭" + }, + { + "bojTagId": 14, + "algorithmKey": "bitmask", + "algorithmName": "비트마스킹" + }, + { + "bojTagId": 15, + "algorithmKey": "general_matching", + "algorithmName": "일반적인 매칭" + }, + { + "bojTagId": 16, + "algorithmKey": "burnside", + "algorithmName": "번사이드 보조정리" + }, + { + "bojTagId": 18, + "algorithmKey": "centroid_decomposition", + "algorithmName": "센트로이드 분할" + }, + { + "bojTagId": 19, + "algorithmKey": "crt", + "algorithmName": "중국인의 나머지 정리" + }, + { + "bojTagId": 20, + "algorithmKey": "convex_hull", + "algorithmName": "볼록 껍질" + }, + { + "bojTagId": 21, + "algorithmKey": "delaunay", + "algorithmName": "델로네 삼각분할" + }, + { + "bojTagId": 22, + "algorithmKey": "dijkstra", + "algorithmName": "데이크스트라" + }, + { + "bojTagId": 23, + "algorithmKey": "directed_mst", + "algorithmName": "유향 최소 신장 트리" + }, + { + "bojTagId": 24, + "algorithmKey": "divide_and_conquer", + "algorithmName": "분할 정복" + }, + { + "bojTagId": 25, + "algorithmKey": "dp", + "algorithmName": "다이나믹 프로그래밍" + }, + { + "bojTagId": 26, + "algorithmKey": "euclidean", + "algorithmName": "유클리드 호제법" + }, + { + "bojTagId": 27, + "algorithmKey": "extended_euclidean", + "algorithmName": "확장 유클리드 호제법" + }, + { + "bojTagId": 28, + "algorithmKey": "fft", + "algorithmName": "고속 푸리에 변환" + }, + { + "bojTagId": 29, + "algorithmKey": "flt", + "algorithmName": "페르마의 소정리" + }, + { + "bojTagId": 31, + "algorithmKey": "floyd_warshall", + "algorithmName": "플로이드–워셜" + }, + { + "bojTagId": 32, + "algorithmKey": "gaussian_elimination", + "algorithmName": "가우스 소거법" + }, + { + "bojTagId": 33, + "algorithmKey": "greedy", + "algorithmName": "그리디 알고리즘" + }, + { + "bojTagId": 34, + "algorithmKey": "hall", + "algorithmName": "홀의 결혼 정리" + }, + { + "bojTagId": 35, + "algorithmKey": "hld", + "algorithmName": "Heavy-light 분할" + }, + { + "bojTagId": 36, + "algorithmKey": "hungarian", + "algorithmName": "헝가리안" + }, + { + "bojTagId": 38, + "algorithmKey": "inclusion_and_exclusion", + "algorithmName": "포함 배제의 원리" + }, + { + "bojTagId": 39, + "algorithmKey": "exponentiation_by_squaring", + "algorithmName": "분할 정복을 이용한 거듭제곱" + }, + { + "bojTagId": 40, + "algorithmKey": "kmp", + "algorithmName": "KMP" + }, + { + "bojTagId": 41, + "algorithmKey": "lca", + "algorithmName": "최소 공통 조상" + }, + { + "bojTagId": 42, + "algorithmKey": "line_intersection", + "algorithmName": "선분 교차 판정" + }, + { + "bojTagId": 43, + "algorithmKey": "lis", + "algorithmName": "가장 긴 증가하는 부분 수열: O(n log n)" + }, + { + "bojTagId": 44, + "algorithmKey": "manacher", + "algorithmName": "매내처" + }, + { + "bojTagId": 45, + "algorithmKey": "flow", + "algorithmName": "최대 유량" + }, + { + "bojTagId": 46, + "algorithmKey": "mitm", + "algorithmName": "중간에서 만나기" + }, + { + "bojTagId": 47, + "algorithmKey": "miller_rabin", + "algorithmName": "밀러–라빈 소수 판별법" + }, + { + "bojTagId": 48, + "algorithmKey": "mcmf", + "algorithmName": "최소 비용 최대 유량" + }, + { + "bojTagId": 49, + "algorithmKey": "mst", + "algorithmName": "최소 스패닝 트리" + }, + { + "bojTagId": 50, + "algorithmKey": "mo", + "algorithmName": "mo\\'s" + }, + { + "bojTagId": 51, + "algorithmKey": "mobius_inversion", + "algorithmName": "뫼비우스 반전 공식" + }, + { + "bojTagId": 52, + "algorithmKey": "offline_dynamic_connectivity", + "algorithmName": "오프라인 동적 연결성 판정" + }, + { + "bojTagId": 53, + "algorithmKey": "palindrome_tree", + "algorithmName": "회문 트리" + }, + { + "bojTagId": 54, + "algorithmKey": "pbs", + "algorithmName": "병렬 이분 탐색" + }, + { + "bojTagId": 55, + "algorithmKey": "pst", + "algorithmName": "퍼시스턴트 세그먼트 트리" + }, + { + "bojTagId": 56, + "algorithmKey": "point_in_convex_polygon", + "algorithmName": "볼록 다각형 내부의 점 판정" + }, + { + "bojTagId": 57, + "algorithmKey": "point_in_non_convex_polygon", + "algorithmName": "오목 다각형 내부의 점 판정" + }, + { + "bojTagId": 58, + "algorithmKey": "pollard_rho", + "algorithmName": "폴라드 로" + }, + { + "bojTagId": 59, + "algorithmKey": "priority_queue", + "algorithmName": "우선순위 큐" + }, + { + "bojTagId": 60, + "algorithmKey": "pythagoras", + "algorithmName": "피타고라스 정리" + }, + { + "bojTagId": 61, + "algorithmKey": "rabin_karp", + "algorithmName": "라빈–카프" + }, + { + "bojTagId": 62, + "algorithmKey": "recursion", + "algorithmName": "재귀" + }, + { + "bojTagId": 63, + "algorithmKey": "regex", + "algorithmName": "정규 표현식" + }, + { + "bojTagId": 64, + "algorithmKey": "rotating_calipers", + "algorithmName": "회전하는 캘리퍼스" + }, + { + "bojTagId": 65, + "algorithmKey": "segtree", + "algorithmName": "세그먼트 트리" + }, + { + "bojTagId": 66, + "algorithmKey": "lazyprop", + "algorithmName": "느리게 갱신되는 세그먼트 트리" + }, + { + "bojTagId": 67, + "algorithmKey": "sieve", + "algorithmName": "에라토스테네스의 체" + }, + { + "bojTagId": 68, + "algorithmKey": "sliding_window", + "algorithmName": "슬라이딩 윈도우" + }, + { + "bojTagId": 69, + "algorithmKey": "splay_tree", + "algorithmName": "스플레이 트리" + }, + { + "bojTagId": 70, + "algorithmKey": "sprague_grundy", + "algorithmName": "스프라그–그런디 정리" + }, + { + "bojTagId": 71, + "algorithmKey": "stack", + "algorithmName": "스택" + }, + { + "bojTagId": 72, + "algorithmKey": "queue", + "algorithmName": "큐" + }, + { + "bojTagId": 73, + "algorithmKey": "deque", + "algorithmName": "덱" + }, + { + "bojTagId": 74, + "algorithmKey": "tree_set", + "algorithmName": "트리를 사용한 집합과 맵" + }, + { + "bojTagId": 75, + "algorithmKey": "stoer_wagner", + "algorithmName": "스토어–바그너" + }, + { + "bojTagId": 76, + "algorithmKey": "scc", + "algorithmName": "강한 연결 요소" + }, + { + "bojTagId": 77, + "algorithmKey": "suffix_array", + "algorithmName": "접미사 배열과 LCP 배열" + }, + { + "bojTagId": 78, + "algorithmKey": "topological_sorting", + "algorithmName": "위상 정렬" + }, + { + "bojTagId": 79, + "algorithmKey": "trie", + "algorithmName": "트라이" + }, + { + "bojTagId": 80, + "algorithmKey": "two_pointer", + "algorithmName": "두 포인터" + }, + { + "bojTagId": 81, + "algorithmKey": "disjoint_set", + "algorithmName": "분리 집합" + }, + { + "bojTagId": 82, + "algorithmKey": "voronoi", + "algorithmName": "보로노이 다이어그램" + }, + { + "bojTagId": 83, + "algorithmKey": "z", + "algorithmName": "z" + }, + { + "bojTagId": 84, + "algorithmKey": "sparse_table", + "algorithmName": "희소 배열" + }, + { + "bojTagId": 87, + "algorithmKey": "dp_bitfield", + "algorithmName": "비트필드를 이용한 다이나믹 프로그래밍" + }, + { + "bojTagId": 89, + "algorithmKey": "cht", + "algorithmName": "볼록 껍질을 이용한 최적화" + }, + { + "bojTagId": 90, + "algorithmKey": "knuth", + "algorithmName": "크누스 최적화" + }, + { + "bojTagId": 91, + "algorithmKey": "divide_and_conquer_optimization", + "algorithmName": "분할 정복을 사용한 최적화" + }, + { + "bojTagId": 92, + "algorithmKey": "dp_tree", + "algorithmName": "트리에서의 다이나믹 프로그래밍" + }, + { + "bojTagId": 93, + "algorithmKey": "eulerian_path", + "algorithmName": "오일러 경로" + }, + { + "bojTagId": 94, + "algorithmKey": "rb_tree", + "algorithmName": "레드-블랙 트리" + }, + { + "bojTagId": 95, + "algorithmKey": "number_theory", + "algorithmName": "정수론" + }, + { + "bojTagId": 96, + "algorithmKey": "parsing", + "algorithmName": "파싱" + }, + { + "bojTagId": 97, + "algorithmKey": "sorting", + "algorithmName": "정렬" + }, + { + "bojTagId": 98, + "algorithmKey": "link_cut_tree", + "algorithmName": "링크/컷 트리" + }, + { + "bojTagId": 100, + "algorithmKey": "geometry", + "algorithmName": "기하학" + }, + { + "bojTagId": 101, + "algorithmKey": "ternary_search", + "algorithmName": "삼분 탐색" + }, + { + "bojTagId": 102, + "algorithmKey": "implementation", + "algorithmName": "구현" + }, + { + "bojTagId": 103, + "algorithmKey": "linear_programming", + "algorithmName": "선형 계획법" + }, + { + "bojTagId": 104, + "algorithmKey": "matroid", + "algorithmName": "매트로이드" + }, + { + "bojTagId": 105, + "algorithmKey": "top_tree", + "algorithmName": "탑 트리" + }, + { + "bojTagId": 106, + "algorithmKey": "sweeping", + "algorithmName": "스위핑" + }, + { + "bojTagId": 107, + "algorithmKey": "dp_connection_profile", + "algorithmName": "커넥션 프로파일을 이용한 다이나믹 프로그래밍" + }, + { + "bojTagId": 108, + "algorithmKey": "dp_deque", + "algorithmName": "덱을 이용한 다이나믹 프로그래밍" + }, + { + "bojTagId": 109, + "algorithmKey": "ad_hoc", + "algorithmName": "애드 혹" + }, + { + "bojTagId": 110, + "algorithmKey": "berlekamp_massey", + "algorithmName": "벌리캠프–매시" + }, + { + "bojTagId": 111, + "algorithmKey": "calculus", + "algorithmName": "미적분학" + }, + { + "bojTagId": 112, + "algorithmKey": "kitamasa", + "algorithmName": "키타마사" + }, + { + "bojTagId": 113, + "algorithmKey": "lucas", + "algorithmName": "뤼카 정리" + }, + { + "bojTagId": 114, + "algorithmKey": "bayes", + "algorithmName": "베이즈 정리" + }, + { + "bojTagId": 115, + "algorithmKey": "randomization", + "algorithmName": "무작위화" + }, + { + "bojTagId": 116, + "algorithmKey": "physics", + "algorithmName": "물리학" + }, + { + "bojTagId": 117, + "algorithmKey": "arbitrary_precision", + "algorithmName": "임의 정밀도 / 큰 수 연산" + }, + { + "bojTagId": 119, + "algorithmKey": "euler_characteristic", + "algorithmName": "오일러 지표 (χ=V-E+F)" + }, + { + "bojTagId": 120, + "algorithmKey": "trees", + "algorithmName": "트리" + }, + { + "bojTagId": 121, + "algorithmKey": "arithmetic", + "algorithmName": "사칙연산" + }, + { + "bojTagId": 122, + "algorithmKey": "numerical_analysis", + "algorithmName": "수치해석" + }, + { + "bojTagId": 123, + "algorithmKey": "offline_queries", + "algorithmName": "오프라인 쿼리" + }, + { + "bojTagId": 124, + "algorithmKey": "math", + "algorithmName": "수학" + }, + { + "bojTagId": 125, + "algorithmKey": "bruteforcing", + "algorithmName": "브루트포스 알고리즘" + }, + { + "bojTagId": 126, + "algorithmKey": "bfs", + "algorithmName": "너비 우선 탐색" + }, + { + "bojTagId": 127, + "algorithmKey": "dfs", + "algorithmName": "깊이 우선 탐색" + }, + { + "bojTagId": 128, + "algorithmKey": "constructive", + "algorithmName": "해 구성하기" + }, + { + "bojTagId": 129, + "algorithmKey": "bidirectional_search", + "algorithmName": "양방향 탐색" + }, + { + "bojTagId": 130, + "algorithmKey": "sqrt_decomposition", + "algorithmName": "제곱근 분할법" + }, + { + "bojTagId": 131, + "algorithmKey": "geometry_3d", + "algorithmName": "3차원 기하학" + }, + { + "bojTagId": 132, + "algorithmKey": "geometry_hyper", + "algorithmName": "4차원 이상의 기하학" + }, + { + "bojTagId": 134, + "algorithmKey": "alien", + "algorithmName": "Aliens 트릭" + }, + { + "bojTagId": 135, + "algorithmKey": "dominator_tree", + "algorithmName": "도미네이터 트리" + }, + { + "bojTagId": 136, + "algorithmKey": "hash_set", + "algorithmName": "해시를 사용한 집합과 맵" + }, + { + "bojTagId": 137, + "algorithmKey": "case_work", + "algorithmName": "많은 조건 분기" + }, + { + "bojTagId": 138, + "algorithmKey": "tsp", + "algorithmName": "외판원 순회 문제" + }, + { + "bojTagId": 139, + "algorithmKey": "prefix_sum", + "algorithmName": "누적 합" + }, + { + "bojTagId": 140, + "algorithmKey": "game_theory", + "algorithmName": "게임 이론" + }, + { + "bojTagId": 141, + "algorithmKey": "simulation", + "algorithmName": "시뮬레이션" + }, + { + "bojTagId": 142, + "algorithmKey": "heuristics", + "algorithmName": "휴리스틱" + }, + { + "bojTagId": 143, + "algorithmKey": "cactus", + "algorithmName": "선인장" + }, + { + "bojTagId": 144, + "algorithmKey": "linear_algebra", + "algorithmName": "선형대수학" + }, + { + "bojTagId": 145, + "algorithmKey": "tree_isomorphism", + "algorithmName": "트리 동형 사상" + }, + { + "bojTagId": 146, + "algorithmKey": "discrete_log", + "algorithmName": "이산 로그" + }, + { + "bojTagId": 147, + "algorithmKey": "discrete_sqrt", + "algorithmName": "이산 제곱근" + }, + { + "bojTagId": 148, + "algorithmKey": "knapsack", + "algorithmName": "배낭 문제" + }, + { + "bojTagId": 149, + "algorithmKey": "discrete_kth_root", + "algorithmName": "이산 k제곱근" + }, + { + "bojTagId": 150, + "algorithmKey": "euler_tour_technique", + "algorithmName": "오일러 경로 테크닉" + }, + { + "bojTagId": 151, + "algorithmKey": "euler_phi", + "algorithmName": "오일러 피 함수" + }, + { + "bojTagId": 152, + "algorithmKey": "bitset", + "algorithmName": "비트 집합" + }, + { + "bojTagId": 153, + "algorithmKey": "biconnected_component", + "algorithmName": "이중 연결 요소" + }, + { + "bojTagId": 154, + "algorithmKey": "linked_list", + "algorithmName": "연결 리스트" + }, + { + "bojTagId": 155, + "algorithmKey": "merge_sort_tree", + "algorithmName": "머지 소트 트리" + }, + { + "bojTagId": 157, + "algorithmKey": "slope_trick", + "algorithmName": "함수 개형을 이용한 최적화" + }, + { + "bojTagId": 158, + "algorithmKey": "string", + "algorithmName": "문자열" + }, + { + "bojTagId": 159, + "algorithmKey": "rope", + "algorithmName": "로프" + }, + { + "bojTagId": 160, + "algorithmKey": "majority_vote", + "algorithmName": "보이어–무어 다수결 투표" + }, + { + "bojTagId": 161, + "algorithmKey": "coordinate_compression", + "algorithmName": "값 / 좌표 압축" + }, + { + "bojTagId": 162, + "algorithmKey": "min_enclosing_circle", + "algorithmName": "최소 외접원" + }, + { + "bojTagId": 163, + "algorithmKey": "hirschberg", + "algorithmName": "히르쉬버그" + }, + { + "bojTagId": 164, + "algorithmKey": "modular_multiplicative_inverse", + "algorithmName": "모듈로 곱셈 역원" + }, + { + "bojTagId": 165, + "algorithmKey": "monotone_queue_optimization", + "algorithmName": "단조 큐를 이용한 최적화" + }, + { + "bojTagId": 166, + "algorithmKey": "multi_segtree", + "algorithmName": "다차원 세그먼트 트리" + }, + { + "bojTagId": 167, + "algorithmKey": "mfmc", + "algorithmName": "최대 유량 최소 컷 정리" + }, + { + "bojTagId": 168, + "algorithmKey": "planar_graph", + "algorithmName": "평면 그래프" + }, + { + "bojTagId": 169, + "algorithmKey": "smaller_to_larger", + "algorithmName": "작은 집합에서 큰 집합으로 합치는 테크닉" + }, + { + "bojTagId": 170, + "algorithmKey": "parametric_search", + "algorithmName": "매개 변수 탐색" + }, + { + "bojTagId": 171, + "algorithmKey": "permutation_cycle_decomposition", + "algorithmName": "순열 사이클 분할" + }, + { + "bojTagId": 172, + "algorithmKey": "precomputation", + "algorithmName": "런타임 전의 전처리" + }, + { + "bojTagId": 173, + "algorithmKey": "dancing_links", + "algorithmName": "춤추는 링크" + }, + { + "bojTagId": 174, + "algorithmKey": "knuth_x", + "algorithmName": "크누스 X" + }, + { + "bojTagId": 175, + "algorithmKey": "data_structures", + "algorithmName": "자료 구조" + }, + { + "bojTagId": 176, + "algorithmKey": "0_1_bfs", + "algorithmName": "0-1 너비 우선 탐색" + }, + { + "bojTagId": 177, + "algorithmKey": "probability", + "algorithmName": "확률론" + }, + { + "bojTagId": 178, + "algorithmKey": "statistics", + "algorithmName": "통계학" + }, + { + "bojTagId": 179, + "algorithmKey": "linearity_of_expectation", + "algorithmName": "기댓값의 선형성" + }, + { + "bojTagId": 180, + "algorithmKey": "duality", + "algorithmName": "쌍대성" + }, + { + "bojTagId": 181, + "algorithmKey": "dual_graph", + "algorithmName": "쌍대 그래프" + }, + { + "bojTagId": 182, + "algorithmKey": "suffix_tree", + "algorithmName": "접미사 트리" + }, + { + "bojTagId": 183, + "algorithmKey": "green", + "algorithmName": "그린 정리" + }, + { + "bojTagId": 184, + "algorithmKey": "simulated_annealing", + "algorithmName": "담금질 기법" + }, + { + "bojTagId": 185, + "algorithmKey": "differential_cryptanalysis", + "algorithmName": "차분 공격" + }, + { + "bojTagId": 186, + "algorithmKey": "a_star", + "algorithmName": "a*" + }, + { + "bojTagId": 187, + "algorithmKey": "pick", + "algorithmName": "픽의 정리" + }, + { + "bojTagId": 188, + "algorithmKey": "centroid", + "algorithmName": "센트로이드" + }, + { + "bojTagId": 189, + "algorithmKey": "pigeonhole_principle", + "algorithmName": "비둘기집 원리" + }, + { + "bojTagId": 190, + "algorithmKey": "half_plane_intersection", + "algorithmName": "반평면 교집합" + }, + { + "bojTagId": 191, + "algorithmKey": "circulation", + "algorithmName": "서큘레이션" + }, + { + "bojTagId": 192, + "algorithmKey": "stable_marriage", + "algorithmName": "안정 결혼 문제" + }, + { + "bojTagId": 193, + "algorithmKey": "tree_compression", + "algorithmName": "트리 압축" + }, + { + "bojTagId": 196, + "algorithmKey": "multipoint_evaluation", + "algorithmName": "다중 대입값 계산" + }, + { + "bojTagId": 197, + "algorithmKey": "bipartite_graph", + "algorithmName": "이분 그래프" + }, + { + "bojTagId": 198, + "algorithmKey": "generating_function", + "algorithmName": "생성 함수" + }, + { + "bojTagId": 199, + "algorithmKey": "utf8", + "algorithmName": "utf-8 입력 처리" + }, + { + "bojTagId": 200, + "algorithmKey": "degree_sequence", + "algorithmName": "차수열" + }, + { + "bojTagId": 201, + "algorithmKey": "chordal_graph", + "algorithmName": "현 그래프" + }, + { + "bojTagId": 202, + "algorithmKey": "geometric_boolean_operations", + "algorithmName": "도형에서의 불 연산" + }, + { + "bojTagId": 203, + "algorithmKey": "birthday", + "algorithmName": "생일 문제" + }, + { + "bojTagId": 204, + "algorithmKey": "tree_decomposition", + "algorithmName": "트리 분할" + }, + { + "bojTagId": 205, + "algorithmKey": "hackenbush", + "algorithmName": "하켄부시 게임" + }, + { + "bojTagId": 206, + "algorithmKey": "cartesian_tree", + "algorithmName": "데카르트 트리" + }, + { + "bojTagId": 207, + "algorithmKey": "dp_sum_over_subsets", + "algorithmName": "부분집합의 합 다이나믹 프로그래밍" + }, + { + "bojTagId": 208, + "algorithmKey": "gradient_descent", + "algorithmName": "경사 하강법" + }, + { + "bojTagId": 209, + "algorithmKey": "polynomial_interpolation", + "algorithmName": "다항식 보간법" + }, + { + "bojTagId": 210, + "algorithmKey": "flood_fill", + "algorithmName": "플러드 필" + }, + { + "bojTagId": 211, + "algorithmKey": "functional_graph", + "algorithmName": "함수형 그래프" + }, + { + "bojTagId": 212, + "algorithmKey": "lte", + "algorithmName": "지수승강 보조정리" + }, + { + "bojTagId": 213, + "algorithmKey": "dag", + "algorithmName": "방향 비순환 그래프" + }, + { + "bojTagId": 214, + "algorithmKey": "lgv", + "algorithmName": "린드스트롬–게셀–비엔노 보조정리" + }, + { + "bojTagId": 215, + "algorithmKey": "shortest_path", + "algorithmName": "최단 경로" + }, + { + "bojTagId": 216, + "algorithmKey": "deque_trick", + "algorithmName": "덱을 이용한 구간 최댓값 트릭" + }, + { + "bojTagId": 217, + "algorithmKey": "dp_digit", + "algorithmName": "자릿수를 이용한 다이나믹 프로그래밍" + }, + { + "bojTagId": 218, + "algorithmKey": "floor_sum", + "algorithmName": "유리 등차수열의 내림 합" + } +] \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index b2d0bd5..0000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,40 +0,0 @@ -spring: - main: - web-application-type: none - - datasource: - driver-class-name: org.h2.Driver - url: jdbc:h2:mem:~/new_morandi - username: sa - password: - - business: - driverClassName: org.mariadb.jdbc.Driver - jdbcUrl: jdbc:mariadb://127.0.0.1:3306/new_morandi?allowPublicKeyRetrieval=true&useSSL=false - username: root - password: Pepsi1943@ - # driver-class-name: org.mariadb.jdbc.Driver - # url: jdbc:mariadb://127.0.0.1:3306/new_morandi?allowPublicKeyRetrieval=true&useSSL=false - - -# jdbc: -# initialize-schema: always - - - - jpa: - hibernate: - ddl-auto: validate - properties: - hibernate: - format_sql: true - show_sql: true - batch: - jdbc: - initialize-schema: always - job: - enabled: true - - name: newBaekjoonProblemJob - -