[회사일] Enum 추가, Formatter 적용
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를 추가해야겠군요.
흠.. 그리드는 간단하니깐 걍 오늘 추가해야겠네요.