VO 와 DTO 는 둘 다 모델에 해당하는 객체로 그 차이는 단순하다.

VO는 불변객체이고, DTO는 가변객체이다.

가변객체는 그냥 getter, setter 가 포함된 bean 을 생각하면 되고

불변객체는 setter를 포함하지 않아서 변화를 허용하지 않는 걸 말한다.

이에 DTO에는 데이터를 변환하는 비즈니스 로직이 포함될 수도 있다.

 

지금까지 DTO 같은 성격의 객체는 실무에서 많이 사용했다. 

그런데 VO 성격의 객체는 거의 사용해본 적이 없다.

불변객체...대체 언제 필요할까? 솔직히 없어도 된다.

그래도 굳이 나눠지는 개념이니 한번 생각해보자.

 

필요하긴 할거다. 접근제한자로 private 도 쓰지 않는가? 

변화를 허용하지 않기 위해 private 로 불변자원을 만든다.

객체도 "내가 처음에 만들어준 데이터 바꾸지 마" 라고 지정할 수 있는거다. 

 

프로젝트에서 VO 같은 사용자 정의 객체가 사용되는 경우를 생각해보자. 

1. DB select 를 담는 경우

2. Controller 파라미터를 담는 경우

 

그 외 비즈니스 로직에서 별도로 사용자 정의 객체가 사용되는 경우도 있겠지만

있다고 하더라도 로직에 의해 값이 유동적으로 변화하는 경우가 많기 때문에 불변인 VO로 볼 수 없다.

 

1번의 경우는 가져온 데이터를 정재하는 경우가 있기 때문에 VO로 적합하지 않다.

Controller 파라미터는 VO가 가능할 거 같다. 

그렇다면 Controller 파라미터를 vo로 해서 가질 수 있는 장점은 뭘까?

장점이 있어야 쓸 거 아닌가..

 

생각나는 장점 중 하나는 해당 VO객체가 거치게 되는 메소드나 클래스가 많을 경우

불변이 보장된다면 추후 시스템 분석이나 버그 디버깅에 도움이 될 수 있다.

일반적으로 빌링 또는 커머스시스템은 굉장히 복잡한 로직을 품고 있는데

로직이 수행되는 중에 값이 변화하지 않는다는 보장만 있다면 해당 경우의 수만 없애는 것만으로도

디버깅에 굉장히 큰 도움이 된다.

 

다만 모델객체를 구현하는 방식에 따라 가변, 불변을 모두 제공할 수 있다.

즉, 일부 데이터만 포함한 생성자에 빌더패턴을 적용하고 수정을 허용할 데이터는

별도로 setter를 만들어 주면 된다.

이렇게 되면 빌더패턴에 포함된 데이터는 불변이 되고(변경하려면 객체를 새로 생성해야 함)

포함되지 않은 데이터는 가변이 된다.

 

그렇다면 또 다시 VO, DTO를 구분하지 않고 이렇게 하나의 객체로 사용할 때 

가질 수 있는 장점은 뭐가 있을까?

가장 먼저 생각나는 것은 로직당 통일된 하나의 모델을 사용할 수 있다는 것이다.

 

예를 들어 유저정보를 수정하는 기능을 아래와 같이 만든다고 생각해보자.

 

변경될 유저정보를 받아서 그 값으로 DB 테이블을 업데이트하고 

정상적으로 업데이트 되었다면 그 값을 다시 리턴하고

업데이트에 실패했다면 DB상 최종데이터를 조회해서 리턴한다.

 

VO, DTO 를 구분할 경우 변경될 유저정보를 받을 UserVO 객체가 필요하고

DB 데이터를 담을 UserDTO 라는 객체가 필요하게 된다.

이러한 VO, DTO 는 비슷한 성격이기 때문에 아무래도 중복코드가 발생할 수 밖에 없다.

하지만 일부에만 빌더패턴을 적용한 객체를 사용할 경우 

UserModel 이라는 하나의 객체로도 충분하게 된다.

 

결국 가변, 불변 기능을 모두 포함한 모델객체를 사용하는게 가장 좋아보인다.

 

테스트 삼아 한번 만들어보자. 유저에 해당하는 모델을 만들 것이다.

 

조건은 아래와 같다.

사용할 데이터는 아이디, 이름, 나이, 등급으로 하고

이 중 아이디, 이름, 나이는 불변이고 등급은 가변으로 할 것이다.

더 나아가 아이디, 이름은 필수값으로 할 것이다.

 

완성된 모델은 아래와 같다.

@Getter
@ToString(exclude={""})
@EqualsAndHashCode(of = {"id", "name", "age"})
public class UserModel {

    @NotBlank(message = "id is required")
    private String id;
    @NotBlank(message = "name is required")
    private String name;
    private int age;
    private String grade;

    @Builder
    private UserModel(String id, String name, int age, String grade){
        Assert.hasText(id, "id is required");
        Assert.hasText(name, "name is required");
        this.id = id;
        this.name = name;
        this.age = age;
        this.grade = grade;
    }

    public void setGrade(String grade) {
        this.grade = grade;
    }
}

마지막으로 이것을 쓰는 것이 단점은 뭐가 있을까?

현재로선 없어보이는데 생각나면 추가 기록하겠다.

 

'밥벌이 > Java' 카테고리의 다른 글

XML파일의 활용  (0) 2015.09.18
BufferedStream 팁  (0) 2015.09.18
Properties 파일 읽기  (0) 2015.09.18
Posted by mypiece
,

Redo와 Undo

밥벌이/Database 2015. 9. 23. 23:06

 - 둘다 복구에 사용된다. Redo의 경우 '다시실행한다' 라는 의미로 제대로 반영되지 못한
   변경사항을 다시 재현하는 역할을 한다. 이를 'roll forward단계'라 한다.
   이때 버퍼 캐시에 최종커밋되지 않은 변경사항까지 모두 복구되기 때문에 이러한 사항들을
   롤백 해야 하는데 이를 Undo라고 하며 '되돌린다' 라는 의미로 'rollback 단계'라고 한다.

 

1. Redo

1) online redo 로그 : 실제 로그버퍼에 버퍼링된 로그 엔트리를 기록, 최소 2개 이상의 파일로

                                구성되며 라운드로빈 방식사용.

2) archived redo 로그 : 라운드로빈 방식에 의해 online redo 로그파일이 재사용 되기 전에 다른 위치로

                                    백업해둔 파일.

▶ Redo로그의 사용 목적
  - Database Recovery : 물리적으로 디스크가 깨지는 등의 문제 발생시 Archived Redo로그가

                                      사용된다.
  - Cache Recovery : 모든 DBMS는 I/O 성능 향상을 위해 버퍼 캐시가 사용되는데 이는 휘발성으로

                                  디스크 상의 데이터 블록에 기록되지 않은 상태에서 인스턴스가 비정상

                                  종료되면 작업내용을 모두 잃게됨.
                                  이에 재기동시 Online Redo 로그에 저장된 기록사항을 읽어 버퍼캐시에만 있고 

                                  데이터 블록에 반영되지 못한 변경사항을 재현함(roll forward단계)
  - Fast Commit : 변경된 메모리 버퍼블록을 딧크 상의 테이터 블록에 기록하는 작업은 Random 엑세스
                          방식으로 이루지기 때문에 느리지만, 로고는 Append 방식으로 기록하므로 상대적으로

                          매우 빠르다. 

  이에 트랜잭션 발생시마다 우선 변경사항을 Append 방식으로 빠르게 로그에 기록하고 
  메모리 블록과 데이터 블록의 동기화는 적절한 수단(DBWR, Checkpoint를 이용해

  나중에 배치처리) 으로 일괄 수행한다.
  이러한 내용들이 Redo로그에 기록되며 오라클은 Redo로그를 믿고 언제든 Recovery

  가 가능한 상태가 되므로 빠르게 Commit을 완료할 수 있다.
  이러한 Fast Commit은 빠르게 트랜잭션을 처리해야 하는 모든 DBMS의 공통적인 메커

  니즘이다.

 

   ▶ Write Ahead Logging : 버퍼 캐시에 있는 블록 버퍼를 갱신하기 전에 먼저 Redo 엔트리를
                                          로그 버퍼에 기록해야 하며, DBWR이 버퍼 캐시로부터 Dirty 블록들을

                                          디스크에 기록하기 전에 먼저 LGWR이 해당 Redo 엔트리를 모두 Redo로그

                                          파일에 기록했음이 보장되어야 한다.
 
2. Undo
 - Undo 세그먼트는 구조적으로 볼 때 데이터를 저장하는 일반 세그먼트와 별반 다르지 않다.
   익스텐트 단위로 확장되고, 빠른 읽기/쓰기를 위해 Undo블록들을 버퍼 캐시에 캐싱하며,
   데이터 유실 방지를 위해 Redo로그에 로깅하는 점도 같다.
   다른 점이라면 저장되는 내용으로, 각 트랜잭션 별로 Undo세그먼트를 할당해주고
   (두 개의 이상의 트랜잭션이 하나의 Undo블록 세그먼트를 할당받아 같이 사용할 수 있음)
   그 트랜잭션이 발생시킨 테이블과 인덱스에 대한 변경사항들을 Undo 레코드 단위로
   Undo세그먼트 블록에 차곡차곡 기록된다.


 ▶ Undo 세그먼트의 사용 목적
  - Transaction Rollback : 트랜잭션에 의한 변경사항을 최종 커밋하지 않고 롤백하고자 할때
                                         Undo 데이터가 사용됨.
  - Transaction Recovery(rollback 단계) : Instance Crash 발생 후 Redo를 이용해서

                                                                'Roll forward단계'가 완료되면 최종커밋되지 않은 변경사항

                                                                까지 모두 복구되는데 이러한 트랜잭션들을 롤백하는데

                                                                사용된다.
  - Read Consistency : DB2, MS-SQL, Sybase 등은 Lock을 통해 읽기 일관성을 구현하지만 오라클은

                                    Undo데이터를 이용해서 이를 구현한다.


 ※ 상세 과정

 트랜잭션을 시작하려면 먼저 Undo 세그먼트 헤더에 있는 트랜잭션 테이블로 부터 슬롯을 할당받아야 하며, 할당받은 슬롯의 헤더에 자신이 현재 Active 상태임을 표시하고서 갱신을 시작한다.
 Active 상태의 트랜잭션이 사용하는 Undo블록과 트랜잭션 테이블 슬롯은 상태정보가 commited로 변경되기 전까지는 절대 다른 트랜잭션에 의해 재사용되지 않는다.
 이때부터 트랜잭션이 발생시키는 데이터 또는 인덱스 블록에 대한 변경사항은 Undo블록에 레코드로서 하나씩 차례대로  기록된다. Undo 세그먼트 헤더는 가장 마지막 Undo 레코드와 Last UBA(Undo Block Address)라는 일종의 포인터로 연결되어 있으며 각 Undo 레코드 간에는 체인형태로 연결되어 있다.
 v$transaction 뷰에 있는 used_ublk와 used_urec 칼럼을 통해 각각 현재 사용중인 Undo블록 개수와 Undo레코드 양을 확인할 수 있다. 인덱스가 전혀 없는 테이블이라면 한건을 갱신할때마다 used_urec 값이 하나씩 증가하지만 인덱스가 있다면 인덱스의 갱신내용까지 레코드로 추가된다.

 

 ▶ 블록 헤더 ITL 슬롯
  - Undo 세그먼트 헤더에 트랜잭션 테이블 슬롯이 있다면 각 데이터 블록과 인덱스 블록헤더에는

     ITL슬롯이 있다.
  - 특정 블록에 속한 레코드를 갱신하려면 먼저 블록 헤더로부터 ITL슬롯을 확보해야 한다.
    거기에 트랜잭션 ID를 기록하고 현재 해당 트랜잭션이 Active 상태임을 표시한 후에 블록 갱신이

    가능하다.
  - 오라클에서는 ITL슬롯 부족으로 인해 트랜잭션이 블로킹 되는 현상을 최소화할 수 있도록 3가지

     옵션을 제공한다.
    테이블을 생성할 때 initrans, maxtrans, pctfree 파라미터를 지정한다.
    각각 ITL슬롯 초기할당 개수, ITL슬롯 최대할당 개수, ITL슬롯 할당에 사용될 수 있는 예약된 데이터

    공간이다.

 

 ▶ Lock Byte
  - 블록헤더에서 블록으로 내려오면 Lock Byte에 대해 알아볼 필요가 있다.
    오라클은 레코드가 저장되는 로우마다 그 헤더에 Lock Byte를 할당해서 해당 로우를 갱신 중인

    트랜잭션의 ITL슬롯번호를 기록해둔다. 이것이 로우단위 Lock이다.
  - 레코드를 생신하려고 할 때 대상 레코드의 Lock Byte가 활성화 되어 있으면 ITL 슬롯을 찾아가고,
    ITL슬롯이 가리키는 트랜잭션 테이블 슬롯을 찾아가서 아직 Active 상태라면 트랜잭션이 완료될때까지

    대기한다.
  - ITL 슬롯에 기록되는 내용중 UBA(Undo Block Address)가 트랜잭션에 의해 발생한 변경 이전

    데이터가 저장된 Undo 블록주소를 가리키는 포인터 정보로 '문장수준 읽기 일관성' 구현을 위해

    CR Copy를 생성해서 과거버전으로 되돌리려고 할때 사용된다.   
 

'밥벌이 > Database' 카테고리의 다른 글

[Oracle] null 값 순위변경  (0) 2015.09.18
[Oracle] 제약조건 활성화/비활성화  (0) 2015.09.18
[Oracle] 외래키 옵션  (0) 2015.09.18
[Oracle] MERGE 구문  (0) 2015.09.18
[Oracle] 계층쿼리  (0) 2015.09.18
Posted by mypiece
,

어노테이션을 사용하지 않고, xml에서 autowire 지시자를 통한 의존성 주입을 위해서는
해당 클래스에 주입을 원하는 변수의 setter 메소드가 반드시 존재해야 한다.
(어노테이션을 사용할 경우 setter 메소드가 별도로 필요하지 않다.)


대부분의 서적이나 인터넷 자료들을 보면 이러한 setter메소드의 필요성은 누락하고
autowire의 옵션별 설명만을 서술한다. 이러한 서술자료를 보면 아래와 같다.


autowire 옵션
 - byName : 프로퍼티의 이름과 같은 이름을 갖는 빈 객체를 설정
 - byType : 프로퍼티의 타입과 같은 타입을 갖는 빈객체를 설정
 - constructor : 생성자 파라미터 타입과 같은 타입을 갖는 빈객체를 생성자에 전달
 - autodetect : constructor 방식을 먼저 적용하고, byType 방식을 적용하여 의존 객체를 설정


설명만 보면 간단하게 이해할 수 있다.

하지만 실제로 의존성 주입을 통한 어플리케이션을 구현하려고 하면
setter 메소드가 없다면 절대 의존성주입은 이루어지지 않는다.
더군다나 setter 메소드에 대한 설명없이는 어노테이션을 사용하지 않는 의존성주입은 설명되기 어렵다.

예를 들어 일반적으로 의존성주입을 위한 클래스를 만들라고 하면 아래와 같을 것이다.

------------------------------------------------
...java...

public class Service{

...

  protected DAO dao;
  public setDAO(DAO dao){
    this.dao = dao;
  }

...


}

...xml...
<bean id="service" class="com.Service" autowire="byName"/>
<bean id="dao" class="com.DAO"/>
--------------------------------------------------------

위와 같이 설정되었다고 한다면 Service 클래스의 맴버변수인 dao에는 com.DAO 의 인스턴스가 주입될 것이다.
autowire 설명만 본다면 당연한 결과이다.
byName : 프로퍼티의 이름과 같은 이름을 갖는 빈 객체를 설정
자 그렇다면 Service 클래스에서 맴버변수인 dao의 이름을 dao2로 바꾼다면 어떻게 될까?
dao2라는 빈이 존재하지 않기 때문에 주입이 되지 않을까?
결과는 아무런 이상이 없이 정상적으로 의존성주입이 수행된다.
단순히 autowire 옵션에 대한 설명만 본다면
이러한 현상은 어떻게 설명될 수 있을까?


어노테이션을 사용하지 않는 의존성 주입 방식의 경우
기본적으로 해당 클래스에 정의한 setter 메소드에 의해 의존성 주입이 수행된다.
결국 의존성 주입시 바라보는 것은 해당 프로퍼티의 이름이 아닌
해당 프로퍼티의 setter 메소드라는 말이다.
autowire="byName" 으로 설정할 경우 set을 접두어로 사용하는 메소드 중에서
접미어로 지정된 명칭을 기준으로 빈 객체를 찾게된다.


결국 byName 에 대한 설명은 아래와 같이 바뀌어야 한다.
byName : 프로퍼티의 setter 메소드의 set을 제외한 이름과 같은 이름을 갖는 빈 객체를 설정

byType의 경우도 마찬가지이다.
프로퍼티의 타입이 아니라 정의된 setter 메소드의 파라미터 타입에 의해
의존성 주입이 결정된다. 이러한 이유로  byType의 경우 setter메소드의 명칭은
set만 접두어로 사용되면 되고 이후 접미어는 어떤 것이 와도 상관없다.
어차피 의존성주입시에는 파라미터의 타입만 확인하기 때문이다.


이렇게 어노테이션을 사용하지 않는 의존성주입의 경우
단순히 해당 맴버변수의 명칭이나 타입이 아닌 해당 setter 메소드의
관점에서 바라봐야만 명확한 정의를 내릴 수 있다.

 

Posted by mypiece
,