Whiteship's Note


[회사일] Enum 추가, Formatter 적용

프로젝트/SLT : 2010.06.08 17:47


Code 도메인에 enum으로 CodeCate 타입 필드를 하나 추가합니다. Enum을 사용할 땐 DB에 저장될 값과 화면에 보여줄 값과 순서를 지정하는게 보통인지라 그런 기능을 갖추게 하는 인터페이스를 하나 정의했었습니다.

public interface PersistentEnum {

String getDescr();

Integer getValue();

int getOrder();

}

enum이 이 인터페이스를 구현하도록 만듭니다. enum에는 상속다운 상속이 없어서 매번 똑같은 코드가 반복되지만 그래도 머.. 필요하기 때문에 어쩔 수 없습니다.

public enum CodeCate implements PersistentEnum {

COLOR(10, "색상", 2),
SEX(15, "성별", 3),
SIZE(20, "사이즈", 1);

private final Integer value;
private final String descr;
private final int order;

private CodeCate(Integer value, String descr, int order) {
this.value = value;
this.descr = descr;
this.order = order;
}

public Integer getValue() {
return value;
}
public String getDescr() {
return descr;
}
public int getOrder() {
return order;
}

}

그리고 하이버네이트에서 이 타입을 알 수 있게 UserType을 만들어줘야 하는데 GenericPesistentEnumUserType을 사용해서 간단하게 만들 수 있습니다.

public class CodeCateUserType extends GenericEnumUserType<CodeCate> implements Serializable {
    
}

이게 끝입니다. Code 도메인에 필드와 매핑 정보를 추가합니다.

        @Column
@Type(type="smdis.domain.usertype.CodeCateUserType")
private CodeCate cate;

위에서 만든 UserType을 지정해주면 됩니다.

여기까지 한다음 웹쪽을 생각해보죠.

새 코드를 입력할 때 코드 카테고리를 선택하려면 코드 카테 목록이 model에 들어있고 화면에서 그 값을 참조할 수 있어야 합니다. 이런 참조형 데이터를 한곳에 모아서 화면에 전달하도록 하죠.

@Component
public class CodeRef {

    public List<CodeCate> getCodeCateList(){
        return PersistentEnumUtil.sortedListOf(CodeCate.class);
    }
}

그리고 CodeController에는 @ModelAttribute를 사용해서 화면에 전달합니다.

        @Autowired CodeRef ref;

        @ModelAttribute("ref")
public CodeRef ref() {
return ref;
}

그럼 이제 화면에서 참조할 수 있죠.

<p class="ui-widget-content"><label>코드종류</label><form:select path="cate" items="${ref.codeCateList}" itemValue="value" itemLabel="descr" /><form:errors path="cate" cssClass="smdis-error-message"/></p>
                
스프링 form 태그를 이용해서 Enum 타입 List를 items에 넘겨주고 그 List 엔티티의 필드 중에서 값이 될 필드와 레이블이 될 필드명을 설정해 줍니다.


그럼 이렇게 화면에 보이는 값는 PersistentEnum의 getDescr() 값으로 보여지고 실제 선택했을때 핸들러로 넘어가게 되는 값은 getValue() 값이 됩니다.

이제 적당한 값을 넣고 저장을 누르면..


이렇게 됩니다. 왜 이런 에러가 나는지는 아시겠죠? 필요한건 CodeCate 타입인데 20이라는 String 타입이 넘어와서 도무지 어떻게 바인딩해야할지 모르겠다고 에러가 나는겁니다. 해결책은 간단합니다. 알려주면 됩니다. 어떻게 바인딩 하면 되는지 스프링한테..

이전까진 PropertyEditor를 사용하던지 아님 직접 핸들러에서 매번 request에서 파라메터 받아서 처리하던지 해야했지만 스프링 3.0에서는 Converter와 Formatter라는게 추가됐습니다. 그 중에서도 웹 용으로는 특수화된 Formatter를 사용하겠습니다.

이전 글에서 만든 GenericPersistentEnumFormatter를 사용해서 만듭니다.

@Component
public class CodeCateFormatter extends GenericPersistentEnumFormatter<CodeCate> {
}

쓰레드 세이프하기 때문에 빈으로 등록해서 싱글톤으로 써도 됩니다. PropertyEditor는 2단계 호출을 거치기 때문에 그다지 안전하지 않았습니다. 매번 new를 사용해서 등록해주는것이 안전했었죠. 이젠 그럴필요가 없으니까 이렇게 합니다.

그리고 이제 이 포매터를 등록하는 일이 남았는데 조금 귀찮습니다;

public class SmdisFormattingConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean {

    @Autowired CodeCateFormatter codeCateFormatter;

    @Override
    protected void installFormatters(FormatterRegistry registry) {
        super.installFormatters(registry);
        registry.addFormatterForFieldType(CodeCate.class, codeCateFormatter);
    }
}

먼저 이렇게 FormattingConversionServiceFactoryBean를 상속한 다음에 installFormatters()를 재정의해서 원하는 포매터를 등록해 줍니다.

마지막으로 이녀석을 conversionService로 등록하고 어댑터에 끼워줘야 합니다. 그나마 이부분은 스프링 3.0에 추가된 mvc 네임스페이스 덕분에 간단해 졌습니다.,

    <mvc:annotation-driven conversion-service="conversionService" />

    <bean id="conversionService" class="smdis.common.formatter.SmdisFormattingConversionServiceFactoryBean"/>

끝.. 이제는 스프링이 CodeCate 타입을 어떻게 바인딩 해야하는지 알고 있기 떄문에 잘 들어갑니다.


내일은 검색과 그리드쪽에도 CodeCate를 추가해야겠군요. 
흠.. 그리드는 간단하니깐 걍 오늘 추가해야겠네요.

top


7.1. MessengerType 클래스 작성.



1. GenericDao 상속 받기.

public class KMessengerType extends GenericEnum

2. GenericDao<클래스명, 값의 타입> 입력하기.

public class KMessengerType extends GenericEnum<KMessengerType, String>

3. 생성자는 private 타입으로 변경하기.

    private KMessengerType(String value, String descr) {
        super(value, descr);
    }

4. 원하는 enum을 public final static 변수로 생성하기.

    public static final KMessengerType MSN = new KMessengerType("M", "MSN");
    public static final KMessengerType NATE = new KMessengerType("N", "Nate On");
    public static final KMessengerType GOOGLE = new KMessengerType("G", "Google Talk");
    public static final KMessengerType Skype = new KMessengerType("S", "Skype");

5. DB에 어떻게 저장되어야 할지 나타내 주는 getType 메소드 정의하기.

    public static int getType() {
        return Types.CHAR;
    }

전체 코드 보기

more..

top


7. Enumeration 사용하도록 리팩터링



Messenger의 Type과 Member의 Role에 특정 값만 들어 갈 수 있도록 하는 것이 보다 효율적으로 판단되어 Enum을 사용하도록 수정합니다.

1. GenericEnum 을 상속 받는 MessengerType고 RoleType 클래스를 작성합니다.

2. Messenger 클래스와 MemberGroup 클래스에 있는 messegerType과 role변수의 타입을 변경합니다.

3. 새로운 타입으로 매핑하기.
top


Enumeration & Iterator

Java : 2006.11.07 19:48


Collection은 지난 번에 살짝 살펴 본 것 같네요. 이번에는 콜렉션 안에 들어있는 요스들을 하나하나 차례대로 페이지 넘기듯이 볼 수 있듯 해주는 Collection View라는 것에 대한 아티클을 읽어 봤습니다.

콜렉션 뷰에는 Enumeration 과 Iterator 그리고 ListIterator가 있는데.. 이중에 Iterator와 ListIterator는 비슷하기 때문에 Enumeration과 Interator의 차이에 대해 알아보겠습니다.

먼저 스냅샷에서 차이가 나는데요. 스냅샷은 여러 콜렉션의 요소들이 영화라고 했을 때 그 영화의 한 순간을 스냅샷이라고 하듯이 콜렉션의 어떤 요소 하나를 스냅샷이라고 하나보다.. 라고 생각을 했었는데요. 그런 순간을 유지 하기 위해서 그 한 순간의 상태를 따로 생성하는 경우와 그렇치 않은 경우가 있나봅니다. Enumeraion의 경우 스냅샷에 대한 보장을 한다는 말이 있는데요. 즉 그 순간 따로 객체를 만드는 것인가 봅니다. 하지만 Iterator는 그렇치 않다고 합니다.

콜렉션 뷰 객체인 Iterator와 ListIterator 객체가 fail-fast 방식인 이유는 스냅샷에 대한 보장을 포함하고있지 않기 때문이다. 즉, 콜렉션의 뷰인 Iterator 객체를 얻었을 때 그 순간의 상태를 따로 생성하지 않기 때문에,Iterator 객체에서 순차적으로 하부 콜렉션 객체를 접근할 때, 콜렉션 객체의 변경에 영향을 받게 된다.

그결과 스냅샷을 찍을 때 따로 객체를 생성하지 않고 원래 콜렉션 객체를 참조 하고 있는 Iterator를 사용하여 콜렉션의 요소들을 보고 있을 때 삭제 or 추가 가 발생할 때 데이타 무결성을 지키기 위해서 런타임 에러를 발생시킵니다. 이런것을 Fail-fast 방식이라고 합니다. 하지만 Enumeration은 그렇치 않겠죠. 그렇다면 'Iterator를 쓰면서 삭제 or 추가를 할 수는 없을까?' 라는 생각이 드는데요. 사실 제일 먼저 든 생각은 'List의 경우에 왜 Iterator를 사용하는 가?' 였습니다. List는 get(int) 메소드를 사용해서 요소들에 바로 접근을 할 수 있기 때문에 Iterator가 꼭 필요한가 라는 생각을 했었는데요. 이건 List의 경우일 때이고 다른 Collection 인터페이스에는 get()이라는 메소드가 없었습니다. 그리고 iterator() 메소드는 있었지요. 그것을 보고 아.. 모든 콜렉션 들은 iterator() 만 알면 모든 요소들에 접근 할 수 있도록 하는 것인가.. 라는 자문자답을 해보게 되었습니다. 다시 원래 질문으로 돌아가서 'Iterator를 사용하면서는 삭제 or 추가를 할 수 없을까?' 라는 바보 같은 질문입니다. 당연히 쓸수가 없지요. 런타임 에러가 발생한다고 했으니깐요. 하지만 스냅샷을 만들어 주면 어떨까요? 그런 방법이 아티클에서 소개되고 있었습니다.

public Iterator snapshotIterator(Collection collection) {
return new ArrayList(collection).iterator();
}

그리고 동기화에 대한 이야기 부분에서 '콜렉션 프레임워크는 동기화를 보장하고 있지 않다.' 라고 합니다. 하지만 Vector의 경우에는 예외라고 생각이 되네요. Vector 클래스에 보면 synchronized 키워드가 많이 있었습니다. 하지만 대부분의 콜렉션의 synchronized 키워드를 찾아 볼 수 없었습니다. 이 것은 동기화를 보장하고 있지 않다는 듯입니다. 즉 동시에 쓰기와 읽기가 가능해 진다는 것인데 이럴 때 발생할 수 있는 문제를 해결하려면 역시 동기화 시키는 방법이 제일 좋겠지요. 그러한 해결책도 역시 아티클에서 소개 되고 있습니다.

Collection c = Collections.synchronizedCollection(myCollection);

끝으로 무엇이 더 좋다고 할 수는 없을 듯합니다. 그때 그때 상황에 따라 다른 듯합니다. 각각의 상황에 적절한 방법을 찾으려면 역시 이것도 알고 저것도 알고 있어야 한다는거~ 따라서 열공!

ps : 분홍색 부분은 원문을 참조한 것입니다.

'Java' 카테고리의 다른 글

Java 오픈소스 되다.(GPL 산하의)  (1) 2006.11.13
Null은 객체인가 아닌가? 2  (3) 2006.11.12
Null은 객체인가 아닌가?  (13) 2006.11.12
Reference Object 활용  (2) 2006.11.09
XML을 EXEL 파일로 바꾸기  (0) 2006.11.08
Enumeration & Iterator  (2) 2006.11.07
Auto (un)boxing은 -128~127  (4) 2006.11.07
탬플릿 클레스 만들기  (3) 2006.11.05
local inner class의 예  (0) 2006.11.05
객체지향의 구멍 static  (0) 2006.11.04
i++와 i=i+1 속도 비교  (0) 2006.11.02
top