ModelMapper
: 객체의 프로퍼티를 다른 객체의 프로퍼티로 맵핑해주는 유틸리티
의존성 추가
<!-- https://mvnrepository.com/artifact/org.modelmapper/modelmapper -->
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.6</version>
</dependency>
토크나이저 설정
modelMapper.getConfiguration()
.setSourceNameTokenizer(NameTokenizers.UNDERSCORE)
.setDestinationNameTokenizer(NameTokenizers.UNDERSCORE)
:UNDERSCORE(_)를 사용했을 때에만 nested 객체를 참조하는 것으로 간주하고 그렇지 않은 경우에는 해당 객체의 직속 프로퍼티에 바인딩 한다.
AccountService updateProfile 메서드, updateNotifications 메서드
//프로필 변경 -> 이미지 변경시에도 사용하므로, profileImage 추가
public void updateProfile(Account account, Profile profile) {
// TODO Auto-generated method stub
account.setUrl(profile.getUrl());
account.setOccupation(profile.getOccupation());
account.setLocation(profile.getLocation());
account.setBio(profile.getBio());
account.setProfileImage(profile.getProfileImage());
//트랜잭션이 끝난 detached상태이기 때문에 직접 DB에 반영을 해준다.
//즉 id값이 있으면 merge를 수행한다.
accountRepository.save(account);
}
//form객체인 notifications에서 가져와서 account에 변경
public void updateNotifications(Account account, @Valid Notifications notifications) {
// TODO Auto-generated method stub
account.setStudyCreatedByEmail(notifications.isStudyCreatedByEmail());
account.setStudyCreatedByWeb(notifications.isStudyCreatedByWeb());
account.setStudyEnrollmentResultByEmail(notifications.isStudyEnrollmentResultByEmail());
account.setStudyEnrollmentResultByWeb(notifications.isStudyEnrollmentResultByWeb());
account.setStudyUpdatedByEmail(notifications.isStudyUpdatedByEmail());
account.setStudyUpdatedByWeb(notifications.isStudyUpdatedByWeb());
accountRepository.save(account);
}
Profile 생성자
// 프로필 폼에 채울 객체에 정보 넣기 -> ModelMapper를 이용해 생성
public Profile(Account account) {
this.bio = account.getBio();
this.url = account.getUrl();
this.occupation = account.getOccupation();
this.location = account.getLocation();
this.profileImage = account.getProfileImage();
}
Notifications 생성자
//알림 메서드 -> 해당 계정에서 정보를 가져온다.
public Notifications(Account account) {
this.studyCreatedByEmail=account.isStudyCreatedByEmail();
this.studyCreatedByWeb=account.isStudyCreatedByWeb();
this.studyEnrollmentResultByEmail=account.isStudyEnrollmentResultByEmail();
this.studyEnrollmentResultByWeb=account.isStudyEnrollmentResultByWeb();
this.studyUpdatedByEmail=account.isStudyUpdatedByEmail();
this.studyUpdatedByWeb=account.isStudyUpdatedByWeb();
}
객체 안의 객체 또한 지원을 하기 때문에 정확한 경로를 지정해줘야한다.
-> 패턴을 지정해줘야한다.
기본값 : 유사한 변수에 매핑을 시킨다.
-> 유사한 변수가 존재한다면, 사용자가 원하는 패턴을 직접 설정해줘야 한다.
AppConfig에 사용할 모델맵퍼에 대한, 빈을 등록시킨다.
//모델맵퍼 사용을 위한 빈 등록
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
AccountService의 updateProfile메서드
//프로필 변경 -> 이미지 변경시에도 사용하므로, profileImage 추가
public void updateProfile(Account account, Profile profile) {
// TODO Auto-generated method stub
//modelMapper의 map이라는 메서드를 이용해, 모델맵퍼 이용
//원본 위치의 데이터를 원하는 객체로 복사
//프로퍼티에 있는 값을 어카운트에 담기 -> (profile에서 account로 복사)
modelMapper.map(profile, account);
//트랜잭션이 끝난 detached상태이기 때문에 직접 DB에 반영을 해준다.
//즉 id값이 있으면 merge를 수행한다.
accountRepository.save(account);
}
-> 해당 경우의 경우에는 문제가 발생하지않는다.
public class Profile {
@Length(max = 35)
private String bio;
@Length(max = 50)
private String url;
@Length(max = 50)
private String occupation;
@Length(max = 50)
private String location;
// 이미지
private String profileImage;
// @NoArgsConstructor를 대신해 사용한다면
// public Profile(){};
public class Account {
// 기본키와 생성 전략
@Id @GeneratedValue
private Long id;
//로그인 방식에서 이메일과 닉네임을 이용한 방식 지원하기 위해
@Column(unique =true) //중복 방지
private String email;
@Column(unique =true)
private String nickname;
private String password;
//이메일 인증 관련 참거짓판단
private boolean emailVerified;
//이메일 검증 토큰 값
private String emailCheckToken;
//회원가입날짜 변수
private LocalDateTime joinedAt;
private String bio;
private String url;
private String occupation;
private String location;
//이미지파일은 varchar 데이터형식보다 크기가 커서 사용하는 어노테이션
//로딩 시간을 설정하는 어노테이션 즉시 로딩과 지연 로딩이 존재
//즉시 로딩 EAGER, 지연 로딩 LAZY
@Lob @Basic(fetch = FetchType.EAGER)
private String profileImage;
//생성, 가입, 갱신정보 알림 설정 -> Email, Web, 둘다
private boolean studyCreatedByEmail;
private boolean studyCreatedByWeb=true;
private boolean studyEnrollmentResultByEmail;
private boolean studyEnrollmentResultByWeb=true;
private boolean studyUpdatedByEmail;
private boolean studyUpdatedByWeb=true;
//1시간 이내인지 확인하기 위해 전송시간 담는 변수
private LocalDateTime emailCheckTokenGeneratedAt;
하지만, notification의 경우 modelmapper의 기본 매칭 설정에 의해 모호한 연관관계는 매핑이 되지 않기때문에 실패한다.
//form객체인 notifications에서 가져와서 account에 변경
public void updateNotifications(Account account, @Valid Notifications notifications) {
// TODO Auto-generated method stub
//modelMapper의 map이라는 메서드를 이용해, 모델맵퍼 이용
//원본 위치의 데이터를 원하는 객체로 복사
//프로퍼티에 있는 값을 어카운트에 담기 -> (notifications에서 account로 복사)
modelMapper.map(notifications, account);
accountRepository.save(account);
}
public class Notifications {
private boolean studyCreatedByEmail;
private boolean studyCreatedByWeb;
private boolean studyEnrollmentResultByEmail;
private boolean studyEnrollmentResultByWeb;
private boolean studyUpdatedByEmail;
private boolean studyUpdatedByWeb;
public class Account {
// 기본키와 생성 전략
@Id @GeneratedValue
private Long id;
//로그인 방식에서 이메일과 닉네임을 이용한 방식 지원하기 위해
@Column(unique =true) //중복 방지
private String email;
@Column(unique =true)
private String nickname;
private String password;
//이메일 인증 관련 참거짓판단
private boolean emailVerified;
//이메일 검증 토큰 값
private String emailCheckToken;
//회원가입날짜 변수
private LocalDateTime joinedAt;
private String bio;
private String url;
private String occupation;
private String location;
//이미지파일은 varchar 데이터형식보다 크기가 커서 사용하는 어노테이션
//로딩 시간을 설정하는 어노테이션 즉시 로딩과 지연 로딩이 존재
//즉시 로딩 EAGER, 지연 로딩 LAZY
@Lob @Basic(fetch = FetchType.EAGER)
private String profileImage;
//생성, 가입, 갱신정보 알림 설정 -> Email, Web, 둘다
private boolean studyCreatedByEmail;
private boolean studyCreatedByWeb=true;
private boolean studyEnrollmentResultByEmail;
private boolean studyEnrollmentResultByWeb=true;
private boolean studyUpdatedByEmail;
private boolean studyUpdatedByWeb=true;
//1시간 이내인지 확인하기 위해 전송시간 담는 변수
private LocalDateTime emailCheckTokenGeneratedAt;
패턴 지정을 위한 모델맵퍼 설정 변경
//모델맵퍼 사용을 위한 빈 등록
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setDestinationNameTokenizer(NameTokenizers.UNDERSCORE)
.setSourceNameTokenizer(NameTokenizers.UNDERSCORE);
//UNDERSCORE가 아니면 하나의 프로퍼티로 간주
return modelMapper;
}
UNDERSCORE(_)가 아니라면 그 이름 전체를 하나의 프로퍼티 이름으로
단, nested 한 표현을 하고 싶다면 configuration에 설정해둔대로 UNDERSCORE를 이용하면 된다.
ModelMapper는 매핑 설정을 통해서 필드명이나 구조가 일치하지 않더라도 TypeMap을 이용하여 매핑시킬 수 있다.
#ModelMapper typeMap 설정
클래스 타입이 같은 경우
modelMapper.typeMap(Item.class, Bill.class).addMappings(mapper -> {
mapper.map(Item::getStock, Bill::setQty);
mapper.map(Item::getPrice, Bill::setSinglePrice);
});
Bill bill2 = modelMapper.map(itemA, Bill.class);
클래스 타입이 다른 경우
modelMapper.typeMap(Item.class, Bill.class).addMappings(mapper -> {
mapper.map(Item::getStock, Bill::setQty);
mapper.map(Item::getPrice, Bill::setSinglePrice);
mapper.using((Converter<Boolean, Double>) context -> context.getSource() ? 20.0 : 0.0)
.map(Item::isSale, Bill::setDiscount);
});
Bill bill2 = modelMapper.map(request, Bill.class);
맵퍼 클래스의 메서드인 map, using, skip을 이용해 원하는 매핑 구조 설정할 수 있다.
Profile 생성자와 Notifications 생성자 모델맵퍼 적용 -> 해당 생성자를 지우고 SettingsController에서 직접 매핑
// 프로필 폼에 채울 객체에 정보 넣기 -> ModelMapper를 이용해 생성
public Profile(Account account) {
this.bio = account.getBio();
this.url = account.getUrl();
this.occupation = account.getOccupation();
this.location = account.getLocation();
this.profileImage = account.getProfileImage();
}
//알림 메서드 -> 해당 계정에서 정보를 가져온다.
public Notifications(Account account) {
this.studyCreatedByEmail=account.isStudyCreatedByEmail();
this.studyCreatedByWeb=account.isStudyCreatedByWeb();
this.studyEnrollmentResultByEmail=account.isStudyEnrollmentResultByEmail();
this.studyEnrollmentResultByWeb=account.isStudyEnrollmentResultByWeb();
this.studyUpdatedByEmail=account.isStudyUpdatedByEmail();
this.studyUpdatedByWeb=account.isStudyUpdatedByWeb();
}
SettingsController
//Profile과 Notifications 생성자를 modelmapper를 이용해서 매핑
private final ModelMapper modelMapper;
기존의 profileUpdateForm, updateNotificationsForm 메서드
//프로필 설정
// Get으로 전달하면, 업데이트 폼 리턴
@GetMapping(SETTINGS_PROFILE_URL)
// 자신의 정보만 수정가능하므로, 현재 자기 자신 정보와 모델정보를 담을 인스턴스를 파라미터로
public String profileUpdateForm(@CurrentUser Account account, Model model) {
model.addAttribute(account);
// 화면에 계정정보를 넣어주고
model.addAttribute(new Profile(account));
// 폼에 사용할 객체를 만들어서 정보를 넣어준다. -> 계정정보를 조회해 채운다.
return SETTINGS_PROFILE_VIEW_NAME;
}
// return은 void로 메서드를 생성하면 생략가능 한데, 뷰네임트랜스레이터가 url네임과 같다고 추측
//알림 설정
@GetMapping(SETTINGS_NOTIFICATIONS_URL)
public String updateNotificationsForm(@CurrentUser Account account, Model model) {
//모델에 현재유저의 정보를 담는다.
model.addAttribute(account);
//또한, form을 채울 객체 생성
model.addAttribute(new Notifications(account));
return SETTINGS_NOTIFICATIONS_VIEW_NAME;
}
변경 후
//프로필 설정
// Get으로 전달하면, 업데이트 폼 리턴
@GetMapping(SETTINGS_PROFILE_URL)
// 자신의 정보만 수정가능하므로, 현재 자기 자신 정보와 모델정보를 담을 인스턴스를 파라미터로
public String profileUpdateForm(@CurrentUser Account account, Model model) {
model.addAttribute(account);
// 화면에 계정정보를 넣어주고
model.addAttribute(modelMapper.map(account,Profile.class));
// 폼에 사용할 객체를 만들어서 정보를 넣어준다. -> 계정정보를 조회해 채운다.
//Profile.class를 사용하면, 프로필 타입의 인스턴스 생성되고, account의 데이터로 채워진다.
return SETTINGS_PROFILE_VIEW_NAME;
}
// return은 void로 메서드를 생성하면 생략가능 한데, 뷰네임트랜스레이터가 url네임과 같다고 추측
//알림 설정
@GetMapping(SETTINGS_NOTIFICATIONS_URL)
public String updateNotificationsForm(@CurrentUser Account account, Model model) {
//모델에 현재유저의 정보를 담는다.
model.addAttribute(account);
//또한, form을 채울 객체 생성
model.addAttribute(modelMapper.map(account,Notifications.class));
// 폼에 사용할 객체를 만들어서 정보를 넣어준다. -> 계정정보를 조회해 채운다.
//Notificatoins.class를 사용하면, 프로필 타입의 인스턴스 생성되고, account의 데이터로 채워진다.
return SETTINGS_NOTIFICATIONS_VIEW_NAME;
}
'Server Programming > Spring Boot Full-Stack Programming' 카테고리의 다른 글
[스프링 풀스택 클론 코딩 - 계정 설정] (2-9) 닉네임 변경 (0) | 2022.09.05 |
---|---|
인텔리J 단축키 (0) | 2022.09.05 |
[스프링 풀스택 클론 코딩 - 계정 설정] (2-7) 알림 설정 (0) | 2022.09.05 |
[스프링 풀스택 클론 코딩 - 계정 설정] (2-5) 패스워드 수정 (0) | 2022.09.02 |
[스프링 풀스택 클론 코딩 - 계정 설정] (2-4) 프로필 이미지 변경 (0) | 2022.09.02 |