function saveImg(){ var imgSend = { imgSendAjaxCall : function () { var data = { userId: $('#imgSendId').val(), }; var form =$('#fileForm')[0]; var formData = new FormData(form); console.log($('#fileBody')[0].files[0]); formData.append('file', $('#fileBody')[0].files[0]); formData.append('key', new Blob([JSON.stringify(data)] , {type: "application/json"}));
이미지 관리는 물리적으로 이미지를 서버에 업로드 한 다음 파일 위치와 이미지 세부 정보를 데이터베이스에 기록하는 방법으로 가장 잘 수행 할 수 있습니다.그 후 사용자가 텍스트 검색, 부품 번호 검색 또는 기타 쿼리를 수행 할 수 있도록 검색 양식을 구성 할 수 있습니다.
BLOB 필드에 이미지를 업로드하는 것은 이미지가 썸네일보다 훨씬 큰 경우 일반적으로 이러한 이미지 데이터가 문제가되는 나쁜 생각입니다.
자동화 :매번 단순반복작업을 쉽고 빠르게 자동화시켜줍니다. 대용량 처리 :그것이 대용량이라 할지라도 가장 최적화된 성능을 보장합니다. 견고성 :예측하지 못한 상황이나 동작에 대한 예외처리도 정의할 수 있습니다. 재사용성 :공통적인 작업을 단위별로 재사용할 수 있습니다.
Read(가져와서) : 원하는 조건의 데이터 레코드를 DB에서 읽어옵니다. Processing(처리하고) :읽어온 데이터를 비즈니스로직을 따라 처리합니다. Write(저장한다.) :처리된 데이터를 DB에 업데이트(저장)합니다.
이 기본구조를 스프링 부트 배치에서 제공하는 인터페이스 관계도를 나타내면 다음과 같습니다.
ItemReader :배치데이터를 읽어오는 인터페이스입니다. DB뿐 아니라 File, XML 등 다양한 타입에서 읽어올 수 있습니다. ItemProcessor :읽어온 데이터를 가공/처리합니다. 즉, 비즈니스 로직을 처리합니다. ItemWriter :처리한 데이터를 DB(또는 파일)에 저장합니다.
굳이ItemProcessor와ItemWriter단계를 별도로 분리하지 않고 그냥ItemWriter에서 처리하고 바로 저장하는 구조가 더 간결해 보일 수도 있지않냐 생각할수 있다.
근데 나눈 이유가 있다.
1. 우리가 controller나 service나 dao 나누듯 각각의 역할을 단순명료하게 분리하기 위함입니다.
저렇게 명확하게 구분 지어 설계하는 것이 견고한 서비스를 만들어가는데 좋은 밑바탕이 될 수 있겠죠.
2. 읽어오는 데이터와 저장하는 데이터의 타입을 분리하기 위함입니다.
스프링부트 배치는 한 번의 트랜잭션 안에 처리(commit)되는 수를 정의할 수 있습니다. 그 역할을 하는 것이 바로chunk라는 단위이지요.
ItemProcessor를 살펴볼까요?
publicinterface ItemProcessor<I, O> { O process(I item) throws Exception; }
이 녀석(인터페이스)의 존재의 이유는 각각의 역할을 분리하기 위해 그리고 읽어올 때와 저장할 때의 데이터 타입이 다른 경우, DB에서 레코드를 읽어와서 파일 형태로 저장할 때 혹은 그 반대일 때를 대응하기 위함입니다.
이 인터페이스의 제네릭의 매개변수가 2개인데요. 앞에 있는<I>는 Input을 의미하고 뒤에 있는<O>는 Output을 의미합니다. 말그대로 인풋과 아웃풋 타입을 정의하여 process 메서드 안에서 우리가 필요한 비즈니스 로직을 구현합니다.
지금까지 내용을 하나의 그림으로 표현 하면 다음과 같습니다.
이 배치 프로세스가 돌아가는 가상의 시나리오를 설계한다고 가정해봅시다.
1. 데이터베이스에서 우리 서비스요금이 청구된 지 30일이 지난 고객 리스트를 읽어온다. (ItemReader)
2. 1번에서 가져온 고객리스트들을 대상으로 다음의 비즈니스를 처리한다. (ItemProcessor) - 각 고객정보의 카드정보를 활용하여 자동결제를 시도한다. - 자동결제를 시도하여 미납금액 결제을 정상적으로 처리된 고객은 요금 청구 완료상태로 전환한다. - 자동결제를 시도하여 미납금액 결제를 실패로 처리된 고객은 서비스구독 이용정지상태로 전환한다. - 자동결제가 성공한 고객에게는 결제가 완료됐다는 안내메시지를 등록된 휴대폰 정보를 통해 SMS로 발송한다. - 자동결제가 실패한 고객에게는 서비스이용정지 안내 메시지를 등록된 휴대폰 정보를 통해 SMS로 발송한다.
3. 2번에서 처리된 고객리스트들를 저장한다. (ItemWriter)
실제 운영되는 서비스와 비슷한 상황의 가상 시나리오를 각각의 인터페이스의 역할에 맞게 분리하여 설계해보았습니다. 스프링 부트 배치 프레임워크가 없이 구현을 해야 한다면 이러한 일련의 과정을 거쳐야 하는 프로세스를 설계하는 모두 개발자의 몫이 되겠죠? 비즈니스 로직은 둘째 치더라도대용량을 다루는 배치처리에 대한 안정성 여부는 또 다른 차원의 문제입니다.
이정도라면 스프링 부트 배치 프레임워크가 제공하는 기본구조가 개발자들을 위해 얼마나 단순하고 명확하게 분리되어 제공하고 있는지를 알 수 있습니다. 스프링 부트 배치 프레임워크가 제공하는 기본구조를 알았다면 이젠 그 기본구조(읽고, 처리하고, 저장한다)를 포함하고 있는 객체를 살펴보겠습니다. 바로 저 파란색으로 칠해진 부분이죠.
Step
기본구조(읽고, 처리하고, 저장한다.)는 Step이라는 객체에서 정의됩니다. 1개의 Step은읽고,처리하고저장하는구조를 가지고 있는 가장 실질적인 배치처리를 담당하는 도메인 객체입니다. 그리고 이 Step은 한 개 혹은 여러 개가 이루어 Job을 표현합니다. 그리고 1개의 Step은 위에서 설명한 것과 같이ItemReader(읽고),ItemProcessor(처리하고),ItemWriter(저장하고)를 정의합니다.
Job
한개 혹은 여러 개의 Step을 이루어 하나의 단위로 만들어 표현한 객체입니다. 스프링 부트 배치 처리에 가장 윗 계층에 있는 객체이죠. Job 객체는 JobBuilderFactory 클래스에서 Job을 생성할 수 있습니다. 더 정확하게 표현하자면JobBuilderFactory에서 생성된 JobBuilder를 통해 Job을 생성할 수 있습니다.
1) JobBuilderFactory
JobBulderFactory 클래스를 살펴보면 다음과 같습니다.
package org.springframework.batch.core.configuration.annotation;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
public class JobBuilderFactory {
private JobRepository jobRepository;
public JobBuilderFactory(JobRepository jobRepository) { // 1
this.jobRepository = jobRepository;
}
public JobBuilder get(String name) { // 2
JobBuilder builder = new JobBuilder(name).repository(jobRepository);
return builder;
}
}
1. JobBuilderFactory의 생성자를 살펴봅시다. JobBuilder를 생성하는JobBuilderFactory가 생성되는 시점에JobRepository를 주입하여JobBuilder에서 사용할Repository로 설정합니다.이 말인 즉슨,JobBuilder가 생성되는 전에JobBuilderFactory는JobRepository를 주입받게 되고 결국JobBuilder는 해당JobRepository를 그대로 사용하게 되니 결국에는 모든JobBuilder는 모두 동일한JobRepository를 사용하게 되겠죠.
2. get메소드가JobBuilder인스턴스를 생성 후 반환하고 있네요. 그러니까JobBuilderFactory에서 get메소드를 호출하게 되면 새로운JobBuilder가 생성된다는 사실을 알 수 있습니다.
2) JobBuilder
JobBuilderFactory를 통해JobBulder를 반환받았습니다. 이제JobBuilder를 통해 Job을 생성할 수 있습니다. JobBuilder의 내부를 살펴보도록 하겠습니다.
package org.springframework.batch.core.job.builder;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.flow.Flow;
public class JobBuilder extends JobBuilderHelper<JobBuilder> {
public JobBuilder(String name) {
super(name);
}
public SimpleJobBuilder start(Step step) {
return new SimpleJobBuilder(this).start(step);
}
public JobFlowBuilder start(Flow flow) {
return new FlowJobBuilder(this).start(flow);
}
public JobFlowBuilder flow(Step step) {
return new FlowJobBuilder(this).start(step);
}
}
각각 다르게 정의된 2개의start메서드그리고 1개의flow메서드가있네요. 반환 타입이 모두Builder인 것으로 보아 사용하려는 의도에 따라 각각 다른 빌더 메서드가 분류되어 있는 것처럼 보입니다.
1.JonBuilderFactory에서JobBuilder를 만들고 2.JobBuilder에서Job을 생성하기 위한Step(or Flow) 파라미터를 받아 그 목적에 맞는 JobBulder를 생성합니다. 3.Job은Step(or Flow) 인스턴스의 컨테이너가 되기 때문에 생성하기 직전에 인스턴스를 전달받습니다.