๐Ÿ“š
TIL
  • README
  • Git
    • Basic
    • Remote Repository
    • Log & Diff
    • Rebase&Cherri-Pick
    • git-flow
  • DevOps
    • Monolithic vs MSA
    • Jenkins ์‹œ์ž‘ํ•˜๊ธฐ
    • Airflow ์‹œ์ž‘ํ•˜๊ธฐ
    • Airflow ์‹œ์ž‘ํ•˜๊ธฐ
    • Build Tools
      • maven
  • ๊ฐœ๋ฐœ ๋ฐฉ๋ฒ•๋ก 
    • TDD
  • Spring
    • IoC
    • Is Spring Bean Thread-Safe?
    • Spring Singleton
    • Component Scan
    • Spring Annotation
    • ์˜์กด ๊ด€๊ณ„ ์ฃผ์ž…(DI)
    • Lombok ํ™œ์šฉํ•˜๊ธฐ
    • Bean ์ƒ๋ช…์ฃผ๊ธฐ์™€ ์ฝœ๋ฐฑ
    • Bean Scope
    • AOP(1) - AOP๋ž€
    • AOP(2) - Aop Proxy
    • AOP(3) - Dynamic Proxy
    • AOP(4) - AspectJ
    • POJO
    • Spring ์„œ๋น„์Šค ๊ตฌ์กฐ
    • Transaction
    • JPA๋ž€?
    • JPA Entity
    • Spring Data JPA
    • Spring Data Specification
    • Model Mapping
    • Cache
    • restTemplate
    • YAML ํŒŒ์ผ ์„ค์ •
    • Spring Boot
      • H2 DB ์„ค์ •
      • ๋‹ค์ค‘ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ •
      • Mybatis ์—ฐ๋™ํ•˜๊ธฐ
    • Spring Batch
      • Batch ์‹œ์ž‘ํ•ด๋ณด๊ธฐ
      • Batch Job Flow
      • Job
      • Step
      • Batch Scope & Job Parameter
      • JobRepository์™€ ๋ฉ”ํƒ€ํ…Œ์ด๋ธ”
      • Chunk ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ
      • ItemReader
      • ItemProcessor
      • ItemWriter
      • Batch Schedular
      • Job๋ณ„ Bean๋“ฑ๋กํ•˜๊ธฐ
      • Batch ๊ตฌํ˜„์‹œ ๋ฐœ์ƒํ•œ ์˜ค๋ฅ˜ ์ •๋ฆฌ
      • Spring Batch Scaling
        • Multithread Job๊ตฌํ˜„์‹œ ์ด์Šˆ์‚ฌํ•ญ
    • Spring test
      • Junit5
        • ํ…Œ์ŠคํŠธ ์ด๋ฆ„ ํ‘œ๊ธฐ
        • ํ…Œ์ŠคํŠธ ๊ทธ๋ฃน ์‚ฌ์ด์˜ ๊ด€๊ณ„
        • ํƒœ๊ทธ์™€ ํ•„ํ„ฐ๋ง
        • ๋™์  ํ…Œ์ŠคํŠธ
        • ํ…Œ์ŠคํŠธ LifeCycle
        • ํ…Œ์ŠคํŠธ ๋ฉ”์„œ๋“œ
        • ํ…Œ์ŠคํŠธ ์ˆœ์„œ
        • AssertJ
        • ํ…Œ์ŠคํŠธ ๋ณ‘๋ ฌ ์‹คํ–‰
        • AssertJ
        • Mock
      • Spring Boot Test DB ๋ถ„๋ฆฌ
      • Spring Batch Test
  • Web Application
    • Web Server & WAS
    • ๊ด€๋ จ ๊ฐœ๋… - HTTP API, HTML, CSR, SSR
    • Servlet
    • JSP
    • Cookie And Session
    • ์˜ˆ์™ธํŽ˜์ด์ง€
    • Java Bean
    • JDBC
    • Connection Pool
    • ํŒŒ์ผ ์—…๋กœ๋“œ
    • Expression Language
    • JSTL
    • FrontControllerํŒจํ„ด Command ํŒจํ„ด
    • Forwarding
    • MVC
    • ํšŒ์›๊ฐ€์ž…์˜ˆ์ œ
    • ์ฐธ๊ณ 
      • ๊ฐœ๋ฐœํ™˜๊ฒฝ์„ค์ •
  • Java+
    • SOAP/WSDL vs REST
    • WSDL์„ JAVA๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ
    • SOAP ํ†ต์‹  OPEN API๋กœ ๊ฐœ๋ฐœํ•ด๋ณด๊ธฐ
  • Java
    • Basic
      • ๋ณ€์ˆ˜์™€ ํƒ€์ž…
      • ์—ฐ์‚ฐ์ž
      • ์กฐ๊ฑด๋ฌธ๊ณผ ๋ฐ˜๋ณต๋ฌธ
      • ์ฐธ์กฐ ํƒ€์ž…
      • ํด๋ž˜์Šค
      • ์ƒ์†(Inheritance)
      • ์ธํ„ฐํŽ˜์ด์Šค(Interface)
      • ์ค‘์ฒฉ ํด๋ž˜์Šค์™€ ์ค‘์ฒฉ ์ธํ„ฐํŽ˜์ด์Šค
      • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
      • API - Object, System, Class, Math, Wrapper
      • API - String, StringBuffer, StringBuilder
      • Thread
      • Generic
      • Lambda
      • Collection - List, Set
      • Collection - Map
      • Collection - Tree
      • Collection - Stack, Queue
      • Stream
      • Reflection
      • ์ •๊ทœํ‘œํ˜„์‹
      • GUI
      • UML
      • Serializable
    • Advanced
      • OutOfMemoryError
      • AutoValue
      • meta-annotation
        • @Retention
        • @Target
        • @Repeatable
    • Effective Java 3/E
      • ITEM 1: Static Factory Method(์ •์  ๋ฉ”์†Œ๋“œ)
      • ITEM 2: Builder Pattern
      • ITEM 3: Singleton
      • ITEM 4: Private Constructor
      • ITEM 5: Dependency Injection
      • ITEM 6: Avoid Unnecessary Object
      • ITEM 7: Eliminate Object Reference
      • ITEM 8: Avoid finalizer and cleaner
      • ITEM 9: try-with-resources
      • ITEM 10: The gerneral contract when overriding equlas
      • ITEM 11: Overriding hashCode
      • ITEM 12: overriding toString
      • ITEM 13: overriding clone judiciously
      • ITEM 14: Consider implementing comparable
      • ITEM 15: ํด๋ž˜์Šค์™€ ๋ฉค๋ฒ„์˜ ์ ‘๊ทผ์„ ์ตœ์†Œํ™”ํ•ด๋ผ
      • ITEM 16: Use Accessor methods
      • ITEM 17: ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ์„ฑ์„ ์ตœ์†Œํ™”ํ•ด๋ผ(๋ถˆ๋ณ€ ํด๋ž˜์Šค)
      • ITEM 18: ์ƒ์†๋ณด๋‹จ ์ปดํฌ์ง€์…˜์„ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 19: ์ƒ์†์„ ๊ณ ๋ คํ•ด ์„ค๊ณ„ํ•˜๊ณ  ๋ฌธ์„œํ™”ํ•ด๋ผ
      • ITEM 20: ์ถ”์ƒ ํด๋ž˜์Šค๋ณด๋‹ค ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์šฐ์„ ํ•˜๋ผ
      • ITEM 21: ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๊ตฌํ˜„ํ•˜๋Š” ์ชฝ์„ ์ƒ๊ฐํ•ด ์„ค๊ณ„ํ•ด๋ผ.
      • ITEM 22: ์ธํ„ฐํŽ˜์ด์Šค๋Š” ํƒ€์ž…์„ ์ •์˜ํ•˜๋Š” ์šฉ๋„๋กœ๋งŒ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 23: ํƒœ๊ทธ ๋‹ฌ๋ฆฐ ํด๋ž˜์Šค๋ณด๋‹ค ํด๋ž˜์Šค ๊ณ„์ธต๊ตฌ์กฐ๋ฅผ ํ™œ์šฉํ•ด๋ผ
      • ITEM 24: ๋ฉค๋ฒ„ ํด๋ž˜์Šค๋Š” ๋˜๋„๋ก static์œผ๋กœ ๊ตฌํ˜„ํ•ด๋ผ
      • ITEM 25: ํ†ฑ๋ ˆ๋ฒจ ํด๋ž˜์Šค๋Š” ํ•œ ํŒŒ์ผ์— ํ•˜๋‚˜๋งŒ ์ƒ์„ฑํ•ด๋ผ.
      • ITEM 26: Raw type์€ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ๋ผ
      • ITEM 27: ๋น„๊ฒ€์‚ฌ ๊ฒฝ๊ณ ๋ฅผ ์ œ๊ฑฐํ•ด๋ผ
      • ITEM 28: ๋ฐฐ์—ด๋ณด๋‹ค๋Š” ๋ฆฌ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 29: ์ด์™•์ด๋ฉด ์ œ๋„ค๋ฆญ ํƒ€์ž…์œผ๋กœ ๋งŒ๋“ค์–ด๋ผ
      • ITEM 30: ์ด์™•์ด๋ฉด ์ œ๋„ค๋ฆญ ๋ฉ”์„œ๋“œ๋กœ ๋งŒ๋“ค์–ด๋ผ
      • ITEM 31 : ํ•œ์ •์  ์™€์ผ๋“œ์นด๋“œ๋ฅผ ์‚ฌ์šฉํ•ด API ์œ ์—ฐ์„ฑ์„ ๋†’์—ฌ๋ผ
      • ITEM 32: ์ œ๋„ค๋ฆญ๊ณผ ๊ฐ€๋ณ€์ธ์ˆ˜๋ฅผ ํ•จ๊ป˜ ์“ธ ๋•Œ๋Š” ์‹ ์ค‘ํ•ด๋ผ
      • ITEM 33: ํƒ€์ž… ์•ˆ์ „ ์ด์ข… ์ปจํ…Œ์ด๋„ˆ๋ฅผ ๊ณ ๋ คํ•ด๋ผ
      • ITEM 34: int ์ƒ์ˆ˜ ๋Œ€์‹  ์—ด๊ฑฐ ํƒ€์ž…์„ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 35: ordinal ๋ฉ”์„œ๋“œ ๋Œ€์‹  ์ธ์Šคํ„ด์Šค ํ•„๋“œ๋ฅผ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 36: ๋น„ํŠธ ํ•„๋“œ ๋Œ€์‹  EnumSet์„ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 37: ordinal ์ธ๋ฑ์‹ฑ ๋Œ€์‹  EnumMap์„ ์‚ฌ์šฉํ•ด๋ผ
      • TEM 38 : ํ™•์žฅํ•  ์ˆ˜ ์žˆ๋Š” ์—ด๊ฑฐํƒ€์ž…์ด ํ•„์š”ํ•˜๋ฉด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 39: ๋ช…๋ช… ํŒจํ„ด๋ณด๋‹ค ์• ๋„ˆํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 40: @Override ์–ด๋…ธํ…Œ์ด์…˜์„ ์ผ๊ด€๋˜๊ฒŒ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 41: ์ •์˜ํ•˜๋ ค๋Š” ๊ฒƒ์ด ํƒ€์ž…์ด๋ผ๋ฉด ๋งˆ์ปค ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 42: ์ต๋ช… ํด๋ž˜์Šค๋ณด๋‹ค๋Š” ๋žŒ๋‹ค๋ฅผ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 43: ๋žŒ๋‹ค๋ณด๋‹ค๋Š” ๋ฉ”์„œ๋“œ ์ฐธ์กฐ๋ฅผ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 44: ํ‘œ์ค€ ํ•จ์ˆ˜ํ˜• ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 45: ์ŠคํŠธ๋ฆผ์€ ์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 46: ์ŠคํŠธ๋ฆผ์—์„œ ๋ถ€์ž‘์šฉ ์—†๋Š” ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 47: ๋ฐ˜ํ™˜ ํƒ€์ž…์œผ๋กœ๋Š” ์ŠคํŠธ๋ฆผ๋ณด๋‹ค ์ปฌ๋ ‰์…˜์ด ๋‚ซ๋‹ค.
      • ITEM 48: ์ŠคํŠธ๋ฆผ ๋ณ‘๋ ฌํ™”๋Š” ์ฃผ์˜ํ•ด์„œ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 49: ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ ์œ ํšจํ•œ์ง€ ๊ฒ€์‚ฌํ•ด๋ผ
      • ITEM 50: ์ ์‹œ์— ๋ฐฉ์–ด์  ๋ณต์‚ฌ๋ณธ์„ ๋งŒ๋“ค์–ด๋ผ
      • ITEM 51: ๋ฉ”์„œ๋“œ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ์‹ ์ค‘ํžˆ ์„ค๊ณ„ํ•ด๋ผ
      • ITEM 52: ๋‹ค์ค‘์ •์˜๋Š” ์‹ ์ค‘ํžˆ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 53: ๊ฐ€๋ณ€์ธ์ˆ˜๋Š” ์‹ ์ค‘ํžˆ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 54: null์ด ์•„๋‹Œ, ๋นˆ ์ปฌ๋ ‰์…˜์ด๋‚˜ ๋ฐฐ์—ด์„ ๋ฐ˜ํ™˜ํ•ด๋ผ
      • ITEM 55: Optional ๋ฐ˜ํ™˜์€ ์‹ ์ค‘ํ•˜๊ฒŒ ํ•ด๋ผ
      • ITEM 56: ๊ณต๊ฐœ๋œ API ์š”์†Œ์—๋Š” ํ•ญ์ƒ ์ฃผ์„์„ ์ž‘์„ฑํ•ด๋ผ
      • ITEM 57: ์ง€์—ญ๋ณ€์ˆ˜์˜ ๋ฒ”์œ„๋ฅผ ์ตœ์†Œํ™”ํ•ด๋ผ
      • ITEM 58: ์ „ํ†ต์ ์ธ for ๋ฌธ๋ณด๋‹ค๋Š” for-each๋ฌธ์„ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 59: ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ตํžˆ๊ณ  ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 60: ์ •ํ™•ํ•œ ๋‹ต์ด ํ•„์š”ํ•˜๋‹ค๋ฉด float์™€ double์€ ํ”ผํ•ด๋ผ
      • ITEM 61: ๋ฐ•์‹ฑ๋œ ๊ธฐ๋ณธ ํƒ€์ž…๋ณด๋‹ค๋Š” ๊ธฐ๋ณธ ํƒ€์ž…์„ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 62: ๋‹ค๋ฅธ ํƒ€์ž…์ด ์ ์ ˆํ•˜๋‹ค๋ฉด ๋ฌธ์ž์—ด ์‚ฌ์šฉ์„ ํ”ผํ•ด๋ผ
      • ITEM 63: ๋ฌธ์ž์—ด ์—ฐ๊ฒฐ์€ ๋А๋ฆฌ๋‹ˆ ์ฃผ์˜ํ•ด๋ผ
      • ITEM 64: ๊ฐ์ฒด๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•ด ์ฐธ์กฐํ•ด๋ผ
      • ITEM 65: ๋ฆฌํ”Œ๋ ‰์…˜๋ณด๋‹ค๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 66: ๋„ค์ดํ‹ฐ๋ธŒ ๋ฉ”์„œ๋“œ๋Š” ์‹ ์ค‘ํžˆ ์‚ฌ์šฉํ•ด๋ผ
      • ITEM 67: ์ตœ์ ํ™”๋Š” ์‹ ์ค‘ํžˆ ํ•ด๋ผ
      • ITEM 68: ์ผ๋ฐ˜์ ์œผ๋กœ ํ†ต์šฉ๋˜๋Š” ๋ช…๋ช… ๊ทœ์น™์„ ๋”ฐ๋ผ๋ผ
    • ๊ฐ์ฒด์ง€ํ–ฅ ์„ค๊ณ„ ์›์น™(SOLID)
    • ๋””์ž์ธํŒจํ„ด
      • Strategy Pattern
      • Template Method Pattern
      • Factory Method Pattern
      • Singleton
      • Delegation
      • Proxy
      • Adapter Pattern
    • ์‹ค์Šต
      • ์ธํ„ฐํŽ˜์ด์Šค ์‹ค์Šต - Vehicle
      • ์ธํ„ฐํŽ˜์ด์Šค ์‹ค์Šต - Remote
      • GUI ์‹ค์Šต - Calculator
      • GUI ์‹ค์Šต - button
      • GUI ์‹ค์Šต - lotto
      • Thread ์‹ค์Šต - ์ขŒ์„์˜ˆ์•ฝ, ๋ฉ”์„ธ์ง€๋ณด๋‚ด๊ธฐ
    • Jar vs War
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค
    • KEY
    • Index
    • Transaction
    • Trigger
    • Procedure / Function
    • Package
    • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฐฐ์›€ํ„ฐ
      • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‹œ์Šคํ…œ
      • ๊ด€๊ณ„๋ฐ์ดํ„ฐ ๋ชจ๋ธ
      • ๊ด€๊ณ„๋Œ€์ˆ˜์™€ SQL
    • MySQL
      • Database๋ž€
      • MySQL ์‹œ์ž‘ํ•˜๊ธฐ
      • MySQL Database
      • MySQL Table
      • CRUD
      • ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค
      • Server์™€ Client
    • PostgreSQL
    • NoSQL
      • Install Cassandra on mac
      • Cassandra๋ž€?
      • NiFi๋ž€
  • Algorithm
    • String
    • Recursion
    • Dynamic Programming
    • Array, Struct, Pointer
    • Math
    • Sort
    • List
    • Stack
    • Queue
    • Graph
    • Tree
    • Maze
    • AVL
    • ์ด์ง„ํƒ์ƒ‰ํŠธ๋ฆฌ(Binary Search Tree)
    • DFS์™€ BFS
    • ๋‹ค์ต์ŠคํŠธ๋ผ ์•Œ๊ณ ๋ฆฌ์ฆ˜(Dijkstra's Algorithm)
    • Red-Black ํŠธ๋ฆฌ
    • A* ์•Œ๊ณ ๋ฆฌ์ฆ˜
    • Heap
    • Huffman Coding
    • Priority Queue
    • Bellman-Ford ์•Œ๊ณ ๋ฆฌ์ฆ˜
    • C++
      • Class
      • STL
        • STL pair
        • STL Container - Associate Container
        • STL Container - Sequence Container
        • STL Container - Container Adapter
  • JavaScript
    • JABASCRIPT BASIC
    • Shallow Copy vs Deep Copy
    • OBJECT MODEL
    • NODE
    • ๋™๊ธฐ ์ฒ˜๋ฆฌ vs ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ
    • AJAX
    • CALLBACK
    • PROMISE
    • DEFERRER
    • UNDERSCORE
    • WEBPACK
    • SCOPE
    • EXECUTION CONTEXT
    • Image Object
    • BFCache๋ž€?
    • history.scrollRestoration
    • Intersection Observer
    • JWT - JSON Web Token
    • HTML vs JSON
  • Vue.js
    • ํ™˜๊ฒฝ์„ค์ •
    • Vue.js๋ž€?
    • Vue Instance
    • Vue Component
    • Vue Router
    • HTTP ํ†ต์‹ 
    • Template
    • Single File Component
    • Vue Animation
    • Vuex
    • Djnago์™€ ์—ฐ๋™ํ•˜๊ธฐ
  • Backbone.js
    • Model
    • Collection
    • Sync
    • view
  • Node.js
    • Doit! - ๋…ธ๋“œ๋กœ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ๋Œ€ํ‘œ์ ์ธ ์„œ๋ฒ„์™€ ์šฉ๋„
    • Doit! - ๋…ธ๋“œ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ณ  ๊ฐœ๋ฐœ ๋„๊ตฌ ์„ค์น˜ํ•˜๊ธฐ
    • Doit! - ๋…ธ๋“œ ๊ฐ„๋‹จํ•˜๊ฒŒ ์‚ดํŽด๋ณด๊ธฐ
    • Doit! - ๋…ธ๋“œ์˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์™€ ์นœํ•ด์ง€๊ธฐ
    • Doit! - ๋…ธ๋“œ์˜ ๊ธฐ๋ณธ ๊ธฐ๋Šฅ ์•Œ์•„๋ณด๊ธฐ
    • Doit! - ์›น ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ
    • Doit! - ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉํ•˜๊ธฐ
    • Doit! - ์ต์Šคํ”„๋ ˆ์Šค ํ”„๋กœ์ ํŠธ๋ฅผ ๋ชจ๋“ˆํ™”ํ•˜๊ธฐ
    • Doit! - ๋ทฐ ํ…œํ”Œ๋ฆฟ ์ ์šฉํ•˜๊ธฐ
    • Doit! - ํŒจ์ŠคํฌํŠธ๋กœ ์‚ฌ์šฉ์ž ์ธ์ฆํ•˜๊ธฐ
    • Doit! - ์ฑ„ํŒ…์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ
    • Doit! - JSON-RPC ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ
  • Python
    • Warning-Could not import the lzma module
    • Pandas
      • Pandas ์ž๋ฃŒ๊ตฌ์กฐ
      • Pandas ๋ฐ์ดํ„ฐ ์ž…์ถœ๋ ฅ
      • DataFrame Data ์‚ดํŽด๋ณด๊ธฐ
      • ์‹œ๊ฐํ™” ๋„๊ตฌ - Matplotlib
  • ML
    • ์ถ”์ฒœ ์‹œ์Šคํ…œ
      • Collaborative Filtering
      • Matrix Factorization
  • Django
    • Basic
      • ํ™˜๊ฒฝ์„ค์ •
      • About Django
      • Start Django Project
      • Secret Key ๊ด€๋ฆฌํ•˜๊ธฐ
      • Settings ๋ถ„๋ฆฌํ•˜๊ธฐ
      • Django App
      • Django View & URL (1)
      • Django Model
        • MySQL ์—ฐ๋™
      • Django Admin
      • Django View & URL (2)
      • Django Template
      • Django Template & View & URL
      • Django Static
      • Django form
    • Advanced
      • Django Generic View
      • Django Automated Testing
      • Django Extenstion Template
      • Django Model Package
      • Django OpenSSL setting
    • REST framework
      • Rest API
      • Serializers
      • ViewSet
    • Error
      • ํ™˜๊ฒฝ์„ค์ • zlib ์˜ค๋ฅ˜๋ฐœ์ƒ
      • ModuleNotFoundError
    • ํŒจํ‚ค์ง€
      • django-debug-toolbar
    • Vue.js ์—ฐ๋™ํ•˜๊ธฐ
  • Ruby
    • variable & input/output
    • ์กฐ๊ฑด๋ฌธ
    • ๋ฐ˜๋ณต๋ฌธ
    • Array & Hash
    • Method
    • Proc&Lamda
    • Class
  • Ruby on Rails
    • Scaffolding
    • Controller
    • Model
    • Model-M:N relation
    • Model Validation
    • ๋ฉ‹์‚ฌ 10์ฃผ์ฐจ ์ˆ˜์—…(Tip)
  • HTML/CSS
    • Udacity - Intro to HTML/CSS
    • Udacity - Responsive Web Design
    • Udacity - Responsive Images
    • HTML Basic
    • CSS Basic
    • HTML5 Sementic Tag
    • HTML ํ…์ŠคํŠธ ๊ด€๋ จ ํƒœ๊ทธ๋“ค
    • HTML5 ๋ฉ€ํ‹ฐ๋ฏธ๋””์–ด
    • HTML ํผ ๊ด€๋ จ ํƒœ๊ทธ๋“ค
    • ํ…์ŠคํŠธ ๊ด€๋ จ ์Šคํƒ€์ผ
    • ์ƒ‰์ƒ๊ณผ ๋ฐฐ๊ฒฝ์„ ์œ„ํ•œ ์Šคํƒ€์ผ
    • ๋ ˆ์ด์•„์›ƒ์„ ์œ„ํ•œ ์Šคํƒ€์ผ
    • CSS ํฌ์ง€์…”๋‹
    • ๋‹ค์žฌ๋‹ค๋Šฅํ•œ CSS3 ์„ ํƒ์ž
    • CSS์™€ ์• ๋‹ˆ๋ฉ”์ด์…˜
    • ๋ฐ˜์‘ํ˜• ์›น์ด๋ž€?
  • OS(์šด์˜์ฒด์ œ)
    • Linux
      • Daemon
      • Cron
      • ํ”„๋กœ์„ธ์Šค ๊ด€๋ จ ๋ช…๋ น์–ด
      • ํ…์ŠคํŠธ ํŒŒ์ผ ๋ช…๋ น์–ด
  • Network
    • ๋„คํŠธ์›Œํฌ ๊ธฐ๋ณธ ๊ฐœ๋…
    • ๋„คํŠธ์›Œํฌ ๊ธฐ๋ณธ ๊ทœ์น™
    • ๋ฌผ๋ฆฌ ๊ณ„์ธต
    • ๋ฐ์ดํ„ฐ ๋งํฌ ๊ณ„์ธต
    • ๋„คํŠธ์›Œํฌ ๊ณ„์ธต
    • ์ „์†ก ๊ณ„์ธต
    • ์‘์šฉ ๊ณ„์ธต
    • ๋„คํŠธ์›Œํฌ ์ „์ฒด ํ๋ฆ„
    • ๋ฌด์„  ๋žœ
  • IT ๊ธฐํƒ€์ง€์‹
    • NAS๋ž€
Powered by GitBook
On this page
  • ํŒŒ์ผ ์ž…๋ ฅ
  • FlatFileItemReader
  • XML
  • JSON
  • Database Reader
  • JDBC
  • Hibernate
  • JPA
  • MyBatisPagingItemReader
  • Spring Data Repository
  • ์ฃผ์˜ ์‚ฌํ•ญ
  • ItemReaderAdapter
  • ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ
  • Skip
  • ์˜ค๋ฅ˜ ๋กœ๊ทธ ๋‚จ๊ธฐ๊ธฐ

Was this helpful?

  1. Spring
  2. Spring Batch

ItemReader

PreviousChunk ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐNextItemProcessor

Last updated 3 years ago

Was this helpful?

chunk

Spring Batch์˜ Reader์—์„œ ์ฝ์–ด์˜ฌ ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ์œ ํ˜•์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ์ž…๋ ฅ ๋ฐ์ดํ„ฐ์—์„œ ์ฝ์–ด์˜ค๊ธฐ

  • ํŒŒ์ผ์—์„œ ์ฝ์–ด์˜ค๊ธฐ

  • DB์—์„œ ์ฝ์–ด์˜ค๊ธฐ

  • Java Message Service ๋“ฑ ๋‹ค๋ฅธ ์†Œ์Šค์—์„œ ์ฝ์–ด์˜ค๊ธฐ

  • ์ปค์Šคํ…€ํ•œ Reader๋กœ ์ฝ์–ด์˜ค๊ธฐ

package org.springframework.batch.item;

public interface ItemReader<T> {

	@Nullable
	T read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException;

}

ItemReader์˜ read()๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด, ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋Š” ์Šคํ… ๋‚ด์—์„œ ์ฒ˜๋ฆฌํ•  Itemํ•œ๊ฐœ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ์Šคํ…์—์„œ๋Š” ์•„์ดํ…œ ๊ฐœ์ˆ˜๋ฅผ ์„ธ์–ด ์ฒญํฌ ๋‚ด ๋ฐ์ดํ„ฐ๊ฐ€ ๋ช‡๊ฐœ๊ฐ€ ์ฒ˜๋ฆฌ๋๋Š”์ง€ ๊ด€๋ฆฌํ•œ๋‹ค. ํ•ด๋‹น Item์€ ItemProcessor๋กœ ์ „๋‹ฌ๋˜๋ฉฐ, ๊ทธ ๋’ค ItemWriter๋กœ ์ „๋‹ฌ๋œ๋‹ค.

๊ฐ€์žฅ ๋Œ€ํ‘œ์ ์ธ ๊ตฌํ˜„์ฒด์ธ JdbcPagingItemReader์˜ ํด๋ž˜์Šค ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

์—ฌ๊ธฐ์„œ ItemReader์™€ ItemStream ์ธํ„ฐํŽ˜์ด์Šค๋„ ๊ฐ™์ด ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

public interface ItemStream {
    void open(ExecutionContext var1) throws ItemStreamException;

    // Batch์˜ ์ฒ˜๋ฆฌ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
    void update(ExecutionContext var1) throws ItemStreamException;

    void close() throws ItemStreamException;
}

ItemStream์€ ์ฃผ๊ธฐ์ ์œผ๋กœ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ณ , ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํ•ด๋‹น ์ƒํƒœ์—์„œ ๋ณต์›ํ•˜๊ธฐ ์œ„ํ•œ ๋งˆ์ปค์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค. ์ฆ‰, ItemReader ์˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ณ  ์‹คํŒจํ•œ ๊ณณ์—์„œ ๋‹ค์‹œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ItemReader์™€ ItemStream ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•˜์—ฌ ์›ํ•˜๋Š” ํ˜•ํƒœ์˜ ItemReader๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

ํŒŒ์ผ ์ž…๋ ฅ

FlatFileItemReader

  • org.springframework.batch.item.file.FlatFileItemReader

  • flat file

    • ํ•œ ๊ฐœ ํ˜น์€ ๊ทธ ์ด์ƒ์˜ ๋ ˆ์ฝ”๋“œ๊ฐ€ ํฌํ•จ๋œ ํŠน์ • ํŒŒ์ผ

    • ํŒŒ์ผ์˜ ๋‚ด์šฉ์„ ๋ด๋„ ๋ฐ์ดํ„ฐ์˜ ์˜๋ฏธ๋ฅผ ์•Œ ์ˆ˜ ์—†๋‹ค.

    • ํŒŒ์ผ ๋‚ด ๋ฐ์ดํ„ฐ์˜ ํฌ๋งท์ด๋‚˜ ์˜๋ฏธ๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฉ”ํƒ€ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋‹ค.

์˜ต์…˜
ํƒ€์ž…
default
์„ค๋ช…
๊ตฌํ˜„์ฒด

comments

String[]

null

๋ฌธ์ž์—ด ๋ฐฐ์—ด์— ํŒŒ์ผ์„ ํŒŒ์‹ฑํ•  ๋–„ ๊ฑด๋„ˆ๋›ฐ์–ด์•ผํ•  ์ฃผ์„ ์ค„์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ ‘๋‘์–ด ์ง€์ •

encoding

String

ํ”Œ๋žซํผ์˜ ๊ธฐ๋ณธ Charset

ํŒŒ์ผ์— ์‚ฌ์šฉ๋œ ๋ฌธ์ž์—ด ์ธ์ฝ”๋”ฉ

lineMapper

LineMapper

null(ํ•„์ˆ˜)

ํŒŒ์ผ ํ•œ์ค„์„ String์œผ๋กœ ์ฝ์€ ๋’ค ์ฒ˜๋ฆฌ ๋Œ€์ƒ์ธ ๋„๋ฉ”์ธ ๊ฐ์ฒด(Item)์œผ๋กœ ๋ณ€ํ™˜

DefaultLineMapper JsonLineMapper PassThroughLineMapper

lineToSkip

int

0

ํŒŒ์ผ์„ ์ฝ์–ด์˜ฌ ๋–„ ๋ช‡ ์ค„์„ ๊ฑด๋„ˆ๋„๊ณ  ์‹œ์ž‘ํ• ์ง€ ์ง€์ •

recordSeparatorPolicy

RecordSeparatorPolicy

DefaultRecordSeparatorPolicy

๊ฐ ์ค„์˜ ๋งˆ์ง€๋ง‰์„ ์ •์˜ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ ๋ณ„๋„๋กœ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ๊ฐœํ–‰ ๋ฌธ์ž๊ฐ€ ๋ ˆ์ฝ”๋“œ์˜ ๋ ๋ถ€๋ถ„์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.

resource

Resource

null(ํ•„์ˆ˜)

์ฝ์„ ๋Œ€์ƒ ๋ฆฌ์†Œ์Šค

skippedLinesCallback

LineCallbackHandler

null

์ค„์„ ๊ฑด๋„ˆ๋›ธ ๋–„ ํ˜ธ์ถœ๋˜๋Š” ์ฝœ๋ฐฑ ์ธํ„ฐํŽ˜์ด์Šค ๊ฑด๋„ˆ๋ˆ ๋ชจ๋“  ์ค„์€ ์ด ์ฝœ๋ฐฑ์ด ํ˜ธ์ถœ๋œ๋‹ค.

strict

boolean

false

true๋กœ ์ง€์ •์‹œ, ๋ฆฌ์†Œ์Šค๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ Exception์„ ๋˜์ง„๋‹ค.

saveState

boolean

true

true : ์žฌ์‹œ์ž‘ ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ฐ ์ฒญํฌ ์ฒ˜๋ฆฌ ํ›„ ItemReader ์ƒํƒœ ์ €์žฅ false : ๋‹ค์ค‘ ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„  false ์ง€์ •

๊ณ ์ •๋œ ๋„ˆ๋น„ ํŒŒ์ผ

Aimee      CHoover    7341Vel Avenue          Mobile          AL35928
Jonas      UGilbert   8852In St.              Saint Paul      MN57321
Regan      MBaxter    4851Nec Av.             Gulfport        MS33193
Octavius   TJohnson   7418Cum Road            Houston         TX51507
Sydnee     NRobinson  894 Ornare. Ave         Olathe          KS25606
Stuart     KMckenzie  5529Orci Av.            Nampa           ID18562
    @Bean
    @StepScope
    public FlatFileItemReader<Customer> customerItemReader(@Value("#{jobParameters['customerFile']}") PathResource inputFile) {
        return new FlatFileItemReaderBuilder<Customer>()
                .name("customerItemReader") // ๊ฐ ์Šคํ…์˜ ExecutionContext์— ์ถ”๊ฐ€๋˜๋Š” ํŠน์ •ํ‚ค์˜ ์ ‘๋‘๋ฌธ์ž๋กœ ์‚ฌ์šฉ๋  ์ด๋ฆ„(saveState false์ธ ๊ฒฝ์šฐ ์ง€์ •ํ•  ํ•„์š”X)
                .resource(inputFile)
                .fixedLength() // FixedLengthBuilder
                .columns(new Range[]{new Range(1,11), new Range(12,12), new Range(13,22), new Range(23,26)
                        , new Range(27,46), new Range(47,62), new Range(63,64), new Range(65,69)}) // ๊ณ ์ •๋„ˆ๋น„
                .names(new String[]{"firstName", "middleInitial", "lastName", "addressNumber", "street"
                        , "city", "state", "zipCode"}) // ๊ฐ ์ปฌ๋Ÿผ๋ช…
//                .strict(false) // ์ •์˜๋œ ํŒŒ์‹ฑ ์ •๋ณด ๋ณด๋‹ค ๋งŽ์€ ํ•ญ๋ชฉ์ด ๋ ˆ์ฝ”๋“œ์— ์žˆ๋Š” ๊ฒฝ์šฐ(true ์˜ˆ์™ธ)
                .targetType(Customer.class) // BeanWrapperFieldSetMapper ์ƒ์„ฑํ•ด ๋„๋ฉ”์ธ ํด๋ ˆ์Šค์— ๊ฐ’์„ ์ฑ„์›€
                .build();
    }

๊ตฌ๋ถ„์ž ํŒŒ์ผ

Aimee,C,Hoover,7341,Vel Avenue,Mobile,AL,35928
Jonas,U,Gilbert,8852,In St.,Saint Paul,MN,57321
Regan,M,Baxter,4851,Nec Av.,Gulfport,MS,33193
Octavius,T,Johnson,7418,Cum Road,Houston,TX,51507
Sydnee,N,Robinson,894,Ornare. Ave,Olathe,KS,25606
Stuart,K,Mckenzie,5529,Orci Av.,Nampa,ID,18562
    @Bean
    @StepScope
    public FlatFileItemReader<Customer> delimitedCustomerItemReader(@Value("#{jobParameters['customerFile']}") PathResource inputFile) {
        return new FlatFileItemReaderBuilder<Customer>()
                .name("delimitedCustomerItemReader") // ๊ฐ ์Šคํ…์˜ ExecutionContext์— ์ถ”๊ฐ€๋˜๋Š” ํŠน์ •ํ‚ค์˜ ์ ‘๋‘๋ฌธ์ž๋กœ ์‚ฌ์šฉ๋  ์ด๋ฆ„(saveState false์ธ ๊ฒฝ์šฐ ์ง€์ •ํ•  ํ•„์š”X)
                .resource(inputFile)
                .delimited() // default(,) DelimitedLineTokenizer๋ฅผ ์‚ฌ์šฉํ•ด ๊ฐ ๋ ˆ์ฝ”๋“œ๋ฅผ FieldSet์œผ๋กœ ๋ณ€ํ™˜
                .names(new String[]{"firstName", "middleInitial", "lastName", "addressNumber", "street"
                        , "city", "state", "zipCode"}) // ๊ฐ ์ปฌ๋Ÿผ๋ช…
                .targetType(Customer.class) // BeanWrapperFieldSetMapper ์ƒ์„ฑํ•ด ๋„๋ฉ”์ธ ํด๋ ˆ์Šค์— ๊ฐ’์„ ์ฑ„์›€
                .build();
    }

FieldSetMapper ์ปค์Šคํ…€

public interface FieldSetMapper<T> {
	T mapFieldSet(FieldSet fieldSet) throws BindException;
}

org.springframework.batch.item.file.mapping.FieldSetMapper๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์ปค์Šคํ…€ ๋งคํผ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

public class CustomFieldSetMapper implements FieldSetMapper<Customer> {

    @Override
    public Customer mapFieldSet(FieldSet fieldSet) throws BindException {
        Customer customer = new Customer();
        customer.setAddress(fieldSet.readString("addressNumber") + " " + fieldSet.readString("street"));
        customer.setCity(fieldSet.readString("city"));
        customer.setFirstName(fieldSet.readString("firstName"));
        customer.setLastName(fieldSet.readString("lastName"));
        customer.setMiddleInitial(fieldSet.readString("middleInitial"));
        customer.setState(fieldSet.readString("state"));
        customer.setZipCode(fieldSet.readString("zipCode"));

        return customer;
    }
}
    @Bean
    @StepScope
    public FlatFileItemReader<Customer> delimitedCustomerItemReader(@Value("#{jobParameters['customerFile']}") PathResource inputFile) {
        return new FlatFileItemReaderBuilder<Customer>()
                .name("delimitedCustomerItemReader")
                .resource(inputFile)
                .delimited() 
                .names(new String[]{"firstName", "middleInitial", "lastName", 														"addressNumber", "street", "city", "state", "zipCode"})
                .fieldSetMapper(new CustomFieldSetMapper()) // customMapper ์„ค์ •
                .build();
    }

.fieldSetMapper()์— ์ปค์Šคํ…€ ๋งคํผ๋ฅผ ์ง€์ •ํ•˜๋ฉด ๋œ๋‹ค.

LineTokenizer ์ปค์Šคํ…€

  • org.springframework.batch.item.file.transform.LineTokenizer

public interface LineTokenizer {
	FieldSet tokenize(@Nullable String line);
}
public class CustomFileLineTokenizer implements LineTokenizer {

    @Setter
    private String delimiter = ",";

    private String[] names = new String[]{
              "firstName"
            , "middleInitial"
            , "lastName"
            , "address"
            , "city"
            , "state"
            , "zipCode"
    };

    private FieldSetFactory fieldSetFactory = new DefaultFieldSetFactory();

    @Override
    public FieldSet tokenize(String line) {

        // ๊ตฌ๋ถ„์ž๋กœ ํ•„๋“œ ๊ตฌ๋ถ„
        String[] fields = line.split(delimiter);

        List<String> parsedFields = new ArrayList<>();

        for (int i = 0; i < fields.length; i++) {
            if (i == 4) {
                // 3,4๋ฒˆ์จฐ ํ•„๋“œ ๋‹จ์ผ ํ•„๋“œ๋กœ ๊ตฌ์„ฑ
                parsedFields.set(i - 1, parsedFields.get(i - 1) + " " + fields[i]);
            } else {
                parsedFields.add(fields[i]);
            }
        }


        // ๊ฐ’์˜ ๋ฐฐ์—ด & ํ•„๋“œ ์ด๋ฆ„ ๋ฐฐ์—ด์„ ๋„˜๊ฒจ ํ•„๋“œ๋ฅผ ์ƒ์„ฑ
        return fieldSetFactory.create(parsedFields.toArray(new String[0]), names);
    }
}
    @Bean
    @StepScope
    public FlatFileItemReader<Customer> lineTokenizerCustomerItemReader(@Value("#{jobParameters['customerFile']}") PathResource inputFile) {
        return new FlatFileItemReaderBuilder<Customer>()
                .name("lineTokenizerCustomerItemReader")
                .resource(inputFile)
                .lineTokenizer(new CustomFileLineTokenizer()) // lineTokenzier Custom
                .targetType(Customer.class) // BeanWrapperFieldSetMapper ์ƒ์„ฑํ•ด ๋„๋ฉ”์ธ ํด๋ ˆ์Šค์— ๊ฐ’์„ ์ฑ„์›€
                .build();
    }

LineMapper

  • org.springframework.batch.item.file.mapping.PatternMatchingCompositeLineMapper

    • ์—ฌ๋Ÿฌ๊ฐœ์˜ LineTokenizer๋กœ ๊ตฌ์„ฑ๋œ Map์„ ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Œ.

      • PatternMatcher<LineTokenizer> tokenizers

    • ๊ฐ LineTokenizer๋ฅผ ํ•„์š”๋กœ ํ•˜๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ FieldSetMapper Map ์„ ์–ธํ•  ์ˆ˜ ์žˆ์Œ.

      • PatternMatcher<FieldSetMapper<T>> patternMatcher

CUST,Warren,Q,Darrow,8272 4th Street,New York,IL,76091
TRANS,1165965,2011-01-22 00:13:29,51.43
CUST,Ann,V,Gates,9247 Infinite Loop Drive,Hollywood,NE,37612
CUST,Erica,I,Jobs,8875 Farnam Street,Aurora,IL,36314
TRANS,8116369,2011-01-21 20:40:52,-14.83
TRANS,8116369,2011-01-21 15:50:17,-45.45
TRANS,8116369,2011-01-21 16:52:46,-74.6
TRANS,8116369,2011-01-22 13:51:05,48.55
TRANS,8116369,2011-01-21 16:51:59,98.53

์—ฌ๋Ÿฌ ํ˜•์‹์œผ๋กœ ๊ตฌ์„ฑ๋œ csv ํŒŒ์ผ์ด๋‹ค.

		@Bean
    public PatternMatchingCompositeLineMapper lineTokenizer() {
        Map<String, LineTokenizer> lineTokenizerMap = new HashMap<>(2);

        lineTokenizerMap.put("TRANS*", transactionLineTokenizer()); // TRANS๋กœ ์‹œ์ž‘ํ•˜๋ฉด transactionLineTokenizer
        lineTokenizerMap.put("CUST*", customerLineTokenizer()); // CUST๋กœ ์‹œ์ž‘ํ•˜๋ฉด, customerLineTokenizer

        Map<String, FieldSetMapper> fieldSetMapperMap = new HashMap<>(2);

        BeanWrapperFieldSetMapper<Customer> customerFieldSetMapper = new BeanWrapperFieldSetMapper<>();
        customerFieldSetMapper.setTargetType(Customer.class);

        fieldSetMapperMap.put("TRANS*", new TransactionFieldSetMapper()); // ์ผ๋ฐ˜์ ์ด์ง€ ์•Š์€ ํƒ€์ž… ํ•„๋“œ ๋ณ€ํ™˜์‹œ FieldSetMapper ํ•„์š”(Date, Double)
        fieldSetMapperMap.put("CUST*", customerFieldSetMapper);

        PatternMatchingCompositeLineMapper lineMappers = new PatternMatchingCompositeLineMapper();

        lineMappers.setTokenizers(lineTokenizerMap);
        lineMappers.setFieldSetMappers(fieldSetMapperMap);

        return lineMappers;
    }

TRANS๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฒฝ์šฐ์™€ CUST๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฒฝ์šฐ ๊ฐ๊ฐ FieldSetMapper, LineTokenizer๋ฅผ ์‚ฌ์šฉํ•ด ํŒŒ์‹ฑ ๋ฐ set์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

    @Bean
    public DelimitedLineTokenizer transactionLineTokenizer() {
        DelimitedLineTokenizer delimitedLineTokenizer = new DelimitedLineTokenizer();

        delimitedLineTokenizer.setNames("prefix", "accountNumber", "transactionDate", "amount");

        return delimitedLineTokenizer;
    }

    @Bean
    public DelimitedLineTokenizer customerLineTokenizer() {
        DelimitedLineTokenizer delimitedLineTokenizer = new DelimitedLineTokenizer();

        delimitedLineTokenizer.setNames("firstName", "middleInitial", "lastName", "address", "city", "state", "zipCode");
        delimitedLineTokenizer.setIncludedFields(1,2,3,4,5,6,7); // prefix์ œ์™ธํ•œ ๋ชจ๋“  ํ•„๋“œ

        return delimitedLineTokenizer;
    }

ItemStreamReader ์ปค์Šคํ…€

๋‘๊ฐœ์˜ ๋‹ค๋ฅธ ํฌ๋งท์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์‚ฌ์‹ค์€ ์„œ๋กœ ์—ฐ๊ด€์ด ์žˆ๋Š” ๋ฐ์ดํ„ฐ์ผ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ ๊ฒฝ์šฐ์—๋Š” ํ•œ๊ฐœ์˜ ๋„๋ฉ”์ธ์ด ๋‹ค๋ฅธ ํ•œ๊ฐœ์˜ ๋„๋ฉ”์ธ์˜ ๋‚ด์šฉ์„ ํฌํ•จํ•˜๊ณ  ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค.

CUST,Warren,Q,Darrow,8272 4th Street,New York,IL,76091
TRANS,1165965,2011-01-22 00:13:29,51.43
CUST,Ann,V,Gates,9247 Infinite Loop Drive,Hollywood,NE,37612
CUST,Erica,I,Jobs,8875 Farnam Street,Aurora,IL,36314
TRANS,8116369,2011-01-21 20:40:52,-14.83
TRANS,8116369,2011-01-21 15:50:17,-45.45
TRANS,8116369,2011-01-21 16:52:46,-74.6
TRANS,8116369,2011-01-22 13:51:05,48.55
TRANS,8116369,2011-01-21 16:51:59,98.53

๊ฑฐ๋ž˜๋‚ด์—ญ(TRANS) ๋ฐ์ดํ„ฐ๋Š” ๊ทธ ์œ„์˜ ๊ณ ๊ฐ(CUST)์˜ ๊ณ„์•ฝ ์ •๋ณด๋ผ๊ณ  ๊ฐ€์ •ํ•ด๋ณผ ๊ฒƒ์ด๋‹ค.

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@ToString
public class Customer {

    private Long id;
    private String firstName;
    private String middleInitial;
    private String lastName;
    private String addressNumber;
		private String street;
    private String city;
    private String state;
    private String zipCode;

    private String address;

    private List<Transaction> transactions; // ๊ณ ๊ฐ์˜ ๊ฐœ์•ฝ์ •๋ณด

}

Custom ๋„๋ฉ”์ธ ๊ฐ์ฒด์— Transaction ๊ฑฐ๋ž˜๋‚ด์—ญ ์ •๋ณด๋ฅผ ํฌํ•จํ•˜๊ฒŒ ๋ณ€๊ฒฝํ•ด์ค€๋‹ค.

public class CustomerFileReader implements ItemStreamReader<Customer> {

    private Object curItem = null;

    private ItemStreamReader<Object> delegate;

    public CustomerFileReader(ItemStreamReader<Object> delegate) {
        this.delegate = delegate;
    }

    @Override
    public Customer read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
        if (curItem == null) {
            curItem = delegate.read(); // ๊ณ ๊ฐ ์ •๋ณด๋ฅผ ์ฝ์Œ.
        }

        Customer item = (Customer) curItem;
        curItem = null;

        if (item != null) {
            item.setTransactions(new ArrayList<>());

            // ๋‹ค์Œ ๊ณ ๊ฐ ๋ ˆ์ฝ”๋“œ๋ฅผ ๋งŒ๋‚˜๊ธฐ ์ „๊นŒ์ง€ ๊ฑฐ๋ž˜๋‚ด์—ญ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฝ๋Š”๋‹ค.
            while (peek() instanceof Transaction) {
                item.getTransactions().add((Transaction) curItem);
                curItem = null;
            }
        }

        return item;
    }

    private Object peek() throws Exception {
        if (curItem == null) {
            curItem = delegate.read();
        }
        return curItem;
    }



    @Override
    public void open(ExecutionContext executionContext) throws ItemStreamException {
        delegate.open(executionContext);
    }

    @Override
    public void update(ExecutionContext executionContext) throws ItemStreamException {
        delegate.update(executionContext);
    }

    @Override
    public void close() throws ItemStreamException {
        delegate.close();
    }
}

์—ฌ๊ธฐ์„œ ํ•ต์‹ฌ์€ read() ๋ฉ”์„œ๋“œ์ด๋‹ค. ํ•œ์ค„์”ฉ ์ฝ์–ด์˜ฌ ๋•Œ ๋‹ค์Œ ๊ณ ๊ฐ์ •๋ณด๊ฐ€ ๋‚˜์˜ฌ๋•Œ๊นŒ์ง€ ๊ฑฐ๋ž˜๋‚ด์—ญ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฝ์–ด ํ•ด๋‹น ๊ณ ๊ฐ์ด ๊ฐ€์ง€๊ณ  ์žˆ๊ฒŒ ํ•œ๋‹ค.

    @Bean
    @StepScope
    public FlatFileItemReader multiLineItemReader(@Value("#{jobParameters['customFile']}") PathResource resource) {
        return new FlatFileItemReaderBuilder<Customer>()
                .name("multiLineItemReader")
                .lineMapper(multiLineTokenizer())
                .resource(resource)
                .build();
    }

    @Bean
    public CustomerFileReader customerFileReader() {
        return new CustomerFileReader(multiLineItemReader(null));
    }

Job์—์„œ itemReader๋ถ€๋ถ„์— ์œ„์—์„œ ์ƒ์„ฑํ•œ CustomerFileReader๋ฅผ ์„ค์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

Customer(id=null, firstName=Warren, middleInitial=Q, lastName=Darrow, addressNumber=null, street=null, city=New York, state=IL, zipCode=76091, address=8272 4th Street, transactions=[Transaction(accountNumber=1165965, transactionDate=Sat Jan 22 00:13:29 KST 2011, amount=51.43, dateFormat=java.text.SimpleDateFormat@7c669100)])
Customer(id=null, firstName=Ann, middleInitial=V, lastName=Gates, addressNumber=null, street=null, city=Hollywood, state=NE, zipCode=37612, address=9247 Infinite Loop Drive, transactions=[])
Customer(id=null, firstName=Erica, middleInitial=I, lastName=Jobs, addressNumber=null, street=null, city=Aurora, state=IL, zipCode=36314, address=8875 Farnam Street, transactions=[Transaction(accountNumber=8116369, transactionDate=Fri Jan 21 20:40:52 KST 2011, amount=-14.83, dateFormat=java.text.SimpleDateFormat@7c669100), Transaction(accountNumber=8116369, transactionDate=Fri Jan 21 15:50:17 KST 2011, amount=-45.45, dateFormat=java.text.SimpleDateFormat@7c669100), Transaction(accountNumber=8116369, transactionDate=Fri Jan 21 16:52:46 KST 2011, amount=-74.6, dateFormat=java.text.SimpleDateFormat@7c669100), Transaction(accountNumber=8116369, transactionDate=Sat Jan 22 13:51:05 KST 2011, amount=48.55, dateFormat=java.text.SimpleDateFormat@7c669100), Transaction(accountNumber=8116369, transactionDate=Fri Jan 21 16:51:59 KST 2011, amount=98.53, dateFormat=java.text.SimpleDateFormat@7c669100)])

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ณ ๊ฐ์ด ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ฑฐ๋ž˜๋‚ด์—ญ์„ ์ถœ๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค.

MultiResourceItemReader

๋™์ผํ•œ ํฌ๋งท์œผ๋กœ ์ž‘์„ฑ๋œ ์—ฌ๋Ÿฌ๊ฐœ์˜ ํŒŒ์ผ์„ ์ฝ์–ด๋“ค์ด๋Š” ItemReader๋ฅผ ์ œ๊ณตํ•œ๋‹ค.

  • org.springframework.batch.item.file.MultiResourceItemReader

MultiResourceItemReader๋Š” ์ฝ์–ด์•ผํ•  ํŒŒ์ผ๋ช…์˜ ํŒจํ„ด์„ MultiResourceItemReader์˜ ์˜์กด์„ฑ์œผ๋กœ ์ •์˜ํ•œ๋‹ค.

public class MultiResourceCustomerFileReader implements ResourceAwareItemReaderItemStream<Customer> {

    private Object curItem = null;

    private ResourceAwareItemReaderItemStream<Object> delegate;

    public MultiResourceCustomerFileReader(ResourceAwareItemReaderItemStream<Object> delegate) {
        this.delegate = delegate;
    }


    /**
     * Resource๋ฅผ ์ฃผ์ž…ํ•จ์œผ๋กœ ItemReader๊ฐ€ ํŒŒ์ผ ๊ด€๋ฆฌํ•˜๋Š” ๋Œ€์‹ 
     * ๊ฐ ํŒŒ์ผ์„ ์Šคํ”„๋ง ๋ฐฐ์น˜๊ฐ€ ์ƒ์„ฑํ•ด ์ฃผ์ž…
     * @param resource
     */
    @Override
    public void setResource(Resource resource) {
        System.out.println(resource);
        this.delegate.setResource(resource);
    }

    @Override
    public Customer read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
        if (curItem == null) {
            curItem = delegate.read(); // ๊ณ ๊ฐ ์ •๋ณด๋ฅผ ์ฝ์Œ.
        }

        Customer item = (Customer) curItem;
        curItem = null;

        if (item != null) {
            item.setTransactions(new ArrayList<>());

            // ๋‹ค์Œ ๊ณ ๊ฐ ๋ ˆ์ฝ”๋“œ๋ฅผ ๋งŒ๋‚˜๊ธฐ ์ „๊นŒ์ง€ ๊ฑฐ๋ž˜๋‚ด์—ญ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฝ๋Š”๋‹ค.
            while (peek() instanceof Transaction) {
                item.getTransactions().add((Transaction) curItem);
                curItem = null;
            }
        }

        return item;
    }

    private Object peek() throws Exception {
        if (curItem == null) {
            curItem = delegate.read();
        }
        return curItem;
    }

    @Override
    public void open(ExecutionContext executionContext) throws ItemStreamException {
        delegate.open(executionContext);
    }

    @Override
    public void update(ExecutionContext executionContext) throws ItemStreamException {
        delegate.update(executionContext);
    }

    @Override
    public void close() throws ItemStreamException {
        delegate.close();
    }
}

์œ„์—์„œ ๋‹ค๋ฃฌ ItemStreamReader ์™€ ๋‹ค๋ฅธ ์ ์€ Resource ์ฃผ์ž…๋ถ€๋ถ„์ด๋‹ค. Resource๋ฅผ ์ฃผ์ž…ํ•˜๊ฒŒ ๋˜๋ฉด ํ•„์š”ํ•œ ๊ฐ ํŒŒ์ผ์„ ์Šคํ”„๋ง ๋ฐฐ์น˜๊ฐ€ ์ƒ์„ฑํ•ด ItemReader์— ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๋‹ค.

@Bean
    @StepScope
    public MultiResourceItemReader multiResourceItemReader(@Value("#{jobParameters['customFile']}") Resource[] resources) {
        return new MultiResourceItemReaderBuilder<>()
                .name("multiResourceItemReader")
                .resources(resources) // resources ๋ฐฐ์—ด
                .delegate(multiResourceCustomerFileReader()) // ์‹ค์ œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์œ„์ž„ ์ปดํฌ๋„ŒํŠธ
                .build();
    }

    @Bean
    public MultiResourceCustomerFileReader multiResourceCustomerFileReader() {
        return new MultiResourceCustomerFileReader(multiResourceCustomerItemReader());
    }

    @Bean
    @StepScope
    public FlatFileItemReader multiResourceCustomerItemReader() {
        return new FlatFileItemReaderBuilder<Customer>()
                .name("multiResourceCustomerItemReader")
                .lineMapper(multiResourceTokenizer())
                .build();
    }

์ฝ์–ด์•ผํ•  ํŒŒ์ผ ๋ชฉ๋ก(resources)์„ ์„ค์ •ํ•ด์ฃผ๊ณ , delegate()์— ์‹ค์ œ๋กœ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์œ„์ž„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ง€์ •ํ•ด์ฃผ๋ฉด๋œ๋‹ค.

์—ฌ๋Ÿฌ ๊ฐœ์˜ ํŒŒ์ผ์„ ๋‹ค๋ฃฐ๋•Œ๋Š” ์žฌ์‹œ์ž‘์„ ํ•˜๊ฒŒ๋˜๋Š” ์ƒํ™ฉ์—์„œ ์Šคํ”„๋ง๋ฐฐ์น˜๊ฐ€ ์ถ”๊ฐ€์ ์ธ ์•ˆ์ •์žฅ์น˜๋ฅผ ์ œ๊ณตํ•ด์ฃผ์ง€ ์•Š๋Š”๋‹ค. ์˜ˆ๋ฅผ๋“ค์–ด file1.csv, file2.csv, file3.csv๊ฐ€ ์žˆ๋Š”๋ฐ, file2.csv ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณผ์ •์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ์žก์ด ์‹คํŒจ ๋œ ์ดํ›„ ์žฌ์‹œ์ž‘์„ ํ• ๋•Œ file4.csv๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค๋ฉด, ์ตœ์ดˆ ์‹คํ–‰์‹œ file4.csv๊ฐ€ ์—†์—ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ํฌํ•จํ•˜์—ฌ ์‹คํ–‰ํ•œ๋‹ค.

์ด๋Ÿฌํ•œ ๋ฌธ์ œ์ ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋ฐฐ์น˜ ์‹คํ–‰ ์‹œ ์‚ฌ์šฉํ•  ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ๋ณ„๋„๋กœ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ด๋ฉฐ, ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ๋ชจ๋“  ํŒŒ์ผ์€ ์ƒˆ๋กœ์šด ๋””๋ ‰ํ„ฐ๋ฆฌ์— ๋„ฃ์–ด์ฃผ์–ด ํ˜„์žฌ ์ˆ˜ํ–‰์ค‘์ธ ์žก์— ์˜ํ–ฅ์„ ์ฃผ์ง€์•Š๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

XML

XML์€ ํŒŒ์ผ ๋‚ด ๋ฐ์ดํ„ฐ๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋Š” ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•ด ํŒŒ์ผ์— ํฌํ•จ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์„ค๋ช…ํ•˜๋ฏ€๋กœ, Flat file๊ณผ๋Š” ๋‹ค๋ฅด๋‹ค.

XML parser๋กœ ์ฃผ๋กœ DOM๊ณผ SAX๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค.

Dom vs SAX vs StAX

DOM(Document Object Model) ๋ฐฉ์‹

  • XML๋ฌธ์„œ ์ „์ฒด๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ๋กœ๋“œํ•˜์—ฌ ๊ฐ’์„ ์ฝ๋Š”๋‹ค.

  • XML๋ฌธ์„œ๋ฅผ ์ฝ์œผ๋ฉด ๋ชจ๋“  Element, Text, Attribute ๋“ฑ์— ๋Œ€ํ•œ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ์ด๋ฅผ Document ๊ฐ์ฒด๋กœ ๋ฆฌํ„ดํ•œ๋‹ค.

  • Document ๊ฐ์ฒด๋Š” DOM API์— ์•Œ๋งž๋Š” ํŠธ๋ฆฌ ๊ตฌ์กฐ์˜ ์ž๋ฐ” ๊ฐ์ฒด๋กœ ํ‘œํ˜„๋˜์–ด ์žˆ๋‹ค.

  • XML๋ฌธ์„œ๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์— ๋ชจ๋‘ ์˜ฌ๋ผ๊ฐ€ ์žˆ์–ด์„œ ๋…ธ๋“œ๋“ค์˜ ๊ฒ€์ƒ‰, ์ˆ˜์ •, ๊ตฌ์กฐ๋ณ€๊ฒฝ์ด ๋น ๋ฅด๊ณ  ์šฉ์ดํ•˜๋‹ค.

  • SAX ๋ฐฉ์‹ ๋ณด๋‹ค ์ง๊ด€์ ์ด๋ฉฐ ํŒŒ์‹ฑ์ด ๋‹จ์ˆœํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋ฐ˜์ ์œผ๋กœ DOM ๋ฐฉ์‹์„ ์ฑ„ํƒํ•˜์—ฌ ๊ฐœ๋ฐœํ•˜๊ฒŒ ๋œ๋‹ค.

SAX(Simple API for XML) ๋ฐฉ์‹

  • SAX ๋ฐฉ์‹์€ XML ๋ฌธ์„œ๋ฅผ ํ•˜๋‚˜์˜ ๊ธด ๋ฌธ์ž์—ด๋กœ ๊ฐ„์ฃผํ•œ๋‹ค.

  • XML๋ฌธ์„œ๋ฅผ ์•ž์—์„œ ๋ถ€ํ„ฐ ์ˆœ์ฐจ์ ์œผ๋กœ ์ฝ์–ด๊ฐ€๋ฉด์„œ ๋…ธ๋“œ๊ฐ€ ์—ด๋ฆฌ๊ณ  ๋‹ซํžˆ๋Š” ๊ณผ์ •์—์„œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

  • ๊ฐ๊ฐ์˜ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒ๋  ๋•Œ๋งˆ๋‹ค ์ˆ˜ํ–‰ํ•˜๊ณ ์ž ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๊ธฐ์ˆ ์„ ์ด์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•œ๋‹ค.

  • XML๋ฌธ์„œ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ์ „๋ถ€ ๋กœ๋”ฉํ•˜๊ณ  ํŒŒ์‹ฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด ์ ๊ณ  ๋‹จ์ˆœํžˆ ์ฝ๊ธฐ๋งŒ ํ• ๋•Œ ์†๋„๊ฐ€ ๋น ๋ฅด๋‹ค.

  • ๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ๋ฅผ ํ•ธ๋“ค๋งํ•˜์—ฌ ๋ณ€์ˆ˜์— ์ €์žฅํ•˜๊ณ  ํ™œ์šฉํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ณต์žกํ•˜๊ณ  ๋…ธ๋“œ ์ˆ˜์ •์ด์–ด๋ ต๋‹ค.

  • XML ์˜ค๋ธŒ์ ํŠธ์— Random Access๋ฅผ ํ•˜์ง€ ๋ชปํ•ด, ์ง€๋‚œ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์ฐธ์กฐํ•  ๊ฒฝ์šฐ ๋‹ค์‹œ ์ฒ˜์Œ๋ถ€ํ„ฐ ์ฝ์–ด์•ผํ•œ๋‹ค.

StAX(Streaming API for XML)

  • StAX๋Š” push ์™€ pull ๋ฐฉ์‹์„ ๋™์‹œ์— ์ œ๊ณตํ•˜๋Š” ํ•˜์ด๋ธŒ๋ฆฌ๋“œํ•œ ํ˜•ํƒœ

  • XML ๋ฌธ์„œ๋ฅผ ํŒŒ์‹ฑํ• ๋•Œ ํ•˜๋‚˜์˜ Fragment๋กœ ๊ตฌ๋ถ„

    • ์ •ํ•ด์ง„ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์ฝ์„๋•Œ๋Š” DOM ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋ฉฐ, Fragement๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์€ SAX์˜ Push ๋ฐฉ์‹์„ ์‚ฌ์šฉ

    • ์ฆ‰, ๊ฐ ์„ธ์…˜์„ ๋…๋ฆฝ์ ์œผ๋กœ ํŒŒ์‹ฑํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณต

์Šคํ”„๋ง ๋ฐฐ์น˜์—์„œ๋Š” StAX ํŒŒ์„œ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

StaxEventItemReader

  • org.springframework.batch.item.xml.StaxEventItemReader

<customers>
    <customer>
        <firstName>Laura</firstName>
        <middleInitial>O</middleInitial>
        <lastName>Minella</lastName>
        <address>2039 Wall Street</address>
        <city>Omaha</city>
        <state>IL</state>
        <zipCode>35446</zipCode>
        <transactions>
            <transaction>
                <accountNumber>829433</accountNumber>
                <transactionDate>2010-10-14 05:49:58</transactionDate>
                <amount>26.08</amount>
            </transaction>
        </transactions>
    </customer>
  ...
</customers>

์œ„ ์˜ˆ์ œ ํŒŒ์ผ์„ ํŒŒ์‹ฑํ•˜๋Š” Reader๋ฅผ ๊ตฌํ˜„ํ•ด๋ณผ๊ฒƒ์ด๋‹ค.

@Bean
    @StepScope
    public StaxEventItemReader<Customer> staxCustomerFileReader(@Value("#{jobParameters['customFile']}")Resource resource) {
        return new StaxEventItemReaderBuilder<Customer>()
                .name("staxCustomerFileReader")
                .resource(resource)
                .addFragmentRootElements("customer") // ํ”„๋ ˆ๊ทธ๋จผํŠธ ๋ฃจํŠธ ์—˜๋ฆฌ๋จผํŠธ
                .unmarshaller(customerMarshaller()) // XML์„ ๋„๋ฉ”์ธ ๊ฐ์ฒด๋กœ ๋ฐ˜ํ™˜ JAXB ์‚ฌ์šฉ
                .build();
    }
  • .addFragmentRootElements() : StaxEventItemReader๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด XML ํ”„๋ž˜ํฌ๋จผํŠธ ๋ฃจํŠธ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์ง€์ • XML๋‚ด์—์„œ Item์œผ๋กœ ์ทจ๊ธ‰ํ•  fragment์˜ root ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์‹๋ณ„ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ

  • .unmarshaller() : org.springframework.oxm.Unmarshaller ๊ตฌํ˜„์ฒด๋ฅผ ์ „๋‹ฌ ๋ฐ›์œผ๋ฉฐ, XML์„ ๋„๋ฉ”์ธ ๊ฐ์ฒด๋กœ ๋ฐ˜ํ™˜

์ด๋ฒˆ ์˜ˆ์ œ์—์„œ๋Š” org.springframework.oxm.jaxb.Jaxb2Marshaller๋ฅผ ์‚ฌ์šฉํ–ˆ์œผ๋ฉฐ, Jaxb2Marshaller๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์˜์กด์„ฑ ์ถ”๊ฐ€๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

build.gradle

dependencies {
    implementation 'org.springframework:spring-oxm'
    implementation 'javax.xml.bind:jaxb-api:2.3.1'
    implementation 'javax.activation:activation:1.1'
    implementation 'com.sun.xml.bind:jaxb-core:2.3.0.1'
    implementation 'com.sun.xml.bind:jaxb-impl:2.3.1'
}

JAXB ์˜์กด์„ฑ๊ณผ Spring OXM ๋ชจ๋“ˆ๋กœ JAXB๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์Šคํ”„๋ง ์ปดํฌ๋„ŒํŠธ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

XML์„ ํŒŒ์‹ฑํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋ ค๋ฉด ๋„๋งค์ธ ๊ฐ์ฒด์— JAXB ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•ด์ค„ ๊ฒƒ์ด๋‹ค.

@NoArgsConstructor
@Getter
@Setter
@ToString
@XmlRootElement
public class Customer {

    private Long id;
    private String firstName;
    private String middleInitial;
    private String lastName;
    private String addressNumber;
    private String street;
    private String city;
    private String state;
    private String zipCode;

    private String address; // customAddressMapper

    private List<Transaction> transactions;

    @XmlElementWrapper(name = "transactions")
    @XmlElement(name = "transaction")
    public void setTransactions(List<Transaction> transactions) {
        this.transactions = transactions;
    }
}
@Getter
@Setter
@ToString
@XmlType(name = "transaction")
public class Transaction {
    private String accountNumber;
    private Date transactionDate;
    private Double amount;

    @Setter(value = AccessLevel.NONE)
    private DateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");

}

๋„๋ฉ”์ธ ๊ฐ์ฒด ์„ค์ •์ด ๋๋‚ฌ์œผ๋ฉด, ๊ฐ ๋ธ”๋ก์„ ํŒŒ์‹ฑํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•  Unmarshaller๋ฅผ ๊ตฌํ˜„ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

		@Bean
    public Jaxb2Marshaller customerMarshaller() {
        Jaxb2Marshaller jaxb2Marshaller = new Jaxb2Marshaller();
        jaxb2Marshaller.setClassesToBeBound(Customer.class, Transaction.class); // ๋„๋ฉ”์ธ ๊ฐ์ฒด

        return jaxb2Marshaller;
    }

JSON

JsonItemReader

  • org.springframework.batch.item.json.JsonItemReader

  • ์ฒญํฌ๋ฅผ ์ฝ์–ด ๊ฐ์ฒด๋กœ ํŒŒ์‹ฑํ•œ๋‹ค.

  • ์‹ค์ œ ํŒŒ์‹ฑ ์ž‘์—…์€ JsonObjectReader ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„์ฒด์— ์œ„์ž„ํ•œ๋‹ค.

JsonObjectReader

์‹ค์ œ๋กœ JSON ๊ฐ์ฒด๋ฅผ ํŒŒ์‹ฑํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค. ์Šคํ”„๋ง ๋ฐฐ์น˜๋Š” 2๊ฐœ์˜ JsonObjectReader๋ฅผ ์ œ๊ณตํ•ด์ค€๋‹ค.

  • Jackson

  • Gson

[
  {
    "firstName": "Laura",
    "middleInitial": "O",
    "lastName": "Minella",
    "address": "2039 Wall Street",
    "city": "Omaha",
    "state": "IL",
    "zipCode": "35446",
    "transactions": [
      {
        "accountNumber": 829433,
        "transactionDate": "2010-10-14 05:49:58",
        "amount": 26.08
      }
    ]
  },
  ...
]

์œ„ ๊ตฌ์กฐ๋กœ๋˜์–ด์žˆ๋Š” JSON์„ ํŒŒ์‹ฑํ•ด ๋ณผ๊ฒƒ์ด๋‹ค.

		@Bean
    @StepScope
    public JsonItemReader<Customer> jsonFileReader(@Value("#{jobParameters['customFile']}") Resource resource) {

        // Jackson์ด JSON์„ ์ฝ๊ณ  ์“ฐ๋Š”๋ฐ ์‚ฌ์šฉํ•˜๋Š” ์ฃผ์š” ํด๋ž˜์Šค
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));

        JacksonJsonObjectReader<Customer> jsonObjectReader = new JacksonJsonObjectReader<>(Customer.class); // ๋ฐ˜ํ™˜ํ•  ํด๋ž˜์Šค ์„ค์ •
        jsonObjectReader.setMapper(objectMapper); // ObjectMapper

        return new JsonItemReaderBuilder<Customer>()
                .name("jsonFileReader")
                .jsonObjectReader(jsonObjectReader) // ํŒŒ์‹ฑ์— ์‚ฌ์šฉ
                .resource(resource)
                .build();
    }
  • ObjectMapper๋Š” Jackson์ด JSON์„ ์ฝ๊ณ  ์“ฐ๋Š”๋ฐ ์‚ฌ์šฉํ•˜๋Š” ์ฃผ์š” ํด๋ž˜์Šค๋กœ ์ปค์Šคํ…€ ๋ฐ์ดํ„ฐ ํฌ๋งท๋“ค์„ ์„ค์ •ํ•˜๋ฉด๋œ๋‹ค.

  • JacksonJsonObjectReader ์ƒ์„ฑ์‹œ ๋ฐ˜ํ™˜ํ•  ํด๋ž˜์Šค๋ฅผ ์„ค์ •ํ•˜๊ณ , ์ปค์Šคํ…€ํ•œ ObjectMapper๋ฅผ ์„ค์ •ํ•ด์ฃผ๋ฉด๋œ๋‹ค.

  • JsonItemReaderBuilder๋Š” ํŒŒ์‹ฑ์— ์‚ฌ์šฉํ•  JsonObjectReader๋ฅผ ์„ค์ •ํ•ด์ฃผ๋ฉด๋œ๋‹ค.

Database Reader

Cursor๋Š” ํ‘œ์ค€ java.sql.ResultSet์œผ๋กœ ๊ตฌํ˜„๋˜๋ฉฐ, ResultSet์ด open๋˜๋ฉด next() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ๋งˆ๋‹ค ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋ฐฐ์น˜ ๋ ˆ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์™€ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์ฆ‰, Cursor ๋ฐฉ์‹์€ DB์™€ ์ปค๋„ฅ์…˜์„ ๋งบ์€ ํ›„, Cursor๋ฅผ ํ•œ์นธ์”ฉ ์˜ฎ๊ธฐ๋ฉด์„œ ์ง€์†์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

(CursorItemReader๋Š” streaming์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌ)

Cursor๋Š” ํ•˜๋‚˜์˜ Connection์œผ๋กœ Batch๊ฐ€ ๋๋‚ ๋•Œ๊ฐ€์ง€ ์‚ฌ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์— Batch๊ฐ€ ๋๋‚˜๊ธฐ์ „์— DB์™€ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ Connection์ด ๋Š์–ด์งˆ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, DB์™€ SocketTimeout์„ ์ถฉ๋ถ„ํžˆ ํฐ ๊ฐ’์œผ๋กœ ์„ค์ •ํ•ด์•ผํ•œ๋‹ค. (๋„คํŠธ์›Œํฌ ์˜ค๋ฒ„ํ—ค๋“œ ์ถ”๊ฐ€) ์ถ”๊ฐ€๋กœ ResultSet์€ ์Šค๋ ˆ๋“œ ์•ˆ์ „์ด ๋ณด์žฅ๋˜์ง€ ์•Š์•„ ๋‹ค์ค‘ ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.

  • JdbcCursorItemReader

  • HibernateCursorItemReader

  • StoredProcedureItemReader

Paging ๋ฐฉ์‹์€ ํ•œ๋ฒˆ์— ์ง€์ •ํ•œ PageSize๋งŒํผ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

SpringBatch์—์„œ offset๊ณผ limit์„ PageSize์— ๋งž๊ฒŒ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์ค€๋‹ค. ๋‹ค๋งŒ ๊ฐ ์ฟผ๋ฆฌ๋Š” ๊ฐœ๋ณ„์ ์œผ๋กœ ์‹คํ–‰๋˜๋ฏ€๋กœ, ํŽ˜์ด์ง•์‹œ ๊ฒฐ๊ณผ๋ฅผ ์ •๋ ฌ(order by)ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.

Batch ์ˆ˜ํ–‰์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ๊ฒฝ์šฐ์—๋Š” PagingItemReader๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. Paging์˜ ๊ฒฝ์šฐ ํ•œ ํŽ˜์ด์ง€๋ฅผ ์ฝ์„๋•Œ๋งˆ๋‹ค Connection์„ ๋งบ๊ณ  ๋Š๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ฌด๋ฆฌ ๋งŽ์€ ๋ฐ์ดํ„ฐ๋ผ๋„ ํƒ€์ž„์•„์›ƒ๊ณผ ๋ถ€ํ•˜ ์—†์ด ์ˆ˜ํ–‰๋  ์ˆ˜ ์žˆ๋‹ค.

  • JdbcPagingItemReader

  • HibernatePagingItemReader

  • JpaPagingItemReader

JDBC

JdbcCursorItemReader

/**
 * --job.name=jdbcCursorItemReaderJob city=Chicago
 */
@RequiredArgsConstructor
@Configuration
public class JdbcCursorCustomerJob {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;
    private final DataSource dataSource;

    @Bean
    public Job jdbcCursorItemReaderJob(){
        return jobBuilderFactory.get("jdbcCursorItemReaderJob")
                .start(jdbcCursorItemReaderStep())
                .build();
    }

    @Bean
    public Step jdbcCursorItemReaderStep(){
        return stepBuilderFactory.get("jdbcCursorItemReaderStep")
                .<Customer, Customer>chunk(10)
                .reader(customerJdbcCursorItemReader())
                .writer(customerJdbcCursorItemWriter())
                .build();
    }

    @Bean
    public JdbcCursorItemReader<Customer> customerJdbcCursorItemReader() {
        return new JdbcCursorItemReaderBuilder<Customer>()
                .name("customerJdbcCursorItemReader")
                .dataSource(dataSource)
                .sql("select * from customer where city = ?")
                .rowMapper(new BeanPropertyRowMapper<>(Customer.class))
                .preparedStatementSetter(citySetter(null))      // ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค์ •
                .build();
    }

    /**
     * ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ SQL๋ฌธ์— ๋งคํ•‘
     * ArgumentPreparedStatementSetter๋Š” ๊ฐ์ฒด ๋ฐฐ์—ด์— ๋‹ด๊ธด ์ˆœ์„œ๋Œ€๋กœ ?์˜ ์œ„์น˜์— ๊ฐ’์œผ๋กœ ์„ค์ •
     * @param city
     * @return
     */
    @Bean
    @StepScope
    public ArgumentPreparedStatementSetter citySetter(@Value("#{jobParameters['city']}") String city) {
        return new ArgumentPreparedStatementSetter(new Object[]{city});
    }

    @Bean
    public ItemWriter customerJdbcCursorItemWriter() {
        return (items) -> items.forEach(System.out::println);
    }
}
  • <T, T> chunk(int chunkSize) : ์ฒซ๋ฒˆ์งธ T๋Š” Reader์—์„œ ๋ฐ˜ํ™˜ํ•  ํƒ€์ž…, ๋‘๋ฒˆ์งธ T๋Š” Writer์— ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„˜์–ด์˜ฌ ํƒ€์ž…์ด๋‹ค.

  • fetchSize : DB์—์„œ ํ•œ๋ฒˆ์— ๊ฐ€์ ธ์˜ฌ ๋ฐ์ดํ„ฐ ์–‘์„ ๋‚˜ํƒ€๋‚ธ๋‹ค. Paging์€ ์‹ค์ œ ์ฟผ๋ฆฌ๋ฅผ limit, offset์œผ๋กœ ๋ถ„ํ•  ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ˜๋ฉด, Cursor๋Š” ๋ถ„ํ•  ์ฒ˜๋ฆฌ์—†์ด ์‹คํ–‰๋˜๋‚˜ ๋‚ด๋ถ€์ ์œผ๋กœ ๊ฐ€์ ธ์˜จ๋Š” ๋ฐ์ดํ„ฐ๋Š” FetchSize๋งŒํผ ๊ฐ€์ ธ์™€ read()๋ฅผ ํ†ตํ•ด์„œ ํ•˜๋‚˜์”ฉ ๊ฐ€์ ธ์˜จ๋‹ค.

  • dataSource : DB์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•  DataSource๊ฐ์ฒด

  • rowMapper : ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ์ธ์Šคํ„ด์Šค๋กœ ๋งคํ•‘ํ•˜๊ธฐ ์œ„ํ•œ ๋งคํผ

    • BeanPropertyRowMapper ๋ฅผ ์‚ฌ์šฉํ•ด ๋„๋ฉ”์ธ ๊ฐ์ฒด์™€ ๋งคํ•‘ํ•ด์ค€๋‹ค.

  • sql : Reader์—์„œ ์‚ฌ์šฉํ•  ์ฟผ๋ฆฌ๋ฌธ

  • preparedStatementSetter: SQL๋ฌธ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค์ •

  • name : Reader์˜ ์ด๋ฆ„, ExecutionContext์— ์ €์žฅ๋˜์–ด์งˆ ์ด๋ฆ„

JdbcPagingItemReader

/**
 * --job.name=jdbcPagingItemReaderJob city=Chicago
 */
@RequiredArgsConstructor
@Configuration
public class JdbcPagingCustomerJob {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;
    private final DataSource dataSource;

    @Bean
    public Job jdbcPagingItemReaderJob(){
        return jobBuilderFactory.get("jdbcPagingItemReaderJob")
                .start(jdbcPagingItemReaderStep())
                .build();
    }

    @Bean
    public Step jdbcPagingItemReaderStep(){
        return stepBuilderFactory.get("jdbcPagingItemReaderStep")
                .<Customer, Customer>chunk(10)
                .reader(customerJdbcPagingItemReader(null, null))
                .writer(customerJdbcPagingItemWriter())
                .build();
    }

    @Bean
    @StepScope
    public JdbcPagingItemReader<Customer> customerJdbcPagingItemReader(
            PagingQueryProvider pagingQueryProvider, @Value("#{jobParameters['city']}") String city) {

        Map<String, Object> params = new HashMap<>(1);
        params.put("city", city);


        return new JdbcPagingItemReaderBuilder<Customer>()
                .name("customerJdbcPagingItemReader")   // Reader์˜ ์ด๋ฆ„, ExecutionContext์— ์ €์žฅ๋˜์–ด์งˆ ์ด๋ฆ„
                .dataSource(dataSource)                 // DB์— ์ ‘๊ทผํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•  DataSource๊ฐ์ฒด
                .queryProvider(pagingQueryProvider)     // PagingQueryProvider
                .parameterValues(params)                // SQL ๋ฌธ์— ์ฃผ์ž…ํ•ด์•ผํ•  ํŒŒ๋ผ๋ฏธํ„ฐ
                .pageSize(10)                           // ๊ฐ ํŽ˜์ด์ง€ ํฌ
                .rowMapper(new BeanPropertyRowMapper<>(Customer.class)) // ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ์ธ์Šคํ„ด์Šค๋กœ ๋งคํ•‘ํ•˜๊ธฐ ์œ„ํ•œ ๋งคํผ
                .build();
    }

    @Bean
    public ItemWriter customerJdbcPagingItemWriter() {
        return (items) -> items.forEach(System.out::println);
    }


    @Bean
    public SqlPagingQueryProviderFactoryBean pagingQueryProvider(){
        SqlPagingQueryProviderFactoryBean queryProvider = new SqlPagingQueryProviderFactoryBean();
        queryProvider.setDataSource(dataSource); // ์ œ๊ณต๋œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํƒ€์ž…์„ ๊ฒฐ์ •(setDatabaseType ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํƒ€์ž… ์„ค์ •๋„ ๊ฐ€๋Šฅ)
        queryProvider.setSelectClause("*");
        queryProvider.setFromClause("from customer");
        queryProvider.setWhereClause("where city = :city");

        Map<String, Order> sortKeys = new HashMap<>();
        sortKeys.put("lastName", Order.ASCENDING);

        queryProvider.setSortKeys(sortKeys);

        return queryProvider;
    }
}

PagingItemReader๋Š” PagingQueryProvider๋ฅผ ํ†ตํ•ด ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ์ƒ์„ฑํ•˜๋Š” ์ด์œ ๋Š” ๊ฐ DB์—๋Š” Paging์„ ์ง€์›ํ•˜๋Š” ์ž์ฒด์ ์ธ ์ „๋žต์ด ์žˆ์œผ๋ฉฐ, Spring์€ ๊ฐ DB์˜ Paging ์ „๋žต์— ๋งž์ถฐ ๊ตฌํ˜„๋˜์–ด์•ผ๋งŒ ํ•œ๋‹ค.

public SqlPagingQueryProviderFactoryBean() {
        this.providers.put(DatabaseType.DB2, new Db2PagingQueryProvider());
        this.providers.put(DatabaseType.DB2VSE, new Db2PagingQueryProvider());
        this.providers.put(DatabaseType.DB2ZOS, new Db2PagingQueryProvider());
        this.providers.put(DatabaseType.DB2AS400, new Db2PagingQueryProvider());
        this.providers.put(DatabaseType.DERBY, new DerbyPagingQueryProvider());
        this.providers.put(DatabaseType.HSQL, new HsqlPagingQueryProvider());
        this.providers.put(DatabaseType.H2, new H2PagingQueryProvider());
        this.providers.put(DatabaseType.MYSQL, new MySqlPagingQueryProvider());
        this.providers.put(DatabaseType.ORACLE, new OraclePagingQueryProvider());
        this.providers.put(DatabaseType.POSTGRES, new PostgresPagingQueryProvider());
        this.providers.put(DatabaseType.SQLITE, new SqlitePagingQueryProvider());
        this.providers.put(DatabaseType.SQLSERVER, new SqlServerPagingQueryProvider());
        this.providers.put(DatabaseType.SYBASE, new SybasePagingQueryProvider());
    }

Spring Batch์—์„œ๋Š” SqlPagingQueryProviderFactoryBean์„ ํ†ตํ•ด DataSource ์„ค์ • ๊ฐ’์„ ๋ณด๊ณ , ์œ„ Provider์ค‘ ํ•˜๋‚˜๋ฅผ ์ž๋™ ์„ ํƒํ•˜๋„๋ก ํ•œ๋‹ค.

SpringBatch์—์„œ offset๊ณผ limit์„ PageSize์— ๋งž๊ฒŒ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์ค€๋‹ค. ๋‹ค๋งŒ ๊ฐ ์ฟผ๋ฆฌ๋Š” ๊ฐœ๋ณ„์ ์œผ๋กœ ์‹คํ–‰๋˜๋ฏ€๋กœ, ๋™์ผํ•œ ๋ ˆ์ฝ”๋“œ ์ •๋ ฌ ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•˜๋ ค๋ฉด ํŽ˜์ด์ง•์‹œ ๊ฒฐ๊ณผ๋ฅผ ์ •๋ ฌ(order by)ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.

๋˜ํ•œ, ์ด ์ •๋ ฌํ‚ค๊ฐ€ ResultSet ๋‚ด์—์„œ ์ค‘๋ณต๋˜์ง€ ์•Š์•„์•ผํ•œ๋‹ค.

SELECT * FROM customer WHERE city = :city ORDER BY lastName ASC LIMIT 10

์‹คํ–‰๋œ ์ฟผ๋ฆฌ ๋กœ๊ทธ๋ฅผ ๋ณด๋ฉด Paging Size์ธ LIMIT 10์ด ๋“ค์–ด๊ฐ„ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

Hibernate

  • ์ž๋ฐ” ORM ๊ธฐ์ˆ ๋กœ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ์ฒด ์ง€ํ–ฅ ๋ชจ๋ธ์„ ๊ด€๊ณ„ํ˜• ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ๋งคํ•‘ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณต

  • XML or Annotation์„ ์‚ฌ์šฉํ•ด ๊ฐ์ฒด๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”๋กœ ๋งคํ•‘

  • ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์งˆ์˜ํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ œ๊ณต

Hibernate ์„ธ์…˜ ๊ตฌํ˜„์ฒด์— ๋”ฐ๋ผ์„œ ๋‹ค๋ฅด๊ฒŒ ์ž‘๋™ํ•œ๋‹ค.

  • ๋ณ„๋„ ์„ค์ •์—†์ด Hibernate๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ผ๋ฐ˜์ ์ธ stateful ์„ธ์…˜ ๊ตฌํ˜„์ฒด๋ฅผ ์‚ฌ์šฉ

    • ์˜ˆ๋ฅผ ๋“ค์–ด ๋ฐฑ๋งŒ๊ฑด์˜ ์•„์ดํ…œ์„ ์ฝ๊ณ  ์ฒ˜๋ฆฌํ•œ๋‹ค๋ฉด Hibernate ์„ธ์…˜์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์กฐํšŒํ•  ๋•Œ ์•„์ดํ…œ์„ ์บ์‹œ์— ์Œ“์œผ๋ฉฐ OutOfMemoryException์ด ๋ฐœ์ƒ

  • Persistence๋กœ ์‚ฌ์šฉํ•˜๋ฉด, ์ง์ ‘ JDBC๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋ณด๋‹ค ๋” ํฐ ๋ถ€ํ•˜๋ฅผ ์œ ๋ฐœ

    • ๋ ˆ์ฝ”๋“œ ๋ฐฑ๋งŒ ๊ฑด์„ ์ฒ˜๋ฆฌํ•  ๋•Œ๋Š” ํ•œ๊ฑด๋‹น ms ๋‹จ์œ„์˜ ์ฐจ์ด๋„ ๊ฑฐ๋Œ€ํ•œ ์ฐจ์ด๊ฐ€ ๋œ๋‹ค.

  • ์Šคํ”„๋ง ๋ฐฐ์น˜๋Š” ์ด๋Ÿฌํ•œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋„๋ก HibernateCursorItemReader, HibernatePagingItemReader๋ฅผ ๊ฐœ๋ฐœ

    • ์ปค๋ฐ‹์‹œ ์„ธ์…˜์„ flushํ•˜๋ฉฐ ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ์— ๊ด€๊ณ„๊ฐ€ ์žˆ๋Š” ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ด์ค€๋‹ค.

build.gradle ์˜์กด์„ฑ ์ถ”๊ฐ€

compileOnly 'org.springframework.boot:spring-boot-starter-data-jpa'

Domain ๊ฐ์ฒด ์ˆ˜์ •

@Entity
@Table(name = "customer")
public class Customer {

    @Id
    private Long id; // pk
    private String firstName;
    private String middleInitial;
    private String lastName;
    private String address;
    private String city;
    private String state;
    private String zipCode;
}
์–ด๋…ธํ…Œ์ด์…˜
์„ค๋ช…

@Entity

๋งคํ•‘ํ•  ๊ฐ์ฒด๊ฐ€ Entity์ž„์„ ๋‚˜ํƒ€๋ƒ„

@Table

Entity๊ฐ€ ๋งคํ•‘๋˜๋Š” ํ…Œ์ด๋ธ” ์ง€์ •

@Id

PK๊ฐ’ ์ง€์ •

TransactionManager ์ปค์Šคํ…€

ํ•˜์ด๋ฒ„๋„ค์ดํŠธ ์„ธ์…˜๊ณผ Datasource๋ฅผ ํ•ฉ์นœ TransactionManager๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

@Component
public class HibernateBatchConfigurer extends DefaultBatchConfigurer {

    private DataSource dataSource;
    private SessionFactory sessionFactory;
    private PlatformTransactionManager transactionManager;

    /**
     * Datasource connection๊ณผ ํ•˜์ด๋ฒ„๋„ค์ดํŠธ ์„ธ์…˜ ์„ค์ •
     * @param dataSource
     * @param entityManagerFactory
     */
    public HibernateBatchConfigurer(DataSource dataSource,
                                    EntityManagerFactory entityManagerFactory) {
        super(dataSource);
        this.dataSource = dataSource;
        this.sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);

        // ํ•˜์ด๋ฒ„๋„ค์ดํŠธ ํŠธ๋žœ์žญ์…˜ ์„ค์ •
        this.transactionManager = new HibernateTransactionManager(this.sessionFactory);
    }

    @Override
    public PlatformTransactionManager getTransactionManager() {
        return this.transactionManager;
    }
}

BatchConfigurer์˜ ์ปค์Šคํ…€ ๊ตฌํ˜„์ฒด๋ฅผ ์‚ฌ์šฉํ•ด HibernateTransactionManager๋ฅผ ๊ตฌ์„ฑํ–ˆ๋‹ค.

HibernateCursorItemReader

/**
 * --job.name=hibernateCursorItemReaderJob city=Chicago
 */
@RequiredArgsConstructor
@Configuration
public class HibernateCursorCustomerJob {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;
    private final EntityManagerFactory entityManagerFactory;

    @Bean
    public Job hibernateCursorItemReaderJob(){
        return jobBuilderFactory.get("hibernateCursorItemReaderJob")
                .start(hibernateCursorItemReaderStep())
                .build();
    }

    @Bean
    public Step hibernateCursorItemReaderStep(){
        return stepBuilderFactory.get("hibernateCursorItemReaderStep")
                .<Customer, Customer>chunk(10)
                .reader(customerHibernateCursorItemReader(null))
                .writer(customerHibernateCursorItemWriter())
                .build();
    }

    @Bean
    @StepScope
    public HibernateCursorItemReader<Customer> customerHibernateCursorItemReader(@Value("#{jobParameters['city']}") String city) {
        return new HibernateCursorItemReaderBuilder<Customer>()
                .name("customerHibernateCursorItemReader") // Reader์˜ ์ด๋ฆ„, ExecutionContext์— ์ €์žฅ๋˜์–ด์งˆ ์ด๋ฆ„
                .sessionFactory(entityManagerFactory.unwrap(SessionFactory.class))
                .queryString("from Customer where city = :city")        // HQL ์ฟผ๋ฆฌ
                .parameterValues(Collections.singletonMap("city", city)) // SQL ๋ฌธ์— ์ฃผ์ž…ํ•ด์•ผํ•  ํŒŒ๋ผ๋ฏธํ„ฐ
                .build();
    }

    @Bean
    public ItemWriter customerHibernateCursorItemWriter() {
        return (items) -> items.forEach(System.out::println);
    }
}

ํ•˜์ด๋ฒ„๋„ค์ดํŠธ ์ฟผ๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ 4๊ฐ€์ง€๊ฐ€ ์กด์žฌํ•œ๋‹ค.

์˜ต์…˜
ํƒ€์ž…
์„ค๋ช…
์˜ˆ์ œ

queryName

String

ํ•˜์ด๋ฒ„๋„ค์ดํŠธ ๊ตฌ์„ฑ์— ํฌํ•จ๋œ ๋„ค์ž„๋“œ ํ•˜์ด๋ฒ„๋„ค์ดํŠธ ์ฟผ๋ฆฌ ์ฐธ์กฐ

https://www.baeldung.com/hibernate-named-query

queryString

String

์Šคํ”„๋ง ๊ตฌ์„ฑ์— ์ถ”๊ฐ€ํ•˜๋Š” HQL ์ฟผ๋ฆฌ

.queryString("from Customer where city = :city")

queryProvider

HibernateQueryProvider

ํ•˜์ด๋ฒ„๋„ค์ดํŠธ ์ฟผ๋ฆฌ(HQL)๋ฅผ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์œผ๋กœ ๋นŒ๋“œ

nativeQuery

String

๋„ค์ดํ‹ฐ๋ธŒ SQL ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•œ ๋’ค ๊ฒฐ๊ณผ๋ฅผ ํ•˜์ด๋ฒ„๋„ค์ดํŠธ๋กœ ๋งคํ•‘ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ

https://data-make.tistory.com/616

HibernatePagingItemReader

/**
 * --job.name=hibernatePagingItemReaderJob city=Chicago
 */
@RequiredArgsConstructor
@Configuration
public class HibernatePagingCustomerJob {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;
    private final EntityManagerFactory entityManagerFactory;

    @Bean
    public Job hibernatePagingItemReaderJob() {
        return jobBuilderFactory.get("hibernatePagingItemReaderJob")
                .start(hibernatePagingItemReaderStep())
                .build();
    }

    @Bean
    public Step hibernatePagingItemReaderStep() {
        return stepBuilderFactory.get("hibernatePagingItemReaderStep")
                .<Customer, Customer>chunk(10)
                .reader(customerHibernatePagingItemReader(null))
                .writer(customerHibernatePagingItemWriter())
                .build();
    }

    @Bean
    @StepScope
    public HibernatePagingItemReader<Customer> customerHibernatePagingItemReader(@Value("#{jobParameters['city']}") String city) {
        return new HibernatePagingItemReaderBuilder<Customer>()
                .name("customerHibernatePagingItemReader") // Reader์˜ ์ด๋ฆ„, ExecutionContext์— ์ €์žฅ๋˜์–ด์งˆ ์ด๋ฆ„
                .sessionFactory(entityManagerFactory.unwrap(SessionFactory.class))
                .queryString("from Customer where city = :city")        // HQL ์ฟผ๋ฆฌ
                .parameterValues(Collections.singletonMap("city", city)) // SQL ๋ฌธ์— ์ฃผ์ž…ํ•ด์•ผํ•  ํŒŒ๋ผ๋ฏธํ„ฐ
                .pageSize(10)       // Cursor์™€ ์œ ์ผํ•œ ์ฐจ์ด์ ! pageSize ์„ค์ •
                .build();
    }

    @Bean
    public ItemWriter customerHibernatePagingItemWriter() {
        return (items) -> items.forEach(System.out::println);
    }
}

Cursor ๋ฐฉ๋ฒ•๊ณผ ์œ ์ผํ•˜๊ฒŒ ๋‹ค๋ฅธ ์ ์€ .pageSize()๋กœ ์‚ฌ์šฉํ•  ํŽ˜์ด์ง€ ํฌ๊ธฐ๋ฅผ ์ง€์ •ํ•ด์•ผํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

JPA

JPA(Java Persisstence API)๋Š” ORM ์˜์—ญ์—์„œ ํ‘œ์ค€ํ™”๋œ ์ ‘๊ทผ๋ฒ•์„ ์ œ๊ณตํ•œ๋‹ค. Hibernate๊ฐ€ ์ดˆ๊ธฐ JPA์— ์˜๊ฐ์„ ์คฌ์œผ๋ฉฐ, ํ˜„์žฌ๋Š” Hibernate๊ฐ€ JPA ๋ช…์„ธ๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋‹ค.

build.gradle ์˜์กด์„ฑ ์ถ”๊ฐ€

compileOnly 'org.springframework.boot:spring-boot-starter-data-jpa'

spring-boot-starter-data-jpa๋Š” JPA๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ ๋ชจ๋“  ํ•„์ˆ˜ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํฌํ•จ๋ผ์žˆ๋‹ค.

JpaCursorItemReader

Spring Batch 4.3์ด ๋ฆด๋ฆฌ์ฆˆ ๋˜๋ฉด์„œ JpaCursorItemReader ๊ฐ€ ๋„์ž…๋˜์—ˆ๋‹ค. ์ด์ „๋ฒ„์ „๊นŒ์ง€๋Š” ์ œ๊ณตํ•˜์ง€ ์•Š์•˜๋‹ค.

/**
 * --job.name=jpaCursorItemReaderJob city=Chicago
 */
@RequiredArgsConstructor
@Configuration
public class JpaCursorCustomerJob {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;
    private final EntityManagerFactory entityManagerFactory;

    @Bean
    public Job jpaCursorItemReaderJob(){
        return jobBuilderFactory.get("jpaCursorItemReaderJob")
                .start(jpaCursorItemReaderStep())
                .build();
    }

    @Bean
    public Step jpaCursorItemReaderStep(){
        return stepBuilderFactory.get("jpaCursorItemReaderStep")
                .<Customer, Customer>chunk(10)
                .reader(customerJpaCursorItemReader(null))
                .writer(customerJpaCursorItemWriter())
                .build();
    }

    @Bean
    @StepScope
    public JpaCursorItemReader<Customer> customerJpaCursorItemReader(@Value("#{jobParameters['city']}") String city) {
        return new JpaCursorItemReaderBuilder<Customer>()
                .name("customerJpaCursorItemReader")
                .entityManagerFactory(entityManagerFactory)
                .queryString("select c from Customer c where c.city = :city")
                .parameterValues(Collections.singletonMap("city", city))
                .build();
    }

    @Bean
    public ItemWriter customerJpaCursorItemWriter() {
        return (items) -> items.forEach(System.out::println);
    }
}

JpaPagingItemReader

/**
 * --job.name=jpaPagingItemReaderJob city=Chicago
 */
@RequiredArgsConstructor
@Configuration
public class JpaPagingCustomerJob {

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;
    private final EntityManagerFactory entityManagerFactory;

    @Bean
    public Job jpaPagingItemReaderJob(){
        return jobBuilderFactory.get("jpaPagingItemReaderJob")
                .start(jpaPagingItemReaderStep())
                .build();
    }

    @Bean
    public Step jpaPagingItemReaderStep(){
        return stepBuilderFactory.get("jpaPagingItemReaderStep")
                .<Customer, Customer>chunk(10)
                .reader(customerJpaPagingItemReader(null))
                .writer(customerJpaPagingItemWriter())
                .build();
    }

    @Bean
    @StepScope
    public JpaPagingItemReader<Customer> customerJpaPagingItemReader(@Value("#{jobParameters['city']}") String city) {
        return new JpaPagingItemReaderBuilder<Customer>()
                .name("customerJpaPagingItemReader")
                .entityManagerFactory(entityManagerFactory)
                .queryString("select c from Customer c where c.city = :city")
                .parameterValues(Collections.singletonMap("city", city))
                .pageSize(10)
                .build();
    }

    @Bean
    public ItemWriter customerJpaPagingItemWriter() {
        return (items) -> items.forEach(System.out::println);
    }
}

.entityManagerFactory๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒƒ ์ด์™ธ์— Jdbc์™€ ํฌ๊ฒŒ ๋‹ค๋ฅธ ์ ์€ ์—†์œผ๋ฉฐ, Cursor์™€ ๋‹ค๋ฅธ์ ์€ pageSize()๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

JPA์—์„œ๋Š” .queryProvider()๋กœ Query ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•ด ์ฟผ๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

MyBatisPagingItemReader

<!--mybatis-config.xml-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="defaultStatementTimeout" value="25"/>
    </settings>
</configuration>
package spring.batch.practice.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

@Slf4j
@Configuration
public class MysqlMybatisConfig {

    @Value("${mybatis.mapper-locations}")
    private String mapperLocations;

    @Bean(name = "mybatisDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    public DataSource dataSource(){
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "mybatisSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("mybatisDataSource") DataSource dataSource, ApplicationContext applicationContext) throws Exception{
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setConfigLocation(applicationContext.getResource("classpath:mybatis/mybatis-config.xml"));
        Resource[] resources = new PathMatchingResourcePatternResolver().getResources(mapperLocations);
        System.out.println(resources[0].getURL());
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));

        return sqlSessionFactoryBean.getObject();

    }

    @Bean
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("mybatisSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="spring.batch.practice.dao.PayMapper">

    <select id="selectPayList" parameterType="hashmap" resultType="spring.batch.practice.domain.Pay">
	<![CDATA[
        SELECT ID, AMOUNT, TX_NAME, TX_DATE_TIME
        FROM PAY
        WHERE AMOUNT <= #{amount}
        ]]>
	</select>

</mapper>
@Slf4j
@RequiredArgsConstructor
@Configuration
public class MybatisPagingItemReaderJobConfiguration {
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    @Autowired
    @Qualifier("mybatisSqlSessionFactory")
    private SqlSessionFactory sqlSessionFactory;


    private static final int CHUNK_SIZE = 10;

    @Bean
    public Job mybatisPagingItemReaderJob() throws Exception {
        return jobBuilderFactory.get("mybatisPagingItemReaderJob")
                .start(mybatisPagingItemReaderStep())
                .build();
    }
    @Bean
    public Step mybatisPagingItemReaderStep() throws Exception {
        return stepBuilderFactory.get("mybatisPagingItemReaderStep")
                .<Pay, Pay>chunk(CHUNK_SIZE)
                .reader(mybatisPagingItemReader())
                .writer(mybatisPagingItemWriter())
                .build();
    }
    @Bean
    public MyBatisPagingItemReader<Pay> mybatisPagingItemReader() throws Exception {
        Map<String, Object> params = new HashMap<>();
        params.put("amount", 2000);

        return new MyBatisPagingItemReaderBuilder<Pay>()
                .pageSize(CHUNK_SIZE)
                .sqlSessionFactory(sqlSessionFactory)
                .queryId("spring.batch.practice.dao.PayMapper.selectPayList")
                .parameterValues(params)
                .build();
    }
    @Bean
    public ItemWriter<Pay> mybatisPagingItemWriter() {
        return list -> {
            for (Pay pay : list) {
                log.info("Current Pay={}", pay);
            }
        };
    }
}

Spring Data Repository

Spring Data๋Š” ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ํŠน์ • ์ธํ„ฐํŽ˜์ด์Šค ์ค‘ ํ•˜๋‚˜๋ฅผ ์ƒ์†ํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉ์ž๊ฐ€ ์ •์˜ํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ๊ฐ€ ํ•ด๋‹น ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.

์Šคํ”„๋ง ๋ฐฐ์น˜๋Š” ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ์˜ PagingAndSotringRepository๋ฅผ ํ™œ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์Šคํ”„๋ง ๋ฐ์ดํ„ฐ์™€ ํ˜ธํ™˜์„ฑ์ด ์ข‹๋‹ค.

RepositoryItemReader

RepositoryItemReader๋Š” JdbcPagingItemReader ๋‚˜ HibernatePagingItemReader๋ฅผ ์‚ฌ์šฉํ• ๋•Œ์™€ ๋™์ผํ•˜๊ฒŒ PagingAndSotringRepository๋ฅผ ์‚ฌ์šฉํ•ด์„œ Paging ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

RepositoryItemReader๋Š” ์–ด๋–ค ์ €์žฅ์†Œ๊ฑด ์ƒ๊ด€์—†์ด ํ•ด๋‹น ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ์— ์งˆ์˜๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์—์„œ ItemReader์™€ ์ฐจ์ด๊ฐ€ ์žˆ๋‹ค.

public interface CustomerRepository extends JpaRepository<Customer, Long> {
    Page<Customer> findByCity(String city, Pageable pageRequest);
}

PagingAndSotringRepository๋ฅผ ์ƒ์†ํ•˜๋Š” Repository๋ฅผ ์ƒ์„ฑํ•ด city ์กฐ๊ฑด์œผ๋กœ ์กฐํšŒํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•˜์˜€๋‹ค.

		@Bean
    @StepScope
    public RepositoryItemReader<Customer> customerRepositoryItemReader(@Value("#{jobParameters['city']}") String city) {
        return new RepositoryItemReaderBuilder<Customer>()
                .name("customerRepositoryItemReader")
                .arguments(Collections.singletonList(city)) // pageable ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ œ์™ธํ•œ arguments
                .methodName("findByCity")                   // ํ˜ธ์ถœํ•  ๋ฉ”์„œ๋“œ๋ช…
                .repository(customerRepository)             // Repository ๊ตฌํ˜„์ฒด
                .sorts(Collections.singletonMap("lastName", Sort.Direction.ASC))
                .build();
    }

์ฃผ์˜ ์‚ฌํ•ญ

  • JpaRepository๋ฅผ ListItemReader, QueueItemReader์— ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ๋œ๋‹ค.

    • ์ด๋ ‡๊ฒŒ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒฝ์šฐ Spring Batch์˜ ์žฅ์ ์ธ Paging & Cursor ๊ตฌํ˜„์ด ์—†์–ด ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

    • JpaRepository๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ RepositoryItemReader๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค.

  • Hibernate, JPA๋“ฑ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•œ Reader ์‚ฌ์šฉ์‹œ fetch size์™€ chunk size๋Š” ๋™์ผํ•œ ๊ฐ’์„ ์œ ์ง€ํ•ด์•ผ ํ•œ๋‹ค.

ItemReaderAdapter

Adapter๋Š” ๋‹ค๋ฅธ ์—˜๋ฆฌ๋ฉ˜ํŠธ์™€ ๋ž˜ํ•‘ํ•˜์—ฌ ์Šคํ”„๋ง ๋ฐฐ์น˜๊ฐ€ ํ•ด๋‹น ์—˜๋ฆฌ๋จผํŠธ์™€ ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•œ๋‹ค.

  • org.springframework.batch.item.adapter.ItemReaderAdapter

public class ItemReaderAdapter<T> extends AbstractMethodInvokingDelegator<T> implements ItemReader<T> {

	/**
	 * @return return value of the target method.
	 */
	@Nullable
	@Override
	public T read() throws Exception {
		return invokeDelegateMethod();
	}

}

ํ˜ธ์ถœ ๋Œ€์ƒ ์„œ๋น„์Šค์˜ ์ฐธ์กฐ์™€ ํ˜ธ์ถœํ•  ๋ฉ”์„œ๋“œ์˜ ์ด๋ฆ„์„ ์˜์กด์„ฑ์œผ๋กœ ๋ฐ›๋Š”๋‹ค.

  • ItemReaderAdapter๊ฐ€ ๋งค๋ฒˆ ํ˜ธ์ถœํ•  ๋•Œ๋งˆ๋‹ค ๋ฐ˜ํ™˜๋˜๋Š” ๊ฐ์ฒด๋Š” ItemReader๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ์ฒด์ด๋‹ค.

  • ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ์ฒ˜๋ฆฌํ•˜๋ฉด ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ๋Š” ๋ฐ˜๋“œ์‹œ null์„ ๋ฐ˜ํ™˜ํ•ด์•ผํ•œ๋‹ค. ์Šคํ”„๋ง ๋ฐฐ์น˜์—๊ฒŒ ํ•ด๋‹น step์˜ ์ž…๋ ฅ์„ ๋ชจ๋‘ ์™„๋ฃŒํ–ˆ์Œ์„ ์•Œ๋ฆฌ๋Š” ๊ฒƒ์ด๋‹ค.

@Component
public class CustomerService {
    private List<Customer> customers;
    private int curIndex;

    private String [] firstNames = {"Michael", "Warren", "Ann", "Terrence",
            "Erica", "Laura", "Steve", "Larry"};
    private String middleInitial = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private String [] lastNames = {"Gates", "Darrow", "Donnelly", "Jobs",
            "Buffett", "Ellison", "Obama"};
    private String [] streets = {"4th Street", "Wall Street", "Fifth Avenue",
            "Mt. Lee Drive", "Jeopardy Lane",
            "Infinite Loop Drive", "Farnam Street",
            "Isabella Ave", "S. Greenwood Ave"};
    private String [] cities = {"Chicago", "New York", "Hollywood", "Aurora",
            "Omaha", "Atherton"};
    private String [] states = {"IL", "NY", "CA", "NE"};

    private Random generator = new Random();

    public CustomerService() {
        curIndex = 0;

        customers = new ArrayList<>();

        for(int i = 0; i < 100; i++) {
            customers.add(buildCustomer());
        }
    }

    private Customer buildCustomer() {
        Customer customer = new Customer();

        customer.setId((long) generator.nextInt(Integer.MAX_VALUE));
        customer.setFirstName(
                firstNames[generator.nextInt(firstNames.length - 1)]);
        customer.setMiddleInitial(
                String.valueOf(middleInitial.charAt(
                        generator.nextInt(middleInitial.length() - 1))));
        customer.setLastName(
                lastNames[generator.nextInt(lastNames.length - 1)]);
        customer.setAddress(generator.nextInt(9999) + " " +
                streets[generator.nextInt(streets.length - 1)]);
        customer.setCity(cities[generator.nextInt(cities.length - 1)]);
        customer.setState(states[generator.nextInt(states.length - 1)]);
        customer.setZipCode(String.valueOf(generator.nextInt(99999)));

        return customer;
    }

    public Customer getCustomer() {
        Customer cust = null;

        if(curIndex < customers.size()) {
            cust = customers.get(curIndex);
            curIndex++;
        }

        return cust;
    }
}

Customer ๊ฐ์ฒด์˜ ๋ชฉ๋ก์„ ๋ฌด์ž‘์œ„๋กœ ์ƒ์„ฑํ•˜๋Š” ์„œ๋น„์Šค์ด๋‹ค.

    @Bean
    public ItemReaderAdapter<Customer> customerServiceItemReader() {
        ItemReaderAdapter<Customer> adapter = new ItemReaderAdapter<>();

        adapter.setTargetObject(customerService);
        adapter.setTargetMethod("getCustomer");

        return adapter;
    }

ItemReaderAdapter์— ๊ธฐ์กด ์„œ๋น„์Šค ์˜ค๋ธŒ์ ํŠธ์™€ ๋ฉ”์„œ๋“œ๋ช…์„ ์ „๋‹ฌํ•˜๋ฉด๋œ๋‹ค.

์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ

์ž…๋ ฅ์—์„œ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฝ๋Š” ์ค‘์— ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ๊ฐ€์ง€์ด๋‹ค.

  1. ์˜ˆ์™ธ๋ฅผ ๋˜์ณ ์ฒ˜๋ฆฌ๋ฅผ ๋ฉˆ์ถ”๊ธฐ

  2. ํŠน์ • ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ ๋ ˆ์ฝ”๋“œ ๊ฑด๋„ˆ๋„๊ธฐ(skip)

Skip

  1. ์–ด๋–ค ์กฐ๊ฑด์—์„œ ๋ ˆ์ฝ”๋“œ๋ฅผ skipํ• ์ง€(์–ด๋–ค ์˜ˆ์™ธ๋ฅผ ๋ฌด์‹œํ•  ๊ฒƒ์ธ์ง€)

  2. ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ๋ ˆ์ฝ”๋“œ๋ฅผ skip ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ• ๊ฒƒ์ธ์ง€

๋ ˆ์ฝ”๋“œ๋ฅผ skipํ• ์ง€ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•  ๋•Œ ์œ„ ๋‘๊ฐ€์ง€ ์š”์†Œ๋ฅผ ๊ณ ๋ คํ•ด์•ผํ•œ๋‹ค.

Skip์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์šฐ์„  faultToLerant()๋ผ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด์•ผํ•œ๋‹ค.

๋ฉ”์„œ๋“œ
์„ค๋ช…

skipLimit()

skip ํ—ˆ์šฉ ํšŒ์ˆ˜. ํ—ˆ์šฉ ํšŒ์ˆ˜๋ฅผ ๋„˜์–ด๊ฐ€๋ฉด job์€ ์‹คํŒจํ•œ๋‹ค. skip()๊ณผ ๋ฐ˜๋“œ์‹œ ๊ฐ™์ด ์จ์•ผ ํ•œ๋‹ค

skip()

ํ•ด๋‹น exception์ด ๋ฐœ์ƒํ–ˆ์„๋•Œ skip

noSkip()

ํ•ด๋‹น exception์ด ๋ฐœ์ƒํ•˜๋ฉด skip์„ ํ•˜์ง€ ์•Š๊ณ  ์˜ค๋ฅ˜๋ฅผ ๋‚ด๊ฒ ๋‹ค๋Š” ๊ฒƒ

skipPolicy()

์šฉ์ž ์ •์˜๋กœ skip์— ๋Œ€ํ•œ policy๋ฅผ ๋งŒ๋“ค์–ด์„œ ์ ์šฉํ•˜๊ณ  ์‹ถ์„๋•Œ ์‚ฌ์šฉ

    @Bean
    public Step skipRecordCopyFileStep() {
        return this.stepBuilderFactory.get("skipRecordCopyFileStep")
                .<Customer, Customer>chunk(10)
                .reader(null)
                .writer(null)
                .faultTolerant()
                .skip(Exception.class)
                .noSkip(ParseException.class)
                .skipLimit(10)
                .build();
    }

SkipPolicy

public interface SkipPolicy {

	boolean shouldSkip(Throwable t, int skipCount) throws SkipLimitExceededException;
}

SkipPolicy ๊ตฌํ˜„์ฒด๋Š” skipํ•  ์˜ˆ์™ธ์™€ ํ—ˆ์šฉ ํšŸ์ˆ˜๋ฅผ ํŒ๋ณ„ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, boolean ๊ฐ’์œผ๋กœ ๋‚ด๋ถ€ ๋กœ์ง์„ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

public class FileVerificationSkipper implements SkipPolicy {
    @Override
    public boolean shouldSkip(Throwable t, int skipCount) throws SkipLimitExceededException {
        
        if (t instanceof FileNotFoundException) {
            return false;
        } else if (t instanceof ParseException && skipCount < 10) {
            return true;
        } else {
            return false;
        }
    }
}

์˜ค๋ฅ˜ ๋กœ๊ทธ ๋‚จ๊ธฐ๊ธฐ

ItemListener๋ฅผ ์‚ฌ์šฉํ•ด ์ž˜๋ชป๋œ ๋ ˆ์ฝ”๋“œ๋ฅผ ๊ธฐ๋กํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃฐ ๊ฒƒ์ด๋‹ค.

public interface ItemReadListener<T> extends StepListener {

	void beforeRead();
	
	void afterRead(T item);
	
	void onReadError(Exception ex);
}

์ž˜๋ชป๋œ ๋ ˆ์ฝ”๋“œ๋ฅผ ์ฝ์—ˆ์„ ๋•Œ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๊ธฐ ์œ„ํ•ด์„œ onReadError ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฐ”๋ผ์ด๋“œํ•ด ItemListenerSupport๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—๋Ÿฌ๋ฅผ ๊ธฐ๋กํ•œ๋‹ค.

๋‹จ์ˆœํžˆ ํŒŒ์ผ์„ ์ฝ์–ด์˜ฌ ๋•Œ๋Š” ์œ„์™€ ๊ฐ™์ด ์ฒ˜๋ฆฌํ•˜๋ฉด ๋˜์ง€๋งŒ, DB๋ฅผ ์ด์šฉํ•œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ ๊ฒฝ์šฐ์—๋Š” ์‹ค์ œ DB์ž…์ถœ๋ ฅ์„ ์Šคํ”„๋ง ์ž์ฒด๋‚˜ ํ•˜์ด๋ฒ„๋„ค์ดํŠธ์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋ฏ€๋กœ, ์Šคํ”„๋ง ๋ฐฐ์น˜์—์„œ ์ฒ˜๋ฆฌํ•  ์˜ˆ์™ธ๊ฐ€ ๋งŽ์ง€ ์•Š๋‹ค.

onReadError ํŒŒ์ผ ์˜ค๋ฅ˜ ๋กœ๊ทธ ๋‚จ๊ธฐ๊ธฐ

@Slf4j
public class CustomerItemListener {

    @OnReadError
    public void onReadError(Exception e) {
        if (e instanceof FlatFileParseException) {
            FlatFileParseException ffpe = (FlatFileParseException) e;

            StringBuilder sb = new StringBuilder();
            sb.append("์˜ค๋ฅ˜ ๋ฐœ์ƒ ๋ผ์ธ : ");
            sb.append(ffpe.getLineNumber());
            sb.append("์ž…๋ ฅ๊ฐ’ : ");
            sb.append(ffpe.getInput());

            log.error(sb.toString(), ffpe);
        } else {
            log.error("์˜ค๋ฅ˜ ๋ฐœ์ƒ", e);
        }
    }
}
    @Bean
		public CustomerItemListener customerItemListener(){
      	return new CustomerItemListener();
    }
    @Bean
    public Step copyFileStep() {
        return this.stepBuilderFactory.get("skipRecordCopyFileStep")
                .<Customer, Customer>chunk(10)
                .reader(null)
                .writer(null)
                .faultTolerant()
                .skip(Exception.class)
                .skipLimit(10)
          			.listener(customerItemListener())
                .build();
    }

์ฐธ๊ณ  :

cursorvspaging

๊ณต์‹๋ฌธ์„œ
https://gohlab2017.tistory.com/3
MybatisItemReader
image-20210209153930033