스프링 프레임워크/springbatch

spring batch chunk기반 step 시작하기

blogger903 2024. 7. 31. 22:35
728x90

환경

IDE: intellij
SpringBootVersion: 3.0.11
Gradle: 8.8
Java: 17

build.gradle.kts

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "3.0.11"
    id("io.spring.dependency-management") version "1.1.0"
    kotlin("jvm") version "1.7.22"
    kotlin("plugin.spring") version "1.7.22"
    kotlin("plugin.jpa") version "1.7.22"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-batch")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    implementation("org.springframework.kafka:spring-kafka:3.2.0")
    implementation("mysql:mysql-connector-java:8.0.33")
    implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta")
    implementation("com.querydsl:querydsl-kotlin:5.0.0")

    annotationProcessor("com.querydsl:querydsl-apt:5.0.0:jakarta")

    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.springframework.batch:spring-batch-test")
    testImplementation("org.junit.jupiter:junit-jupiter-api")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "17"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

Entity

import jakarta.persistence.*
import org.springframework.data.annotation.CreatedDate
import org.springframework.data.annotation.LastModifiedDate
import org.springframework.data.jpa.domain.support.AuditingEntityListener
import java.time.LocalDateTime

@Entity
@EntityListeners(AuditingEntityListener::class)
data class House(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,
    val address: String,
    val price: Double,

    @CreatedDate
    @Column(nullable = false, updatable = false)
    var createdAt: LocalDateTime? = null,

    @LastModifiedDate
    @Column(nullable = false)
    var updatedAt: LocalDateTime? = null,

    @Column(nullable = true)
    var deletedAt: LocalDateTime? = null
)

Repository

@Repository
interface HouseRepository : JpaRepository<House, Long> {

    fun findByCreatedAtBetween(
        startDateTime: LocalDateTime,
        endDateTime: LocalDateTime,
        pageable: Pageable
    ): Page<House>
}

Job

import com.example.kotlinsimplespringbatch.domain.House
import org.springframework.batch.core.Job
import org.springframework.batch.core.Step
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.Chunk
import org.springframework.batch.item.data.RepositoryItemReader
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.transaction.PlatformTransactionManager

@Configuration
class HouseCreationConfigJobConfig(
    private val jobRepository: JobRepository,
    private val transactionManager: PlatformTransactionManager,
    @Qualifier("houseCreationConfirmItemReader")
    private val houseCreationConfirmItemReader: RepositoryItemReader<House>
) {

    val chunkSize: Int = 500
    val jobName = "houseCreationConfirmJob"

    @Bean
    fun houseCreationConfirmJob(houseCreationConfirmStep: Step): Job {
        return JobBuilder(jobName, jobRepository)
            .start(houseCreationConfirmStep)
            .build()
    }

    @Bean
    fun houseCreationConfirmStep(): Step {
        return StepBuilder("houseCreationConfirmStep", jobRepository)
            .chunk<House, House>(chunkSize,transactionManager)
            .reader (houseCreationConfirmItemReader)
            .writer { chunk: Chunk<out House> ->
                chunk.items.forEach { house ->
                    println("Writing house: $house")
                }
            }
            .build()
    }
}

Reader

import com.example.kotlinsimplespringbatch.adapter.repository.HouseRepository
import com.example.kotlinsimplespringbatch.domain.House
import org.springframework.batch.item.data.RepositoryItemReader
import org.springframework.batch.item.data.builder.RepositoryItemReaderBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.domain.Sort
import java.time.*

@Configuration
class HouseCreationConfirmItemReaderConfig {

    val chunkSize: Int = 500
    private final val zoneId: ZoneId = ZoneId.of("Asia/Seoul")

    val startDateTime: LocalDateTime = LocalDate.now(zoneId)
        .atStartOfDay()

    val endDateTime: LocalDateTime = LocalDate.now(zoneId)
        .plusDays(1)
        .atStartOfDay()
    @Bean
    fun houseCreationConfirmItemReader(houseRepository: HouseRepository): RepositoryItemReader<House> {
        return RepositoryItemReaderBuilder<House>()
            .name("houseCreationItemReader")
            .repository(houseRepository)
            .methodName("findByCreatedAtBetween")
            .arguments(listOf(startDateTime, endDateTime))
            .pageSize(chunkSize)
            .sorts(mapOf("id" to Sort.Direction.ASC))
            .build()
    }
}