본문 바로가기
Spring/JPA + Security

[Spring Data JPA] 게시판 띄우는 로직 만들기 - 1 양방향 맵핑 및 이미지 뷰 렌더링

by pyogowoon 2023. 1. 10.

 

 

 

나는 현재 DB에 있는 user , image 를 동시에 띄울 수 있는 프로필화면을 구현하고자 한다.

localhost:1234/user/1 하면 1번유저의 화면,      localhost:1234/user/2 하면 2번 유저의 화면을 보고자한다.

위의 사진은 DB에서 가져온 것이 아닌 default 값으로 설정 해놓은것이다.

 

@RequiredArgsConstructor
@Controller
public class UserController{

   private final UserService userService;


    @GetMapping("user/{id}")
    public String profile(@PathVariable int id, Model model){
        User userEntity = userService.회원프로필(id);

        model.addAttribute("user", userEntity);


        return "user/profile";
    }

 

 

@RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepository userRepository;

    private final BCryptPasswordEncoder bCryptPasswordEncoder;


  
    public User 회원프로필(int userId){
       User userEntity = userRepository.findById(userId).orElseThrow(() ->{
        throw new CustomException("해당 유저 프로필 페이지는 찾을 수 없습니다.");
       });

       return userEntity;

    }
}

 

여기까진 User를 가지고 왔다, 근데 Image를 어떻게 받아올까? User를 받지않고 Image만 받아왔다면 

User 정보는 어디서 가져오며 만약 Image와 User를 동시에 받아오면 id랑은 또 어떻게 연결해야하고 매우 번거롭다.

 

 

그렇기 때문에 우리는 양뱡향 매핑 을 사용하면 해결된다. 

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
public class User {

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

    @Column(unique = true, length = 20)
    private String username;

    @Column(nullable = false)
    private String password;

    private String name;
    private String website;  //웹사이트
    private String bio; // 자기소개

    @Column(nullable = false)
    private String email;
    private String phone;
    private String gender;

    private String profileImageUrl;
    private String role;

    private LocalDateTime createDate;

    @JsonIgnore
    @OneToMany(mappedBy ="user", fetch= FetchType.LAZY)  //user는 Image의 변수를 넣어야함 //나는 연관관계의 주인이 아니다 를 알리는것.
    //이걸 함으로써 DB가 컬럼을 만들지않으며 select 할때 user Id로 등록된 image들을 다 가져옴
    // Lazy = User를 select할떄 해당 User id로 등록된 image들을 가져오지마 - 대신 getImages() 메소드가 호출될 때 가져와
    // Eager = User를 select할때 해당 user Id로 등록된 image들을 전부 join해서 가져와!
    private List<Image> images; // 양방향 매핑    /   DB는 여러개못들어감 (리스트가 뭔지모름)

    @PrePersist  // DB에 INSERT 되기 직전에 실행
    public void createDate(){
        this.createDate = LocalDateTime.now();
    }

}

 

내가 원하는 프로필을 보자면, 

User 의 DB , Subscribe의 DB, Image의 DB 3가지 모두 필요하고, Image의 경우는 User에서 바로 가져올 방법이 없기 때문이다. 그렇기때문에

 private List<Image> images; 를 추가하고 이를 OneToMany 로 연관관계 설정 후 mappedBy ="user" 를 통해 연관관계의 주인이 image가 아님을 알려줘서 DB가 List images 컬럼을 생성하지 못하게 한다. (DB는 원자성의 원리에 따라 하나의 컬럼에 여러개를 못넣기 떄문에 List가 뭔지모른다.) 또한

 

fetch를 Lazy타입으로 설정함으로서 getImage가 된 메소드만 호출하도록 설정. ★★ 아주 중요

 

LAZY

 getImages() 메소드의 image가 호출되면 그때 셀렉트 해서 가져온다

 

EAGER

User를 Select 할때 해당 userId 로 등록된 Image를 전부 조인해서 가져온다

 

@JsonIgnore - JSON의 무한참조를 막는다 양방향 맵핑시에는 꼭 적어주자. 자세한 이유는 찾아보자

 

 

@RequiredArgsConstructor
@Controller
public class UserController {

    private final UserService userService;

    @GetMapping("/user/{id}")
    public String profile(@PathVariable int id, Model model){
        User userEntity = userService.회원프로필(id);
        model.addAttribute("user",userEntity);
        return "user/profile";
    }

Service에서 받아온 값을 user 세션에 담아서 JSP로 보낸다.

 

         <!--아이템들-->

       <c:forEach var="image" items="${user.images}"> <!--EL표현식에서 변수명을 적으면 get함수가 자동호출 -->

<div class="img-box">
            <a href=""> <img src="${image.postImageUrl}"/>
            </a>
            <div class="comment">
               <a href="#" class=""> <i class="fas fa-heart"></i><span>0</span>
               </a>
            </div>
         </div>

       </c:forEach>


         <!--아이템들end-->

이런식으로 평소처럼 EL표현식 사용하여 데이터를 가져오면, img src에 문제가 있어 엑박이뜨는걸 확인할 수 있다.

 

이는 DB에 저장된 경로와 실제 경로가 다르기 때문인데(실제 경로는 yml에 내장된 경로  + DB경로 이므로)

 

그렇기 떄문에 이를 설정해줘야 하는데, 

 

Config 패키지에 WebMvcConfig 클래스를 만든다.

 

*Import 조심! 같은이름 가진거 있음

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.resource.PathResourceResolver;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer { //web 설정파일


    @Value("${file.path}")
    private String uploadFolder;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        WebMvcConfigurer.super.addResourceHandlers(registry);



        registry
                .addResourceHandler("/upload/**")
    //jsp 페이지에서 /upload/** 이런 주소 패턴이 나오면 발동
                .addResourceLocations("file:///" + uploadFolder)
        //     file:///C:/imagesaver/springbootwork/upload/ 이렇게 적힌다는소리
        .setCachePeriod(60*10*6) // 1시간동안 캐싱
                .resourceChain(true)
                .addResolver(new PathResourceResolver()); // 붙여줘야 발동
    }
}

 

해당 설정을 해준 뒤

 

       <!--아이템들-->

       <c:forEach var="image" items="${user.images}"> <!--EL표현식에서 변수명을 적으면 get함수가 자동호출 -->

<div class="img-box">
            <a href=""> <img src="/upload/${image.postImageUrl}"/>
            </a>
            <div class="comment">
               <a href="#" class=""> <i class="fas fa-heart"></i><span>0</span>
               </a>
            </div>
         </div>

       </c:forEach>


         <!--아이템들end-->

 

img src의 경로를 수정하면 완성.

 

Q. LAZY 전략은 get 호출해야한다면서요

 

A. ${} 인 EL표현식은 get함수가 자동으로 호출되기 때문 ( 그냥 문법임, 편하게 쓰려고 만든 문법 get 자동으로 호출해줌)

 

 

 

<!--유저정보 및 사진등록 구독하기-->
<div class="profile-right">
   <div class="name-group">
      <h2>${user.username}</h2>

      <button class="cta" onclick="location.href='/image/upload'">사진등록</button>
      <button class="cta" onclick="toggleSubscribe(this)">구독하기</button>
      <button class="modi" onclick="popup('.modal-info')">
         <i class="fas fa-cog"></i>
      </button>
   </div>

   <div class="subscribe">
      <ul>
         <li><a href=""> 게시물<span>3</span>
         </a></li>
         <li><a href="javascript:subscribeInfoModalOpen();"> 구독정보<span>2</span>
         </a></li>
      </ul>
   </div>
   <div class="state">
      <h4>${user.bio}</h4>
      <h4>${user.website}</h4>
   </div>
</div>

그 외에 EL태그를 사용하여 가져올 데이터들을 적어주면 끝. 

 

 

잘들어온다.

 

 

 

 

*출처 : TherePrograming(현 메타코딩)님의 인스타그램 클론코딩 유료강의

 자세한건 https://cafe.naver.com/metacoding 참고 정말 좋은 강의입니다.

댓글