자바 객체(Object)를 타임리프에서 변수로 활용할 때 문제점

KIM YONG GU·2023년 11월 7일
0

JAVA Knowledge

목록 보기
37/40
post-custom-banner

키워드 : 스프링부트(springboot), JSON 데이터바인드(jackson-databind), 직렬화(serialize), 타임리프(thymeleaf), 자바스크립트(javascript), XML

@PostMapping("/search")
  public String targetSearch(Model model, @RequestParam Map<String, String> requestParams) {

    String pname1 = requestParams.get("pname1");
    String paddress1 = requestParams.get("paddress1");
    Double latclick1 = Double.parseDouble(requestParams.get("latclick1"));
    Double lngclick1 = Double.parseDouble(requestParams.get("lngclick1"));
    Integer radius = Integer.parseInt(requestParams.get("radius"));

    List<Target> searchresult = this.targetService.searchTarget(latclick1, lngclick1, radius);

    model.addAttribute("searchresult", searchresult);

    return "search";
}

일반적으로 model.addAttribute는 타임리프 문법을 이용해 ${searchresult} 형태로 아래와 같이 HTML단에서 JAVA의 객체를 다루는데 사용된다.

 <table>
     <h4> 반경 이내의 장소입니다. </h4>
     <thead>
     <tr>
       <th>장소명</th>
       <th>주소</th>
       <th>위도</th>
       <th>경도</th>
     </tr>
     </thead>
     <tbody>
     <tr th:each="searchresult : ${searchresult}">
       <td th:text="${searchresult.locationName}"></td>
       <td th:text="${searchresult.locationAddress}"></td>
       <td th:text="${searchresult.locationLat}"></td>
     <td th:text="${searchresult.locationLng}"></td>
     </tr>
	</tbody>
</table>

또한 이를 자바스트립트(javascript)에서 사용하는 예시는 다음과 같다.

var targetData = [[${searchresult}]]

var는 모든 데이터형을 받을 수 있기 때문에 searchresult가 여러개의 데이터를 가진 객체라도 무리없이 받을수 있고. searchresult가 가지고 있는 데이터를

targetData.locationName
targetData.locationAddress
targetData.locationLat
targetData.locationLng

의 형태로 사용할 수 있다.

그러나 seachresult의 데이터형을 정의하는 클래스의 엔티티 설계에서 정규화를 위해 테이블을 분리했을 때, FK로 정의된 각 테이블이 서로를 참조하면 문제가 발생하게 된다.

먼저 searchresult의 데이터형을 정의하는 Target 클래스의 엔티티 설계 부분을 살펴보자.

package com.example.greenwalker.Target;

import com.example.greenwalker.Member.Member;

import com.example.greenwalker.TargetInfo.TargetInfo;
import com.example.greenwalker.TargetStatus.TargetStatus;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Entity
public class Target {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private String locationCategory;

  private String locationName;

  private String locationAddress;

  private Double locationLat;

  private Double locationLng;

  @ManyToOne
  private Member member;

  @OneToOne(mappedBy = "target", cascade = CascadeType.REMOVE)
  private TargetInfo targetinfo;

  @OneToOne(mappedBy = "target", cascade = CascadeType.REMOVE)
  private TargetStatus targetstatus;
}

다음은 Target과 서로를 참조하면 Member의 엔티티 설계 부분이다.

package com.example.greenwalker.Member;

import java.util.List;

import com.example.greenwalker.MemberAvatar.MemberAvatar;
import com.example.greenwalker.MemberInfo.MemberInfo;
import com.example.greenwalker.Target.Target;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Entity
public class Member {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(unique = true)
  private String username;

  private String password;

  @Column(unique = true)
  private String email;

  @OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE)
  private List<Target> targetlist;

  @OneToOne(mappedBy = "member", cascade = CascadeType.REMOVE)
  private MemberInfo memberinfo;

  @OneToOne(mappedBy = "member", cascade = CascadeType.REMOVE)
  private MemberAvatar memberavatar;
}

위의 코드와 같이 @ManyToOne, @OneToMany 애노테이션을 이용해 서로를 참조하여 DB가 묶여있다. 정확한 원인은 알 수 없지만 저렇게 두 클래스가 묶여있는 상태에서 서로를 참조하기 때문에 JavaScript 에서 타임리프 문법을 이용해 searchresult 객체를 사용하려 하면 직렬화 및 JSON Databind 에러가 발생한다.

이를 해결하기 위해서는 아래와 같이 타임리프 문법을 자바스크립트에서 구현하여 th:each 태그를 이용해 배열의 자료를 순차적으로 가져와서 사용해야 한다.

 /*[# th:each="searchResult : ${searchresult}"]*/
 
 var a = [[${searchResult.locationName}]];
 var b = [[${searchResult.locationAddress}]];
 var c = [[${searchResult.locationLat}]];
 var d = [[${searchResult.locationLng}]];
 
 /*[/]*/ 

만약 본래의 타임리프 문법을 최소화한 방법을 이용해 사용하려면 (아래와 같이)

var targetData = [[${searchresult}]]

Target 클래스의 @Controller 단에서 서로를 참조해 순환오류가 발생하는 FK 엔티티를 제외한채 필요한 데이터만 담는 별도의 배열을 생성하여 이용하면 오류가 발생하지 않을 것으로 추정된다. 실제로 엔티티를 서로 참조하기 전에는 해당 문법을 그대로 이용해도 오류가 발생하지 않았다.

profile
Engineer, Look Beyond the Code.
post-custom-banner

0개의 댓글