├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── spring-boot-scope-in-action ├── src │ ├── main │ │ ├── resources │ │ │ └── application.yml │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── renuevo │ │ │ ├── proxy │ │ │ ├── ProtoInterface.java │ │ │ ├── ProtoInterfaceImpl.java │ │ │ ├── ProtoProxy.java │ │ │ └── ProtoInterFaceBean.java │ │ │ ├── scope │ │ │ ├── Single.java │ │ │ ├── Proto.java │ │ │ └── ScopeWrapper.java │ │ │ └── SpringBootScopeApplication.java │ └── test │ │ └── java │ │ └── com │ │ └── github │ │ └── renuevo │ │ └── SpringBootScopeApplicationTests.java ├── build.gradle └── assets │ ├── good-image.jpg │ ├── interface-bean.PNG │ ├── prototype-bean.png │ ├── singleton-bean.png │ ├── prototype-getbean.png │ ├── interface-proxy-bean.PNG │ ├── prototype-proxy-bean.png │ ├── prototype-in-singleton.png │ ├── singleton-in-prototype.png │ ├── interface-proxy-bean-safe.PNG │ └── singleton-getbean-and-getPrototype.png ├── spring-boot-rest-docs-in-action ├── README.md ├── src │ ├── main │ │ ├── resources │ │ │ └── application.yml │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── renuevo │ │ │ ├── SpringBootRestDocsApplication.java │ │ │ └── ServletInitializer.java │ └── test │ │ └── java │ │ └── utils │ │ └── ApiDocumentUtils.java └── build.gradle ├── spring-boot-exception-in-action ├── README.md ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── github │ └── renuevo │ ├── SpringBootExceptionApplication.java │ ├── ServletInitializer.java │ ├── controller │ └── Controller.java │ ├── exception │ └── ErrorCode.java │ └── response │ └── ErrorResponse.java ├── spring-boot-jpa-in-action ├── src │ ├── main │ │ ├── resources │ │ │ └── application.yml │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── renuevo │ │ │ ├── SpringBootJpaApplication.java │ │ │ ├── n_plus_one │ │ │ ├── AcademyRepository.java │ │ │ ├── Subject.java │ │ │ ├── Academy.java │ │ │ └── AcademyService.java │ │ │ └── ServletInitializer.java │ └── test │ │ └── java │ │ └── com │ │ └── github │ │ └── renuevo │ │ └── n_plus_one │ │ └── AcademyServiceTest.java └── build.gradle ├── spring-boot-batch-in-action ├── src │ ├── main │ │ ├── resources │ │ │ ├── read_sample │ │ │ │ ├── sample.txt │ │ │ │ ├── sampel2.txt │ │ │ │ ├── sample_data.csv │ │ │ │ ├── sample_json_data.json │ │ │ │ └── sample_xml_data.xml │ │ │ ├── elastic_query │ │ │ │ └── size_query.json │ │ │ ├── application.yml │ │ │ └── schema.sql │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── renuevo │ │ │ ├── querydsl │ │ │ ├── expression │ │ │ │ ├── OrderExpression.java │ │ │ │ ├── WhereStringFunction.java │ │ │ │ ├── WhereNumberFunction.java │ │ │ │ ├── WhereExpression.java │ │ │ │ └── Expression.java │ │ │ ├── options │ │ │ │ ├── QuerydslNoOffsetOptions.java │ │ │ │ ├── QuerydslNoOffsetStringOptions.java │ │ │ │ └── QuerydslNoOffsetNumberOptions.java │ │ │ ├── QuerydslNoOffsetPagingItemReader.java │ │ │ └── QuerydslNoOffsetIdPagingItemReader.java │ │ │ ├── vo │ │ │ ├── XmlItemVo.java │ │ │ ├── ItemVo.java │ │ │ ├── ElasticReaderTestVo.java │ │ │ ├── ElasticWriterTestVo.java │ │ │ ├── JsonItemVo.java │ │ │ └── CsvItemVo.java │ │ │ ├── process │ │ │ ├── JpaChainingProcessFirst.java │ │ │ └── JpaChainingProcessSecond.java │ │ │ ├── ServletInitializer.java │ │ │ ├── entity │ │ │ ├── Tax.java │ │ │ ├── Pay2.java │ │ │ └── Pay.java │ │ │ ├── config │ │ │ ├── JobSecurityConfig.java │ │ │ ├── ElasticRestClientConfig.java │ │ │ ├── QuerydslItemReaderJobConfig.java │ │ │ ├── TxtFileItemReaderJobConfig.java │ │ │ ├── JsonFileItemReaderJobConfig.java │ │ │ ├── AutoIncrementJobConfig.java │ │ │ ├── JpaPagingItemReaderJobConfig.java │ │ │ ├── XmlFileItemReaderJobConfig.java │ │ │ ├── InMemoryBatchConfig.java │ │ │ ├── ElasticItemReaderJobConfig.java │ │ │ ├── ElasticItemScrollReaderJobConfig.java │ │ │ ├── MultiFileItemReaderJobConfig.java │ │ │ ├── JpaItemWriterJobConfig.java │ │ │ ├── CustomItemWriterJobConfig.java │ │ │ └── JdbcCursorItemReaderJobConfig.java │ │ │ ├── data │ │ │ └── StepShareData.java │ │ │ ├── writer │ │ │ ├── JpaItemListWriter.java │ │ │ └── ElasticItemWriter.java │ │ │ ├── SpringBatchApplication.java │ │ │ ├── component │ │ │ └── ParameterTasklet.java │ │ │ ├── job │ │ │ ├── JobParameterComponentConfig.java │ │ │ ├── JobSimpleConfig.java │ │ │ ├── JobParameterConfig.java │ │ │ └── JobSecondConfig.java │ │ │ ├── controller │ │ │ └── JobController.java │ │ │ └── reader │ │ │ ├── ElasticItemReader.java │ │ │ └── ElasticItemScrollReader.java │ └── test │ │ └── java │ │ └── com │ │ └── github │ │ └── renuevo │ │ └── SpringBatchApplicationTests.java ├── assets │ ├── job-step.PNG │ ├── job-step1.PNG │ ├── spribatch.png │ ├── jobparameter.PNG │ ├── json-reader.PNG │ ├── job-completed.PNG │ ├── meta-data-erd.png │ ├── parameter-exist.PNG │ ├── spring-version.PNG │ ├── step-completed.PNG │ ├── multi-file-reader.PNG │ ├── elastic-reader-data.png │ ├── spring-batch-chunk.png │ ├── spring-batch-layers.png │ ├── JdbcPagingItemReader-1.png │ ├── JdbcPagingItemReader-2.png │ ├── jobparameter-component.PNG │ ├── chunk-oriented-processing.png │ ├── spring-boot-batch-param.PNG │ ├── batch-parameter-controller.PNG │ ├── jobparameter-component-fail.PNG │ ├── spring-boot-batch-instance.PNG │ ├── jobparameter-component-complete.PNG │ └── Ch02_SpringBatchArchitecture_Architecture_StepTaskletFlow.png └── build.gradle ├── spring-boot-kafka-in-action ├── src │ └── main │ │ ├── resources │ │ └── application.yml │ │ └── java │ │ └── com │ │ └── github │ │ └── renuevo │ │ ├── model │ │ └── DataModel.java │ │ ├── SpringBootKafkaApplication.java │ │ ├── ServletInitializer.java │ │ ├── consumer │ │ ├── service │ │ │ └── KafkaConsumerService.java │ │ └── config │ │ │ └── KafkaConsumerConfig.java │ │ └── producer │ │ ├── controller │ │ └── ProducerController.java │ │ └── config │ │ └── KafkaProducerConfig.java └── build.gradle ├── spring-boot-guide-in-action ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── github │ └── renuevo │ ├── SpringBootGuideApplication.java │ └── ServletInitializer.java ├── spring-boot-rest-client-in-action ├── src │ └── main │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── renuevo │ │ │ ├── dto │ │ │ ├── CommonResponseDto.java │ │ │ ├── ElasticDataDto.java │ │ │ ├── ElasticParamDto.java │ │ │ ├── PostRequest.java │ │ │ ├── NaverBlogParamDto.java │ │ │ └── NaverResponse.java │ │ │ ├── common │ │ │ ├── ErrorResponse.java │ │ │ ├── TestResponseKotlin.kt │ │ │ ├── TestResponse.java │ │ │ ├── NaverProperty.java │ │ │ ├── CommonConfig.java │ │ │ ├── WebfluxConfig.java │ │ │ └── controller │ │ │ │ └── CommonController.java │ │ │ ├── feign │ │ │ ├── client │ │ │ │ ├── error │ │ │ │ │ ├── SampleErrorFeignClient.java │ │ │ │ │ ├── SampleErrorRetryFeignClient.java │ │ │ │ │ ├── SampleErrorReactiveFeignClient.java │ │ │ │ │ ├── SampleErrorRetryReactiveFeignClient.java │ │ │ │ │ └── SampleErrorReactiveFeignCustomClient.java │ │ │ │ ├── SampleBuildFeignClient.java │ │ │ │ ├── SampleFeignClient.java │ │ │ │ ├── SampleLocalFeignClient.java │ │ │ │ ├── SampleReactiveFeignClient.java │ │ │ │ ├── SampleCircuitFeignClient.java │ │ │ │ ├── SampleReactiveLocalFeignClient.java │ │ │ │ └── SampleFeignClientBuild.java │ │ │ ├── annotation │ │ │ │ ├── ToJsonExpander.java │ │ │ │ ├── CustomParam.java │ │ │ │ └── CustomParamAnnotationProcess.java │ │ │ └── config │ │ │ │ ├── ReactiveCustomFeignConfig.java │ │ │ │ ├── CustomFeignConfig.java │ │ │ │ ├── CustomFeignErrorDecoder.java │ │ │ │ ├── FeignErrorDecoder.java │ │ │ │ ├── ReactiveFeignErrorDecoder.java │ │ │ │ ├── FeignRetryErrorDecoder.java │ │ │ │ └── ReactiveFeignConfig.java │ │ │ ├── resttemplate │ │ │ ├── controller │ │ │ │ └── RestTemplateController.java │ │ │ ├── service │ │ │ │ └── RestTemplateService.java │ │ │ └── RestTemplateConfig.java │ │ │ ├── webclient │ │ │ ├── config │ │ │ │ ├── WebClientQueryEncoder.java │ │ │ │ └── WebClientConfig.java │ │ │ └── controller │ │ │ │ └── WebClientController.java │ │ │ ├── SpringBootRestClientApplication.java │ │ │ └── elastic │ │ │ └── ElasticRestClientConfig.java │ │ └── resources │ │ └── application.yml ├── README.md └── build.gradle ├── spring-boot-redis-in-action ├── src │ └── main │ │ ├── resources │ │ └── application.yml │ │ └── java │ │ └── com │ │ └── github │ │ └── renuevo │ │ ├── domain │ │ ├── person │ │ │ ├── PersonRedisRepository.java │ │ │ ├── PersonEntity.java │ │ │ └── PersonController.java │ │ └── simple │ │ │ ├── RedisController.java │ │ │ └── SimpleTestService.java │ │ ├── SpringBootRedisApplication.java │ │ └── config │ │ ├── RedisConfig.java │ │ └── ReactiveRedisConfig.java └── build.gradle ├── spring-boot-event-in-action ├── build.gradle └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── renuevo │ │ │ ├── repo │ │ │ ├── store │ │ │ │ ├── JpaStoreRepository.java │ │ │ │ ├── event │ │ │ │ │ ├── OrderEvent.java │ │ │ │ │ └── OrderEventHandler.java │ │ │ │ ├── StoreMapper.java │ │ │ │ ├── Store.java │ │ │ │ └── StoreRdbRepository.java │ │ │ └── order │ │ │ │ ├── JpaOrderRepository.java │ │ │ │ ├── OrderHistory.java │ │ │ │ ├── OrderRdbRepository.java │ │ │ │ └── OrderMapper.java │ │ │ ├── domain │ │ │ ├── order │ │ │ │ ├── OrderRepository.java │ │ │ │ └── OrderDataModel.java │ │ │ └── store │ │ │ │ ├── StoreRepository.java │ │ │ │ └── StoreDataModel.java │ │ │ ├── SpringBootEventApplication.java │ │ │ └── ServletInitializer.java │ └── resources │ │ └── application.yml │ └── test │ ├── resources │ └── application.yml │ └── java │ └── com │ └── github │ └── renuevo │ ├── setup │ ├── OrderHistoryBuilder.java │ └── StoreBuilder.java │ └── SpringBootEventApplicationTests.java ├── settings.gradle ├── .gitignore ├── README.md └── gradlew.bat /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spring-boot-scope-in-action/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /spring-boot-rest-docs-in-action/README.md: -------------------------------------------------------------------------------- 1 | # Spring Rest Docs Init :tada: -------------------------------------------------------------------------------- /spring-boot-rest-docs-in-action/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /spring-boot-exception-in-action/README.md: -------------------------------------------------------------------------------- 1 | # Spring Exception Control 2 | 3 | -------------------------------------------------------------------------------- /spring-boot-jpa-in-action/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | jpa: 3 | show-sql: true -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/resources/read_sample/sample.txt: -------------------------------------------------------------------------------- 1 | 첫번째 2 | 두번째 3 | 세번째 4 | 네번째 5 | 다섯번째 -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/resources/read_sample/sampel2.txt: -------------------------------------------------------------------------------- 1 | 여섯번째 2 | 일곱번째 3 | 여덟번째 4 | 아홉번째 5 | 열번째 -------------------------------------------------------------------------------- /spring-boot-kafka-in-action/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | kafka: 2 | bootstrapAddress: localhost:9092 3 | 4 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/resources/elastic_query/size_query.json: -------------------------------------------------------------------------------- 1 | { 2 | "from": $from, 3 | "size": $size 4 | } -------------------------------------------------------------------------------- /spring-boot-scope-in-action/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter' 3 | } -------------------------------------------------------------------------------- /spring-boot-exception-in-action/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-web' 3 | } -------------------------------------------------------------------------------- /spring-boot-guide-in-action/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-web' 3 | } 4 | -------------------------------------------------------------------------------- /spring-boot-rest-docs-in-action/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | 3 | dependencies { 4 | implementation 'org.springframework.boot:spring-boot-starter-web' 5 | } 6 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/job-step.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/job-step.PNG -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/job-step1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/job-step1.PNG -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/spribatch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/spribatch.png -------------------------------------------------------------------------------- /spring-boot-scope-in-action/assets/good-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-scope-in-action/assets/good-image.jpg -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/jobparameter.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/jobparameter.PNG -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/json-reader.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/json-reader.PNG -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/job-completed.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/job-completed.PNG -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/meta-data-erd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/meta-data-erd.png -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/parameter-exist.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/parameter-exist.PNG -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/spring-version.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/spring-version.PNG -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/step-completed.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/step-completed.PNG -------------------------------------------------------------------------------- /spring-boot-scope-in-action/assets/interface-bean.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-scope-in-action/assets/interface-bean.PNG -------------------------------------------------------------------------------- /spring-boot-scope-in-action/assets/prototype-bean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-scope-in-action/assets/prototype-bean.png -------------------------------------------------------------------------------- /spring-boot-scope-in-action/assets/singleton-bean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-scope-in-action/assets/singleton-bean.png -------------------------------------------------------------------------------- /spring-boot-scope-in-action/src/main/java/com/github/renuevo/proxy/ProtoInterface.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.proxy; 2 | 3 | public interface ProtoInterface { 4 | } 5 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/multi-file-reader.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/multi-file-reader.PNG -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/dto/CommonResponseDto.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.dto; 2 | 3 | public class CommonResponseDto { 4 | } 5 | -------------------------------------------------------------------------------- /spring-boot-scope-in-action/assets/prototype-getbean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-scope-in-action/assets/prototype-getbean.png -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/elastic-reader-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/elastic-reader-data.png -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/spring-batch-chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/spring-batch-chunk.png -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/spring-batch-layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/spring-batch-layers.png -------------------------------------------------------------------------------- /spring-boot-scope-in-action/assets/interface-proxy-bean.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-scope-in-action/assets/interface-proxy-bean.PNG -------------------------------------------------------------------------------- /spring-boot-scope-in-action/assets/prototype-proxy-bean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-scope-in-action/assets/prototype-proxy-bean.png -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/JdbcPagingItemReader-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/JdbcPagingItemReader-1.png -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/JdbcPagingItemReader-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/JdbcPagingItemReader-2.png -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/jobparameter-component.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/jobparameter-component.PNG -------------------------------------------------------------------------------- /spring-boot-scope-in-action/assets/prototype-in-singleton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-scope-in-action/assets/prototype-in-singleton.png -------------------------------------------------------------------------------- /spring-boot-scope-in-action/assets/singleton-in-prototype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-scope-in-action/assets/singleton-in-prototype.png -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/chunk-oriented-processing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/chunk-oriented-processing.png -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/spring-boot-batch-param.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/spring-boot-batch-param.PNG -------------------------------------------------------------------------------- /spring-boot-scope-in-action/assets/interface-proxy-bean-safe.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-scope-in-action/assets/interface-proxy-bean-safe.PNG -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/batch-parameter-controller.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/batch-parameter-controller.PNG -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/jobparameter-component-fail.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/jobparameter-component-fail.PNG -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/spring-boot-batch-instance.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/spring-boot-batch-instance.PNG -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/jobparameter-component-complete.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/jobparameter-component-complete.PNG -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/resources/read_sample/sample_data.csv: -------------------------------------------------------------------------------- 1 | number,item 2 | 1,one 3 | 2,two 4 | 3,three 5 | 4,four 6 | 5,five 7 | 6,six 8 | 7,seven 9 | 8,eight 10 | 9,nine 11 | 10,ten -------------------------------------------------------------------------------- /spring-boot-scope-in-action/assets/singleton-getbean-and-getPrototype.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-scope-in-action/assets/singleton-getbean-and-getPrototype.png -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/querydsl/expression/OrderExpression.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.querydsl.expression; 2 | 3 | public enum OrderExpression { 4 | ASC, DESC 5 | } 6 | -------------------------------------------------------------------------------- /spring-boot-scope-in-action/src/main/java/com/github/renuevo/scope/Single.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.scope; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | @Component 6 | public class Single { 7 | } 8 | -------------------------------------------------------------------------------- /spring-boot-redis-in-action/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | redis: 3 | lettuce: 4 | pool: 5 | max-active: 8 6 | max-idle: 10 7 | min-idle: 2 8 | port: 6379 9 | host: 127.0.0.1 -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/dto/ElasticDataDto.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ElasticDataDto { 7 | String word; 8 | } 9 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/vo/XmlItemVo.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.vo; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class XmlItemVo { 7 | int number; 8 | String data; 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /spring-boot-batch-in-action/assets/Ch02_SpringBatchArchitecture_Architecture_StepTaskletFlow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/renuevo/spring-boot-in-action/HEAD/spring-boot-batch-in-action/assets/Ch02_SpringBatchArchitecture_Architecture_StepTaskletFlow.png -------------------------------------------------------------------------------- /spring-boot-event-in-action/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-web' 3 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 4 | runtimeOnly 'com.h2database:h2' 5 | } 6 | -------------------------------------------------------------------------------- /spring-boot-kafka-in-action/src/main/java/com/github/renuevo/model/DataModel.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.model; 2 | 3 | import lombok.*; 4 | 5 | @Data 6 | public class DataModel { 7 | private String name; 8 | private Integer age; 9 | } 10 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/vo/ItemVo.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class ItemVo { 9 | String item; 10 | } 11 | -------------------------------------------------------------------------------- /spring-boot-scope-in-action/src/test/java/com/github/renuevo/SpringBootScopeApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import org.springframework.boot.test.context.SpringBootTest; 4 | 5 | @SpringBootTest 6 | class SpringBootScopeApplicationTests { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/vo/ElasticReaderTestVo.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.vo; 2 | 3 | 4 | import lombok.Data; 5 | 6 | @Data 7 | public class ElasticReaderTestVo { 8 | 9 | private int key; 10 | private String name; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/repo/store/JpaStoreRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.repo.store; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface JpaStoreRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /spring-boot-redis-in-action/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'io.lettuce:lettuce-core:6.1.8.RELEASE' 3 | implementation group: 'org.springframework.data', name: 'spring-data-redis' 4 | 5 | implementation 'org.springframework.boot:spring-boot-starter-webflux' 6 | } 7 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/repo/order/JpaOrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.repo.order; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface JpaOrderRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /spring-boot-scope-in-action/src/main/java/com/github/renuevo/scope/Proto.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.scope; 2 | 3 | import org.springframework.context.annotation.Scope; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | @Scope("prototype") 8 | public class Proto { 9 | } 10 | -------------------------------------------------------------------------------- /spring-boot-redis-in-action/src/main/java/com/github/renuevo/domain/person/PersonRedisRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.domain.person; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | 5 | 6 | public interface PersonRedisRepository extends CrudRepository { 7 | } 8 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/vo/ElasticWriterTestVo.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | 6 | @Data 7 | @AllArgsConstructor 8 | public class ElasticWriterTestVo { 9 | 10 | private int key; 11 | private String name; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/domain/order/OrderRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.domain.order; 2 | 3 | import java.util.List; 4 | 5 | public interface OrderRepository { 6 | 7 | List findOrderAll(); 8 | OrderDataModel saveOrder(OrderDataModel orderDataModel); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/resources/read_sample/sample_json_data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "number" : 1, 4 | "data" : "one" 5 | }, 6 | { 7 | "number" : 2, 8 | "data" : "two" 9 | }, 10 | { 11 | "number" : 3, 12 | "data" : "three" 13 | }, 14 | { 15 | "number" : 4, 16 | "data" : "four" 17 | } 18 | ] -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/vo/JsonItemVo.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class JsonItemVo { 11 | int number; 12 | String data; 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/vo/CsvItemVo.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class CsvItemVo { 11 | 12 | int number; 13 | String item; 14 | } 15 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/test/java/com/github/renuevo/SpringBatchApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import org.junit.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | public class SpringBatchApplicationTests { 8 | 9 | @Test 10 | public void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/dto/ElasticParamDto.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.dto; 2 | 3 | import lombok.Data; 4 | 5 | import javax.validation.constraints.Min; 6 | 7 | @Data 8 | public class ElasticParamDto { 9 | 10 | String keyword; 11 | 12 | @Min(1) 13 | int limit =3; 14 | 15 | @Min(0) 16 | int page = 0; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/domain/store/StoreRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.domain.store; 2 | 3 | import com.github.renuevo.domain.order.OrderDataModel; 4 | 5 | public interface StoreRepository { 6 | 7 | StoreDataModel saveStore(StoreDataModel storeDataModel); 8 | StoreDataModel orderStore(OrderDataModel orderDataModel); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/repo/store/event/OrderEvent.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.repo.store.event; 2 | 3 | import com.github.renuevo.repo.order.OrderHistory; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @Getter 8 | @RequiredArgsConstructor 9 | public class OrderEvent { 10 | 11 | private final OrderHistory orderHistory; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/querydsl/expression/WhereStringFunction.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.querydsl.expression; 2 | 3 | import com.querydsl.core.types.dsl.BooleanExpression; 4 | import com.querydsl.core.types.dsl.StringPath; 5 | 6 | @FunctionalInterface 7 | public interface WhereStringFunction { 8 | BooleanExpression apply(StringPath id, int page, String currentId); 9 | } 10 | -------------------------------------------------------------------------------- /spring-boot-scope-in-action/src/main/java/com/github/renuevo/proxy/ProtoInterfaceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.proxy; 2 | 3 | import lombok.Getter; 4 | 5 | public class ProtoInterfaceImpl implements ProtoInterface { 6 | @Getter 7 | String name; 8 | 9 | public ProtoInterfaceImpl() { 10 | } 11 | 12 | public ProtoInterfaceImpl(String name) { 13 | this.name = name; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /spring-boot-scope-in-action/src/main/java/com/github/renuevo/proxy/ProtoProxy.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.proxy; 2 | 3 | import org.springframework.context.annotation.Scope; 4 | import org.springframework.context.annotation.ScopedProxyMode; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) 9 | public class ProtoProxy { 10 | } 11 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/common/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.common; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | @Getter 8 | @Builder 9 | public class ErrorResponse { 10 | private final HttpStatus httpStatus; 11 | private final String errorMessage; 12 | private final String errorCode; 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/dto/PostRequest.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class PostRequest { 13 | private String data; 14 | private String dataMessage; 15 | } 16 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'spring-boot-in-action' 2 | include 'spring-boot-batch-in-action', 'spring-boot-scope-in-action', 'spring-boot-exception-in-action','spring-boot-rest-client-in-action','spring-boot-rest-docs-in-action' 3 | include 'spring-boot-jpa-in-action' 4 | include 'spring-boot-redis-in-action' 5 | include 'spring-boot-guide-in-action' 6 | include 'spring-boot-kafka-in-action' 7 | include 'spring-boot-event-in-action' 8 | 9 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/common/TestResponseKotlin.kt: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.common 2 | 3 | data class TestResponseKotlin( 4 | // @JsonProperty("test_name") 5 | var testName: String, 6 | // @JsonProperty("test_number") 7 | var testNumber: String, 8 | var statusCode: STATUS_CODE 9 | 10 | ) 11 | 12 | enum class STATUS_CODE { 13 | OK, 14 | FAIL 15 | } -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/querydsl/expression/WhereNumberFunction.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.querydsl.expression; 2 | 3 | import com.querydsl.core.types.dsl.BooleanExpression; 4 | import com.querydsl.core.types.dsl.NumberPath; 5 | 6 | @FunctionalInterface 7 | public interface WhereNumberFunction> { 8 | BooleanExpression apply(NumberPath id, int page, N currentId); 9 | } 10 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/common/TestResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.common; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Data 9 | @Builder 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class TestResponse { 13 | 14 | private String testName; 15 | private String testNumber; 16 | } 17 | -------------------------------------------------------------------------------- /spring-boot-jpa-in-action/src/main/java/com/github/renuevo/SpringBootJpaApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootJpaApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(SpringBootJpaApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-kafka-in-action/src/main/java/com/github/renuevo/SpringBootKafkaApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootKafkaApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(SpringBootKafkaApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/domain/order/OrderDataModel.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.domain.order; 2 | 3 | import com.github.renuevo.domain.store.StoreDataModel; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | @Getter 8 | @Builder 9 | public class OrderDataModel { 10 | private final Long id; 11 | private final StoreDataModel storeDataModel; 12 | private final Integer count; 13 | private final String name; 14 | } 15 | -------------------------------------------------------------------------------- /spring-boot-guide-in-action/src/main/java/com/github/renuevo/SpringBootGuideApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootGuideApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringBootGuideApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-rest-docs-in-action/src/main/java/com/github/renuevo/SpringBootRestDocsApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootRestDocsApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(SpringBootRestDocsApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spring-boot-exception-in-action/src/main/java/com/github/renuevo/SpringBootExceptionApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SpringBootExceptionApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SpringBootExceptionApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-jpa-in-action/src/main/java/com/github/renuevo/n_plus_one/AcademyRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.n_plus_one; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.jpa.repository.Query; 5 | 6 | import java.util.List; 7 | 8 | public interface AcademyRepository extends JpaRepository { 9 | 10 | @Query("select a from Academy a join fetch a.subjectSet") 11 | List findeAllJoinFetch(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/build/** 2 | **/data/** 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | .gradle 16 | 17 | ### IntelliJ IDEA ### 18 | .idea 19 | *.iws 20 | *.iml 21 | *.ipr 22 | out/ 23 | 24 | ### NetBeans ### 25 | /nbproject/private/ 26 | /nbbuild/ 27 | /dist/ 28 | /nbdist/ 29 | /.nb-gradle/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | main: 3 | banner-mode: 'off' 4 | allow-bean-definition-overriding: true 5 | datasource: 6 | hikari: 7 | driver-class-name: org.h2.Driver 8 | jdbc-url: jdbc:h2:mem:spring-event 9 | username: sa 10 | password: 11 | h2: 12 | console: 13 | enabled: true 14 | jpa: 15 | show-sql: true 16 | hibernate: 17 | ddl-auto: create 18 | database-platform: org.hibernate.dialect.H2Dialect -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | main: 3 | banner-mode: 'off' 4 | allow-bean-definition-overriding: true 5 | datasource: 6 | hikari: 7 | driver-class-name: org.h2.Driver 8 | jdbc-url: jdbc:h2:mem:spring-event 9 | username: sa 10 | password: 11 | h2: 12 | console: 13 | enabled: true 14 | jpa: 15 | show-sql: true 16 | hibernate: 17 | ddl-auto: create 18 | database-platform: org.hibernate.dialect.H2Dialect -------------------------------------------------------------------------------- /spring-boot-scope-in-action/src/main/java/com/github/renuevo/scope/ScopeWrapper.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.scope; 2 | 3 | import com.github.renuevo.proxy.ProtoProxy; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | @AllArgsConstructor 10 | public class ScopeWrapper { 11 | 12 | @Getter 13 | Single single; 14 | 15 | @Getter 16 | Proto proto; 17 | 18 | @Getter 19 | ProtoProxy protoProxy; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/domain/store/StoreDataModel.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.domain.store; 2 | 3 | import com.github.renuevo.repo.order.OrderHistory; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.util.List; 8 | 9 | @Getter 10 | @Builder 11 | public class StoreDataModel { 12 | 13 | private final Long id; 14 | private final String name; 15 | private final Integer stock; 16 | private final List orderHistoryList; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/repo/store/StoreMapper.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.repo.store; 2 | 3 | import com.github.renuevo.domain.store.StoreDataModel; 4 | import org.mapstruct.Mapper; 5 | import org.mapstruct.factory.Mappers; 6 | 7 | @Mapper 8 | public interface StoreMapper { 9 | StoreMapper INSTANCE = Mappers.getMapper(StoreMapper.class); 10 | 11 | StoreDataModel sotreToStoreDataModel(Store store); 12 | 13 | Store storeDataModelToStore(StoreDataModel storeDataModel); 14 | } 15 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/client/error/SampleErrorFeignClient.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.client.error; 2 | 3 | import org.springframework.cloud.openfeign.FeignClient; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | 6 | @FeignClient(value = "error-client", url = "http://localhost:8080") 7 | public interface SampleErrorFeignClient { 8 | 9 | @GetMapping("get500") 10 | String get500(); 11 | 12 | @GetMapping("get400") 13 | String get400(); 14 | } 15 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/resttemplate/controller/RestTemplateController.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.resttemplate.controller; 2 | 3 | import com.github.renuevo.resttemplate.service.RestTemplateService; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | @RequiredArgsConstructor 9 | public class RestTemplateController { 10 | 11 | private final RestTemplateService restTemplateService; 12 | 13 | 14 | } 15 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/resources/read_sample/sample_xml_data.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | one 6 | 7 | 8 | 2 9 | two 10 | 11 | 12 | 3 13 | three 14 | 15 | 16 | 4 17 | four 18 | 19 | 20 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/process/JpaChainingProcessFirst.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.process; 2 | 3 | import com.github.renuevo.entity.Pay; 4 | import com.github.renuevo.entity.Pay2; 5 | import org.springframework.batch.item.ItemProcessor; 6 | 7 | public class JpaChainingProcessFirst implements ItemProcessor { 8 | @Override 9 | public Pay2 process(Pay pay) throws Exception { 10 | return new Pay2(pay.getId(), pay.getAmount(), pay.getTxName(), pay.getTxDateTime()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/process/JpaChainingProcessSecond.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.process; 2 | 3 | 4 | import com.github.renuevo.entity.Pay2; 5 | import com.github.renuevo.entity.Tax; 6 | import org.springframework.batch.item.ItemProcessor; 7 | 8 | public class JpaChainingProcessSecond implements ItemProcessor { 9 | @Override 10 | public Tax process(Pay2 pay2) throws Exception { 11 | return new Tax(pay2.getId(), (long) (pay2.getAmount() * 0.1), "location"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/SpringBootEventApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableAsync; 6 | 7 | @EnableAsync 8 | @SpringBootApplication 9 | public class SpringBootEventApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(SpringBootEventApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-jpa-in-action/src/main/java/com/github/renuevo/ServletInitializer.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import org.springframework.boot.builder.SpringApplicationBuilder; 4 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 5 | 6 | public class ServletInitializer extends SpringBootServletInitializer { 7 | 8 | @Override 9 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 10 | return application.sources(SpringBootJpaApplication.class); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-redis-in-action/src/main/java/com/github/renuevo/SpringBootRedisApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.web.reactive.config.EnableWebFlux; 6 | 7 | @EnableWebFlux 8 | @SpringBootApplication 9 | public class SpringBootRedisApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(SpringBootRedisApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/client/error/SampleErrorRetryFeignClient.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.client.error; 2 | 3 | import org.springframework.cloud.openfeign.FeignClient; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | 6 | @FeignClient(value = "error-retry-client", url = "http://localhost:8080") 7 | public interface SampleErrorRetryFeignClient { 8 | 9 | @GetMapping("get500") 10 | String get500(); 11 | 12 | @GetMapping("get400") 13 | String get400(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/ServletInitializer.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import org.springframework.boot.builder.SpringApplicationBuilder; 4 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 5 | 6 | public class ServletInitializer extends SpringBootServletInitializer { 7 | 8 | @Override 9 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 10 | return application.sources(SpringBootEventApplication.class); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-kafka-in-action/src/main/java/com/github/renuevo/ServletInitializer.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import org.springframework.boot.builder.SpringApplicationBuilder; 4 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 5 | 6 | public class ServletInitializer extends SpringBootServletInitializer { 7 | 8 | @Override 9 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 10 | return application.sources(SpringBootKafkaApplication.class); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-guide-in-action/src/main/java/com/github/renuevo/ServletInitializer.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | 4 | import org.springframework.boot.builder.SpringApplicationBuilder; 5 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 6 | 7 | public class ServletInitializer extends SpringBootServletInitializer { 8 | 9 | @Override 10 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 11 | return application.sources(SpringBootGuideApplication.class); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /spring-boot-exception-in-action/src/main/java/com/github/renuevo/ServletInitializer.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import org.springframework.boot.builder.SpringApplicationBuilder; 4 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 5 | 6 | public class ServletInitializer extends SpringBootServletInitializer { 7 | 8 | @Override 9 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 10 | return application.sources(SpringBootExceptionApplication.class); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/common/NaverProperty.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.common; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.boot.context.properties.ConstructorBinding; 7 | 8 | 9 | @Getter 10 | @AllArgsConstructor 11 | @ConstructorBinding 12 | @ConfigurationProperties(prefix = "naver.client") 13 | public class NaverProperty { 14 | private String id; 15 | private String secret; 16 | } 17 | -------------------------------------------------------------------------------- /spring-boot-rest-docs-in-action/src/main/java/com/github/renuevo/ServletInitializer.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import org.springframework.boot.builder.SpringApplicationBuilder; 4 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 5 | 6 | public class ServletInitializer extends SpringBootServletInitializer { 7 | 8 | @Override 9 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 10 | return application.sources(SpringBootRestDocsApplication.class); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/test/java/com/github/renuevo/setup/OrderHistoryBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.setup; 2 | 3 | import com.github.renuevo.repo.order.OrderHistory; 4 | import com.github.renuevo.repo.store.Store; 5 | 6 | public class OrderHistoryBuilder { 7 | 8 | public static OrderHistory orderHistoryBuilder(Store store){ 9 | return OrderHistory.builder() 10 | .id(null) 11 | .count(1) 12 | .name("Test Order") 13 | .store(store) 14 | .build(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/ServletInitializer.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | /* web dependencies 주석으로 인한 주석처리 4 | import org.springframework.boot.builder.SpringApplicationBuilder; 5 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; 6 | 7 | public class ServletInitializer extends SpringBootServletInitializer { 8 | 9 | @Override 10 | protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { 11 | return application.sources(SpringBatchApplication.class); 12 | } 13 | 14 | } 15 | */ -------------------------------------------------------------------------------- /spring-boot-exception-in-action/src/main/java/com/github/renuevo/controller/Controller.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.controller; 2 | 3 | import org.springframework.http.ResponseEntity; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | public class Controller { 9 | 10 | @GetMapping("/test") 11 | public ResponseEntity errorTest() throws Exception { 12 | if (true) 13 | throw new Exception(""); 14 | return ResponseEntity.ok("test"); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/annotation/ToJsonExpander.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.annotation; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import feign.Param; 5 | import lombok.SneakyThrows; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | @Slf4j 9 | public class ToJsonExpander implements Param.Expander{ 10 | private final ObjectMapper objectMapper = new ObjectMapper(); 11 | 12 | @Override 13 | @SneakyThrows 14 | public String expand(Object value) { 15 | return objectMapper.writeValueAsString(value); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/common/CommonConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.common; 2 | 3 | import com.fasterxml.jackson.databind.DeserializationFeature; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class CommonConfig { 10 | 11 | 12 | @Bean 13 | public ObjectMapper commonObjectMapper(){ 14 | return new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/client/error/SampleErrorReactiveFeignClient.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.client.error; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import reactivefeign.spring.config.ReactiveFeignClient; 5 | import reactor.core.publisher.Mono; 6 | 7 | @ReactiveFeignClient(name = "sample-error-reactive-feign-client", url = "http://localhost:8080") 8 | public interface SampleErrorReactiveFeignClient { 9 | 10 | @GetMapping("get500") 11 | Mono get500(); 12 | 13 | @GetMapping("get400") 14 | Mono get400(); 15 | } 16 | -------------------------------------------------------------------------------- /spring-boot-redis-in-action/src/main/java/com/github/renuevo/domain/person/PersonEntity.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.domain.person; 2 | 3 | import lombok.*; 4 | import org.springframework.data.annotation.Id; 5 | import org.springframework.data.redis.core.RedisHash; 6 | 7 | import java.io.Serializable; 8 | import java.time.LocalDate; 9 | 10 | @ToString 11 | @Getter 12 | @Builder 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @RedisHash("presonEntity") 16 | public class PersonEntity implements Serializable { 17 | 18 | @Id 19 | private String name; 20 | private Integer age; 21 | private LocalDate birthday; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/dto/NaverBlogParamDto.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.dto; 2 | 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 4 | import com.fasterxml.jackson.databind.annotation.JsonNaming; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | 8 | import java.time.LocalDate; 9 | 10 | 11 | @Getter 12 | @Builder 13 | @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) 14 | public class NaverBlogParamDto { 15 | private final String query; 16 | private final Integer display; 17 | private final Integer start; 18 | private final String sort; 19 | } 20 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/client/error/SampleErrorRetryReactiveFeignClient.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.client.error; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import reactivefeign.spring.config.ReactiveFeignClient; 5 | import reactor.core.publisher.Mono; 6 | 7 | @ReactiveFeignClient(value = "sample-error-reactive-retry-feign-client", url = "http://localhost:8080") 8 | public interface SampleErrorRetryReactiveFeignClient { 9 | 10 | @GetMapping("get500") 11 | Mono get500(); 12 | 13 | @GetMapping("get400") 14 | Mono get400(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/config/ReactiveCustomFeignConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.config; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Primary; 6 | 7 | @Slf4j 8 | public class ReactiveCustomFeignConfig { 9 | 10 | @Primary 11 | @Bean 12 | public feign.codec.ErrorDecoder customErrorDecoder() { 13 | return (methodKey, response) -> { 14 | log.info("custom feign"); 15 | return new Exception(response.body().toString()); 16 | }; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/config/CustomFeignConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import feign.codec.ErrorDecoder; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Primary; 8 | 9 | @RequiredArgsConstructor 10 | public class CustomFeignConfig { 11 | 12 | private final ObjectMapper objectMapper; 13 | 14 | @Primary 15 | @Bean 16 | public ErrorDecoder customErrorDecoder(){ 17 | return new CustomFeignErrorDecoder(objectMapper); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/test/java/com/github/renuevo/setup/StoreBuilder.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.setup; 2 | 3 | import com.github.renuevo.repo.store.Store; 4 | 5 | public class StoreBuilder { 6 | 7 | public static Store storeBuilder() { 8 | return Store.builder() 9 | .id(null) 10 | .name("Test Store") 11 | .stock(100) 12 | .build(); 13 | } 14 | 15 | public static Store storeNumberBuilder(int number) { 16 | return Store.builder() 17 | .id(null) 18 | .name("Test Store " + number) 19 | .stock(100) 20 | .build(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/config/CustomFeignErrorDecoder.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import feign.Response; 5 | import feign.codec.ErrorDecoder; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | @Slf4j 10 | @RequiredArgsConstructor 11 | public class CustomFeignErrorDecoder implements ErrorDecoder { 12 | 13 | private final ObjectMapper objectMapper; 14 | 15 | @Override 16 | public Exception decode(String methodKey, Response response) { 17 | log.info(objectMapper.toString()); 18 | return null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/config/FeignErrorDecoder.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.config; 2 | 3 | import feign.Response; 4 | import feign.codec.ErrorDecoder; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | @Slf4j 8 | public class FeignErrorDecoder implements ErrorDecoder { 9 | @Override 10 | public Exception decode(String methodKey, Response response) { 11 | log.error("요청에 실패 하였습니다 : {}", response.body()); 12 | 13 | switch (response.status()) { 14 | case 400: 15 | case 500: 16 | default: 17 | return new Exception(response.body().toString()); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spring-boot-redis-in-action/src/main/java/com/github/renuevo/domain/simple/RedisController.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.domain.simple; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | @RequiredArgsConstructor 9 | @RestController 10 | @RequestMapping("/simple") 11 | public class RedisController { 12 | 13 | private final SimpleTestService simpleTestService; 14 | 15 | @GetMapping("/test") 16 | public void simpleTest(){ 17 | simpleTestService.simpleTest(); 18 | } 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /spring-boot-kafka-in-action/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | implementation 'org.springframework.boot:spring-boot-starter-web' 3 | implementation group: 'org.springframework.kafka', name: 'spring-kafka', version: '2.8.4' 4 | implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.11.0' 5 | implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.11.0' 6 | 7 | /* 8 | implementation group: 'org.apache.kafka', name: 'kafka-clients', version: '2.2.2' 9 | implementation group: 'org.apache.kafka', name: 'kafka_2.12', version: '2.2.2' 10 | implementation group: 'org.apache.kafka', name: 'kafka-streams', version: '2.2.2' 11 | */ 12 | } -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/annotation/CustomParam.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.annotation; 2 | 3 | import org.springframework.core.annotation.AliasFor; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | 11 | /** 12 | * @see CustomParamAnnotationProcess 13 | */ 14 | @Target(ElementType.PARAMETER) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface CustomParam { 17 | 18 | @AliasFor("name") 19 | String value() default ""; 20 | 21 | @AliasFor("value") 22 | String name() default ""; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/repo/order/OrderHistory.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.repo.order; 2 | 3 | import com.github.renuevo.repo.store.Store; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.*; 10 | 11 | @Getter 12 | @Builder 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @Entity 16 | public class OrderHistory { 17 | 18 | @Id 19 | @GeneratedValue 20 | private Long id; 21 | 22 | private Integer count; 23 | private String name; 24 | 25 | @ManyToOne(fetch = FetchType.LAZY) 26 | @JoinColumn(name = "store_id") 27 | private Store store; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/dto/NaverResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.dto; 2 | 3 | import lombok.*; 4 | 5 | import java.util.List; 6 | 7 | @Getter 8 | @Builder 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | public class NaverResponse { 12 | private String lastBuildDate; 13 | private int total; 14 | private int start; 15 | private int display; 16 | private List items; 17 | 18 | @Data 19 | public static class Item{ 20 | private String title; 21 | private String link; 22 | private String description; 23 | private String bloggername; 24 | private String bloggerlink; 25 | private String postdate; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /spring-boot-jpa-in-action/build.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | querydslVersion = "5.0.0" 3 | } 4 | 5 | 6 | dependencies { 7 | implementation 'org.springframework.boot:spring-boot-starter-web' 8 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 9 | runtimeOnly 'com.h2database:h2' 10 | 11 | annotationProcessor("jakarta.persistence:jakarta.persistence-api") 12 | annotationProcessor("jakarta.annotation:jakarta.annotation-api") 13 | 14 | implementation("com.querydsl:querydsl-jpa:${querydslVersion}") 15 | annotationProcessor("com.querydsl:querydsl-apt:${querydslVersion}:jpa") 16 | testImplementation("com.querydsl:querydsl-jpa:${querydslVersion}") 17 | testAnnotationProcessor("com.querydsl:querydsl-apt:${querydslVersion}:jpa") 18 | 19 | } -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/client/SampleBuildFeignClient.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.client; 2 | 3 | import com.github.renuevo.dto.NaverBlogParamDto; 4 | import com.github.renuevo.dto.NaverResponse; 5 | import org.springframework.cloud.openfeign.SpringQueryMap; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestHeader; 8 | 9 | public interface SampleBuildFeignClient { 10 | 11 | @GetMapping(value="blog.json") 12 | NaverResponse naverBlogSearch( 13 | @RequestHeader("X-Naver-Client-Id") String id, 14 | @RequestHeader("X-Naver-Client-Secret") String secret, 15 | @SpringQueryMap NaverBlogParamDto naverBlogParam); 16 | } 17 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/client/error/SampleErrorReactiveFeignCustomClient.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.client.error; 2 | 3 | import com.github.renuevo.feign.config.ReactiveCustomFeignConfig; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import reactivefeign.spring.config.ReactiveFeignClient; 6 | import reactor.core.publisher.Mono; 7 | 8 | @ReactiveFeignClient(name = "sample-error-reactive-custom-feign-client", 9 | url = "http://localhost:8080", 10 | configuration = ReactiveCustomFeignConfig.class) 11 | public interface SampleErrorReactiveFeignCustomClient { 12 | 13 | @GetMapping("get500") 14 | Mono get500(); 15 | 16 | @GetMapping("get400") 17 | Mono get400(); 18 | } 19 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/repo/store/event/OrderEventHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.repo.store.event; 2 | 3 | import com.github.renuevo.repo.order.OrderMapper; 4 | import com.github.renuevo.repo.store.StoreRdbRepository; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.context.event.EventListener; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | @RequiredArgsConstructor 11 | public class OrderEventHandler { 12 | 13 | private final StoreRdbRepository storeRdbRepository; 14 | 15 | 16 | @EventListener 17 | public void orderInsert(OrderEvent orderEvent){ 18 | storeRdbRepository.orderStore(OrderMapper.INSTANCE.orderToOrderDataModel(orderEvent.getOrderHistory())); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /spring-boot-exception-in-action/src/main/java/com/github/renuevo/exception/ErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum ErrorCode { 7 | 8 | // Common 9 | INVALID_INPUT_VALUE(400, "C001", " Invalid Input Value"), 10 | METHOD_NOT_ALLOWED(405, "C002", " Invalid Input Value"), 11 | INTERNAL_SERVER_ERROR(500, "C003", "Server Error"), 12 | INVALID_TYPE_VALUE(400, "C004", " Invalid Type Value"); 13 | 14 | private final String code; 15 | private final String message; 16 | private final int status; 17 | 18 | ErrorCode(final int status, final String code, final String message) { 19 | this.status = status; 20 | this.message = message; 21 | this.code = code; 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/resttemplate/service/RestTemplateService.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.resttemplate.service; 2 | 3 | import com.github.renuevo.dto.NaverResponse; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.web.client.RestTemplate; 8 | 9 | @RequiredArgsConstructor 10 | @Service 11 | public class RestTemplateService { 12 | 13 | @Value("${naver.url.search}") 14 | private String url; 15 | private final RestTemplate restTemplate; 16 | 17 | public NaverResponse naverRestTemplateCall() { 18 | return restTemplate.getForObject(url + "/{subUrl}", NaverResponse.class, "blog.json"); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/entity/Tax.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.entity; 2 | 3 | 4 | import lombok.*; 5 | 6 | import javax.persistence.Entity; 7 | import javax.persistence.GeneratedValue; 8 | import javax.persistence.GenerationType; 9 | import javax.persistence.Id; 10 | 11 | @Setter 12 | @Getter 13 | @Entity 14 | @ToString 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | public class Tax { 18 | 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | private Long id; 22 | private Long pay_id; 23 | private Long pay_tax; 24 | private String location; 25 | 26 | public Tax(Long pay_id, Long pay_tax, String location){ 27 | this.pay_id = pay_id; 28 | this.pay_tax = pay_tax; 29 | this.location = location; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/config/ReactiveFeignErrorDecoder.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.config; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import reactivefeign.client.ReactiveHttpResponse; 5 | import reactivefeign.client.statushandler.ReactiveStatusHandler; 6 | import reactor.core.publisher.Mono; 7 | 8 | @Slf4j 9 | public class ReactiveFeignErrorDecoder implements ReactiveStatusHandler { 10 | @Override 11 | public boolean shouldHandle(int status) { 12 | return status >= 400; 13 | } 14 | 15 | @Override 16 | public Mono decode(String methodTag, ReactiveHttpResponse response) { 17 | log.error("요청에 실패 하였습니다 : {}", response.body()); 18 | return Mono.error(new Exception(response.body().toString())); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/config/JobSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | import com.github.renuevo.vo.XmlItemVo; 4 | import com.thoughtworks.xstream.XStream; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.oxm.xstream.XStreamMarshaller; 7 | 8 | @Configuration 9 | public class JobSecurityConfig { 10 | 11 | //Security framework of XStream not initialized, XStream is probably vulnerable. 12 | //https://stackoverflow.com/questions/49450397/vulnerability-warning-with-xstreammarshaller 13 | public JobSecurityConfig(XStreamMarshaller marshaller) { 14 | XStream xstream = marshaller.getXStream(); 15 | XStream.setupDefaultSecurity(xstream); 16 | xstream.allowTypes(new Class[]{XmlItemVo.class}); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /spring-boot-redis-in-action/src/main/java/com/github/renuevo/domain/simple/SimpleTestService.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.domain.simple; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.data.redis.core.RedisTemplate; 6 | import org.springframework.data.redis.core.ValueOperations; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Slf4j 10 | @RequiredArgsConstructor 11 | @Service 12 | public class SimpleTestService { 13 | 14 | private final RedisTemplate redisTemplate; 15 | 16 | public void simpleTest() { 17 | ValueOperations valueOperations = redisTemplate.opsForValue(); 18 | valueOperations.set("name", "rubber.duck"); 19 | log.info("Simple Test Name : {}", valueOperations.get("name")); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/data/StepShareData.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.data; 2 | 3 | 4 | import com.google.common.collect.Maps; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | *
11 |  * @className : StepShareData
12 |  * @author : Deokhwa.Kim
13 |  * @since : 2020-01-08
14 |  * 
15 | */ 16 | @Component 17 | public class StepShareData { 18 | 19 | private final Map shareDataMap = Maps.newConcurrentMap(); 20 | 21 | public void putData(Long key, Object value) { 22 | this.shareDataMap.put(key, value); 23 | } 24 | 25 | public Object get(Long key) { 26 | return this.shareDataMap.getOrDefault(key, null); 27 | } 28 | 29 | public void clear() { 30 | this.shareDataMap.clear(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /spring-boot-scope-in-action/src/main/java/com/github/renuevo/SpringBootScopeApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import com.github.renuevo.scope.ScopeService; 4 | import lombok.AllArgsConstructor; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | 9 | @AllArgsConstructor 10 | @SpringBootApplication 11 | public class SpringBootScopeApplication implements CommandLineRunner { 12 | 13 | private final ScopeService scopeService; 14 | 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(SpringBootScopeApplication.class, args); 18 | } 19 | 20 | @Override 21 | public void run(String... args) throws Exception { 22 | scopeService.scopeTest(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /spring-boot-jpa-in-action/src/main/java/com/github/renuevo/n_plus_one/Subject.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.n_plus_one; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.*; 8 | 9 | @Entity 10 | @NoArgsConstructor 11 | public class Subject { 12 | 13 | @Id 14 | @GeneratedValue 15 | private Long id; 16 | 17 | @Getter 18 | private String name; 19 | 20 | @ManyToOne 21 | @JoinColumn(name = "academy_id", foreignKey = @ForeignKey(name = "FK_SUBJECT_ACADEMY")) 22 | private Academy academy; 23 | 24 | @Builder 25 | public Subject(String name, Academy academy) { 26 | this.name = name; 27 | this.academy = academy; 28 | } 29 | 30 | public void updateAcademy(Academy academy){ 31 | this.academy = academy; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/client/SampleFeignClient.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.client; 2 | 3 | 4 | import com.github.renuevo.dto.NaverBlogParamDto; 5 | import com.github.renuevo.dto.NaverResponse; 6 | import org.springframework.cloud.openfeign.FeignClient; 7 | import org.springframework.cloud.openfeign.SpringQueryMap; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestHeader; 10 | 11 | @FeignClient(name = "sample-feign-client", url = "${naver.url.search}") 12 | public interface SampleFeignClient { 13 | 14 | @GetMapping(value = "blog.json") 15 | NaverResponse naverBlogSearch( 16 | @RequestHeader("X-Naver-Client-Id") String id, 17 | @RequestHeader("X-Naver-Client-Secret") String secret, 18 | @SpringQueryMap NaverBlogParamDto naverBlogParamDto); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/client/SampleLocalFeignClient.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.client; 2 | 3 | import com.github.renuevo.common.TestResponseKotlin; 4 | import com.github.renuevo.dto.PostRequest; 5 | import org.springframework.cloud.openfeign.FeignClient; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | @FeignClient(name = "sample-local-feign-client", url = "http://localhost:8080") 13 | public interface SampleLocalFeignClient { 14 | 15 | @PostMapping("/post") 16 | String postCall(PostRequest postRequest); 17 | 18 | @PostMapping("/post") 19 | String postMapCall(Map postRequestMap); 20 | 21 | @GetMapping("/kotlin-response") 22 | List testKotlinResponse(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/writer/JpaItemListWriter.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.writer; 2 | 3 | import com.google.common.collect.Lists; 4 | import org.springframework.batch.item.database.JpaItemWriter; 5 | 6 | import java.util.List; 7 | 8 | //List로 기존 JpaItemWriter를 오버라이딩해서 등록 9 | public class JpaItemListWriter extends JpaItemWriter> { 10 | 11 | private JpaItemWriter jpaItemWriter; 12 | 13 | public JpaItemListWriter(JpaItemWriter jpaItemWriter){ 14 | this.jpaItemWriter = jpaItemWriter; 15 | } 16 | 17 | @Override 18 | public void write(List> items) { //처음 List는 chunkSize이고 안에 List가 실제 Item List 19 | List list = Lists.newArrayList(); 20 | 21 | for(List subList : items){ 22 | list.addAll(subList); //전체 List를 1개의 List를 변경해서 return 23 | } 24 | 25 | jpaItemWriter.write(list); 26 | } 27 | } -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | main: 3 | banner-mode: 'off' 4 | allow-bean-definition-overriding: true 5 | datasource: 6 | hikari: 7 | #continue-on-error: true 8 | driver-class-name: org.h2.Driver 9 | #jdbc-url: jdbc:h2:file:C:/Users/user/h2/spring-batch;AUTO_SERVER=TRUE 10 | jdbc-url: jdbc:h2:file:E:/dev/h2/spring-batch;AUTO_SERVER=TRUE 11 | #jdbc-url: jdbc:h2:mem:spring-batch 12 | #jdbc-url: jdbc:h2:file:./spring-boot-batch-in-action/data/spring-batch;AUTO_SERVER=TRUE; 13 | username: sa 14 | password: 15 | jpa: 16 | show-sql: true 17 | hibernate: 18 | ddl-auto: none 19 | database-platform: org.hibernate.dialect.H2Dialect 20 | batch: 21 | job: 22 | names: ${job.name:NONE} 23 | 24 | search: 25 | es-host: localhost 26 | es-port: 9200 27 | 28 | logging: 29 | level: 30 | org.springframework.batch: debug -------------------------------------------------------------------------------- /spring-boot-rest-docs-in-action/src/test/java/utils/ApiDocumentUtils.java: -------------------------------------------------------------------------------- 1 | package utils; 2 | 3 | /* 4 | import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor; 5 | import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor; 6 | 7 | import static org.springframework.restdocs.operation.preprocess.Preprocessors.*; 8 | 9 | */ 10 | 11 | public interface ApiDocumentUtils { 12 | 13 | /* 14 | static OperationRequestPreprocessor getDocumentRequest() { 15 | return preprocessRequest( 16 | modifyUris() 17 | .scheme("https") 18 | .host("company.docs.api.com") 19 | .removePort(), 20 | prettyPrint()); 21 | } 22 | 23 | static OperationResponsePreprocessor getDocumentResponse() { 24 | return preprocessResponse(prettyPrint()); 25 | } 26 | 27 | */ 28 | } 29 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/client/SampleReactiveFeignClient.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.client; 2 | 3 | 4 | import com.github.renuevo.dto.NaverResponse; 5 | import org.springframework.cloud.openfeign.SpringQueryMap; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestHeader; 8 | import reactivefeign.spring.config.ReactiveFeignClient; 9 | import reactor.core.publisher.Mono; 10 | 11 | import java.util.Map; 12 | 13 | @ReactiveFeignClient(name = "sample-reactive-feign-client", url = "${naver.url.search}") 14 | public interface SampleReactiveFeignClient { 15 | 16 | @GetMapping(value = "blog.json") 17 | Mono naverBlogSearch( 18 | @RequestHeader("X-Naver-Client-Id") String id, 19 | @RequestHeader("X-Naver-Client-Secret") String secret, 20 | @SpringQueryMap Map naverBlogParamDto); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/config/FeignRetryErrorDecoder.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.config; 2 | 3 | import feign.Response; 4 | import feign.RetryableException; 5 | import feign.codec.ErrorDecoder; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import java.sql.Date; 9 | import java.time.LocalDate; 10 | 11 | @Slf4j 12 | public class FeignRetryErrorDecoder implements ErrorDecoder { 13 | @Override 14 | public Exception decode(String methodKey, Response response) { 15 | log.error("요청에 실패 하였습니다 : {}", response.body()); 16 | 17 | switch (response.status()) { 18 | case 400: 19 | log.error("다시 요청 합니다"); 20 | return new RetryableException(response.status(),"error", response.request().httpMethod(), Date.valueOf(LocalDate.now()), response.request()); 21 | case 500: 22 | default: 23 | return new Exception(response.body().toString()); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/webclient/config/WebClientQueryEncoder.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.webclient.config; 2 | 3 | import com.fasterxml.jackson.core.type.TypeReference; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.util.LinkedMultiValueMap; 8 | import org.springframework.util.MultiValueMap; 9 | 10 | import java.util.Map; 11 | 12 | @RequiredArgsConstructor 13 | @Component 14 | public class WebClientQueryEncoder { 15 | 16 | private final ObjectMapper objectMapper; 17 | 18 | public MultiValueMap getQueryEncode(Object queryDto) { 19 | MultiValueMap params = new LinkedMultiValueMap<>(); 20 | Map queryMap = objectMapper.convertValue(queryDto, new TypeReference<>() { 21 | }); 22 | params.setAll(queryMap); 23 | return params; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/SpringBatchApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.elasticsearch.client.RestHighLevelClient; 6 | import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; 7 | import org.springframework.boot.CommandLineRunner; 8 | import org.springframework.boot.SpringApplication; 9 | import org.springframework.boot.autoconfigure.SpringBootApplication; 10 | 11 | @AllArgsConstructor 12 | @EnableBatchProcessing 13 | @SpringBootApplication 14 | public class SpringBatchApplication implements CommandLineRunner { 15 | 16 | private final RestHighLevelClient restHighLevelClient; 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(SpringBatchApplication.class, args); 20 | } 21 | 22 | @Override 23 | public void run(String... args) throws Exception { 24 | restHighLevelClient.close(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/webclient/config/WebClientConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.webclient.config; 2 | 3 | import com.github.renuevo.common.NaverProperty; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.web.reactive.function.client.WebClient; 8 | 9 | @Configuration 10 | public class WebClientConfig { 11 | 12 | @Bean 13 | WebClient naverWebClient(@Value("${naver.url.search}") String naverUrl, NaverProperty naverProperty) { 14 | return WebClient.builder().baseUrl(naverUrl).defaultHeaders(httpHeaders -> { 15 | httpHeaders.add("X-Naver-Client-Id", naverProperty.getId()); 16 | httpHeaders.add("X-Naver-Client-Secret", naverProperty.getSecret()); 17 | }).build(); 18 | } 19 | 20 | @Bean 21 | WebClient commonWebClient() { 22 | return WebClient.create(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /spring-boot-jpa-in-action/src/main/java/com/github/renuevo/n_plus_one/Academy.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.n_plus_one; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.*; 9 | import java.util.LinkedHashSet; 10 | import java.util.Set; 11 | 12 | @Entity 13 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 14 | public class Academy { 15 | 16 | @Id 17 | @GeneratedValue 18 | private Long id; 19 | 20 | private String name; 21 | 22 | @Getter 23 | @OneToMany(cascade = CascadeType.ALL) 24 | @JoinColumn(name="academy_id") 25 | private Set subjectSet = new LinkedHashSet<>(); 26 | 27 | @Builder 28 | public Academy(String name, Set subjects) { 29 | this.name = name; 30 | if(subjects != null) this.subjectSet = subjects; 31 | } 32 | 33 | public void addSubject(Subject subject){ 34 | this.subjectSet.add(subject); 35 | subject.updateAcademy(this); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/repo/store/Store.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.repo.store; 2 | 3 | import com.github.renuevo.repo.order.OrderHistory; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.*; 10 | import java.util.LinkedHashSet; 11 | import java.util.Set; 12 | 13 | @Getter 14 | @Builder 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | @Entity 18 | public class Store { 19 | 20 | @Id 21 | @GeneratedValue 22 | private Long id; 23 | 24 | private String name; 25 | private Integer stock; 26 | 27 | @OneToMany 28 | @JoinColumn(name = "store_id") 29 | private final Set orderHistorySet = new LinkedHashSet<>(); 30 | 31 | public boolean orderCount(OrderHistory orderHistory) { 32 | if (stock - orderHistory.getCount() >= 0) { 33 | this.orderHistorySet.add(orderHistory); 34 | return true; 35 | } 36 | return false; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/SpringBootRestClientApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | import com.github.renuevo.feign.config.FeignConfig; 4 | import com.github.renuevo.feign.config.ReactiveFeignConfig; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.context.properties.ConfigurationPropertiesScan; 8 | import org.springframework.cloud.openfeign.EnableFeignClients; 9 | import org.springframework.web.reactive.config.EnableWebFlux; 10 | import reactivefeign.spring.config.EnableReactiveFeignClients; 11 | 12 | @EnableFeignClients(defaultConfiguration = FeignConfig.class) 13 | @EnableReactiveFeignClients(defaultConfiguration = ReactiveFeignConfig.class) 14 | @EnableWebFlux 15 | @SpringBootApplication 16 | @ConfigurationPropertiesScan 17 | public class SpringBootRestClientApplication { 18 | public static void main(String[] args) { 19 | SpringApplication.run(SpringBootRestClientApplication.class, args); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spring-boot-scope-in-action/src/main/java/com/github/renuevo/proxy/ProtoInterFaceBean.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.proxy; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.context.annotation.Scope; 6 | import org.springframework.context.annotation.ScopedProxyMode; 7 | 8 | @Configuration 9 | public class ProtoInterFaceBean { 10 | 11 | @Bean(name = "ProtoInterfaceProxy") 12 | @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) 13 | public ProtoInterface getProtoInterfaceProxyBean() { 14 | return new ProtoInterfaceImpl(); 15 | } 16 | 17 | @Bean(name = "ProtoInterface") 18 | @Scope(value = "prototype") 19 | public ProtoInterface getProtoInterfaceBean() { 20 | return new ProtoInterfaceImpl(); 21 | } 22 | 23 | @Bean(name = "ProtoInterfaceProxySafe") 24 | @Scope(value = "prototype") 25 | public ProtoInterfaceImpl getProtoInterfaceProxySafeBean() { 26 | return new ProtoInterfaceImpl("Safe Bean"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/client/SampleCircuitFeignClient.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.client; 2 | 3 | import com.github.renuevo.dto.NaverBlogParamDto; 4 | import com.github.renuevo.dto.NaverResponse; 5 | import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Slf4j 11 | @Component 12 | @RequiredArgsConstructor 13 | public class SampleCircuitFeignClient { 14 | private final SampleFeignClient sampleFeignClient; 15 | 16 | @CircuitBreaker(name = "sample-circuit-feign-client", fallbackMethod = "fallback") 17 | public NaverResponse naverBlogSearch(String id, String secret, NaverBlogParamDto naverBlogParamDto) { 18 | return sampleFeignClient.naverBlogSearch(id, secret, naverBlogParamDto); 19 | } 20 | 21 | public NaverResponse fallback(String id, String secret, NaverBlogParamDto naverBlogParamDto, Exception exception) { 22 | return new NaverResponse(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS pay; 2 | DROP TABLE IF EXISTS pay2; 3 | DROP TABLE IF EXISTS tax; 4 | 5 | create table pay ( 6 | id bigint not null auto_increment, 7 | amount bigint, 8 | tx_name varchar(255), 9 | tx_date_time datetime, 10 | primary key (id) 11 | ); 12 | 13 | create table pay2 ( 14 | id bigint not null auto_increment, 15 | amount bigint, 16 | tx_name varchar(255), 17 | tx_date_time datetime, 18 | primary key (id) 19 | ); 20 | 21 | create table tax ( 22 | id bigint not null auto_increment, 23 | pay_id bigint, 24 | pay_tax bigint, 25 | location varchar(255), 26 | primary key (id) 27 | ); 28 | 29 | 30 | insert into pay (amount, tx_name, tx_date_time) VALUES (1000, 'trade1', '2018-09-10 00:00:00'); 31 | insert into pay (amount, tx_name, tx_date_time) VALUES (2000, 'trade2', '2018-09-10 00:00:00'); 32 | insert into pay (amount, tx_name, tx_date_time) VALUES (3000, 'trade3', '2018-09-10 00:00:00'); 33 | insert into pay (amount, tx_name, tx_date_time) VALUES (4000, 'trade4', '2018-09-10 00:00:00'); -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/client/SampleReactiveLocalFeignClient.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.client; 2 | 3 | 4 | import com.github.renuevo.common.TestResponse; 5 | import com.github.renuevo.dto.PostRequest; 6 | import com.github.renuevo.feign.config.ReactiveFeignConfig; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import reactivefeign.spring.config.ReactiveFeignClient; 10 | import reactor.core.publisher.Mono; 11 | 12 | import java.util.Map; 13 | 14 | @ReactiveFeignClient(name = "sample-local-reactive-feign-client", url = "http://localhost:8080", configuration = ReactiveFeignConfig.class) 15 | public interface SampleReactiveLocalFeignClient { 16 | 17 | @PostMapping("/post") 18 | Mono postCall(PostRequest postRequest); 19 | 20 | @PostMapping("/post") 21 | Mono postMapCall(Map postRequestMap); 22 | 23 | @GetMapping("/camel/test") 24 | Mono camelCall(); 25 | 26 | @GetMapping("/snake/test") 27 | Mono snakeCall(); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /spring-boot-kafka-in-action/src/main/java/com/github/renuevo/consumer/service/KafkaConsumerService.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.consumer.service; 2 | 3 | import com.github.renuevo.model.DataModel; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.kafka.annotation.KafkaListener; 6 | import org.springframework.kafka.support.KafkaHeaders; 7 | import org.springframework.messaging.handler.annotation.Header; 8 | import org.springframework.messaging.handler.annotation.Payload; 9 | import org.springframework.stereotype.Service; 10 | 11 | @Slf4j 12 | @Service 13 | public class KafkaConsumerService { 14 | 15 | @KafkaListener(topics = "kafka-test-topic") 16 | public void listenWithHeaders(@Payload String message, @Header(KafkaHeaders.RECEIVED_PARTITION_ID) int partition) { 17 | log.info("Received Message : {} / From Partition : {}", message, partition); 18 | } 19 | 20 | @KafkaListener( 21 | topics = "kafka-data-model-topic", 22 | containerFactory = "kafkaListenerContainerDataModelFactory") 23 | public void dataModelListener(DataModel dataModel) { 24 | log.info("Received DataModel : {} ", dataModel.toString()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/entity/Pay2.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import javax.persistence.Entity; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.GenerationType; 11 | import javax.persistence.Id; 12 | import java.time.LocalDateTime; 13 | 14 | @Setter 15 | @Getter 16 | @ToString 17 | @NoArgsConstructor 18 | @Entity(name = "pay2") 19 | public class Pay2 { 20 | 21 | @Id 22 | @GeneratedValue(strategy = GenerationType.IDENTITY) 23 | private Long id; 24 | private Long amount; 25 | private String txName; 26 | private LocalDateTime txDateTime; 27 | 28 | public Pay2(Long amount, String txName, LocalDateTime txDateTime) { 29 | this.amount = amount; 30 | this.txName = txName; 31 | this.txDateTime = txDateTime; 32 | } 33 | 34 | public Pay2(Long id, Long amount, String txName, LocalDateTime txDateTime) { 35 | this.id = id; 36 | this.amount = amount; 37 | this.txName = txName; 38 | this.txDateTime = txDateTime; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/writer/ElasticItemWriter.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.writer; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.github.renuevo.vo.ElasticWriterTestVo; 5 | import lombok.Builder; 6 | import org.elasticsearch.action.index.IndexRequest; 7 | import org.elasticsearch.client.RequestOptions; 8 | import org.elasticsearch.client.RestHighLevelClient; 9 | import org.springframework.batch.item.ItemWriter; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | @Builder 15 | public class ElasticItemWriter implements ItemWriter { 16 | 17 | private RestHighLevelClient restHighLevelClient; 18 | private IndexRequest indexRequest; 19 | private ObjectMapper objectMapper; 20 | 21 | @Override 22 | public void write(List items) throws Exception { 23 | objectMapper = new ObjectMapper(); 24 | for (ElasticWriterTestVo elasticWriterTestVo : items) { 25 | indexRequest.source(objectMapper.convertValue(elasticWriterTestVo, Map.class)); 26 | restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT); 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/common/WebfluxConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.common; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.codec.ServerCodecConfigurer; 7 | import org.springframework.http.codec.json.Jackson2JsonDecoder; 8 | import org.springframework.http.codec.json.Jackson2JsonEncoder; 9 | import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; 10 | import org.springframework.web.reactive.config.WebFluxConfigurer; 11 | 12 | @RequiredArgsConstructor 13 | @Configuration 14 | public class WebfluxConfig implements WebFluxConfigurer { 15 | 16 | private final Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder; 17 | 18 | @Override 19 | public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) { 20 | ObjectMapper objectMapper = jackson2ObjectMapperBuilder.build(); 21 | configurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper)); 22 | configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/README.md: -------------------------------------------------------------------------------- 1 | # HTTP Rest Call 2 | 3 | 4 | 5 | # Elastic Rest Async Call 6 | ```json 7 | 8 | POST _bulk 9 | {"index":{"_index":"restclient_test_1","_id":"1"}} 10 | {"word":"스팀게임"} 11 | {"index":{"_index":"restclient_test_1","_id":"2"}} 12 | {"word":"스팀게임 추천"} 13 | {"index":{"_index":"restclient_test_1","_id":"3"}} 14 | {"word":"스팀게임 추천 2019"} 15 | {"index":{"_index":"restclient_test_1","_id":"4"}} 16 | {"word":"스팀게임 환불"} 17 | {"index":{"_index":"restclient_test_1","_id":"5"}} 18 | {"word":"스팀게임 싸게"} 19 | {"index":{"_index":"restclient_test_1","_id":"6"}} 20 | {"word":"스팀게임 순위"} 21 | {"index":{"_index":"restclient_test_1","_id":"7"}} 22 | {"word":"스팀게임 추천 2020"} 23 | {"index":{"_index":"restclient_test_1","_id":"8"}} 24 | {"word":"스팀게임 환불하는법"} 25 | 26 | ``` 27 | 28 | 29 | ```json 30 | 31 | PUT restclient_test_1 32 | { 33 | "settings": { 34 | "index": { 35 | "number_of_shards": 1, 36 | "number_of_replicas": 1 37 | } 38 | }, 39 | "mappings": { 40 | "properties": { 41 | "word": { 42 | "type": "text", 43 | "fields": { 44 | "keyword": { 45 | "type": "keyword" 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | ``` -------------------------------------------------------------------------------- /spring-boot-batch-in-action/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | repositories { 3 | maven { url 'https://jitpack.io' } //add repositories 4 | } 5 | 6 | ext { 7 | set("elasticsearch.version", "7.3.2") 8 | } 9 | 10 | 11 | dependencies { 12 | implementation 'org.springframework.boot:spring-boot-starter-batch' 13 | implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' 14 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 15 | implementation group: 'com.thoughtworks.xstream', name: 'xstream', version: '1.4.11.1' 16 | implementation group: 'org.springframework', name: 'spring-oxm', version: '5.1.10.RELEASE' 17 | implementation group: 'org.elasticsearch.client', name: 'elasticsearch-rest-high-level-client', version: '6.5.4' 18 | //implementation group: 'org.elasticsearch', name: 'elasticsearch', version: '6.5.4' 19 | implementation 'com.github.renuevo:drcode_library:1.2' //add releases current version 20 | runtimeOnly 'com.h2database:h2' 21 | 22 | implementation 'com.querydsl:querydsl-jpa' 23 | implementation 'com.querydsl:querydsl-apt' 24 | 25 | testImplementation 'org.springframework.batch:spring-batch-test' 26 | 27 | //implementation 'org.springframework.boot:spring-boot-starter-web' 28 | } -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/component/ParameterTasklet.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.component; 2 | 3 | 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.batch.core.StepContribution; 6 | import org.springframework.batch.core.configuration.annotation.StepScope; 7 | import org.springframework.batch.core.scope.context.ChunkContext; 8 | import org.springframework.batch.core.step.tasklet.Tasklet; 9 | import org.springframework.batch.repeat.RepeatStatus; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Slf4j 14 | @Component 15 | @StepScope 16 | public class ParameterTasklet implements Tasklet { 17 | 18 | @Value("#{jobParameters[requestDate]}") 19 | private String requestDate; 20 | 21 | public ParameterTasklet() { 22 | log.info("[========= Create ParameterTasklet ==========]"); //생성 시점 확인 23 | } 24 | 25 | @Override 26 | public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { 27 | log.info("[========= This is ParameterTasklet Step ==========]"); 28 | log.info("[========= requestDate {} ==========]", requestDate); 29 | return RepeatStatus.FINISHED; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/client/SampleFeignClientBuild.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.client; 2 | 3 | import feign.Contract; 4 | import feign.Feign; 5 | import feign.codec.Decoder; 6 | import feign.codec.Encoder; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | 12 | /** 13 | *
14 |  * @className : SampleFeignClientConfig
15 |  * @author : Deokhwa.Kim
16 |  * @since : 2020-03-19
17 |  * 
18 | */ 19 | @Configuration 20 | @RequiredArgsConstructor 21 | //@Import(FeignConfig.class) 22 | public class SampleFeignClientBuild { 23 | 24 | private final Encoder encoder; 25 | private final Decoder decoder; 26 | private final Contract contract; 27 | 28 | @Bean 29 | public SampleBuildFeignClient recruitSearchClient(@Value("${naver.url.search}") String searchUrl) { 30 | //https://github.com/OpenFeign/feign#dynamic-query-parameters 31 | return Feign.builder() 32 | .encoder(encoder) 33 | .decoder(decoder) 34 | .contract(contract) 35 | .target(SampleBuildFeignClient.class, searchUrl); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/job/JobParameterComponentConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.job; 2 | 3 | import com.github.renuevo.component.ParameterTasklet; 4 | import lombok.AllArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.batch.core.Job; 7 | import org.springframework.batch.core.Step; 8 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 9 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | 14 | @Slf4j 15 | @Configuration 16 | @AllArgsConstructor 17 | public class JobParameterComponentConfig { 18 | 19 | private final JobBuilderFactory jobBuilderFactory; 20 | private final StepBuilderFactory stepBuilderFactory; 21 | private final ParameterTasklet parameterTasklet; 22 | 23 | @Bean 24 | public Job JobParameterComponentBean() { 25 | return jobBuilderFactory.get("jobParameterScope") 26 | .start(scopeStep()) 27 | .build(); 28 | } 29 | 30 | @Bean 31 | public Step scopeStep() { 32 | return stepBuilderFactory.get("scopeStep") 33 | .tasklet(parameterTasklet) 34 | .build(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/repo/order/OrderRdbRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.repo.order; 2 | 3 | import com.github.renuevo.domain.order.OrderDataModel; 4 | import com.github.renuevo.domain.order.OrderRepository; 5 | import com.github.renuevo.repo.store.event.OrderEvent; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.context.ApplicationEventPublisher; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import java.util.List; 11 | 12 | @RequiredArgsConstructor 13 | @Repository 14 | public class OrderRdbRepository implements OrderRepository { 15 | 16 | private final JpaOrderRepository jpaOrderRepository; 17 | private final ApplicationEventPublisher publisher; 18 | 19 | @Override 20 | public List findOrderAll() { 21 | return OrderMapper 22 | .INSTANCE 23 | .collectionOrderToOrderDataModelList(jpaOrderRepository.findAll()); 24 | } 25 | 26 | @Override 27 | public OrderDataModel saveOrder(OrderDataModel orderDataModel) { 28 | OrderHistory orderHistory = jpaOrderRepository.save(OrderMapper.INSTANCE.orderDataModelToOrder(orderDataModel)); 29 | publisher.publishEvent(new OrderEvent(orderHistory)); 30 | return OrderMapper.INSTANCE.orderToOrderDataModel(orderHistory); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/config/ReactiveFeignConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import reactivefeign.ReactiveOptions; 6 | import reactivefeign.retry.BasicReactiveRetryPolicy; 7 | import reactivefeign.retry.ReactiveRetryPolicy; 8 | import reactivefeign.webclient.WebReactiveOptions; 9 | 10 | /** 11 | * 안타깝게도 reactive feign의 경우 따로 decoder 설정이 없다 12 | * 기본적으로 netty면 netty의 기본 message bind 형식을 따른다 13 | */ 14 | @Configuration 15 | public class ReactiveFeignConfig { 16 | 17 | @Bean 18 | public ReactiveOptions reactiveOptions() { 19 | return new WebReactiveOptions.Builder() 20 | .setReadTimeoutMillis(3000) 21 | .setConnectTimeoutMillis(10000) 22 | .build(); 23 | } 24 | 25 | /** 26 | * {@link https://github.com/reactor/reactor} 27 | * this is required reactor-bom library `reactor.util.retry` -> reactor-core 2.3.4 28 | */ 29 | @Bean 30 | public ReactiveRetryPolicy reactiveRetryPolicy() { 31 | return BasicReactiveRetryPolicy.retryWithBackoff(3, 5); 32 | } 33 | 34 | @Bean 35 | public feign.codec.ErrorDecoder reactiveStatusHandler() { 36 | return new FeignErrorDecoder(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spring-boot-redis-in-action/src/main/java/com/github/renuevo/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.redis.connection.RedisConnectionFactory; 6 | import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; 7 | import org.springframework.data.redis.core.RedisKeyValueAdapter; 8 | import org.springframework.data.redis.core.RedisTemplate; 9 | import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; 10 | import org.springframework.data.redis.serializer.StringRedisSerializer; 11 | 12 | @Configuration 13 | @EnableRedisRepositories(enableKeyspaceEvents = RedisKeyValueAdapter.EnableKeyspaceEvents.ON_STARTUP) //해당설정이 없을시 redis key list 영구적으로 남음 14 | public class RedisConfig { 15 | 16 | @Bean 17 | public RedisConnectionFactory redisConnectionFactory() { 18 | return new LettuceConnectionFactory(); 19 | } 20 | 21 | @Bean 22 | public RedisTemplate redisTemplate() { 23 | RedisTemplate redisTemplate = new RedisTemplate<>(); 24 | redisTemplate.setConnectionFactory(redisConnectionFactory()); 25 | redisTemplate.setKeySerializer(new StringRedisSerializer()); 26 | return redisTemplate; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/querydsl/expression/WhereExpression.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.querydsl.expression; 2 | 3 | import com.querydsl.core.types.dsl.BooleanExpression; 4 | import com.querydsl.core.types.dsl.NumberPath; 5 | import com.querydsl.core.types.dsl.StringPath; 6 | 7 | public enum WhereExpression { 8 | GT( 9 | (id, page, currentId) -> page == 0? id.goe(currentId): id.gt(currentId), 10 | (id, page, currentId) -> page == 0? id.goe(currentId): id.gt(currentId)), 11 | LT( 12 | (id, page, currentId) -> page == 0? id.loe(currentId): id.lt(currentId), 13 | (id, page, currentId) -> page == 0? id.loe(currentId): id.lt(currentId) 14 | ); 15 | 16 | private final WhereStringFunction string; 17 | private final WhereNumberFunction number; 18 | 19 | WhereExpression(WhereStringFunction string, WhereNumberFunction number) { 20 | this.string = string; 21 | this.number = number; 22 | } 23 | 24 | public BooleanExpression expression (StringPath id, int page, String currentId) { 25 | return this.string.apply(id, page, currentId); 26 | } 27 | 28 | @SuppressWarnings("unchecked") 29 | public >BooleanExpression expression (NumberPath id, int page, N currentId) { 30 | return this.number.apply(id, page, currentId); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /spring-boot-redis-in-action/src/main/java/com/github/renuevo/config/ReactiveRedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; 6 | import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; 7 | import org.springframework.data.redis.serializer.RedisSerializationContext; 8 | import org.springframework.data.redis.serializer.RedisSerializationContext.RedisSerializationContextBuilder; 9 | import org.springframework.data.redis.core.ReactiveRedisTemplate; 10 | import org.springframework.data.redis.serializer.StringRedisSerializer; 11 | 12 | @Configuration 13 | public class ReactiveRedisConfig { 14 | 15 | @Bean 16 | public ReactiveRedisTemplate reactiveJsonObjectRedisTemplate( 17 | ReactiveRedisConnectionFactory connectionFactory) { 18 | 19 | RedisSerializationContextBuilder builder = RedisSerializationContext 20 | .newSerializationContext(new StringRedisSerializer()); 21 | 22 | RedisSerializationContext serializationContext = builder 23 | .value(new GenericJackson2JsonRedisSerializer()).build(); 24 | 25 | return new ReactiveRedisTemplate<>(connectionFactory, serializationContext); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /spring-boot-jpa-in-action/src/main/java/com/github/renuevo/n_plus_one/AcademyService.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.n_plus_one; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.transaction.annotation.Transactional; 7 | 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | @Slf4j 12 | @AllArgsConstructor 13 | @Service 14 | public class AcademyService { 15 | 16 | private final AcademyRepository academyRepository; 17 | 18 | @Transactional(readOnly = true) 19 | public List findAllSubjectNames(){ 20 | return extractSubjectNames(academyRepository.findAll()); 21 | } 22 | 23 | 24 | @Transactional(readOnly = true) 25 | public List findJoinAllSubjectNames(){ 26 | return extractSubjectNames(academyRepository.findeAllJoinFetch()); 27 | } 28 | 29 | /** 30 | * Lazy Load를 수행하기 위해 메소드를 별도로 생성 31 | */ 32 | private List extractSubjectNames(List academies){ 33 | log.info(">>>>>>>>[모든 과목을 추출한다]<<<<<<<<<"); 34 | log.info("Academy Size : {}", academies.size()); 35 | 36 | List resultList = academies.stream() 37 | .map(a -> a.getSubjectSet().iterator().next().getName()) 38 | .collect(Collectors.toList()); 39 | 40 | log.info("N Plus One 발생 여부 확인"); 41 | return resultList; 42 | } 43 | } -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/querydsl/expression/Expression.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.querydsl.expression; 2 | 3 | import com.querydsl.core.types.OrderSpecifier; 4 | import com.querydsl.core.types.dsl.BooleanExpression; 5 | import com.querydsl.core.types.dsl.NumberPath; 6 | import com.querydsl.core.types.dsl.StringPath; 7 | 8 | public enum Expression { 9 | ASC(WhereExpression.GT, OrderExpression.ASC), 10 | DESC(WhereExpression.LT, OrderExpression.DESC); 11 | 12 | private final WhereExpression where; 13 | private final OrderExpression order; 14 | 15 | Expression(WhereExpression where, OrderExpression order) { 16 | this.where = where; 17 | this.order = order; 18 | } 19 | 20 | public boolean isAsc() { 21 | return this == ASC; 22 | } 23 | 24 | public BooleanExpression where(StringPath id, int page, String currentId) { 25 | return where.expression(id, page, currentId); 26 | } 27 | 28 | public > BooleanExpression where(NumberPath id, int page, N currentId) { 29 | return where.expression(id, page, currentId); 30 | } 31 | 32 | public OrderSpecifier order(StringPath id) { 33 | return isAsc() ? id.asc() : id.desc(); 34 | } 35 | 36 | public > OrderSpecifier order(NumberPath id) { 37 | return isAsc() ? id.asc() : id.desc(); 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/repo/store/StoreRdbRepository.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.repo.store; 2 | 3 | import com.github.renuevo.domain.order.OrderDataModel; 4 | import com.github.renuevo.domain.store.StoreDataModel; 5 | import com.github.renuevo.domain.store.StoreRepository; 6 | import com.github.renuevo.repo.order.OrderMapper; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.stereotype.Repository; 10 | 11 | @Slf4j 12 | @RequiredArgsConstructor 13 | @Repository 14 | public class StoreRdbRepository implements StoreRepository { 15 | 16 | private final JpaStoreRepository jpaStoreRepository; 17 | 18 | @Override 19 | public StoreDataModel saveStore(StoreDataModel storeDataModel) { 20 | return StoreMapper.INSTANCE 21 | .sotreToStoreDataModel( 22 | jpaStoreRepository.save(StoreMapper.INSTANCE.storeDataModelToStore(storeDataModel)) 23 | ); 24 | } 25 | 26 | @Override 27 | public StoreDataModel orderStore(OrderDataModel orderDataModel) { 28 | return jpaStoreRepository.findById(orderDataModel.getStoreDataModel().getId()) 29 | .filter(store -> store.orderCount(OrderMapper.INSTANCE.orderDataModelToOrder(orderDataModel))) 30 | .map(jpaStoreRepository::save) 31 | .map(StoreMapper.INSTANCE::sotreToStoreDataModel) 32 | .orElseThrow(() -> new RuntimeException("스토어 재고가 부족 합니다.")); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/entity/Pay.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.entity; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | 8 | import javax.persistence.Entity; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.GenerationType; 11 | import javax.persistence.Id; 12 | import java.time.LocalDateTime; 13 | import java.time.format.DateTimeFormatter; 14 | 15 | @Setter 16 | @Getter 17 | @Entity 18 | @ToString 19 | @NoArgsConstructor 20 | public class Pay { 21 | 22 | private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"); 23 | 24 | @Id 25 | @GeneratedValue(strategy = GenerationType.IDENTITY) 26 | private Long id; 27 | private Long amount; 28 | private String txName; 29 | private LocalDateTime txDateTime; 30 | 31 | public Pay(Long amount, String txName, String txDateTime) { 32 | this.amount = amount; 33 | this.txName = txName; 34 | this.txDateTime = convertStringToTime(txDateTime); 35 | } 36 | 37 | public Pay(Long id, Long amount, String txName, String txDateTime) { 38 | this.id = id; 39 | this.amount = amount; 40 | this.txName = txName; 41 | this.txDateTime = convertStringToTime(txDateTime); 42 | } 43 | 44 | private LocalDateTime convertStringToTime(String txDateTime) { 45 | return LocalDateTime.parse(txDateTime, DATE_TIME_FORMATTER); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/main/java/com/github/renuevo/repo/order/OrderMapper.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.repo.order; 2 | 3 | import com.github.renuevo.domain.order.OrderDataModel; 4 | import com.github.renuevo.repo.store.StoreMapper; 5 | import org.mapstruct.IterableMapping; 6 | import org.mapstruct.Mapper; 7 | import org.mapstruct.Mapping; 8 | import org.mapstruct.Named; 9 | import org.mapstruct.factory.Mappers; 10 | 11 | import java.util.Collection; 12 | import java.util.List; 13 | 14 | @Mapper(imports = {StoreMapper.class}) 15 | public interface OrderMapper { 16 | OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class); 17 | 18 | @IterableMapping(qualifiedByName = "orderToOrderDataModel") 19 | List collectionOrderToOrderDataModelList(Collection orderHistoryCollection); 20 | 21 | @Named("orderToOrderDataModel") 22 | @Mapping(target = "storeDataModel" ,expression = "java(StoreMapper.INSTANCE.sotreToStoreDataModel(orderHistory.getStore()))") 23 | OrderDataModel orderToOrderDataModel(OrderHistory orderHistory); 24 | 25 | @Mapping(target = "store", expression = "java(StoreMapper.INSTANCE.storeDataModelToStore(orderDataModel.getStoreDataModel()))") 26 | OrderHistory orderDataModelToOrder(OrderDataModel orderDataModel); 27 | 28 | @Mapping(target = "id", ignore = true) 29 | @Mapping(target = "store", expression = "java(StoreMapper.INSTANCE.storeDataModelToStore(orderDataModel.getStoreDataModel()))") 30 | OrderHistory orderDataModelToNewOrder(OrderDataModel orderDataModel); 31 | } 32 | -------------------------------------------------------------------------------- /spring-boot-redis-in-action/src/main/java/com/github/renuevo/domain/person/PersonController.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.domain.person; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import java.time.LocalDate; 11 | import java.util.Random; 12 | 13 | @Slf4j 14 | @RequiredArgsConstructor 15 | @RestController 16 | @RequestMapping("/person") 17 | public class PersonController { 18 | 19 | private final PersonRedisRepository personRedisRepository; 20 | 21 | @GetMapping("/save/{name}") 22 | public String save(@PathVariable String name) { 23 | PersonEntity personEntity = PersonEntity.builder() 24 | .name(name) 25 | .age((new Random()).nextInt(30) + 1) 26 | .birthday(LocalDate.now()) 27 | .build(); 28 | 29 | personEntity = personRedisRepository.save(personEntity); 30 | log.info("PersonEntity Save : {}", personEntity); 31 | return personEntity.toString(); 32 | } 33 | 34 | @GetMapping("/get/{name}") 35 | public PersonEntity get(@PathVariable String name) { 36 | PersonEntity personEntity = personRedisRepository.findById(name).orElseThrow(); 37 | log.info("PersonEntity Get : {}", personEntity); 38 | return personEntity; 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /spring-boot-kafka-in-action/src/main/java/com/github/renuevo/producer/controller/ProducerController.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.producer.controller; 2 | 3 | import com.github.renuevo.model.DataModel; 4 | import com.github.renuevo.producer.service.KafkaProducerService; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.ModelAttribute; 9 | import org.springframework.web.bind.annotation.RequestParam; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | @RequiredArgsConstructor 13 | @RestController("/kafka") 14 | public class ProducerController { 15 | 16 | private final KafkaProducerService kafkaProducerService; 17 | 18 | @GetMapping("/push/msg") 19 | public void pushMassage(@RequestParam String msg) { 20 | this.kafkaProducerService.sendMessage(msg); 21 | } 22 | 23 | @GetMapping("/push_async/msg") 24 | public ResponseEntity pushAsyncMassage(@RequestParam String msg) { 25 | this.kafkaProducerService.sendAsyncMessage(msg); 26 | return ResponseEntity.ok("success"); 27 | } 28 | 29 | @GetMapping("/push/data_model") 30 | public void pushDataModel(@ModelAttribute DataModel dataModel) { 31 | this.kafkaProducerService.sendDataModel(dataModel); 32 | } 33 | 34 | @GetMapping("/push_async/data_model") 35 | public void pushAsyncDataModel(@ModelAttribute DataModel dataModel) { 36 | this.kafkaProducerService.sendAsyncDataModel(dataModel); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/controller/JobController.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.controller; 2 | 3 | /* web dependencies 주석으로 인한 주석처리 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.batch.core.Job; 6 | import org.springframework.batch.core.JobParameters; 7 | import org.springframework.batch.core.JobParametersBuilder; 8 | import org.springframework.batch.core.launch.JobLauncher; 9 | import org.springframework.beans.factory.annotation.Qualifier; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | @Slf4j 15 | @RestController 16 | public class JobController { 17 | 18 | private final JobLauncher jobLauncher; 19 | private final Job job; 20 | 21 | public JobController(JobLauncher jobLauncher, @Qualifier("JobParameterBean") Job job) { 22 | this.jobLauncher = jobLauncher; 23 | this.job = job; 24 | } 25 | 26 | @GetMapping("/launchjob") 27 | public String handle(@RequestParam("requestDate") String requestDate) { 28 | try { 29 | JobParameters jobParameters = new JobParametersBuilder() 30 | .addString("requestDate", requestDate) 31 | .addLong("time", System.currentTimeMillis()) 32 | .toJobParameters(); 33 | jobLauncher.run(job, jobParameters); 34 | } catch (Exception e) { 35 | log.error("Error : {}", e.getMessage(), e); 36 | } 37 | return "Deon"; 38 | } 39 | } 40 | */ -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | main: 3 | banner-mode: 'off' 4 | allow-bean-definition-overriding: true 5 | jackson: 6 | property-naming-strategy: SNAKE_CASE 7 | 8 | naver: 9 | client: 10 | id: ${naver.id:NONE} 11 | secret: ${naver.secret:NONE} 12 | url: 13 | search: https://openapi.naver.com/v1/search 14 | 15 | search: 16 | es-host: localhost 17 | es-port: 9200 18 | es-index: ${es.index:restclient_test_1} 19 | 20 | 21 | management.health.elasticsearch.enabled: false 22 | management.health.circuitbreakers.enabled: true 23 | 24 | 25 | logging: 26 | level: 27 | com.github.renuevo.feign.client: debug 28 | reactivefeign.client.log: debug 29 | 30 | 31 | feign: 32 | client: 33 | config: 34 | default: 35 | loggerLevel: basic 36 | errorDecoder: com.github.renuevo.feign.config.FeignErrorDecoder 37 | error-retry-client: 38 | loggerLevel: basic 39 | errorDecoder: com.github.renuevo.feign.config.FeignRetryErrorDecoder 40 | 41 | 42 | reactive: 43 | feign: 44 | client: 45 | config: 46 | default: 47 | # statusHandler: com.github.renuevo.feign.config.ReactiveFeignErrorDecoder 48 | options: 49 | connectTimeoutMillis: 10000 50 | readTimeoutMillis: 3000 51 | errorDecoder: com.github.renuevo.feign.config.FeignErrorDecoder # 기존 ErrorDecoder 사용 가능 statusHandler가 있을 시 statusHandler 우선 52 | sample-error-reactive-retry-feign-client: 53 | errorDecoder: com.github.renuevo.feign.config.FeignRetryErrorDecoder 54 | cloud: 55 | enabled: false 56 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/job/JobSimpleConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.job; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.batch.core.Job; 6 | import org.springframework.batch.core.Step; 7 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 8 | import org.springframework.batch.core.configuration.annotation.JobScope; 9 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 10 | import org.springframework.batch.repeat.RepeatStatus; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | 15 | @Slf4j 16 | @Configuration 17 | @AllArgsConstructor 18 | public class JobSimpleConfig { 19 | 20 | private final JobBuilderFactory jobBuilderFactory; 21 | private final StepBuilderFactory stepBuilderFactory; 22 | 23 | @Bean 24 | public Job jobBean() { 25 | return jobBuilderFactory.get("testJob") 26 | .start(stepBean(null)) 27 | .build(); 28 | } 29 | 30 | @Bean 31 | @JobScope 32 | public Step stepBean(@Value("#{jobParameters[requestDate]}") String requestDate) { 33 | return stepBuilderFactory.get("testStep") 34 | .tasklet(((contribution, chunkContext) -> { //Tasklet Step 안에서 실행될 기능 35 | log.info("[========= This is Step ==========]"); 36 | log.info("[========= requestDate {} ==========]", requestDate); 37 | return RepeatStatus.FINISHED; 38 | })) 39 | .build(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot In Action :sparkles: 2 | 3 | 🚀이후 프로젝트 개발은 [spring-boot-kotlin-in-action](https://github.com/renuevo/spring-boot-kotlin-in-action)에서 작성됩니다 4 | 5 | --- 6 | 7 | ## Introduction 8 | Springboot를 개발 공부를 정리 하는 용도의 Project 9 | 10 | [Spring Scope](https://github.com/renuevo/spring-boot-in-action/tree/master/spring-boot-scope-in-action) 11 | > 스프링 스코프의 관하여 12 | 13 |
14 | 15 | [Spring Batch](https://github.com/renuevo/spring-boot-in-action/tree/master/spring-boot-batch-in-action) 16 | > spring batch 가이드 17 | 18 |
19 | 20 | [Spring Exception](https://github.com/renuevo/spring-boot-in-action/tree/master/spring-boot-exception-in-action) 21 | > ControllerAdvice를 활용한 expection 관리 22 | 23 |
24 | 25 | [Rest Client](https://github.com/renuevo/spring-boot-in-action/tree/master/spring-boot-rest-client-in-action) 26 | > * web client 27 | > * feign client 28 | > * resttemplate 29 | > * elastic client 30 | 31 |
32 | 33 | [Spring Redis](https://github.com/renuevo/spring-boot-in-action/tree/master/spring-boot-rest-client-in-action) 34 | > redisTemplate과 RedisRepository를 사용하는 방법 35 | 36 |
37 | 38 | [Spring Kafka](https://github.com/renuevo/spring-boot-in-action/tree/master/spring-boot-kafka-in-action) 39 | > kafka manage akhq 구성 docker compose와 spring에서 pus/con하는 방법 40 | 41 |
42 | 43 | [Spring Event](https://github.com/renuevo/spring-boot-in-action/tree/master/spring-boot-event-in-action) 44 | > spring event 기본적인 사용방법 45 | > 사용하시려면 [TransactionalEventListener](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/event/TransactionalEventListener.html) 46 | 도 같이 공부하셔서 사용하시길 권장드립니다 47 | 48 |
49 | 50 | [Spring Jpa](https://github.com/renuevo/spring-boot-in-action/tree/master/spring-boot-jpa-in-action) 51 | > spring jpa 기본 사용 방법 및 1+N이슈 :construction: 52 | 53 | --- 54 | 55 | **이제 코틀린 프로젝트로..🚀** 56 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/querydsl/options/QuerydslNoOffsetOptions.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.querydsl.options; 2 | 3 | import com.github.renuevo.querydsl.expression.Expression; 4 | import com.querydsl.core.types.Path; 5 | import com.querydsl.jpa.impl.JPAQuery; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | import javax.annotation.Nonnull; 9 | import java.lang.reflect.Field; 10 | 11 | @Slf4j 12 | public abstract class QuerydslNoOffsetOptions { 13 | 14 | protected final String fieldName; 15 | protected final Expression expression; 16 | 17 | public QuerydslNoOffsetOptions(@Nonnull Path field, 18 | @Nonnull Expression expression) { 19 | String[] qField = field.toString().split("\\."); 20 | this.fieldName = qField[qField.length - 1]; 21 | this.expression = expression; 22 | 23 | if (log.isDebugEnabled()) { 24 | log.debug("fieldName= " + fieldName); 25 | } 26 | } 27 | 28 | public String getFieldName() { 29 | return fieldName; 30 | } 31 | 32 | public abstract void initKeys(JPAQuery query, int page); 33 | 34 | protected abstract void initFirstId(JPAQuery query); 35 | 36 | protected abstract void initLastId(JPAQuery query); 37 | 38 | public abstract JPAQuery createQuery(JPAQuery query, int page); 39 | 40 | public abstract void resetCurrentId(T item); 41 | 42 | protected Object getFiledValue(T item) { 43 | try { 44 | Field field = item.getClass().getDeclaredField(fieldName); 45 | field.setAccessible(true); 46 | return field.get(item); 47 | } catch (NoSuchFieldException | IllegalAccessException e) { 48 | log.error("Not Found or Not Access Field= " + fieldName, e); 49 | throw new IllegalArgumentException("Not Found or Not Access Field"); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /spring-boot-jpa-in-action/src/test/java/com/github/renuevo/n_plus_one/AcademyServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.n_plus_one; 2 | 3 | import com.google.common.collect.Lists; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.AfterEach; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | import static org.hamcrest.CoreMatchers.is; 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | 13 | import java.util.List; 14 | 15 | @SpringBootTest 16 | class AcademyServiceTest { 17 | 18 | @Autowired 19 | private AcademyRepository academyRepository; 20 | 21 | @Autowired 22 | private AcademyService academyService; 23 | 24 | 25 | @AfterEach 26 | void cleanAll() { 27 | academyRepository.deleteAll(); 28 | } 29 | 30 | 31 | @BeforeEach 32 | void setup() { 33 | List academies = Lists.newArrayList(); 34 | 35 | for (int i = 0; i < 10; i++) { 36 | Academy academy = Academy.builder() 37 | .name("강남스쿨" + i) 38 | .build(); 39 | 40 | academy.addSubject(Subject.builder().name("자바웹개발" + i).build()); 41 | academies.add(academy); 42 | } 43 | 44 | academyRepository.saveAll(academies); 45 | } 46 | 47 | @Test 48 | void Academy여러개를_조회시_Subject가_N1_쿼리가발생한다() throws Exception { 49 | //given 50 | List subjectNames = academyService.findAllSubjectNames(); 51 | 52 | //then 53 | assertThat(subjectNames.size(), is(10)); 54 | } 55 | 56 | 57 | @Test 58 | void Academy_join_fetch사용() throws Exception { 59 | //given 60 | List subjectNames = academyService.findJoinAllSubjectNames(); 61 | 62 | //then 63 | assertThat(subjectNames.size(), is(10)); 64 | 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/elastic/ElasticRestClientConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.elastic; 2 | 3 | import com.google.common.collect.Lists; 4 | import lombok.NoArgsConstructor; 5 | import org.apache.http.Header; 6 | import org.apache.http.HttpHeaders; 7 | import org.apache.http.HttpHost; 8 | import org.apache.http.message.BasicHeader; 9 | import org.elasticsearch.client.RestClient; 10 | import org.elasticsearch.client.RestClientBuilder; 11 | import org.elasticsearch.client.RestHighLevelClient; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | 16 | import java.util.List; 17 | import java.util.Objects; 18 | 19 | /** 20 | *
21 |  * @className : ElasticRestClientConfig
22 |  * @author : Deokhwa.Kim
23 |  * @since : 2020-01-03
24 |  * @summary : Elastic Rest Client Configuration
25 |  * 
26 | */ 27 | @Configuration 28 | @NoArgsConstructor 29 | public class ElasticRestClientConfig { 30 | 31 | @Value("${search.es-port}") 32 | private Integer elasticPort; 33 | 34 | @Value("${search.es-host}") 35 | public List elasticHostList; 36 | 37 | public RestClientBuilder restClientBuilder() { 38 | List httpHostList = Lists.newArrayList(); 39 | Header[] headers = {new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json"), new BasicHeader(HttpHeaders.CONTENT_ENCODING, "UTF-8")}; 40 | 41 | Objects.requireNonNull(elasticHostList).forEach(it -> httpHostList.add(new HttpHost(it, elasticPort, "http"))); 42 | return RestClient.builder(httpHostList.toArray(new HttpHost[0])) 43 | //.setMaxRetryTimeoutMillis(10000) 44 | .setDefaultHeaders(headers); 45 | } 46 | 47 | @Bean(destroyMethod = "close") 48 | public RestHighLevelClient restHighLevelClient() { 49 | return new RestHighLevelClient(restClientBuilder()); 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'kotlin' 2 | apply plugin: 'kotlin-spring' 3 | apply plugin: 'kotlin-allopen' 4 | 5 | ext { 6 | set("springCloudVersion", "2021.0.1") 7 | set("elasticsearch.version", "7.3.2") 8 | } 9 | 10 | dependencyManagement { 11 | imports { 12 | mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" 13 | } 14 | } 15 | 16 | dependencies { 17 | implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") 18 | implementation("org.springframework.boot:spring-boot-starter-webflux") 19 | 20 | implementation("org.elasticsearch:elasticsearch:7.3.2") 21 | implementation("org.elasticsearch.client:elasticsearch-rest-high-level-client:7.3.2") 22 | 23 | implementation("org.springframework.cloud:spring-cloud-starter-openfeign:2.2.6.RELEASE") 24 | implementation("org.springframework.cloud:spring-cloud-starter-netflix-hystrix:2.2.10.RELEASE") 25 | 26 | implementation("com.playtika.reactivefeign:feign-reactor-spring-cloud-starter:2.0.26") 27 | implementation("org.springframework.cloud:spring-cloud-starter-loadbalancer:2.2.9.RELEASE") 28 | 29 | implementation("org.springframework.boot:spring-boot-starter-validation:${springBootVersion}") 30 | implementation("org.springframework.boot:spring-boot-starter-actuator") 31 | implementation("org.springframework.boot:spring-boot-starter-aop") 32 | implementation("io.github.resilience4j:resilience4j-spring-boot2:1.7.0") 33 | 34 | implementation("com.fasterxml.jackson.core:jackson-core:2.11.0") 35 | implementation("com.fasterxml.jackson.core:jackson-databind:2.11.0") 36 | implementation("org.apache.httpcomponents:httpclient:4.5.10") 37 | implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.3") 38 | } 39 | 40 | compileKotlin { 41 | kotlinOptions { 42 | jvmTarget = JavaVersion.VERSION_11 43 | } 44 | } 45 | 46 | compileTestKotlin { 47 | kotlinOptions { 48 | jvmTarget = JavaVersion.VERSION_11 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/config/ElasticRestClientConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | import com.google.common.collect.Lists; 4 | import lombok.NoArgsConstructor; 5 | import org.apache.http.Header; 6 | import org.apache.http.HttpHeaders; 7 | import org.apache.http.HttpHost; 8 | import org.apache.http.message.BasicHeader; 9 | import org.elasticsearch.client.RestClient; 10 | import org.elasticsearch.client.RestClientBuilder; 11 | import org.elasticsearch.client.RestHighLevelClient; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | 16 | import java.util.List; 17 | import java.util.Objects; 18 | 19 | /** 20 | *
21 |  * @className : ElasticRestClientConfig
22 |  * @author : Deokhwa.Kim
23 |  * @since : 2020-01-03
24 |  * @summary : Elastic Rest Client Configuration
25 |  * 
26 | */ 27 | @Configuration 28 | @NoArgsConstructor 29 | public class ElasticRestClientConfig { 30 | 31 | @Value("${search.es-port}") 32 | private Integer elasticPort; 33 | 34 | @Value("${search.es-host}") 35 | public List elasticHostList; 36 | 37 | @SuppressWarnings("unchecked") 38 | //@Bean(name = "customRestClientBuilder") 39 | public RestClientBuilder restClientBuilder() { 40 | List httpHostList = Lists.newArrayList(); 41 | Header[] headers = {new BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json"), new BasicHeader(HttpHeaders.CONTENT_ENCODING, "UTF-8")}; 42 | 43 | Objects.requireNonNull(elasticHostList).forEach(it -> { 44 | httpHostList.add(new HttpHost(it, elasticPort, "http")); 45 | }); 46 | 47 | return RestClient.builder(httpHostList.toArray(new HttpHost[0])) 48 | //.setMaxRetryTimeoutMillis(10000) 49 | .setDefaultHeaders(headers); 50 | } 51 | 52 | @Bean 53 | public RestHighLevelClient restHighLevelClient() { 54 | return new RestHighLevelClient(restClientBuilder()); 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/resttemplate/RestTemplateConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.resttemplate; 2 | 3 | import com.github.renuevo.common.NaverProperty; 4 | import com.google.common.collect.Lists; 5 | import org.apache.http.Header; 6 | import org.apache.http.client.HttpClient; 7 | import org.apache.http.impl.client.HttpClientBuilder; 8 | import org.apache.http.message.BasicHeader; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.context.annotation.Primary; 12 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 13 | import org.springframework.http.client.SimpleClientHttpRequestFactory; 14 | import org.springframework.web.client.RestTemplate; 15 | 16 | @Configuration 17 | public class RestTemplateConfig { 18 | 19 | @Bean("componentsClientRestTemplate") 20 | public RestTemplate componentsClientRestTemplate(NaverProperty naverProperty) { 21 | HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); 22 | factory.setConnectTimeout(1000); //1초 23 | factory.setReadTimeout(1000); //1초 24 | 25 | Header id = new BasicHeader("X-Naver-Client-Id", naverProperty.getId()); 26 | Header secret = new BasicHeader("X-Naver-Client-Secret", naverProperty.getSecret()); 27 | 28 | HttpClient httpClient = HttpClientBuilder.create() 29 | .setMaxConnTotal(100) 30 | .setMaxConnPerRoute(5) 31 | .setDefaultHeaders(Lists.newArrayList(id, secret)) 32 | .build(); 33 | factory.setHttpClient(httpClient); 34 | return new RestTemplate(factory); 35 | } 36 | 37 | @Primary 38 | @Bean("simpleClientRestTemplate") 39 | public RestTemplate simpleClientRestTemplate() { 40 | SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); 41 | factory.setConnectTimeout(1000); //1초 42 | factory.setReadTimeout(1000); //1초 43 | return new RestTemplate(factory); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/feign/annotation/CustomParamAnnotationProcess.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.feign.annotation; 2 | 3 | import feign.MethodMetadata; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.cloud.openfeign.AnnotatedParameterProcessor; 6 | 7 | import java.lang.annotation.Annotation; 8 | import java.lang.reflect.Method; 9 | import java.util.Collection; 10 | import java.util.Map; 11 | 12 | import static feign.Util.checkState; 13 | 14 | @Slf4j 15 | public class CustomParamAnnotationProcess implements AnnotatedParameterProcessor { 16 | private static final Class ANNOTATION = CustomParam.class; 17 | 18 | @Override 19 | public Class getAnnotationType() { 20 | return ANNOTATION; 21 | } 22 | 23 | @Override 24 | public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) { 25 | int parameterIndex = context.getParameterIndex(); 26 | Class parameterType = method.getParameterTypes()[parameterIndex]; 27 | MethodMetadata data = context.getMethodMetadata(); 28 | 29 | if (Map.class.isAssignableFrom(parameterType)) { 30 | checkState(data.queryMapIndex() == null, "Query map can only be present once."); 31 | data.queryMapIndex(parameterIndex); 32 | return true; 33 | } 34 | 35 | CustomParam customParam = ANNOTATION.cast(annotation); 36 | String name = customParam.value(); 37 | context.setParameterName(name); 38 | Collection query = context.setTemplateParameter(name, data.template().queries().get(name)); 39 | data.template().query(name, query); 40 | 41 | /** 42 | * class type not execute in feign client 43 | * data.indexToExpanderClass().put(parameterIndex, ToJsonExpander.class); 44 | * 45 | * indexToExpander not working in reactive feign client 46 | * result data format is name={key1: value1, key2: value2} 47 | */ 48 | data.indexToExpander().put(parameterIndex, new ToJsonExpander()); 49 | 50 | return true; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /spring-boot-event-in-action/src/test/java/com/github/renuevo/SpringBootEventApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo; 2 | 3 | 4 | import com.github.renuevo.domain.order.OrderDataModel; 5 | import com.github.renuevo.repo.order.OrderMapper; 6 | import com.github.renuevo.repo.order.OrderRdbRepository; 7 | import com.github.renuevo.repo.store.JpaStoreRepository; 8 | import com.github.renuevo.repo.store.Store; 9 | import com.github.renuevo.setup.OrderHistoryBuilder; 10 | import com.github.renuevo.setup.StoreBuilder; 11 | import org.junit.jupiter.api.BeforeEach; 12 | import org.junit.jupiter.api.Test; 13 | import org.junit.jupiter.api.extension.ExtendWith; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.test.context.junit.jupiter.SpringExtension; 17 | import org.springframework.transaction.annotation.Transactional; 18 | 19 | import static org.assertj.core.api.Assertions.assertThat; 20 | 21 | @SpringBootTest 22 | @ExtendWith(SpringExtension.class) 23 | class SpringBootEventApplicationTests { 24 | 25 | @Autowired 26 | JpaStoreRepository jpaStoreRepository; 27 | 28 | @Autowired 29 | OrderRdbRepository orderRdbRepository; 30 | 31 | @BeforeEach 32 | public void init() { 33 | //given 34 | //List.of(1, 2, 3, 4, 5).forEach(number -> jpaStoreRepository.save(StoreBuilder.storeNumberBuilder(number))); 35 | } 36 | 37 | @Test 38 | @Transactional 39 | void orderEventTest() { 40 | 41 | //given 42 | Store store = jpaStoreRepository.save(StoreBuilder.storeBuilder()); 43 | 44 | //when 45 | OrderDataModel orderDataModel = orderRdbRepository.saveOrder(OrderMapper 46 | .INSTANCE 47 | .orderToOrderDataModel(OrderHistoryBuilder.orderHistoryBuilder(store))); 48 | 49 | //then 50 | store = jpaStoreRepository.getOne(store.getId()); 51 | assertThat(store.getOrderHistorySet() 52 | .stream() 53 | .findFirst() 54 | .orElseThrow() 55 | .getId()) 56 | .isEqualTo(orderDataModel.getId()); 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/config/QuerydslItemReaderJobConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | import com.github.renuevo.entity.Pay; 4 | import com.github.renuevo.querydsl.QuerydslNoOffsetPagingItemReader; 5 | import com.github.renuevo.querydsl.expression.Expression; 6 | import com.github.renuevo.querydsl.options.QuerydslNoOffsetNumberOptions; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.batch.core.Job; 10 | import org.springframework.batch.core.Step; 11 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 12 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 13 | import org.springframework.batch.item.ItemWriter; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | 17 | import javax.persistence.EntityManagerFactory; 18 | 19 | @Slf4j 20 | @Configuration 21 | @RequiredArgsConstructor 22 | public class QuerydslItemReaderJobConfig { 23 | 24 | private final JobBuilderFactory jobBuilderFactory; 25 | private final StepBuilderFactory stepBuilderFactory; 26 | private final EntityManagerFactory entityManagerFactory; 27 | 28 | private static final int CHUNK_SIZE = 10; 29 | 30 | @Bean 31 | public Job querydslItemReaderJob() { 32 | return jobBuilderFactory.get("querydslItemReaderJob") 33 | .start(querydslItemReaderStep()) 34 | .build(); 35 | } 36 | 37 | @Bean 38 | public Step querydslItemReaderStep() { 39 | return stepBuilderFactory.get("jpaPagingItemReaderStep") 40 | .chunk(CHUNK_SIZE) 41 | .reader(querydslNoOffsetPagingItemReader()) 42 | .writer(querydslItemWriter()) 43 | .build(); 44 | } 45 | 46 | @Bean 47 | public QuerydslNoOffsetPagingItemReader querydslNoOffsetPagingItemReader() { 48 | return null; 49 | } 50 | 51 | @Bean 52 | public ItemWriter querydslItemWriter() { 53 | return list -> { 54 | for (Pay pay : list) { 55 | log.info("Current Pay = {}", pay); 56 | } 57 | }; 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/config/TxtFileItemReaderJobConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | import com.github.renuevo.vo.ItemVo; 4 | import lombok.AllArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.batch.core.Job; 7 | import org.springframework.batch.core.Step; 8 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 9 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 10 | import org.springframework.batch.item.file.FlatFileItemReader; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | import org.springframework.core.io.ClassPathResource; 14 | 15 | 16 | /** 17 | *
18 |  * @className : TxtFileItemReaderJobConfig
19 |  * @author : Deokhwa.Kim
20 |  * @since : 2020-02-14
21 |  * @summary : TXT FILE ITEM READER EXAMPLE
22 |  * 
23 | */ 24 | @Slf4j 25 | @Configuration 26 | @AllArgsConstructor 27 | public class TxtFileItemReaderJobConfig { 28 | 29 | private final JobBuilderFactory jobBuilderFactory; 30 | private final StepBuilderFactory stepBuilderFactory; 31 | 32 | private static final int chunkSize = 2; 33 | 34 | @Bean 35 | public Job TxtFileItemReaderJob() { 36 | return jobBuilderFactory.get("txtFileItemReaderJob") 37 | .start(txtFileItemReaderStep()) 38 | .build(); 39 | } 40 | 41 | @Bean 42 | public Step txtFileItemReaderStep() { 43 | return stepBuilderFactory.get("txtFileItemReaderStep") 44 | .chunk(chunkSize) 45 | .reader(txtFileItemReader()) 46 | .writer(itemVo -> itemVo.stream() 47 | .map(ItemVo::toString) 48 | .forEach(log::info)) 49 | .build(); 50 | } 51 | 52 | @Bean 53 | public FlatFileItemReader txtFileItemReader() { 54 | FlatFileItemReader flatFileItemReader = new FlatFileItemReader<>(); 55 | flatFileItemReader.setResource(new ClassPathResource("read_sample/sample.txt")); 56 | flatFileItemReader.setLineMapper((line, lineNumber) -> new ItemVo(line)); 57 | return flatFileItemReader; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/config/JsonFileItemReaderJobConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | import com.github.renuevo.vo.JsonItemVo; 4 | import lombok.AllArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.batch.core.Job; 7 | import org.springframework.batch.core.Step; 8 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 9 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 10 | import org.springframework.batch.item.json.JacksonJsonObjectReader; 11 | import org.springframework.batch.item.json.JsonItemReader; 12 | import org.springframework.batch.item.json.builder.JsonItemReaderBuilder; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.core.io.ClassPathResource; 16 | 17 | @Slf4j 18 | @Configuration 19 | @AllArgsConstructor 20 | public class JsonFileItemReaderJobConfig { 21 | 22 | private final JobBuilderFactory jobBuilderFactory; 23 | private final StepBuilderFactory stepBuilderFactory; 24 | 25 | private static final int chunkSize = 2; 26 | 27 | @Bean 28 | public Job jsonFileItemReaderJob() { 29 | return jobBuilderFactory.get("jsonFileItemReaderJob") 30 | .start(jsonFileItemReaderStep()) 31 | .build(); 32 | } 33 | 34 | @Bean 35 | public Step jsonFileItemReaderStep() { 36 | return stepBuilderFactory.get("jsonFileItemReaderStep") 37 | .chunk(chunkSize) 38 | .reader(jsonItemReader()) 39 | .writer(jsonItemVos -> jsonItemVos 40 | .stream() 41 | .map(JsonItemVo::toString) 42 | .forEach(log::info)) 43 | .build(); 44 | } 45 | 46 | 47 | @Bean 48 | public JsonItemReader jsonItemReader(){ 49 | return new JsonItemReaderBuilder() 50 | .jsonObjectReader(new JacksonJsonObjectReader<>(JsonItemVo.class)) 51 | .resource(new ClassPathResource("/read_sample/sample_json_data.json")) 52 | .name("jsonItemReader") 53 | .build(); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /spring-boot-kafka-in-action/src/main/java/com/github/renuevo/producer/config/KafkaProducerConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.producer.config; 2 | 3 | import com.github.renuevo.model.DataModel; 4 | import com.google.common.collect.Maps; 5 | import org.apache.kafka.clients.producer.ProducerConfig; 6 | import org.apache.kafka.common.serialization.StringSerializer; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.kafka.core.DefaultKafkaProducerFactory; 11 | import org.springframework.kafka.core.KafkaTemplate; 12 | import org.springframework.kafka.core.ProducerFactory; 13 | import org.springframework.kafka.support.serializer.JsonSerializer; 14 | 15 | import java.util.Map; 16 | 17 | @Configuration 18 | public class KafkaProducerConfig { 19 | 20 | @Value(value = "${kafka.bootstrapAddress}") 21 | private String bootstrapAddress; 22 | 23 | @Bean 24 | public ProducerFactory producerFactory() { 25 | Map configProps = Maps.newConcurrentMap(); 26 | configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress); 27 | configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); 28 | configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); 29 | return new DefaultKafkaProducerFactory<>(configProps); 30 | } 31 | 32 | @Bean 33 | public KafkaTemplate kafkaTemplate() { 34 | return new KafkaTemplate<>(producerFactory()); 35 | } 36 | 37 | 38 | @Bean("dataModelProducerFactory") 39 | public ProducerFactory dataModelProducerFactory() { 40 | Map configProps = Maps.newConcurrentMap(); 41 | configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress); 42 | configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); 43 | configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class); 44 | return new DefaultKafkaProducerFactory<>(configProps); 45 | } 46 | 47 | @Bean("dataModelKafkaTemplate") 48 | public KafkaTemplate dataModelKafkaTemplate() { 49 | return new KafkaTemplate<>(dataModelProducerFactory()); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/reader/ElasticItemReader.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.reader; 2 | 3 | import com.github.renuevo.es.EsMapper; 4 | import com.google.common.collect.Lists; 5 | import lombok.Builder; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.elasticsearch.action.search.SearchRequest; 8 | import org.elasticsearch.action.search.SearchResponse; 9 | import org.elasticsearch.client.RequestOptions; 10 | import org.elasticsearch.client.RestHighLevelClient; 11 | import org.elasticsearch.index.query.QueryBuilder; 12 | import org.elasticsearch.search.builder.SearchSourceBuilder; 13 | import org.springframework.batch.item.data.AbstractPaginatedDataItemReader; 14 | 15 | import java.util.Iterator; 16 | import java.util.List; 17 | import java.util.Objects; 18 | 19 | @Slf4j 20 | @Builder 21 | public class ElasticItemReader extends AbstractPaginatedDataItemReader { 22 | 23 | private RestHighLevelClient restHighLevelClient; 24 | private final EsMapper esMapper = new EsMapper(); 25 | private SearchRequest searchRequest; 26 | private final SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); 27 | private String sort; 28 | private int pageSize; 29 | private QueryBuilder queryBuilder; 30 | private Class classType; 31 | private String name; 32 | 33 | @Override 34 | protected Iterator doPageRead() { 35 | List list = Lists.newArrayList(); 36 | try { 37 | 38 | if (page == 0) { 39 | if (queryBuilder != null) searchSourceBuilder.query(queryBuilder); 40 | searchSourceBuilder.sort(Objects.requireNonNullElse(sort, "_doc")); 41 | } 42 | 43 | searchSourceBuilder.from(page * pageSize); 44 | searchSourceBuilder.size(pageSize); 45 | searchRequest.source(searchSourceBuilder); 46 | SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); 47 | list = esMapper.getSearchSource(searchResponse.toString(), classType); 48 | 49 | } catch (Exception e) { 50 | log.info("Elastic doReadPage Error {}", e.getMessage(), e); 51 | } 52 | 53 | return list.iterator(); 54 | } 55 | 56 | @Override 57 | protected void doOpen() throws Exception { 58 | if (searchRequest == null) throw new Exception("SearchRequest Not Exist"); 59 | if (classType == null) throw new Exception("ClassType Not Exist"); 60 | setName(this.name); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/webclient/controller/WebClientController.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.webclient.controller; 2 | 3 | import com.github.renuevo.dto.NaverBlogParamDto; 4 | import com.github.renuevo.dto.NaverResponse; 5 | import com.github.renuevo.webclient.config.WebClientQueryEncoder; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.beans.factory.annotation.Qualifier; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestParam; 11 | import org.springframework.web.bind.annotation.RestController; 12 | import org.springframework.web.reactive.function.client.WebClient; 13 | import reactor.core.publisher.Mono; 14 | 15 | @RequiredArgsConstructor 16 | @RestController 17 | @RequestMapping("/webclient") 18 | public class WebClientController { 19 | 20 | @Qualifier("commonWebClient") 21 | private final WebClient commonWebClient; 22 | 23 | @Qualifier("naverWebClient") 24 | private final WebClient naverWebClient; 25 | 26 | private final WebClientQueryEncoder webclientQueryEncoder; 27 | 28 | @GetMapping("/search") 29 | public NaverResponse getNaverSearch(@RequestParam("query") String query) { 30 | return naverWebClient.get() 31 | .uri(uriBuilder -> uriBuilder 32 | .path("/blog.json") 33 | .queryParams(webclientQueryEncoder.getQueryEncode( 34 | NaverBlogParamDto.builder().query(query).start(1).display(10).sort("sim").build())) 35 | .build()) 36 | .retrieve() 37 | .bodyToMono(NaverResponse.class) 38 | .toProcessor() //Deprecated. -> share() 39 | .block(); 40 | } 41 | 42 | @GetMapping("/webflux/search") 43 | public Mono getNaverWebfluxSearch(@RequestParam("query") String query) { 44 | return naverWebClient.get() 45 | .uri(uriBuilder -> uriBuilder 46 | .path("/blog.json") 47 | .queryParams(webclientQueryEncoder.getQueryEncode( 48 | NaverBlogParamDto.builder().query(query).start(1).display(10).sort("sim").build())) 49 | .build()) 50 | .retrieve() 51 | .bodyToMono(NaverResponse.class); 52 | } 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/querydsl/QuerydslNoOffsetPagingItemReader.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.querydsl; 2 | 3 | import com.github.renuevo.querydsl.options.QuerydslNoOffsetOptions; 4 | import com.querydsl.jpa.JPQLQuery; 5 | import com.querydsl.jpa.impl.JPAQuery; 6 | import com.querydsl.jpa.impl.JPAQueryFactory; 7 | import org.springframework.util.ClassUtils; 8 | import org.springframework.util.CollectionUtils; 9 | 10 | import javax.persistence.EntityManagerFactory; 11 | import javax.persistence.EntityTransaction; 12 | import java.util.function.Function; 13 | 14 | public class QuerydslNoOffsetPagingItemReader extends QuerydslPagingItemReader { 15 | 16 | private QuerydslNoOffsetOptions options; 17 | 18 | private QuerydslNoOffsetPagingItemReader() { 19 | super(); 20 | setName(ClassUtils.getShortName(QuerydslNoOffsetPagingItemReader.class)); 21 | } 22 | 23 | public QuerydslNoOffsetPagingItemReader(EntityManagerFactory entityManagerFactory, 24 | int pageSize, 25 | QuerydslNoOffsetOptions options, 26 | Function> queryFunction) { 27 | super(entityManagerFactory, pageSize, queryFunction); 28 | setName(ClassUtils.getShortName(QuerydslNoOffsetPagingItemReader.class)); 29 | this.options = options; 30 | } 31 | 32 | @Override 33 | @SuppressWarnings("unchecked") 34 | protected void doReadPage() { 35 | 36 | EntityTransaction tx = getTxOrNull(); 37 | 38 | JPQLQuery query = createQuery().limit(getPageSize()); 39 | 40 | initResults(); 41 | 42 | fetchQuery(query, tx); 43 | 44 | resetCurrentIdIfNotLastPage(); 45 | } 46 | 47 | @Override 48 | protected JPAQuery createQuery() { 49 | JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); 50 | JPAQuery query = queryFunction.apply(queryFactory); 51 | options.initKeys(query, getPage()); 52 | 53 | return options.createQuery(query, getPage()); 54 | } 55 | 56 | private void resetCurrentIdIfNotLastPage() { 57 | if (isNotEmptyResults()) { 58 | options.resetCurrentId(getLastItem()); 59 | } 60 | } 61 | 62 | private boolean isNotEmptyResults() { 63 | return !CollectionUtils.isEmpty(results) && results.get(0) != null; 64 | } 65 | 66 | private T getLastItem() { 67 | return results.get(results.size() - 1); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/job/JobParameterConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.job; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.batch.core.Job; 6 | import org.springframework.batch.core.Step; 7 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 8 | import org.springframework.batch.core.configuration.annotation.JobScope; 9 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 10 | import org.springframework.batch.core.configuration.annotation.StepScope; 11 | import org.springframework.batch.core.step.tasklet.Tasklet; 12 | import org.springframework.batch.repeat.RepeatStatus; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | 17 | /** 18 | *
19 |  * @className : JobParameterConfig
20 |  * @author : Deokhwa.Kim
21 |  * @since : 2019-12-13
22 |  * 
23 | */ 24 | @Slf4j 25 | @Configuration 26 | @AllArgsConstructor 27 | public class JobParameterConfig { 28 | 29 | private final JobBuilderFactory jobBuilderFactory; 30 | private final StepBuilderFactory stepBuilderFactory; 31 | 32 | @Bean 33 | public Job JobParameterBean() { 34 | return jobBuilderFactory.get("jobScope") 35 | .start(scopeStep1(null)) 36 | .next(scopeStep2()) 37 | .build(); 38 | } 39 | 40 | @Bean 41 | @JobScope //Step에 대해서 설정 42 | public Step scopeStep1(@Value("#{jobParameters[requestDate]}") String requestDate) { 43 | return stepBuilderFactory.get("scopeStep1") 44 | .tasklet((contribution, chunkContext) -> { 45 | log.info("[========= This is scopeStep1 ==========]"); 46 | log.info("{}",requestDate); 47 | return RepeatStatus.FINISHED; 48 | }).build(); 49 | } 50 | 51 | @Bean 52 | public Step scopeStep2(){ 53 | return stepBuilderFactory.get("scopeStep2") 54 | .tasklet(scopeStep2Tasklet(null)) 55 | .build(); 56 | } 57 | 58 | @Bean 59 | @StepScope //Tasklet에 대해서 설정 60 | public Tasklet scopeStep2Tasklet(@Value("#{jobParameters[requestDate]}") String requestDate){ 61 | return (contribution, chunkContext) -> { 62 | log.info("[========= This is scopeStep2 ==========]"); 63 | log.info("{}",requestDate); 64 | return RepeatStatus.FINISHED; 65 | }; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/config/AutoIncrementJobConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | import com.github.renuevo.entity.Pay; 4 | import lombok.AllArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.batch.core.Job; 7 | import org.springframework.batch.core.Step; 8 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 9 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 10 | import org.springframework.batch.core.launch.support.RunIdIncrementer; 11 | import org.springframework.batch.item.ItemWriter; 12 | import org.springframework.batch.item.database.JdbcCursorItemReader; 13 | import org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.jdbc.core.BeanPropertyRowMapper; 17 | 18 | import javax.sql.DataSource; 19 | 20 | @Slf4j 21 | @Configuration 22 | @AllArgsConstructor 23 | public class AutoIncrementJobConfig { 24 | 25 | private final JobBuilderFactory jobBuilderFactory; 26 | private final StepBuilderFactory stepBuilderFactory; 27 | private final DataSource dataSource; 28 | 29 | private static final int chunkSize = 10; 30 | 31 | @Bean 32 | public Job autoIncrementJob() { 33 | return jobBuilderFactory.get("autoIncrementJob") 34 | .incrementer(new RunIdIncrementer()) 35 | .start(autoIncrementStep()) 36 | .build(); 37 | } 38 | 39 | @Bean 40 | public Step autoIncrementStep() { 41 | return stepBuilderFactory.get("autoIncrementStep") 42 | .chunk(chunkSize) 43 | .reader(autoIncrementReader()) 44 | .writer(autoIncrementWriter()) 45 | .build(); 46 | } 47 | 48 | @Bean 49 | public JdbcCursorItemReader autoIncrementReader() { 50 | return new JdbcCursorItemReaderBuilder() 51 | .fetchSize(chunkSize) 52 | .dataSource(dataSource) 53 | .rowMapper(new BeanPropertyRowMapper<>(Pay.class)) 54 | .sql("SELECT id, amount, tx_name, tx_date_time FROM pay") 55 | .name("jdbcBatchItemWriter") 56 | .build(); 57 | } 58 | 59 | @Bean 60 | public ItemWriter autoIncrementWriter() { 61 | return list -> { 62 | for (Pay pay : list) { 63 | log.info("Current Pay = {}", pay); 64 | } 65 | }; 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/querydsl/QuerydslNoOffsetIdPagingItemReader.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.querydsl; 2 | 3 | import com.github.renuevo.querydsl.options.QuerydslNoOffsetNumberOptions; 4 | import com.querydsl.jpa.JPQLQuery; 5 | import com.querydsl.jpa.impl.JPAQuery; 6 | import com.querydsl.jpa.impl.JPAQueryFactory; 7 | import org.springframework.util.ClassUtils; 8 | import org.springframework.util.CollectionUtils; 9 | 10 | import javax.persistence.EntityManagerFactory; 11 | import javax.persistence.EntityTransaction; 12 | import java.util.function.Function; 13 | 14 | public class QuerydslNoOffsetIdPagingItemReader> extends QuerydslPagingItemReader { 15 | 16 | private QuerydslNoOffsetNumberOptions options; 17 | 18 | private QuerydslNoOffsetIdPagingItemReader() { 19 | super(); 20 | setName(ClassUtils.getShortName(QuerydslNoOffsetIdPagingItemReader.class)); 21 | } 22 | 23 | public QuerydslNoOffsetIdPagingItemReader(EntityManagerFactory entityManagerFactory, 24 | int pageSize, 25 | QuerydslNoOffsetNumberOptions options, 26 | Function> queryFunction) { 27 | super(entityManagerFactory, pageSize, queryFunction); 28 | setName(ClassUtils.getShortName(QuerydslNoOffsetIdPagingItemReader.class)); 29 | this.options = options; 30 | } 31 | 32 | @Override 33 | @SuppressWarnings("unchecked") 34 | protected void doReadPage() { 35 | 36 | EntityTransaction tx = getTxOrNull(); 37 | 38 | JPQLQuery query = createQuery().limit(getPageSize()); 39 | 40 | initResults(); 41 | 42 | fetchQuery(query, tx); 43 | 44 | resetCurrentIdIfNotLastPage(); 45 | } 46 | 47 | @Override 48 | protected JPAQuery createQuery() { 49 | JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager); 50 | JPAQuery query = queryFunction.apply(queryFactory); 51 | options.initKeys(query, getPage()); 52 | 53 | return options.createQuery(query, getPage()); 54 | } 55 | 56 | private void resetCurrentIdIfNotLastPage() { 57 | if (isNotEmptyResults()) { 58 | options.resetCurrentId(getLastItem()); 59 | } 60 | } 61 | 62 | private boolean isNotEmptyResults() { 63 | return !CollectionUtils.isEmpty(results) && results.get(0) != null; 64 | } 65 | 66 | private T getLastItem() { 67 | return results.get(results.size() - 1); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/querydsl/options/QuerydslNoOffsetStringOptions.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.querydsl.options; 2 | 3 | import com.github.renuevo.querydsl.expression.Expression; 4 | import com.querydsl.core.types.OrderSpecifier; 5 | import com.querydsl.core.types.dsl.BooleanExpression; 6 | import com.querydsl.core.types.dsl.StringPath; 7 | import com.querydsl.jpa.impl.JPAQuery; 8 | import lombok.Getter; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import javax.annotation.Nonnull; 12 | 13 | @Getter 14 | @Slf4j 15 | public class QuerydslNoOffsetStringOptions extends QuerydslNoOffsetOptions { 16 | 17 | private String currentId; 18 | private String lastId; 19 | 20 | private final StringPath field; 21 | 22 | public QuerydslNoOffsetStringOptions(@Nonnull StringPath field, 23 | @Nonnull Expression expression) { 24 | super(field, expression); 25 | this.field = field; 26 | } 27 | 28 | @Override 29 | public void initKeys(JPAQuery query, int page) { 30 | if(page == 0) { 31 | initFirstId(query); 32 | initLastId(query); 33 | 34 | if (log.isDebugEnabled()) { 35 | log.debug("First Key= "+currentId+", Last Key= "+ lastId); 36 | } 37 | } 38 | } 39 | 40 | @Override 41 | protected void initFirstId(JPAQuery query) { 42 | currentId = query.clone() 43 | .select(field) 44 | .orderBy(expression.isAsc()? field.asc() : field.desc()) 45 | .fetchFirst(); 46 | } 47 | 48 | @Override 49 | protected void initLastId(JPAQuery query) { 50 | lastId = query.clone() 51 | .select(field) 52 | .orderBy(expression.isAsc()? field.desc() : field.asc()) 53 | .fetchFirst(); 54 | } 55 | 56 | @Override 57 | public JPAQuery createQuery(JPAQuery query, int page) { 58 | if (currentId == null) { 59 | return query; 60 | } 61 | 62 | return query 63 | .where(whereExpression(page)) 64 | .orderBy(orderExpression()); 65 | } 66 | 67 | private BooleanExpression whereExpression(int page) { 68 | return expression.where(field, page, currentId); 69 | } 70 | 71 | private OrderSpecifier orderExpression() { 72 | return expression.order(field); 73 | } 74 | 75 | @Override 76 | public void resetCurrentId(T item) { 77 | currentId = (String) getFiledValue(item); 78 | if (log.isDebugEnabled()) { 79 | log.debug("Current Select Key= " + currentId); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/config/JpaPagingItemReaderJobConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | import com.github.renuevo.entity.Pay; 4 | import lombok.AllArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.batch.core.Job; 7 | import org.springframework.batch.core.Step; 8 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 9 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 10 | import org.springframework.batch.item.ItemWriter; 11 | import org.springframework.batch.item.database.JpaPagingItemReader; 12 | import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | 16 | import javax.persistence.EntityManagerFactory; 17 | 18 | /** 19 | *
20 |  * @className : JpaPagingItemReaderJobConfig
21 |  * @author : Deokhwa.Kim
22 |  * @since : 2019-12-28
23 |  * @summary : Jpa Paging Item Reader
24 |  * 
25 | */ 26 | @Slf4j 27 | @Configuration 28 | @AllArgsConstructor 29 | public class JpaPagingItemReaderJobConfig { //JPA는 Paging만 지원함 30 | 31 | private final JobBuilderFactory jobBuilderFactory; 32 | private final StepBuilderFactory stepBuilderFactory; 33 | private final EntityManagerFactory entityManagerFactory; 34 | 35 | private static final int chunkSize = 10; 36 | 37 | @Bean 38 | public Job jpaPagingItemReaderJob() { 39 | return jobBuilderFactory.get("jpaPagingItemReaderJob") 40 | .start(jpaPagingItemReaderStep()) 41 | .build(); 42 | } 43 | 44 | @Bean 45 | public Step jpaPagingItemReaderStep() { 46 | return stepBuilderFactory.get("jpaPagingItemReaderStep") 47 | .chunk(chunkSize) 48 | .reader(jpaPagingItemReader()) 49 | .writer(jpaPagingItemWriter()) 50 | .build(); 51 | } 52 | 53 | @Bean 54 | public JpaPagingItemReader jpaPagingItemReader() { 55 | return new JpaPagingItemReaderBuilder() 56 | .name("jpaPagingItemReader") 57 | .entityManagerFactory(entityManagerFactory) //DataSource가 아닌 EntityManagerFactory를 통한 접근 58 | .pageSize(chunkSize) 59 | .queryString("SELECT p FROM Pay p WHERE amount >= 2000 ORDER BY id ASC") //ORDER 조건은 필수! 60 | .build(); 61 | } 62 | 63 | @Bean 64 | public ItemWriter jpaPagingItemWriter() { 65 | return list -> { 66 | for (Pay pay : list) { 67 | log.info("Current Pay = {}", pay); 68 | } 69 | }; 70 | } 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/querydsl/options/QuerydslNoOffsetNumberOptions.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.querydsl.options; 2 | 3 | import com.github.renuevo.querydsl.expression.Expression; 4 | import com.querydsl.core.types.OrderSpecifier; 5 | import com.querydsl.core.types.dsl.BooleanExpression; 6 | import com.querydsl.core.types.dsl.NumberPath; 7 | import com.querydsl.jpa.impl.JPAQuery; 8 | import lombok.Getter; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import javax.annotation.Nonnull; 12 | 13 | @Slf4j 14 | @Getter 15 | public class QuerydslNoOffsetNumberOptions> extends QuerydslNoOffsetOptions { 16 | 17 | private N currentId; 18 | private N lastId; 19 | 20 | private final NumberPath field; 21 | 22 | public QuerydslNoOffsetNumberOptions(@Nonnull NumberPath field, 23 | @Nonnull Expression expression) { 24 | super(field, expression); 25 | this.field = field; 26 | } 27 | 28 | @Override 29 | public void initKeys(JPAQuery query, int page) { 30 | if(page == 0) { 31 | initFirstId(query); 32 | initLastId(query); 33 | 34 | if (log.isDebugEnabled()) { 35 | log.debug("First Key= "+currentId+", Last Key= "+ lastId); 36 | } 37 | } 38 | } 39 | 40 | @Override 41 | protected void initFirstId(JPAQuery query) { 42 | currentId = query.clone() 43 | .select(field) 44 | .orderBy(expression.isAsc()? field.asc() : field.desc()) 45 | .fetchFirst(); 46 | } 47 | 48 | @Override 49 | protected void initLastId(JPAQuery query) { 50 | lastId = query.clone() 51 | .select(field) 52 | .orderBy(expression.isAsc()? field.desc() : field.asc()) 53 | .fetchFirst(); 54 | } 55 | 56 | @Override 57 | public JPAQuery createQuery(JPAQuery query, int page) { 58 | if(currentId == null) { 59 | return query; 60 | } 61 | 62 | return query 63 | .where(whereExpression(page)) 64 | .orderBy(orderExpression()); 65 | } 66 | 67 | private BooleanExpression whereExpression(int page) { 68 | return expression.where(field, page, currentId); 69 | } 70 | 71 | private OrderSpecifier orderExpression() { 72 | return expression.order(field); 73 | } 74 | 75 | @Override 76 | public void resetCurrentId(T item) { 77 | //noinspection unchecked 78 | currentId = (N) getFiledValue(item); 79 | 80 | if (log.isDebugEnabled()) { 81 | log.debug("Current Select Key= " + currentId); 82 | } 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/config/XmlFileItemReaderJobConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | import com.github.renuevo.vo.XmlItemVo; 4 | import com.google.common.collect.Maps; 5 | import lombok.AllArgsConstructor; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.batch.core.Job; 8 | import org.springframework.batch.core.Step; 9 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 10 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 11 | import org.springframework.batch.item.xml.StaxEventItemReader; 12 | import org.springframework.batch.item.xml.builder.StaxEventItemReaderBuilder; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.core.io.ClassPathResource; 16 | import org.springframework.oxm.xstream.XStreamMarshaller; 17 | 18 | import java.util.Map; 19 | 20 | @Slf4j 21 | @Configuration 22 | @AllArgsConstructor 23 | public class XmlFileItemReaderJobConfig { 24 | 25 | private final JobBuilderFactory jobBuilderFactory; 26 | private final StepBuilderFactory stepBuilderFactory; 27 | private static final int chunkSize = 2; 28 | 29 | @Bean 30 | public Job xmlFileItemReaderJob() { 31 | return jobBuilderFactory.get("xmlFileItemReaderJob") 32 | .start(xmlFileItemReaderStep()) 33 | .build(); 34 | } 35 | 36 | @Bean 37 | public Step xmlFileItemReaderStep() { 38 | return stepBuilderFactory.get("xmlFileItemReaderStep") 39 | .chunk(chunkSize) 40 | .reader(xmlFileItemReader()) 41 | .writer(xmlItemVos -> xmlItemVos 42 | .stream() 43 | .map(XmlItemVo::toString) 44 | .forEach(log::info)) 45 | .build(); 46 | } 47 | 48 | @Bean 49 | public StaxEventItemReader xmlFileItemReader() { 50 | return new StaxEventItemReaderBuilder() 51 | .name("xmlFileItemReader") 52 | .resource(new ClassPathResource("/read_sample/sample_xml_data.xml")) 53 | .addFragmentRootElements("item") 54 | .unmarshaller(itemMarshaller()) 55 | .build(); 56 | } 57 | 58 | @Bean 59 | public XStreamMarshaller itemMarshaller() { 60 | Map> aliases = Maps.newHashMap(); 61 | aliases.put("item", XmlItemVo.class); 62 | aliases.put("number", Integer.class); 63 | aliases.put("data", String.class); 64 | XStreamMarshaller xStreamMarshaller = new XStreamMarshaller(); 65 | xStreamMarshaller.setAliases(aliases); 66 | return xStreamMarshaller; 67 | } 68 | 69 | 70 | } 71 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/config/InMemoryBatchConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | import org.springframework.batch.core.configuration.annotation.BatchConfigurer; 4 | import org.springframework.batch.core.explore.JobExplorer; 5 | import org.springframework.batch.core.explore.support.MapJobExplorerFactoryBean; 6 | import org.springframework.batch.core.launch.JobLauncher; 7 | import org.springframework.batch.core.launch.support.SimpleJobLauncher; 8 | import org.springframework.batch.core.repository.JobRepository; 9 | import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean; 10 | import org.springframework.batch.support.transaction.ResourcelessTransactionManager; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.transaction.PlatformTransactionManager; 13 | 14 | import javax.annotation.PostConstruct; 15 | 16 | /** 17 | *
18 |  * @className : JobConfig
19 |  * @author : Deokhwa.Kim
20 |  * @since : 2020-08-13
21 |  * 
22 | */ 23 | //@Configuration 24 | public class InMemoryBatchConfig implements BatchConfigurer { 25 | 26 | private PlatformTransactionManager transactionManager; 27 | private JobRepository jobRepository; 28 | private JobLauncher jobLauncher; 29 | private JobExplorer jobExplorer; 30 | 31 | // @PostConstruct 32 | public void init() throws Exception { 33 | if (this.transactionManager == null) this.transactionManager = new ResourcelessTransactionManager(); 34 | 35 | MapJobRepositoryFactoryBean mapJobRepositoryFactoryBean = new MapJobRepositoryFactoryBean(this.transactionManager); 36 | MapJobExplorerFactoryBean mapJobExplorerFactoryBean = new MapJobExplorerFactoryBean(mapJobRepositoryFactoryBean); 37 | 38 | SimpleJobLauncher jobLauncher = new SimpleJobLauncher(); 39 | this.jobRepository = mapJobRepositoryFactoryBean.getObject(); 40 | assert this.jobRepository != null; 41 | jobLauncher.setJobRepository(this.jobRepository); 42 | 43 | this.jobExplorer = mapJobExplorerFactoryBean.getObject(); 44 | 45 | mapJobExplorerFactoryBean.afterPropertiesSet(); 46 | jobLauncher.afterPropertiesSet(); 47 | this.jobLauncher = jobLauncher; 48 | 49 | } 50 | 51 | @Override 52 | public JobRepository getJobRepository() throws Exception { 53 | return this.jobRepository; 54 | } 55 | 56 | @Override 57 | public PlatformTransactionManager getTransactionManager() throws Exception { 58 | return this.transactionManager; 59 | } 60 | 61 | @Override 62 | public JobLauncher getJobLauncher() throws Exception { 63 | return this.jobLauncher; 64 | } 65 | 66 | @Override 67 | public JobExplorer getJobExplorer() throws Exception { 68 | return this.jobExplorer; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/config/ElasticItemReaderJobConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | 4 | import com.github.renuevo.reader.ElasticItemReader; 5 | import com.github.renuevo.vo.ElasticReaderTestVo; 6 | import lombok.AllArgsConstructor; 7 | import lombok.SneakyThrows; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.elasticsearch.action.search.SearchRequest; 10 | import org.elasticsearch.client.RestHighLevelClient; 11 | import org.springframework.batch.core.Job; 12 | import org.springframework.batch.core.Step; 13 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 14 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 15 | import org.springframework.batch.item.ItemWriter; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.Configuration; 18 | 19 | /** 20 | *
21 |  * @className : ElasticItemReaderJobConfig
22 |  * @author : Deokhwa.Kim
23 |  * @since : 2020-01-03
24 |  * @summary : Elastic Item Reader Example
25 |  * 
26 | */ 27 | @Slf4j 28 | @Configuration 29 | @AllArgsConstructor 30 | public class ElasticItemReaderJobConfig { 31 | 32 | private final JobBuilderFactory jobBuilderFactory; 33 | private final StepBuilderFactory stepBuilderFactory; 34 | private final RestHighLevelClient restHighLevelClient; 35 | 36 | private final static int chunkSize = 8; 37 | 38 | @Bean 39 | public Job elasticItemReaderJob() { 40 | return jobBuilderFactory.get("elasticItemReaderJob") 41 | .start(elasticItemReaderStep()) 42 | .build(); 43 | } 44 | 45 | @Bean 46 | public Step elasticItemReaderStep() { 47 | return stepBuilderFactory.get("elasticItemReaderStep") 48 | .chunk(chunkSize) 49 | .reader(elasticItemReader()) 50 | .writer(elasticItemReaderWriter()) //processor 까지 한번에 처리되고 list로 넘어옴 51 | .build(); 52 | } 53 | 54 | @Bean 55 | @SneakyThrows 56 | public ElasticItemReader elasticItemReader() { 57 | return ElasticItemReader.builder() 58 | .sort("key") 59 | .restHighLevelClient(restHighLevelClient) 60 | .searchRequest(new SearchRequest("reader_test")) 61 | .classType(ElasticReaderTestVo.class) 62 | .pageSize(chunkSize) 63 | .name("elasticItemReader") 64 | .build(); 65 | } 66 | 67 | public ItemWriter elasticItemReaderWriter() { 68 | return list -> { 69 | for (ElasticReaderTestVo elasticReaderTestVo : list) { 70 | log.info("elastic = {}", elasticReaderTestVo); 71 | } 72 | }; 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/job/JobSecondConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.job; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.batch.core.ExitStatus; 7 | import org.springframework.batch.core.Job; 8 | import org.springframework.batch.core.Step; 9 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 10 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 11 | import org.springframework.batch.repeat.RepeatStatus; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | 15 | @Slf4j 16 | @Configuration 17 | @AllArgsConstructor 18 | public class JobSecondConfig { 19 | 20 | private final JobBuilderFactory jobBuilderFactory; 21 | private final StepBuilderFactory stepBuilderFactory; 22 | 23 | @Bean 24 | public Job jobSecondBean(){ 25 | return jobBuilderFactory.get("testJob2") 26 | .start(jobStep1()) 27 | .on("FAILED") //FAILED 이면 28 | .to(jobStep3()) //step3 실행 29 | .on("*") //to와 관계없이 30 | .end() //반환 종료 31 | .from(jobStep1()) //on을 사용한후 추가적 이벤트 캐치 32 | .on("*") //FAILED외의 모든 것 33 | .to(jobStep2()) //step2 실행 34 | .next(jobStep3()) //정상 종료되면 step3 실행 35 | .on("*") //step3 결과 상관없이 36 | .end() //반환 종료 37 | .from(jobStep1()) 38 | .end() //build 종료 39 | .build(); 40 | } 41 | 42 | 43 | @Bean 44 | public Step jobStep1(){ 45 | return stepBuilderFactory.get("step1") 46 | .tasklet((contribution, chunkContext) -> { 47 | log.info("[========= This is Step1 ==========]"); 48 | //contribution.setExitStatus(ExitStatus.FAILED); //Status Failed 49 | return RepeatStatus.FINISHED; 50 | }) 51 | .build(); 52 | } 53 | 54 | @Bean 55 | public Step jobStep2(){ 56 | return stepBuilderFactory.get("step2") 57 | .tasklet((contribution, chunkContext) -> { 58 | log.info("[========= This is Step2 ==========]"); 59 | return RepeatStatus.FINISHED; 60 | }) 61 | .build(); 62 | } 63 | 64 | @Bean 65 | public Step jobStep3(){ 66 | return stepBuilderFactory.get("step3") 67 | .tasklet((contribution, chunkContext) -> { 68 | log.info("[========= This is Step3 ==========]"); 69 | contribution.setExitStatus(ExitStatus.FAILED); //Status Failed 70 | return RepeatStatus.FINISHED; 71 | }) 72 | .build(); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /spring-boot-rest-client-in-action/src/main/java/com/github/renuevo/common/controller/CommonController.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.common.controller; 2 | 3 | import com.github.renuevo.common.ErrorResponse; 4 | import com.github.renuevo.common.TestResponseKotlin; 5 | import com.github.renuevo.dto.PostRequest; 6 | import org.springframework.http.HttpHeaders; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import java.util.List; 15 | 16 | @RestController 17 | public class CommonController { 18 | 19 | @GetMapping("/get500") 20 | public ResponseEntity getInternalError() { 21 | return ResponseEntity 22 | .status(HttpStatus.INTERNAL_SERVER_ERROR) 23 | .body(ErrorResponse.builder() 24 | .httpStatus(HttpStatus.INTERNAL_SERVER_ERROR) 25 | .errorCode("500") 26 | .errorMessage("500 에러가 발생 했습니다") 27 | .build() 28 | ); 29 | } 30 | 31 | 32 | @GetMapping("/get400") 33 | public ResponseEntity getBadrequestError() { 34 | return ResponseEntity 35 | .status(HttpStatus.BAD_REQUEST) 36 | .body(ErrorResponse.builder() 37 | .httpStatus(HttpStatus.BAD_REQUEST) 38 | .errorCode("400") 39 | .errorMessage("400 에러가 발생 했습니다") 40 | .build() 41 | ); 42 | } 43 | 44 | @PostMapping("/post") 45 | public String postCommonResponse(@RequestBody PostRequest postRequest) { 46 | return postRequest.toString(); 47 | } 48 | 49 | @GetMapping("/timeout") 50 | public String test() { 51 | return "test"; 52 | } 53 | 54 | @GetMapping("/camel/test") 55 | public ResponseEntity camelTest() { 56 | HttpHeaders responseHeaders = new HttpHeaders(); 57 | responseHeaders.add("Content-Type", "application/json;"); 58 | return new ResponseEntity<>("{\"testName\" : \"test\"}", responseHeaders, HttpStatus.OK); 59 | } 60 | 61 | @GetMapping("/snake/test") 62 | public ResponseEntity snakeTest() { 63 | HttpHeaders responseHeaders = new HttpHeaders(); 64 | responseHeaders.add("Content-Type", "application/json;"); 65 | return new ResponseEntity<>("{\"test_name\" : \"test\"}", responseHeaders, HttpStatus.OK); 66 | } 67 | 68 | @GetMapping("/kotlin-response") 69 | public List testKotlinResponse() { 70 | return List.of(); //new TestResponseKotlin("rubber", "duck", STATUS_CODE.OK); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/config/ElasticItemScrollReaderJobConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | import com.github.renuevo.reader.ElasticItemScrollReader; 4 | import com.github.renuevo.vo.ElasticReaderTestVo; 5 | import lombok.AllArgsConstructor; 6 | import lombok.SneakyThrows; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.elasticsearch.action.search.SearchRequest; 9 | import org.elasticsearch.client.RestHighLevelClient; 10 | import org.springframework.batch.core.Job; 11 | import org.springframework.batch.core.Step; 12 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 13 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 14 | import org.springframework.batch.item.ItemWriter; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | 18 | /** 19 | *
20 |  * @className : ElasticItemScrollReaderJobConfig
21 |  * @author : Deokhwa.Kim
22 |  * @since : 2020-01-05
23 |  * @summary : Elastic Scroll Item Reader Example
24 |  * 
25 | */ 26 | @Slf4j 27 | @Configuration 28 | @AllArgsConstructor 29 | public class ElasticItemScrollReaderJobConfig { 30 | 31 | private final JobBuilderFactory jobBuilderFactory; 32 | private final StepBuilderFactory stepBuilderFactory; 33 | private final RestHighLevelClient restHighLevelClient; 34 | 35 | private final static int chunkSize = 3; 36 | 37 | @Bean 38 | public Job elasticItemScrollReaderJob() { 39 | return jobBuilderFactory.get("elasticItemScrollReaderJob") 40 | .start(elasticItemScrollReaderStep()) 41 | .build(); 42 | } 43 | 44 | @Bean 45 | public Step elasticItemScrollReaderStep() { 46 | return stepBuilderFactory.get("elasticItemScrollReaderStep") 47 | .chunk(chunkSize) 48 | .reader(elasticItemScrollReader()) 49 | .writer(elasticItemScrollReaderWriter()) //processor 까지 한번에 처리되고 list로 넘어옴 50 | .build(); 51 | } 52 | 53 | @Bean 54 | @SneakyThrows 55 | public ElasticItemScrollReader elasticItemScrollReader() { 56 | return ElasticItemScrollReader.builder() 57 | .restHighLevelClient(restHighLevelClient) 58 | .searchRequest(new SearchRequest("reader_test")) 59 | .classType(ElasticReaderTestVo.class) 60 | .pageSize(chunkSize) 61 | .name("elasticItemScrollReader") 62 | .build(); 63 | } 64 | 65 | public ItemWriter elasticItemScrollReaderWriter() { 66 | return list -> { 67 | for (ElasticReaderTestVo elasticReaderTestVo : list) { 68 | log.info("elastic = {}", elasticReaderTestVo); 69 | } 70 | }; 71 | } 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/config/MultiFileItemReaderJobConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | 4 | import com.github.renuevo.vo.ItemVo; 5 | import lombok.AllArgsConstructor; 6 | import lombok.SneakyThrows; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.batch.core.Job; 9 | import org.springframework.batch.core.Step; 10 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 11 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 12 | import org.springframework.batch.item.file.FlatFileItemReader; 13 | import org.springframework.batch.item.file.MultiResourceItemReader; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.core.io.Resource; 17 | import org.springframework.core.io.ResourceLoader; 18 | import org.springframework.core.io.support.ResourcePatternUtils; 19 | 20 | /** 21 | *
22 |  * @className : MultiFileItemReaderJobConfig
23 |  * @author : Deokhwa.Kim
24 |  * @since : 2020-03-04
25 |  * 
26 | */ 27 | @Slf4j 28 | @Configuration 29 | @AllArgsConstructor 30 | public class MultiFileItemReaderJobConfig { 31 | 32 | private final ResourceLoader resourceLoader; 33 | private final JobBuilderFactory jobBuilderFactory; 34 | private final StepBuilderFactory stepBuilderFactory; 35 | 36 | private static final int chunkSize = 2; 37 | 38 | @Bean 39 | public Job multiTxtFileItemReaderJob() { 40 | return jobBuilderFactory.get("multiTxtFileItemReaderJob") 41 | .start(multiTxtFileItemReaderStep()) 42 | .build(); 43 | } 44 | 45 | @Bean 46 | public Step multiTxtFileItemReaderStep() { 47 | return stepBuilderFactory.get("multiTxtFileItemReaderStep") 48 | .chunk(chunkSize) 49 | .reader(multiResourceItemReader()) 50 | .writer(itemVo -> itemVo.stream() 51 | .map(ItemVo::toString) 52 | .forEach(log::info)) 53 | .build(); 54 | } 55 | 56 | @Bean 57 | @SneakyThrows 58 | public MultiResourceItemReader multiResourceItemReader(){ 59 | MultiResourceItemReader resourceItemReader = new MultiResourceItemReader(); 60 | resourceItemReader.setResources(ResourcePatternUtils.getResourcePatternResolver(this.resourceLoader).getResources("read_sample/*.txt")); 61 | resourceItemReader.setDelegate(multiFileItemReader()); 62 | return resourceItemReader; 63 | } 64 | 65 | 66 | @Bean 67 | public FlatFileItemReader multiFileItemReader() { 68 | FlatFileItemReader flatFileItemReader = new FlatFileItemReader<>(); 69 | flatFileItemReader.setLineMapper((line, lineNumber) -> new ItemVo(line)); 70 | return flatFileItemReader; 71 | } 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/config/JpaItemWriterJobConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | import com.github.renuevo.entity.Pay; 4 | import com.github.renuevo.entity.Pay2; 5 | import lombok.AllArgsConstructor; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.batch.core.Job; 8 | import org.springframework.batch.core.Step; 9 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 10 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 11 | import org.springframework.batch.item.ItemProcessor; 12 | import org.springframework.batch.item.database.JpaItemWriter; 13 | import org.springframework.batch.item.database.JpaPagingItemReader; 14 | import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | 18 | import javax.persistence.EntityManagerFactory; 19 | 20 | /** 21 | *
22 |  * @className : JpaItemWriterJobConfig
23 |  * @author : Deokhwa.Kim
24 |  * @since : 2020-02-14
25 |  * @summary : Jpa Item Writer Example
26 |  * 
27 | */ 28 | @Slf4j 29 | @Configuration 30 | @AllArgsConstructor 31 | public class JpaItemWriterJobConfig { 32 | 33 | private final JobBuilderFactory jobBuilderFactory; 34 | private final StepBuilderFactory stepBuilderFactory; 35 | private final EntityManagerFactory entityManagerFactory; 36 | 37 | private static final int chunkSize = 10; 38 | 39 | @Bean 40 | public Job jpaItemWriterJob(){ 41 | return jobBuilderFactory.get("jpaItemWriterJob") 42 | .start(jpaItemWriterStep()) 43 | .build(); 44 | } 45 | 46 | @Bean 47 | public Step jpaItemWriterStep(){ 48 | return stepBuilderFactory.get("jpaItemWriterStep") 49 | .chunk(chunkSize) //Pay에서 Pay2로 Write 50 | .reader(jpaItemWriterReader()) //Pay Reader 51 | .processor(jpaItemProcessor()) //Pay To Pay2 52 | .writer(jpaItemWriter()) 53 | .build(); 54 | } 55 | 56 | @Bean 57 | public JpaPagingItemReader jpaItemWriterReader(){ 58 | return new JpaPagingItemReaderBuilder() 59 | .name("jpaItemWriterReader") 60 | .entityManagerFactory(entityManagerFactory) 61 | .pageSize(chunkSize) 62 | .queryString("SELECT p FROM Pay p ORDER BY id ASC") //Paging Reader Add Order 63 | .build(); 64 | } 65 | 66 | @Bean 67 | public ItemProcessor jpaItemProcessor(){ 68 | return pay -> new Pay2(pay.getAmount(), pay.getTxName(), pay.getTxDateTime()); 69 | } 70 | 71 | 72 | @Bean 73 | public JpaItemWriter jpaItemWriter(){ 74 | JpaItemWriter jpaItemWriter = new JpaItemWriter<>(); 75 | jpaItemWriter.setEntityManagerFactory(entityManagerFactory); 76 | return jpaItemWriter; 77 | } 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /spring-boot-kafka-in-action/src/main/java/com/github/renuevo/consumer/config/KafkaConsumerConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.consumer.config; 2 | 3 | import com.github.renuevo.model.DataModel; 4 | import com.google.common.collect.Maps; 5 | import org.apache.kafka.clients.consumer.ConsumerConfig; 6 | import org.apache.kafka.common.serialization.StringDeserializer; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.kafka.annotation.EnableKafka; 11 | import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; 12 | import org.springframework.kafka.core.ConsumerFactory; 13 | import org.springframework.kafka.core.DefaultKafkaConsumerFactory; 14 | import org.springframework.kafka.support.serializer.JsonDeserializer; 15 | 16 | import java.util.Map; 17 | 18 | @EnableKafka 19 | @Configuration 20 | public class KafkaConsumerConfig { 21 | 22 | @Value(value = "${kafka.bootstrapAddress}") 23 | private String bootstrapAddress; 24 | 25 | @Bean 26 | public ConsumerFactory consumerFactory() { 27 | 28 | String groupId = "kafka-test-group-id-1"; 29 | Map props = Maps.newConcurrentMap(); 30 | 31 | props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress); 32 | props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); 33 | props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); 34 | props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); 35 | return new DefaultKafkaConsumerFactory<>(props); 36 | } 37 | 38 | @Bean 39 | public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory() { 40 | ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); 41 | factory.setConsumerFactory(consumerFactory()); 42 | return factory; 43 | } 44 | 45 | 46 | @Bean 47 | public ConsumerFactory consumerDataModelFactory() { 48 | 49 | String groupId = "kafka-test-group-id-2"; 50 | Map props = Maps.newConcurrentMap(); 51 | props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress); 52 | props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); 53 | return new DefaultKafkaConsumerFactory<>( 54 | props, 55 | new StringDeserializer(), 56 | new JsonDeserializer<>(DataModel.class)); 57 | } 58 | 59 | @Bean("kafkaListenerContainerDataModelFactory") 60 | public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerDataModelFactory() { 61 | ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); 62 | factory.setConsumerFactory(consumerDataModelFactory()); 63 | return factory; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/config/CustomItemWriterJobConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | 4 | import com.github.renuevo.entity.Pay; 5 | import com.github.renuevo.entity.Pay2; 6 | import lombok.AllArgsConstructor; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.batch.core.Job; 9 | import org.springframework.batch.core.Step; 10 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 11 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 12 | import org.springframework.batch.item.ItemProcessor; 13 | import org.springframework.batch.item.ItemWriter; 14 | import org.springframework.batch.item.database.JpaPagingItemReader; 15 | import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder; 16 | import org.springframework.context.annotation.Bean; 17 | import org.springframework.context.annotation.Configuration; 18 | 19 | import javax.persistence.EntityManagerFactory; 20 | 21 | 22 | /** 23 | *
24 |  * @className : CustomItemWriterJobConfig
25 |  * @author : Deokhwa.Kim
26 |  * @since : 2019-12-31
27 |  * @summary : Custom Item Writer And ItemProcessor Data Transform Example
28 |  * 
29 | */ 30 | @Slf4j 31 | @Configuration 32 | @AllArgsConstructor 33 | public class CustomItemWriterJobConfig { 34 | 35 | private final JobBuilderFactory jobBuilderFactory; 36 | private final StepBuilderFactory stepBuilderFactory; 37 | private final EntityManagerFactory entityManagerFactory; 38 | 39 | private static final int chunkSize = 10; 40 | 41 | @Bean 42 | public Job customItemWriterJob() { 43 | return jobBuilderFactory.get("customItemWriterJob") 44 | .start(customItemWriterStep()) 45 | .build(); 46 | } 47 | 48 | @Bean 49 | public Step customItemWriterStep() { 50 | return stepBuilderFactory.get("customItemWriterStep") 51 | .chunk(chunkSize) 52 | .reader(customItemWriterReader()) 53 | .processor(customItemWriterProcessor()) 54 | .writer(customItemWriter()) 55 | .build(); 56 | } 57 | 58 | @Bean 59 | public JpaPagingItemReader customItemWriterReader() { 60 | return new JpaPagingItemReaderBuilder() 61 | .name("customItemWriterReader") 62 | .entityManagerFactory(entityManagerFactory) 63 | .pageSize(chunkSize) 64 | .queryString("SELECT p FROM Pay p") 65 | .build(); 66 | } 67 | 68 | @Bean 69 | public ItemProcessor customItemWriterProcessor() { 70 | return pay -> new Pay2(pay.getAmount(), pay.getTxName(), pay.getTxDateTime()); 71 | } 72 | 73 | @Bean 74 | public ItemWriter customItemWriter() { 75 | return list -> { //ItemWriter에서 write는 구현해 주면 된다 76 | for (Pay2 item : list) { 77 | log.info("Current Pay = {}", item); 78 | } 79 | }; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/reader/ElasticItemScrollReader.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.reader; 2 | 3 | import com.github.renuevo.es.EsMapper; 4 | import com.google.common.collect.Lists; 5 | import lombok.Builder; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.elasticsearch.action.search.SearchRequest; 8 | import org.elasticsearch.action.search.SearchResponse; 9 | import org.elasticsearch.action.search.SearchScrollRequest; 10 | import org.elasticsearch.client.RequestOptions; 11 | import org.elasticsearch.client.RestHighLevelClient; 12 | import org.elasticsearch.common.unit.TimeValue; 13 | import org.elasticsearch.index.query.QueryBuilder; 14 | import org.elasticsearch.search.builder.SearchSourceBuilder; 15 | import org.springframework.batch.item.data.AbstractPaginatedDataItemReader; 16 | 17 | import java.util.Iterator; 18 | import java.util.List; 19 | import java.util.Objects; 20 | 21 | @Slf4j 22 | @Builder 23 | public class ElasticItemScrollReader extends AbstractPaginatedDataItemReader { 24 | 25 | private RestHighLevelClient restHighLevelClient; 26 | private final EsMapper esMapper = new EsMapper(); 27 | private SearchRequest searchRequest; 28 | private SearchScrollRequest searchScrollRequest; 29 | private final SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); 30 | private String scrollId; 31 | private String sort; 32 | private int pageSize; 33 | private QueryBuilder queryBuilder; 34 | private Class classType; 35 | private String name; //reader Name 36 | 37 | @Override 38 | protected void doOpen() throws Exception { 39 | if (searchRequest == null) throw new Exception("SearchRequest Not Exist"); 40 | if (classType == null) throw new Exception("ClassType Not Exist"); 41 | setName(this.name); 42 | } 43 | 44 | @Override 45 | protected Iterator doPageRead() { 46 | List list = Lists.newArrayList(); 47 | SearchResponse searchResponse; 48 | try { 49 | 50 | if (page == 0) { 51 | if (queryBuilder != null) searchSourceBuilder.query(queryBuilder); 52 | searchSourceBuilder.sort(Objects.requireNonNullElse(sort, "_doc")); //Performance 53 | searchSourceBuilder.size(pageSize); 54 | searchRequest.source(searchSourceBuilder); 55 | searchRequest.scroll(TimeValue.timeValueMinutes(3)); 56 | searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); 57 | }else{ 58 | searchScrollRequest = new SearchScrollRequest(scrollId); 59 | searchScrollRequest.scroll(TimeValue.timeValueMinutes(3)); 60 | searchResponse = restHighLevelClient.scroll(searchScrollRequest,RequestOptions.DEFAULT); 61 | } 62 | 63 | this.scrollId = searchResponse.getScrollId(); 64 | list = esMapper.getSearchSource(searchResponse.toString(), classType); 65 | 66 | } catch (Exception e) { 67 | log.info("Elastic doReadPage Error {}", e.getMessage(), e); 68 | } 69 | 70 | return list.iterator(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /spring-boot-exception-in-action/src/main/java/com/github/renuevo/response/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.response; 2 | 3 | import com.github.renuevo.exception.ErrorCode; 4 | import com.google.common.collect.Lists; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import org.springframework.validation.BindingResult; 9 | import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; 10 | 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | 14 | @Getter 15 | @NoArgsConstructor 16 | public class ErrorResponse { 17 | 18 | private String message; 19 | private int status; 20 | private List errors; 21 | private String code; 22 | 23 | private ErrorResponse(final ErrorCode code, final List errors) { 24 | this.message = code.getMessage(); 25 | this.status = code.getStatus(); 26 | this.errors = errors; 27 | this.code = code.getCode(); 28 | } 29 | 30 | private ErrorResponse(final ErrorCode code) { 31 | this.message = code.getMessage(); 32 | this.status = code.getStatus(); 33 | this.code = code.getCode(); 34 | this.errors = Lists.newArrayList(); 35 | } 36 | 37 | 38 | public static ErrorResponse of(final ErrorCode code, final BindingResult bindingResult) { 39 | return new ErrorResponse(code, FieldError.of(bindingResult)); 40 | } 41 | 42 | public static ErrorResponse of(final ErrorCode code) { 43 | return new ErrorResponse(code); 44 | } 45 | 46 | public static ErrorResponse of(final ErrorCode code, final List errors) { 47 | return new ErrorResponse(code, errors); 48 | } 49 | 50 | public static ErrorResponse of(MethodArgumentTypeMismatchException e) { 51 | final String value = e.getValue() == null ? "" : e.getValue().toString(); 52 | final List errors = ErrorResponse.FieldError.of(e.getName(), value, e.getErrorCode()); 53 | return new ErrorResponse(ErrorCode.INVALID_TYPE_VALUE, errors); 54 | } 55 | 56 | @Getter 57 | @NoArgsConstructor 58 | @AllArgsConstructor 59 | public static class FieldError { 60 | private String field; 61 | private String value; 62 | private String reason; 63 | 64 | public static List of(final String field, final String value, final String reason) { 65 | List fieldErrors = Lists.newArrayList(); 66 | fieldErrors.add(new FieldError(field, value, reason)); 67 | return fieldErrors; 68 | } 69 | 70 | private static List of(final BindingResult bindingResult) { 71 | final List fieldErrors = bindingResult.getFieldErrors(); 72 | return fieldErrors.stream() 73 | .map(error -> new FieldError( 74 | error.getField(), 75 | error.getRejectedValue() == null ? "" : error.getRejectedValue().toString(), 76 | error.getDefaultMessage() 77 | )).collect(Collectors.toList()); 78 | } 79 | } 80 | 81 | 82 | } 83 | -------------------------------------------------------------------------------- /spring-boot-batch-in-action/src/main/java/com/github/renuevo/config/JdbcCursorItemReaderJobConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.renuevo.config; 2 | 3 | import com.github.renuevo.entity.Pay; 4 | import lombok.AllArgsConstructor; 5 | import lombok.SneakyThrows; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.batch.core.Job; 8 | import org.springframework.batch.core.Step; 9 | import org.springframework.batch.core.configuration.annotation.JobBuilderFactory; 10 | import org.springframework.batch.core.configuration.annotation.StepBuilderFactory; 11 | import org.springframework.batch.item.ItemWriter; 12 | import org.springframework.batch.item.database.JdbcCursorItemReader; 13 | import org.springframework.batch.item.database.builder.JdbcCursorItemReaderBuilder; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | import org.springframework.jdbc.core.BeanPropertyRowMapper; 17 | import org.springframework.jdbc.core.SingleColumnRowMapper; 18 | 19 | import javax.sql.DataSource; 20 | 21 | /** 22 | *
23 |  * @className : JdbcCursorItemReaderJobConfig
24 |  * @author : Deokhwa.Kim
25 |  * @since : 2019-12-25
26 |  * @summary : Jdbc Cursor Item Reader Example
27 |  * 
28 | */ 29 | @Slf4j 30 | @Configuration 31 | @AllArgsConstructor 32 | public class JdbcCursorItemReaderJobConfig { 33 | 34 | private final JobBuilderFactory jobBuilderFactory; 35 | private final StepBuilderFactory stepBuilderFactory; 36 | private final DataSource dataSource; 37 | 38 | private static final int chunkSize = 10; //트랜잭션 범위 39 | 40 | @Bean 41 | @SneakyThrows 42 | public Job jdbcCursorItemReaderJob() { 43 | return jobBuilderFactory.get("jdbcCursorItemReaderJob") // job name 44 | .start(jdbcCursorItemReaderStep()) 45 | .build(); 46 | } 47 | 48 | @Bean 49 | public Step jdbcCursorItemReaderStep() { 50 | return stepBuilderFactory.get("jdbcCursorItemReaderStep") //step name 51 | .chunk(chunkSize) //Reader의 반환타입 & Writer의 파라미터타입 52 | .reader(jdbcCursorItemReader()) 53 | //.processor() 생략 54 | .writer(jdbcCursorItemWriter()) 55 | .build(); 56 | } 57 | 58 | 59 | @Bean 60 | public JdbcCursorItemReader jdbcCursorItemReader() { 61 | return new JdbcCursorItemReaderBuilder() //Cursor는 하나의 Connection으로 사용하기 때문에 Timeout 시간을 길게 부여해야 한다 62 | .fetchSize(chunkSize) //Database에서 가져오는 개수 / read()를 통해 1개씩 (Paging과 다름) 63 | .dataSource(dataSource) 64 | //.rowMapper(SingleColumnRowMapper.newInstance(Long.class)) 65 | //.sql("SELECT id FROM pay") 66 | .rowMapper(new BeanPropertyRowMapper<>(Pay.class)) 67 | .sql("SELECT id, amount, tx_name, tx_date_time FROM pay") 68 | .name("jdbcCursorItemReader") //reader name 69 | .build(); 70 | } 71 | 72 | @Bean 73 | public ItemWriter jdbcCursorItemWriter() { 74 | return list -> { 75 | for (Pay pay : list) { 76 | log.info("Current Pay = {}", pay); 77 | } 78 | }; 79 | } 80 | 81 | } 82 | --------------------------------------------------------------------------------