2011년 10월 16일 일요일

FluentNHibernate 을 이용한 IUserType 매핑

NHibernate의 기능 중에 가장 큰 장점 중에 하나가 IUserType, ICompositeUserType 을 이용하여, RDBMS 저장소의 컬럼과 속성을 1:1 매핑이라던지, Component 로 직접 매핑이 아닌, 무언가 2차적인 처리를 수행할 수 있는 장치를 두었다는 것입니다.

제가 가장 많이 쓰는 IUserType

  1. 암호화, 압축 등을 수행하여 저장소에 저장하고, 로드 시에는 원본 데이터로 복원하여 제공해 주고, WHERE 절도 자동으로 되는 그런 방식
  2. 여러 속성을 가지는 class 나 struct 를 하나의 Property 처럼 사용할 수 있도록 해주는 ICompositeUserType 을 이용하는 것읍니다. 에를 들면, 주차 (WeekOfYear)는 Year 와 WeekOfYear 두 값이 같이 저장되어야 하는데, 따로 작업하면 안되므려 YearAndWeek 라는 struct 로 정의하여 사용하고, 기간을 나타내는 TimeRange 같은 경우에는 StartTime, EndTime 을 항상 같이 가지고 다녀야 하기 때문에, Component 로 표현하기 보다 ICompositeUserType으로 표현하면 훨씬 좋습니다.

자 그럼 IUserType 으로 NHiberante용 사용자 정의 수형을 만드는 것은 여기 설명을 참고하는 것으로 하고, 이런 사용자 수형을 FluentNHibernate 으로 매핑하는 방법에 대해 설명 드리겠습니다.

우선 사용자 정의 수형을 가지는 엔티티 정의를 보시면,

[Serializable]
public class FUserTypeEntity : DataEntityBase<Int32>
{
public virtual string Name { get; set; }
public virtual string Password { get; set; }
public virtual string Password2 { get; set; }

public virtual string CompressedString { get; set; }

public virtual byte[] CompressedBlob { get; set; }

private TimeRange _activePeriod;

public virtual TimeRange ActivePeriod
{
get { return _activePeriod ?? (_activePeriod = new TimeRange()); }
set { _activePeriod = value; }
}

public virtual YearAndWeek ActiveYearWeek { get; set; }

public virtual Type LanguageType { get; set; }

public virtual DateTime? UpdateTimestamp { get; set; }

public override int GetHashCode()
{
if(IsSaved)
return base.GetHashCode();

return HashTool.Compute(Name, Password);
}
}


과 같습니다. 보시면, CompressedString, CompressedBlob는 IUserType 을 구현한 문자열이나 byte[] 을 압축하여 문자열로 저장하는 UserType 으로 매핑됩니다. 다음으로   ActivePeriod 나   ActiveYearWeek 는 ICompositeUserType 을 구현했습니다. ActivePeriod는 StartTime, EndTime 이라는 속성을 가지고 있는 class 이고, YearAndWeek 는 Year 와 WeekOfYear 값을 가지는 struct 입니다.



이제 Fluent 방식으로 FUserTypeEntity 를 매핑을 하면, 다음과 같습니다.



public class FUserTypeEntityMapping : ClassMap<FUserTypeEntity>
{
public FUserTypeEntityMapping()
{
Table("FUserTypeEntity");
DynamicInsert();
DynamicUpdate();
LazyLoad();

Id(x => x.Id).GeneratedBy.Native();

Map(x => x.Name);

Map(x => x.Password)
.CustomType<RijndaelEncryptStringUserType>()
.Length(MappingContext.MAX_ANSI_STRING_LENGTH_SQL_SERVER);

Map(x => x.Password2)
.CustomType<AriaEncryptStringUserType>()
.Length(MappingContext.MAX_ANSI_STRING_LENGTH_SQL_SERVER);

Map(x => x.CompressedString)
.CustomType<GZipStringUserType>()
.Length(MappingContext.MAX_ANSI_STRING_LENGTH_SQL_SERVER);

Map(x => x.CompressedBlob)
.CustomType<SevenZipBlobUserType>()
.Length(MappingContext.MAX_ANSI_STRING_LENGTH_SQL_SERVER);

Map(x => x.ActivePeriod)
.CustomType<TimeRangeUserType>()
.Columns.Clear()
.Columns.Add("ACTIVE_FROM_DATE")
.Columns.Add("ACTIVE_TO_DATE");

Map(x => x.ActiveYearWeek)
.CustomType<YearAndWeekUserType>()
.Columns.Clear()
.Columns.Add("ACTIVE_YEAR")
.Columns.Add("ACTIVE_WEEK");

Map(x => x.UpdateTimestamp).CustomType("Timestamp");
}
}



우선, Password, Password2 를 보시게 되면,  둘 다 암호화를 해서 저장하는 UserType 을 지정하고, 길이를 CLOB나 VARCHAR(MAX)를 할 수 있도록 합니다.  RijndaelEncryptStringUserType 은 미국 암호화 AES 규격을 따른 것이고,   AriaEncryptStringUserType 은 한국 암호화 규격을 따른 것입니다.



다음으로 압축 저장을 수행해주는 CompressString, CompressedBlob 를 보시면, GZip 으로 압축하던가, 7Zip 알고리즘으로 압축하여 저장해 줍니다. 물론, 로드 시에는 압축을 풀어서 엔티티에서는 Plain Text 나 Byte가 됩니다.



위 두 가지 방식은 모두 IUserType 을 구현한 Custom UserType 이므로, 속성:컬럼이 1:1입니다. 그러므로 CustomType<XxxxUserType>() 을 지정해 주면 알아서 됩니다. 물론 컬럼 명을 변경하고자 한다면,  .Column(“xxxx”) 를 사용하면 되겠지요^^



그럼 이번에는 ICompositeUserType을 사용한 속성에 대한 매핑을 보시죠. 위의 예에서는 ActivePeriod, ActiveYearWeek 입니다. ActivePeriod 는 컬럼 명에서도 유추할 수 있듯이, 기간을 나타냅니다. TimeRangeUserType 으로 수형을 지정하고, 자동으로 매핑되는 컬럼 명을 무시하고 (Columns.Clear()) 순서대로 컬럼 명을 새로 지정했습니다. (한 엔티티에 여러 개의 TimeRangeUserType 이 존재할 수 있으니까요)



FluentNHibernate 1.3 에서는 ICompositeUserType에 대한 매핑 방식이 달라졌지만, 그래도 아직은 FNH-1.2 가 주류이므로, 이렇게 작성했습니다.

FluentNHibernate 으로 CustomType 에 대한 매핑에 대한 설명이 별로 없는 것 같아 작성해 봤습니다…

댓글 없음: