Life Developer
인생 개발자
Developer (136)
[Spring Batch]2.application과 배치관련 .properties

@SpringBootApplication
@EnableBatchProcessing //추가해줘야함.
public class Application extends SpringBootServletInitializer {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder app) {
return app.sources(Application.class);
}

}

 

 

spring.jpa.show-sql = true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update

 

#자동으로 배치 관련 테이블 생성해줌

spring.batch.jdbc.initialize-schema: always

  Comments,     Trackbacks
json, formData 로 파일첨부

1개 첨부시인경우임.

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"}));

$.ajax({
            type: 'POST',
            url: 'http://localhost:8080/app'+'/dashboard/sendImg.json',
            processData: false,
            contentType:false,
            data: formData,
        }).done(function() {
            alert('사진이 전송되었습니다.');
            window.location.href = '/dashboard/home.do';
        }).fail(function (error) {
            alert(JSON.stringify(error));
});
    }
    
};

    imgSend.imgSendAjaxCall();
}

 

 

중요

1.processData는 일반적으로 서버에 전달되는 데이터는 query String 형태로 전달됩니다. data파라미터로 전달된 데이터는 jQuery내부적으로 query string으로 만드는데, 파일전송에는 이를 피해야함으로 false설정해줍니다.

2.contentType은 default값이 "application/x-www-form=urlencoded; charset=UTF-8"인데 파일을 보내줄때는 multipart/form-data로 전송해야하기때문에 false로 설정해줍니다. 파일을전송하고 디버깅모드로 체크해보면 Content-Type:multipart/form-data로 나오는걸 볼수있습니다.

 

컨트롤러@PostMapping("sendImg.json")
public Long sendImg(@RequestPart(value = "key") ImgDto imgDto,
@RequestPart(value = "file") MultipartFile file) {

System.out.println(imgDto.toString());
System.out.println(file.getOriginalFilename());

return null; //임시코드
  
  }

 

 

 

참고

이미지 관리는 물리적으로 이미지를 서버에 업로드 한 다음 파일 위치와 이미지 세부 정보를 데이터베이스에 기록하는 방법으로 가장 잘 수행 할 수 있습니다. 그 후 사용자가 텍스트 검색, 부품 번호 검색 또는 기타 쿼리를 수행 할 수 있도록 검색 양식을 구성 할 수 있습니다.

BLOB 필드에 이미지를 업로드하는 것은 이미지가 썸네일보다 훨씬 큰 경우 일반적으로 이러한 이미지 데이터가 문제가되는 나쁜 생각입니다. 

 

  Comments,     Trackbacks
[Spring Batch]1.기본개념과 예제

왜?

자동화 : 매번 단순반복작업을 쉽고 빠르게 자동화시켜줍니다.
대용량 처리 : 그것이 대용량이라 할지라도 가장 최적화된 성능을 보장합니다.
견고성 : 예측하지 못한 상황이나 동작에 대한 예외처리도 정의할 수 있습니다.
재사용성 : 공통적인 작업을 단위별로 재사용할 수 있습니다.

 

Read(가져와서) : 원하는 조건의 데이터 레코드를 DB에서 읽어옵니다.
Processing(처리하고) : 읽어온 데이터를 비즈니스로직을 따라 처리합니다.
Write(저장한다.) : 처리된 데이터를 DB에 업데이트(저장)합니다.

이 기본구조를 스프링 부트 배치에서 제공하는 인터페이스 관계도를 나타내면 다음과 같습니다.

스프링부트 배치의 인터페이스 관계도 (ItemReader, ItemProcessor, ItemWriter)

ItemReader : 배치데이터를 읽어오는 인터페이스입니다. DB뿐 아니라 File, XML 등 다양한 타입에서 읽어올 수 있습니다.
ItemProcessor :
 읽어온 데이터를 가공/처리합니다. 즉, 비즈니스 로직을 처리합니다.

ItemWriter :
 처리한 데이터를 DB(또는 파일)에 저장합니다.

 

굳이 ItemProcessor ItemWriter 단계를 별도로 분리하지 않고 그냥 ItemWriter에서 처리하고 바로 저장하는 구조가 더 간결해 보일 수도 있지않냐 생각할수 있다.

근데 나눈 이유가 있다.

 

1. 우리가 controller나 service나 dao 나누듯 각각의 역할을 단순명료하게 분리하기 위함입니다.

저렇게 명확하게 구분 지어 설계하는 것이 견고한 서비스를 만들어가는데 좋은 밑바탕이 될 수 있겠죠.

2. 읽어오는 데이터와 저장하는 데이터의 타입을 분리하기 위함입니다.

스프링부트 배치는 한 번의 트랜잭션 안에 처리(commit)되는 수를 정의할 수 있습니다. 그 역할을 하는 것이 바로 chunk라는 단위이지요. 

ItemProcessor를 살펴볼까요?

public interface 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, Job, JobLauncher)

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) 인스턴스의 컨테이너가 되기 때문에 생성하기 직전에 인스턴스를 전달받습니다.

 

이쯤하고 담에.

  Comments,     Trackbacks
[Oracle]새로운 계정 생성, 권한추가

--P910509가 이름, P1234가 암호

CREATE USER N910509 IDENTIFIED BY P1234;

-- 오라클 12c 이후 버젼은 유저이름 앞에 "C##"을 붙여야한다고 함

--P910509가 이름, P1234가 암호

GRANT CONNECT , resource, dba TO N910509;

- RESOURCE : 개체를 생성, 변경, 제거 할 수 있는 권한(DDL,DML 사용가능)
- CONNECT : 데이터베이스에 연결할 수 있는 권한
- DBA : 데이터베이스 관리자 권한


COMMIT;

SELECT * FROM all_users;

  Comments,     Trackbacks
[Angular]AppRoutingModule로 화면 주소 redirect하기2

app.module 은 AppRoutingModule 을 import받는다.

 

@NgModule({

  declarations: [

    AppComponent,

  ],

  imports: [

    BrowserModule,

    AppRoutingModule,

    LayoutModule,

    SectionModule,

  ],

  exports : [AppComponent],

  providers: [],

  bootstrap: [AppComponent]

})

export class AppModule { }

 

AppRoutingModule을 보면 

routes 를 만들어forRoot에 넣고 import 받는다.(넣고 받는게 좀 이해가 안가긴함. export할때 넣고 하는게아님..)

그리고 그걸 다시 export 해서 내보낸다.

 

const routesRoutes = [

  {

    path : '',

    redirectTo : 'stopwatch',

    pathMatch : 'full',

  },

];

 

@NgModule({

  imports: [RouterModule.forRoot(routes)],

  exports: [RouterModule]

})

export class AppRoutingModule { }

 

 

그리고 app.component.html은 아래와같다.

<app-header></app-header>

 

<app-section></app-section>

 

<app-footer></app-footer>

 

 

여기서 header와 footer는 layout module로 실행된다.

@NgModule({

  declarations: [

    HeaderComponent,

    FooterComponent,

  ],

 

  exports : [

    HeaderComponent,

    FooterComponent,

  ],

  imports: [

    CommonModule,

  ]

})

export class LayoutModule { }

 

 

그리고

<app-section></app-section> 을보자.

SectionModule은 StopwatchModule을 import 받는다.

그리고 import 받을때 routes를 만들어서 박아버린다.

아까 

{

    path : '',

    redirectTo : 'stopwatch',

    pathMatch : 'full',

  },

를 만들어서 넘겨줬으니(주소가 ''이면 stopwatch로 가거라)

이에 대응하는 놈은 박아준다. (stopwatch로 가면 component를 실행하거라)

{

    path : 'stopwatch',

    component : StopwatchComponent,

  }

이렇게!!

 

 

const routesRoutes = [

  {

    path : 'stopwatch',

    component : StopwatchComponent,

  }

];

 

@NgModule({

  declarations: [

    SectionComponent,

  ],

  imports: [

    CommonModule,

    StopwatchModule,

    RouterModule.forChild(routes),

  ],

  exports : [

    RouterModule,

    SectionComponent,

  ]

})

export class SectionModule { }

 

멘탈꺠진다

'Developer' 카테고리의 다른 글

json, formData 로 파일첨부  (0) 2022.09.14
[Spring Batch]1.기본개념과 예제  (0) 2022.09.13
[Angular]AppRoutingModule로 화면 주소 redirect하기  (0) 2020.10.04
[Angular]module?  (0) 2020.10.03
[Angular]export해서 import 하는법  (2) 2020.10.03
  Comments,     Trackbacks
[Angular]AppRoutingModule로 화면 주소 redirect하기

app.module.ts 를 보면

AppRoutingModule를 import 한다.

여기로 가서 아래처럼 화면주소값이 ''이면 index로 가게 할수 있다.

{

    path : '', - 주소값을 이렇게 지정하면

    redirectTo : 'index', - 여기로 이동해라

    pathMatch : 'full', - 정확히 ''여야 한다. 만약 '123' 이면 정확히 123 이어야 한다.

  },

  {

    path : 'index', - index에 이동하면

    component : AppComponent, - 이것을 실행해라. 는뜻

  }

 

 

import { NgModule } from '@angular/core';

import { RoutesRouterModule } from '@angular/router';

import { AppComponent } from './app.component';

 

const routesRoutes = [

  {

    path : '',

    redirectTo : 'index',

    pathMatch : 'full',

  },

  {

    path : 'index',

    component : AppComponent

  }

];

 

@NgModule({

  imports: [RouterModule.forRoot(routes)],

  exports: [RouterModule]

})

export class AppRoutingModule { }

 

  Comments,     Trackbacks
[Angular]module?

module 에 declarations 로 사용 컴포넌트를 정의하며,

imports 해서 module을 참조한다.

declarations에 사용할 컴포넌트가 없는데 사용할수있다?

그것은 module을 import 했기 때문이다.

 

그리고 그 module 역시 module.ts를 보면 experts 한다고 써놨을것이다.

뭐, 이렇게...

exports : [

    TestComponent

  ],

 

의존성 주입이라고도 한다. DI.

어려웠는데 구조를 아는것이 거의 절반 아는것이다...라는걸 느꼈다.

 

아직 앵귤러 실무는 안해봤지만.

 

 

==========================================================

 

@NgModule({

  declarations: [

    AppComponent,

    HelloComponent,

    HeaderComponent,

    FooterComponent,

    SectionComponent,

    TimeDisplayComponent,

    ButtonsComponent,

  ],

  imports: [

    BrowserModule,

    AppRoutingModule,

    LayoutModule

  ],

  exports :[AppComponent],

  providers: [],

  bootstrap: [AppComponent]

})

export class AppModule { }

  Comments,     Trackbacks
[Angular]export해서 import 하는법

export해서 import 하는법

 

export default 는 하나만 할수 있고 import 할때 중괄호 안쓰고 불러야함.

 

import * as all from './a'; 처럼 * as all 써도됨,

근데 다가져와서 용량 낭비

===============================================



export let aaa=10;

 

export class App{

    

}

 

class Angular{

    namer="angular";

}

 

export class Item{

    itemName="toy";

}

export default Angular;

 

================================================



import {aaaAppfrom './a';

 

console.log(aaa);

 

let app=App;

 

import Angular from './a';

 

let a=new Angular();

a.namer;

 

import {Itemfrom './a';

 

let b=new Item();

b.itemName;

  Comments,     Trackbacks