728x90

Example Code

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors().configurationSource(request -> {
                    CorsConfiguration config = new CorsConfiguration();
                    config.setAllowedOrigins(Collections.singletonList("*")); // Allow localhost
                    config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
                    config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
                    config.setAllowCredentials(false);
                    return config;
                })
                .and()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .httpBasic()
                .authenticationEntryPoint(customAuthenticationEntryPoint)
                .and()
                .exceptionHandling().accessDeniedHandler(customAccessDeniedHandler)
                .and()
                .authorizeRequests()
                .antMatchers("/signup").permitAll()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated();

        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }

 

In example, setAllowCredentials method was seen "false". if want to set "setAllowedOrigins" method for asteroid, means '*', it need to be "false".

 

if not, will see next issue.

ev-api-1  | java.lang.IllegalArgumentException: When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.
ev-api-1  | 	at org.springframework.web.cors.CorsConfiguration.validateAllowCredentials(CorsConfiguration.java:495) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.springframework.web.cors.CorsConfiguration.checkOrigin(CorsConfiguration.java:599) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.springframework.web.cors.DefaultCorsProcessor.checkOrigin(DefaultCorsProcessor.java:174) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.springframework.web.cors.DefaultCorsProcessor.handleInternal(DefaultCorsProcessor.java:116) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.springframework.web.cors.DefaultCorsProcessor.processRequest(DefaultCorsProcessor.java:95) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:87) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.11.jar!/:5.7.11]
ev-api-1  | 	at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-5.7.11.jar!/:5.7.11]
ev-api-1  | 	at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-5.7.11.jar!/:5.7.11]
ev-api-1  | 	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.11.jar!/:5.7.11]
ev-api-1  | 	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:112) ~[spring-security-web-5.7.11.jar!/:5.7.11]
ev-api-1  | 	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:82) ~[spring-security-web-5.7.11.jar!/:5.7.11]
ev-api-1  | 	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.11.jar!/:5.7.11]
ev-api-1  | 	at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) ~[spring-security-web-5.7.11.jar!/:5.7.11]
ev-api-1  | 	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.11.jar!/:5.7.11]
ev-api-1  | 	at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) ~[spring-security-web-5.7.11.jar!/:5.7.11]
ev-api-1  | 	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.11.jar!/:5.7.11]
ev-api-1  | 	at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221) ~[spring-security-web-5.7.11.jar!/:5.7.11]
ev-api-1  | 	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186) ~[spring-security-web-5.7.11.jar!/:5.7.11]
ev-api-1  | 	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.30.jar!/:5.3.30]
ev-api-1  | 	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:168) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1790) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.80.jar!/:na]
ev-api-1  | 	at java.base/java.lang.Thread.run(Thread.java:840) ~[na:na]
728x90

What is the purpose of com.google.common.base.Verify when we have com.google.common.base.Preconditions?

The Verify class looks nice but it has an @Beta annotation, should I use it?

 

 

The difference is semantic. Verify is used to ensure that invariants don't change, that Code which has been engineered to do a certain thing is actually doing that thing. In spirit:

int x = divide(10, 5);
Verify.verify(x == 2, "X should be 2");

 

Preconditions, on the other hand, are expected to fail when bad input is passed to a certain portion of the program, usually either from the user to the code, or from client code to code within another library. In spirit:

public int divide(int x, int y) {
  Preconditions.checkArgument(y != 0, "Can't divide by 0!");
  return x / y;
}

As to whether you should use a @Beta class, that entirely depends on the scope and foreseeable lifetime of the application you are building, and, asked alone, would probably be flagged as a "Primarily Opinion-Based" question.

'Java' 카테고리의 다른 글

[Spring] ST_intersects method  (1) 2024.09.09
[Pageable] withSort  (0) 2024.08.30
Spring Batch - Chunk 지향 프로세싱(Mybatis)  (0) 2024.07.17
Pattern and Matcher  (0) 2024.07.16
SecureRandom  (0) 2024.07.12
728x90

Sometimes changes made to the model or to the ORM may not reflect accurately on the database even after an execution of SchemaUpdate.

If the error actually seems to lack a sensible explanation, try recreating the database (or at least creating a new one) and scaffolding it with SchemaExport.

 

2024-07-23 14:30:43.399  WARN 17560 --- [ault-executor-0] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 1364, SQLState: HY000
2024-07-23 14:30:43.399 ERROR 17560 --- [ault-executor-0] o.h.engine.jdbc.spi.SqlExceptionHelper   : Field 'objectid' doesn't have a default value
2024-07-23 14:30:43.407 ERROR 17560 --- [ault-executor-0] c.c.otp.service.impl.OtpMgrServiceImpl   : Error saving OTP Entity: 

org.springframework.orm.jpa.JpaSystemException: could not execute statement; nested exception is org.hibernate.exception.GenericJDBCException: could not execute statement
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:331) ~[spring-orm-5.3.27.jar:5.3.27]
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233) ~[spring-orm-5.3.27.jar:5.3.27]
	at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551) ~[spring-orm-5.3.27.jar:5.3.27]
	at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) ~[spring-tx-5.3.27.jar:5.3.27]
	at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242) ~[spring-tx-5.3.27.jar:5.3.27]
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152) ~[spring-tx-5.3.27.jar:5.3.27]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.27.jar:5.3.27]
	at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174) ~[spring-data-jpa-2.7.12.jar:2.7.12]
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.27.jar:5.3.27]
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-5.3.27.jar:5.3.27]
	at org.springframework.aop.framework.ReflectiveMe

 

종종 ORM 관련되서 맛탱이가 가는 경우가 있는데, 정상적으로 save method 를 실행하지 못하며 신기하게 에러를 뱉는 경우가 있다.

 

디버깅에서 잡고 찔러봐도 분명히 데이터는 들어가는데, 왜 default value를 저장하는지 모르겠다 하고.

결론은 Stack Overflow 에서처럼 테이블 드랍시키고 다시 만들면 쑤욱 하고 들어간다

 

https://stackoverflow.com/questions/804514/hibernate-field-id-doesnt-have-a-default-value

 

Hibernate: "Field 'id' doesn't have a default value"

I'm facing what I think is a simple problem with Hibernate, but can't solve it (Hibernate forums being unreachable certainly doesn't help). I have a simple class I'd like to persist, but keep gett...

stackoverflow.com

 

 

 

728x90

Step

일반적으로 Spring Batch는 대용량 데이터를 다루는 경우가 많기 때문에 Tasklet보다 상대적으로 트랜잭션의 단위를 짧게 하여 처리할 수 있는 ItemReader, ItemProcessor, ItemWriter를 이용한 Chunk 지향 프로세싱을 이용한다.

작업 로직이 Chunk 지향 처리를 하기에는 너무 간단하거나, 부 자연스러운 경우 Tasklet을 사용하는 것이 좋다.

 

Chunk
한 번에 하나씩 데이터(row)를 읽어 Chunk라는 덩어리를 만든 뒤, Chunk 단위로 트랜잭션을 다루는 것
Chunk 단위로 트랜잭션을 수행하기 때문에 실패할 경우엔 해당 Chunk 만큼만 롤백이 되고, 이전에 커밋된 트랜잭션 범위까지는 반영이 된다.
Chunk 기반 Step은 ItemReader, ItemProcessor, ItemWriter라는 3개의 주요 부분으로 구성될 수 있다.

ItemReader, ItemProcessor, ItemWriter
Chunk 모델을 구현하면서 데이터의 입력/처리/출력 3가지 프로세스로 분할하기 위한 인터페이스로 데이터 IO를 담당하는 ItemReader와 ItemWriter는 데이터 베이스와 파일을 Java객체 컨버팅을 제공하기에 Spring Batch를 사용하는 것으로 충분히 컨버팅이 가능하다. ItemProcessor는 입력 확인 및 비즈니스 로직을 구현한다. 

  • 읽기(Read) — Database에서 배치 처리를 할 Data를 읽어온다.
  • 처리(Processing) — 읽어온 Data를 가공, 처리를 한다. (필수사항X)
  • 쓰기(Write) — 가공, 처리한 데이터를 Database에 저장한다.

 

Spring Batch에는 다양한 ItemReader와 ItemWriter가 존재한다.

대용량 배치 처리를 하게 되면 Item을 읽어 올 때 Paging 처리를 하는 게 효과적이다.

Spring Batch Reader에서는 이러한 Paging 처리를 지원하고 있다. 또한 적절한 Paging처리와 Chunk Size(한 번에 처리될 트랜잭션)를 설정하여 더욱 효과적인 배치 처리를 할 수 있다.

 

Paging Size와 Chunk Size

Paging Size가 10이며 Chunk Size가 20일 경우 2번의 Read가 이루어진 후에 1번의 Transaction이 수행된다.

한 번의 Transaction을 위해 2번의 쿼리 수행이 발생하게 되는 것이다.

적절한 Paging Size와 Chunk Size에 대해 Spring Batch에는 다음과 같이 적혀 있다.

Setting a fairly large page size and using a commit interval that matches the page size should provide better performance.
페이지 크기를 상당히 크게 설정하고 페이지 크기와 일치하는 커밋 간격을 사용하면 성능이 향상됩니다.

한번의 Read 쿼리 수행 시 1번의 Transaction을 위해 두 설정의 값을 일치를 시키는 게 가장 좋은 성능 향상 방법이며 특별한 이유가 없는 한 Paging Size와 Chunk Size를 동일하게 설정하는 것을 추천한다.

 

PagingReader 사용 시 필수 사항

  • SQL에 Order By 지정
  • SQL에 Offset과 Limit 지정 

 

Chunk 지향 프로세싱의 장점

Chunk 지향 프로세싱을 사용하지 않는다 하더라도 개발자가 충분히 비슷한 로직으로 구현을 할 수도 있다.

하지만 Chunk 지향 프로세싱은 단순히 Chunk 단위의 트랜잭션만 제공해주는 것은 아니다.
내결함성 (Falut tolernat)을 위한 다양한 기능들을 제공하고 있다는 것이다.

 

레코드 건너뛰기(Skip)

public Step step() throws Exception {
        return stepBuilderFactory.get("ChunkStep")
        		//<Input Type, Output Type>
                .<UserVO,UserVO>chunk(CHUNK_AND_PAGE_SIZE)
                .reader(reader())
                .processor(processor())
                .writer(writer())
                .faultTolerant()
                .skip(Exception.class)
                .noSkip(IllegalAccessException.class)
                .skipLimit(10)
                .listener(loggingChunkListener)
                .build();
}

입력에서 레코드를 읽는 중에 에러가 발생했을 때는 몇 가지 선택지가 존재한다. 먼저 예외를 던져 처리를 멈추는 것이다.

이 방법은 너무 극단적이다. 1만 명에 사람에게 만원씩 입금해야 한다고 했을 때 1000번째에서 예외가 발생하였다고 생각해보자. 나머지 9000명은 돈을 받을 수 없다. 

 

Spring Batch는 그 대신 특정 예외가 발생했을 때 레코드를 건너뛰는 Skip 기능을 제공한다. 

 

레코드를 건너뛸지 여부를 결정할 때 고려해야 할 두 가지 요소가 있다. 
어떤 조건에서 레코드를 건너뛸 것인가, 특히 어떤 예외를 무시할 것인가이다. 
레코드를 건너뛰려면 어떤 예외를 건너뛰게 할지, 몇 번까지 예외를 허용할지 설정하기만 하면 된다.

 

건너뛰고 싶은 예외보다는 건너뛰고 싶지 않은 예외를 설정하는 구성이 더 간편할 수도 있다.

 

소스에 대한 해석은 다음과 같다.

Exception을 Skip 하고 IllegalAccessException은 Skip하지 않는다. 즉, IllegalAccessException만 Skip하지 않는다는 뜻이다. IllegalAccessException을 제외한 Exception을 상속한 모든 예외를 10번까지 건너뛸 수 있음을 나타낸다.

 

.listener(loggingChunkListener)

 

 

@Component
public class LoggingChunkListener implements ChunkListener {
	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	//Chunk 실행 전
	@Override
	public void beforeChunk(ChunkContext context) {
		StepContext stepContext = context.getStepContext();
		StepExecution stepExecution = stepContext.getStepExecution();
		logger.debug("###### beforeChunk : " + stepExecution.getReadCount());
	}

	//Chunk 실행 후
	@Override
	public void afterChunk(ChunkContext context) {	
		StepContext stepContext = context.getStepContext();
		StepExecution stepExecution = stepContext.getStepExecution();
		logger.debug("###### afterChunk : " + stepExecution.getCommitCount());
	}

	//Chunk 수행 중 Error가 발생했을시
	@Override
	public void afterChunkError(ChunkContext context) {
		StepContext stepContext = context.getStepContext();
		StepExecution stepExecution = stepContext.getStepExecution();
		logger.debug("##### afterChunkError : " + stepExecution.getRollbackCount());
	}

}

listener를 통해 Chunk별로 Log를 출력할 수 있다. afterChunkError메소드를 활용하면 예외처리가 발생한 Chunk들의 정보를 출력할 수 있을 것이다.


Example (Mybatis)

@Configuration
public class ExampleJobChunkConfiguration {

	@Autowired
	private JobBuilderFactory jobBuilderFactory;
	@Autowired
    private StepBuilderFactory stepBuilderFactory;
    @Autowired
    public SqlSessionFactory sqlSessionFactory;
    
    private int CHUNK_SIZE = 2;
    
    @Bean
    public Job exampleJobChunk() throws Exception {
        
        Job exampleJob = jobBuilderFactory.get("ExampleJobChunk")
                .start(step())
                .build();

        return exampleJob;
    }

    @Bean
    @JobScope
    public Step step() throws Exception {
        return stepBuilderFactory.get("ChunkStep")
        		//<Input Type, Output Type>
                .<UserVO,UserVO>chunk(CHUNK_SIZE)
                .reader(reader())
                .processor(processor())
                .writer(writer())
                .build();
    }

    @Bean
    @StepScope
    public MyBatisPagingItemReader<UserVO> reader() throws Exception {
		/*
		 * Paging 처리 시 OrderBy는 필수
		 */
    	MyBatisPagingItemReader<UserVO> reader = new MyBatisPagingItemReader<>();
    	reader.setPageSize(CHUNK_SIZE);
    	reader.setSqlSessionFactory(sqlSessionFactory);
    	reader.setQueryId("Mapper.id");
    	
    	return reader;
    }

    @Bean
    @StepScope
    public ItemProcessor<UserVO, UserVO> processor(){

    	return new ItemProcessor<UserVO, UserVO>() {
            @Override
            public UserVO process(UserVO model) throws Exception {

            	model.setNick(model.getNick() + "1");

                return model;
            }
        };
    }

    @Bean
    @StepScope
    public MyBatisBatchItemWriter<UserVO> writer(){
    	MyBatisBatchItemWriter<UserVO> writer = new MyBatisBatchItemWriter<>();
    	writer.setSqlSessionFactory(sqlSessionFactory);
    	writer.setStatementId("mapper.id");
    	return writer;
    }

}

 

<select id="chunkSelectTest" resultType="VO경로">
	SELECT
		...
	FROM
		...
	ORDER BY COLUMN1, COLUMN2, ...
	LIMIT #{_skiprows}, #{_pagesize}
</select>

SQL에는 정렬을 해야 한다. Paging처리를 할 때마다 SQL이 실행되기 때문에 순서가 보장되어야 하기 때문이다.

또한 Offset과 Limit을 지정해야 Paging처리를 하여 순차적으로 조회할 수 있다.

 

@JobScope, @StepScope

Chunk 지향 처리 Example을 확인하면 @JobScope와 @StepScope Annotation을 확인할 수 있다.

@JobScope는 Step 선언문에 사용 가능하며 @StepScope는 Step을 구성하는 ItemReader, ItemProcessor, ItemWriter에 사용이 가능합니다.

@JobScope와 @StepScope는 Singleton 패턴이 아닌 Annotation이 명시된 메소드의 실행 시점에 Bean이 생성되게 된다. 또한 @JobScope와 @StepScope Bean이 생성될 때 JobParameter가 생성되기 때문에 JobParameter 사용하기 위해선 반드시 Scope를 지정해주어야 한다. 이는 LateBinding을 하여 JobParameter를 비즈니스 로직 단계에서 할당하여 보다 유연한 설계를 가능하게 하고 서로 다른 Step이 서로를 침범하지 않고 병렬로 실행되게 하기 위함입니다.

+ Recent posts