1. Dozer Mapper란?
Dozer는 한 객체에서 다른 객체로 데이터를 재귀 적으로 복사하는 Java Bean to Java Bean 매퍼입니다.
Dozer는 단순 특성 맵핑, 복합 유형 맵핑, 양방향 맵핑, 암시적 명시적 맵핑 및 재귀 맵핑을 지원합니다.
여기에는 요소 레벨에서 맵핑해야하는 맵핑 콜렉션 속성도 포함됩니다.
Dozer는 속성 이름 간 매핑을 지원할뿐만 아니라 유형 간 자동 변환도 지원합니다.
Dozer는 리플렉션을 사용하여 대부분의 필드 매핑을 자동으로 수행 할 수 있지만 모든 사용자 지정 매핑은 XML 형식으로 미리 설명 할 수 있습니다.
Dozer는 JMapper와 다르게 양방향 매핑을 지원합니다.
매핑은 양방향이므로 클래스 간의 관계는 하나만 정의하면됩니다.
두 객체의 속성 이름이 동일하면 이러한 필드에 대해 명시적인 속성 매핑을 수행하지 않아도 됩니다.
Dozer는 데이터베이스의 내부 도메인 객체가 외부 프리젠 테이션 레이어 나 외부 소비자로 번지지 않도록 합니다.
또한 도메인 객체를 외부 API 호출에 매핑하거나 그 반대로 매핑 할 수 있습니다.
응용 프로그램이 병렬 객체 계층 구조를 지원해야하는 이유는 여러 가지가 있을 수 있습니다.
- 외부 코드와 통합
- 직렬화 요구 사항
- 프레임 워크 통합
건축 레이어의 분리
경우에 따라 직접 제어하지 않는 자주 변경되는 객체 계층으로부터 코드베이스를 보호하는 것이 효율적입니다. 이 경우 Dozer는 애플리케이션과 외부 객체 사이의 브릿지 역할을 합니다.
매핑이 반영 방식으로 수행되므로 모든 변경 사항이 API를 손상시키는 것은 아닙니다. 예를 들어 객체가 Number에서 String으로 변경되면 코드가 자동으로 해결되므로 코드가 계속 작동합니다.
2. Dozer API 다운로드
도저 다운 :: https://sourceforge.net/projects/dozer/files/
Maven ::
<dependency>
<groupId>net.sf.dozer</groupId>
<artifactId>dozer</artifactId>
<version>5.4.0</version>
</dependency>
3. Dozer에서의 Bean의 형태
@Mapping 주석을 필드의 getter에 직접 넣습니다. Dozer가 양방향 맵핑을 추가한 것을 발견하면. 즉, 주석을 한 번 입력하면 두 가지 변환 유형 모두에 대한 매핑이 생성됩니다. 유형 변환 (예 : String-Long)이 자동으로 변환됩니다
//Source Model
public class SourceBean {
private Long id;
private String name;
@Mapping("binaryData")
private String data;
@Mapping("pk")
public Long getId() {
return this.id;
}
public String getName() {
return this.name;
}
}
//Target Model
public class TargetBean {
private String pk;
private String name;
private String binaryData;
public void setPk(String pk) {
this.pk = pk;
}
public void setName(String name) {
this.name = name;
}
}
- 주어진 Bean을 Dozer와 맵핑하면 3개의 필드가 모두 맵핑됩니다.
- "name"속성은 명명 규칙에 따라 자동적으로 매핑됩니다.
- 속성 "id"는 "pk"로 변환됩니다. "data"필드는 "binaryData"로 이동합니다.
- Private -> Public 접근 지정자는 자동으로 처리.
즉, Source Model에서 Target Model로 변환할 때 Target Model에 필드 이름이 일치하는 경우 별도로 매핑할 필요는 없고, 일치하지 않을 경우에 어노테이션으로 매핑을 하면 된다.
4. 기본적인 Dozer 사용 방법
Bean 모델을 생성한다.
//Source Model
public class Source {
private String name;
private int age;
public Source() {}
public Source(String name, int age) {
this.name = name;
this.age = age;
}
// standard getters and setters
}
//Desination Model
public class Dest {
private String name;
private int age;
public Dest() {}
public Dest(String name, int age) {
this.name = name;
this.age = age;
}
// standard getters and setters
}
DozerBeanMapper를 전역으로 생성하고 초기화 한다.
DozerBeanMapper mapper;
@Before
public void before() throws Exception {
mapper = new DozerBeanMapper();
}
후에 초기화된 DozerBeanMapper를 사용해서 Dest 클래스에 Source 클래스를 매핑시킨다.
public void givenSourceObjectAndDestClass_whenMapsSameNameFieldsCorrectly_
thenCorrect() {
Source source = new Source("Baeldung", 10);
Dest dest = mapper.map(source, Dest.class);
}
5. Data Conversion
Dozer는 매핑 된 속성 중 하나가 다른 데이터 유형(long -> String, double -> int) 인 경우 Dozer 맵핑 엔진은 자동으로 데이터 유형 변환을 수행한다.
//Source Model
public class Source2 {
private String id;
private double points;
public Source2() {}
public Source2(String id, double points) {
this.id = id;
this.points = points;
}
// standard getters and setters
}
//Destination Model
public class Dest2 {
private int id;
private int points;
public Dest2() {}
public Dest2(int id, int points) {
super();
this.id = id;
this.points = points;
}
// standard getters and setters
}
실제로 매퍼 객체를 이용해서 매핑을 하게 되면
public void givenSourceAndDestWithDifferentFieldTypes_
whenMapsAndAutoConverts_thenCorrect() {
Source2 source = new Source2("320", 15.2);
Dest2 dest = mapper.map(source, Dest2.class);
}
Double 타입인 15.2가 int 타입인 15로 변환된 것을 확인할 수 있다.
6. Annotation의 사용
Dozer에서는 기본적으로 xml 파일을 사용하지만 간단하고 직관적인 매핑일 경우 Annotation을 사용해 처리할 수 있다.
기본적인 get/set Method를 정의하고, Dozer에서는 Annotation을 사용해서 매핑할 때 Destination Model 측의 get/set Method위에 매핑 시킬 필드명을 작성해야 한다.
//Source Model
public class Person {
private String name;
private String nickname;
private int age;
public Person() {}
public Person(String name, String nickname, int age) {
super();
this.name = name;
this.nickname = nickname;
this.age = age;
}
// standard getters and setters
}
//Destination Model
public class Personne {
private String nom;
private String surnom;
private int age;
public Personne() {}
public Personne(String nom, String surnom, int age) {
super();
this.nom = nom;
this.surnom = surnom;
this.age = age;
}
@Mapping("name")
public String getNom() {
return nom;
}
@Mapping("nickname")
public String getSurnom() {
return surnom;
}
}
Source Model의 name을 Destination Model의 nom로, nickname을 surnom으로 매핑시키는 코드다.
public void givenAnnotatedSrcFields_whenMapsToRightDestField_thenCorrect() {
Person2 englishAppPerson = new Person2("Jean-Claude Van Damme", "JCVD", 55);
Personne2 frenchAppPerson = mapper.map(englishAppPerson, Personne2.class);
}
Dozer에서는 양방향 매핑을 지원한다. 즉, Source나 Destination이나 한쪽에서 매핑을 정의했으면 반대로 클래스를 초기화 하더라도 자동적으로 매핑이 된다는 이야기다.
public void givenAnnotatedSrcFields_whenMapsToRightDestFieldBidirectionally_
thenCorrect() {
Personne2 frenchAppPerson = new Personne2("Jason Statham", "transporter", 49);
Person2 englishAppPerson = mapper.map(frenchAppPerson, Person2.class);
}
결과는 동일하다.
7. Custom Converters
Dozer에서도 String 타입을 long 타입으로 변환할 때, 특정한 알고리즘으로 변환해야하는 경우에 사용자 정의 변환(Custom Converters)를 지원하는 기능이 있다.
Long 또는 Unix Time 데이터인 1182882159000이 있다고 가정했을 때, String 타입의 iso형식으로 2007-06-26T21:22:39Z처럼 변환이 필요한 경우가 있다.
먼저 객체를 Source와 Destination Model을 만들어본다.
//Source Model
public class Personne3 {
private String name;
private long dtob;
public Personne3(String name, long dtob) {
super();
this.name = name;
this.dtob = dtob;
}
// standard getters and setters
}
//Destination Model
public class Person3 {
private String name;
private String dtob;
public Person3(String name, String dtob) {
super();
this.name = name;
this.dtob = dtob;
}
// standard getters and setters
}
현재 dtob의 data type이 long과 String인 것을 확인해 볼 수 있다.
다음으로 사용자 정의 변환을 위한 Converter 클래스를 생성한다.
public class MyCustomConvertor implements CustomConverter {
@Override
public Object convert(Object dest, Object source, Class<?> arg2, Class<?> arg3) {
if (source == null)
return null;
if (source instanceof Personne3) {
Personne3 person = (Personne3) source;
Date date = new Date(person.getDtob());
DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
String isoDate = format.format(date);
return new Person3(person.getName(), isoDate);
} else if (source instanceof Person3) {
Person3 person = (Person3) source;
DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
Date date = format.parse(person.getDtob());
long timestamp = date.getTime();
return new Personne3(person.getName(), timestamp);
}
}
}
Dozer는 양방향성을 지원하기 때문에 Personne3 < - > Person3가 될 수 있도록 Object를 instanceof로 조회해서 각 클래스에 따른 데이터 처리를 하는 코드가 작성되었다.
추가적으로 2개의 클래스가 Converter를 사용할 것이라는 명시를 하기 위해 xml파일을 작성해야 한다.
dozer_custom_convertor.xml
<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozer.sourceforge.net"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://dozer.sourceforge.net
http://dozer.sourceforge.net/schema/beanmapping.xsd">
<configuration>
<custom-converters>
<converter type="com.baeldung.dozer.MyCustomConvertor">
<class-a>com.baeldung.dozer.Personne3</class-a>
<class-b>com.baeldung.dozer.Person3</class-b>
<class-b>com.baeldung.dozer.Person3</class-b>
</converter>
</custom-converters>
</configuration>
</mappings>
그 다음 실제로 매핑이 잘 되는지 코드를 실행시켜 본다.
// Mapper에 xml파일 등록하는 함수
public void configureMapper(String... mappingFileUrls) {
mapper.setMappingFiles(Arrays.asList(mappingFileUrls));
}
//Person3 기준
public void givenSrcAndDestWithDifferentFieldTypes_whenAbleToCustomConvert_
thenCorrect() {
configureMapper("dozer_custom_convertor.xml");
String dateTime = "2007-06-26T21:22:39Z";
long timestamp = new Long("1182882159000");
Person3 person = new Person3("Rich", dateTime);
Personne3 person0 = mapper.map(person, Personne3.class);
}
//Personne3 기준
public void givenSrcAndDestWithDifferentFieldTypes_
whenAbleToCustomConvertBidirectionally_thenCorrect() {
configureMapper("dozer_custom_convertor.xml");
String dateTime = "2007-06-26T21:22:39Z";
long timestamp = new Long("1182882159000");
Personne3 person = new Personne3("Rich", timestamp);
Person3 person0 = mapper.map(person, Person3.class);
}
실행결과
8. XML을 사용하는 경우
Dozer는 양방향 매핑을 기본적으로 지원한다. 먼저 오브젝트 클래스 2개를 생성하자.
public class Person {
private String name;
private String nickname;
private int age;
public Person() {}
public Person(String name, String nickname, int age) {
super();
this.name = name;
this.nickname = nickname;
this.age = age;
}
// standard getters and setters
}
public class Personne {
private String nom;
private String surnom;
private int age;
public Personne() {}
public Personne(String nom, String surnom, int age) {
super();
this.nom = nom;
this.surnom = surnom;
this.age = age;
}
// standard getters and setters
}
그리고 매핑을 선언할 xml파일을 생성한다.
dozer_mapping.xml
<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozer.sourceforge.net"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://dozer.sourceforge.net
http://dozer.sourceforge.net/schema/beanmapping.xsd">
<mapping>
<class-a>com.baeldung.dozer.Personne</class-a>
<class-b>com.baeldung.dozer.Person</class-b>
<field>
<a>nom</a>
<b>name</b>
</field>
<field>
<a>surnom</a>
<b>nickname</b>
</field>
</mapping>
</mappings>
xml파일을 해석하자면 클래스 a의 필드 nom을 name과 매핑, 클래스 b의 필드 surnom을 nickname과 매핑한다는 의미다.
추가적으로 필드 이름이 같을 경우엔 매핑 선언을 할 필요가 없다.
사용자 정의 XML 파일을 src 폴더 바로 아래의 클래스 경로에 배치한다.
public void configureMapper(String... mappingFileUrls) {
mapper.setMappingFiles(Arrays.asList(mappingFileUrls));
}
전역으로 선언한 mapper에 xml파일을 등록하고,
코드를 실행시켜보면 매핑이 잘 되는 것을 확인할 수 있다.
public void givenSrcAndDestWithDifferentFieldNamesWithCustomMapper_
whenMaps_thenCorrect() {
configureMapper("dozer_mapping.xml");
Personne frenchAppPerson = new Personne("Sylvester Stallone", "Rambo", 70);
Person englishAppPerson = mapper.map(frenchAppPerson, Person.class);
}
9. Wildcard Mapper
와일드 카드 매퍼는 기본값이 true로, xml에 등록된 오브젝트, 즉 Source로 등록한 클래스의 모든 필드를 자동적으로 매핑한다는 것이다. 모든 필드를 매핑할 필요 없이 명시적으로 2개의 필드만 매핑을 할 경우엔 false값으로 설정하고 xml을 작성한다.
새로운 xml 파일을 생성한다.
dozer_mapping2.xml
<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozer.sourceforge.net"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://dozer.sourceforge.net
http://dozer.sourceforge.net/schema/beanmapping.xsd">
<mapping wildcard="false">
<class-a>com.baeldung.dozer.Personne</class-a>
<class-b>com.baeldung.dozer.Person</class-b>
<field>
<a>nom</a>
<b>name</b>
</field>
<field>
<a>surnom</a>
<b>nickname</b>
</field>
</mapping>
</mappings>
public void givenSrcAndDest_whenMapsOnlySpecifiedFields_thenCorrect() {
configureMapper("dozer_mapping2.xml");
Person englishAppPerson = new Person("Shawn Corey Carter","Jay Z", 46);
Personne frenchAppPerson = mapper.map(englishAppPerson, Personne.class);
}
코드를 실행시켜보면 Person과 Personne의 필드인 age가 매핑되지 않고 값이 0이 된 것을 확인할 수 있다.
'Java > JMapper' 카테고리의 다른 글
[Java] JMapper .jar 파일 다운로드 방법 (0) | 2020.05.20 |
---|---|
[Java] JMapper 사용 방법 (0) | 2020.03.23 |