역직렬화할 수 없는 객체 데이터가 존재한다는 것인데. 나 같은 경우에는 User 정보에서 Pw값을 넣어주지 않으려고 ResponseUser에서 Pw라는 변수를 필드에 선언하지 않았더니 다음과 같은 에러가 나왔다.
해당 에러는 @JsonIgnoreProperties(ignoreUnknown = true)
를 통해 해결할 수 있는데 이 외에도
@JsonIgnoreProperties({"pw", "기타"})
선언한 필드 요소 제외
@JsonIgnoreProperties(ignoreUnknown = true)
선언한 필드 이외 모든 요소 제외
두가지 방법으로 사용할 수 있다.
java.time.LocalDateTime
not supported by default: add Module해당 내용은 ObjectMapper가 LocalDateTime의 직렬화, 역직렬화를 못하기 때문에 나오는 에러 문구인데 에러 문구를 잘 보면 답이 다 나와있다.
Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling...
jackson-datatype-jsr310 라이브러리를 설치하여 모듈을 추가해 달라고 하는데 그대로 해보자
<!-- jackson LocalDateTime 직렬화, 역직렬화 module 추가 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.1</version>
</dependency>
의존성을 추가해주고 만약 다른 version을 검색해보고 싶다면 👉여기에서 검색된 JSR310을 찾아보면 될거 같다.
private final JavaTimeModule javaTimeModule = new JavaTimeModule();
@Override
public ResponseAdminUser join() {
//테스트 데이터
AdminUser adminUser = AdminUser.builder()
.userId("test")
.userPw(passwordEncoder.encode("test"))
.build();
AdminUser saveUser = adminUserRepository.save(adminUser);
return objectMapper.registerModule(javaTimeModule).convertValue(saveUser, ResponseAdminUser.class);
}
실제 사용하는 코드는 다음과 같이 .registerModule(javaTimeModule)
로 모듈을 추가하여 사용하면 된다.
IllegalArgumentException: Infinite recursion
에러로 이유는 내가 User 테이블의 구조를 1:다 다:1
- 다:1 1:다
로 설정해 두어서 데이터를 가져올 경우 중간 데이터에서 자신을 또 호출하고 있어 무한루프에 빠진다.
밸덩 양방향 관계 해결방법의 내용을 보면
@JsonIdentityInfo
와 @JsonManagedReference
, @JsonBackReference
를 사용해서 해결할 수 있다.
@JsonBackReference(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id”)
property로 선언된 값을 구분값으로 지정하며 1개 이상의 중복 데이터가 존재할 경우 변경되지 않도록 한다.
@JsonManagedReference(value = "원하는 문자열")
value 이름으로 등록하며 해당 데이터를 참조하여 출력한다.
@JsonBackReference(value = "adminUser")
value 이름으로 등록하며 @JsonManagedReference에서 등록된 이름을 찾아 연결되며 해당 데이터를 참조하지 않고 출력하지 않는다.
해결한 내용을 코드로 보면
@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Table
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "adminUserIdx")
public class AdminUser{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) //mysql auto increment
@Column(name = "admin_user_idx")
private Long adminUserIdx;
...
@OneToMany(mappedBy = "adminUser", fetch = FetchType.EAGER) //default가 eager지만 더 표시해주려고 작성함
@JsonManagedReference(value = "adminUser")
private List<AdminUserRoleMapping> roles = new ArrayList<>();
}
@JsonManagedReference
를 사용하여 adminUser로 등록했다.
@Entity
@Getter
@Setter
@Table
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "idx")
public class AdminUserRoleMapping {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idx;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "admin_user_idx")
@JsonBackReference(value = "adminUser")
private AdminUser adminUser;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "admin_user_role_idx")
private AdminUserRole adminUserRole;
}
@JsonBackReference
를 통해 데이터를 참조하지 않도록 설정했다.
@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Table
public class AdminUserRole {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "admin_user_role_idx")
private Long adminUserAuthIdx;
private String role;
@OneToMany(mappedBy = "adminUserRole", fetch = FetchType.LAZY)
private List<AdminUserRoleMapping> adminUsers = new ArrayList<>();
}
이렇게 1:다 다:1
- 다:1 1:다
매핑된 테이블을 ObjectMapper로 파싱해봤다.
통합 테스트는 실제 Spring이 실행되는 환경과 동일한 환경에서 테스트를 진행하는 방식으로 Spring 컨테이너가 모두 동작하며 DI까지 해야하므로 시간이 오래 걸린다는 단점이 있다. 하지만 정말로 데이터가 잘 들어가고 해당 데이터를 다시 출력해보는 과정용으로는 통합 테스트가 좋은거 같다.
@SpringBootTest
class AdminUserServiceImplTest {
//
@Autowired
AdminUserService adminUserService;
...
@DisplayName("회원 조회")
@Test
void findAll(){
Iterable<ResponseAdminUser> users = adminUserService.findAll();
users.forEach(user -> {
System.out.println("user = " + user);
if(user.getRoles().size() != 0) System.out.println("user = " + user.getRoles().get(0).getAdminUserRole().getRole());
});
}
}
회원 조회 테스트를 다음과 같이 작성하고 given, when, then 형식으로 짜야하지만 아직 로직이 완성된 것도 아니고 테스트 용이므로 출력만 해보았다.
데이터도 잘 가져오고 ObjectMapper로 변환된 데이터로 잘 출력되는 것을 확인할 수 있었다.