Wanna be Brilliant Full-Stack Developer

2/14 SpringBoot Image 뷰 렌더링하기 본문

Back-End/Spring Boot

2/14 SpringBoot Image 뷰 렌더링하기

Flashpacker 2022. 2. 14. 18:34


목표

이제 userEntity 만 유저컨트롤러를 통해서 모델에 전달해서 모델에 userEntity를 전달하면 이userEntity 데이터가 jsp profile 페이지까지 들고가진다 

이제 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>

여기서 images는 양방형 매핑에 images이다. 

 

package com.cos.photogramstart.config;

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);
			
			//C:/workspace/springbootwork/upload/
			registry
			.addResourceHandler("/upload/**") // jsp 페이지에서 /upload/** 이런 주소패턴이 나오면 발동
			.addResourceLocations("file:///"+ uploadFolder)
			.setCachePeriod(60*10*6)
			.resourceChain(true)
			.addResolver(new PathResourceResolver());
		}
}

JSP 페이지에 /upload/** 가 있으면 

("file:///"+ uploadFolder) 이 발동된다.

위에 uploadFolder는 C:/workspace/springbootwork/upload/ 이다. 

 

.setCachePeriod(60*10*6) 이건 무슨뜻이냐면 60초 X 10 X 6은 1시간동안 이미지 캐시를 가지고있다.

이게 어떻게 발동하냐면 내가 profile jsp 가서 

<a href=""> <img src="/upload/${image.postImageUrl }" /> 이런 패턴이 발동되면 

 file:///C:/workspace/springbootwork/upload/ 주소로 바꿔준다 

 

데이터는 전송되었지만 새로 업로드를 하려고하니 오류가 나오니 다시한번 설정을 해보려고한다! - 47강


이미지를 등록할때 나온 오류를 잡아보려고한다 

잡아 보기 위해서는 개념 정리가 조금 필요하다! 

오류가 나온 이유는? 이미지 서비스에가서 사진 업로드가 될떄 System.out.println(imageEntity); 이렇게 뿌린것이 있다. 

이것을 주석처리하면 잘나온다? 그러면 애떄문에 오류가났다는건데 

오류가 나는 이유는 imageEntity 를 보면 Image를 Sysout하면 Getter들이 다 내부적으로 출력된다. 

 

@Data 어노테이션을 사용하면 toString이 자동으로 만들어진다

여기서문제가 머냐면 user를 출력할떄 문제가 된다. 

Image.java에서 

@Override
public String toString() {
return "Image [id=" + id + ", caption=" + caption + ", postImageUrl=" + postImageUrl 
+ ", createDate=" + createDate + "]"; 이렇게하면 그러면 오브젝트를 출력할떄 user를 호출안한다 

이렇게하면 업로드를 하여도 전혀 문제없이 넘어간다.

그래서 JPA 할때는 오브젝트를 출력하는 sysout을 조심해야하고 

또한 컨트롤러에서 리턴할때도 오브젝트를 하면 무한참조가 일어날수 있으니 조심해야한다! 

 

여기서 해결됬으니 끝이아니라 개념을 잡아야한다! 

 

요청이 들어오면 클라이언트가 들어온 요청에 대해서 디스패처가 받아서 어떤 컨트롤러를 선택할지 잡아서

컨트롤러를 찾으면 세션이 만들어진다. 

여기서 말하는 세션이란 DB에 접근할 수 있는 세션!  이떄부터 컨트롤러부터 셀렉트를 할 수 있다. 

서로 요청하여 영속성 컨텍스트에 데이터가 있으면 바로 응답하고 만약에 없으면 영속성 컨텍스트 안에 DB에 데이터를 요청해서 응답을 받고 다시 응답을 하는 과정!

레파지토리가 DB나 영속성 컨텍스트에 있는 데이터를 받고 응답을 할텐데 응답을 하면 서비스 가 응답된 비즈니스 처리를 하고 처리를 다된 결과를 컨트롤러에게 다시 돌려준다. 

컨트롤러는 그것을 받아서 데이터로 응답 해줄지, Html응답해줄지 결정을 하여 돌려준다! 

세션이 닫히는 타이밍이 스프링에서 언제냐면  서비스가 끝나는 시점에 닫힌다. 

세션이 종료가 되면 컨트롤러단에서 LAZY로딩이 불가능하다. 왜 불가능하냐면 세션이 종료되니까 지연된 로딩을 못한다. 

지연 로딩은 무엇인가? 유저라는 클래스가 있고 유저가 Id를 가지고 있고 username을 가지고 있고 images를 가지고 있다.이런경우에는  images는 다른 테이블건데 id와 username은 셀렉트해서 잘가지고 갔는데 images가 OneToMany or ManyToOne으로 잡혀있는지 FetchType.Lazy이면 초반에 유저를 셀렉트 할떄 영속성 컨텍스트에서 이미지를 안가져오고 User만 가져온다. 만약에 Eager이면 조인해서 바로 들고오니까 User와 Image를 함께 가져온다. 

우리는 LAZY를 Images로 잡았다. 

LAZY 로딩이니까 처음에는 DB에서 USer만 가져온다 들고와서 세션이 종료되니까 종료된 뒤에 LAZY로딩을 하려고하면 안된다. LAZY로딩한다는거는 images부분을 다시 셀렉트해서 재요청한다는건데 images를 다시 셀렉트하려고 DB에 물어보려고하는데 세션종료됬다고 못들어가니 LAZY로딩이 불가능하다. 

 

만약 applicaton.yml 에서 jpa 부분에 open-in-view 를 true로 하면 머가 바뀌는가? 

open-in-view를 true로 하면 view단까지 세션을 오픈한다는것이다 . 그말은 세션 종료 위치가 바뀐다는뜻 

또한 ImageService 에서 사진업로드를 할때 Transactional 을 안걸었는데

서비스단에서 데이터베이스를 어떤 변형을 줄때는 @Transactional을 무조건 걸어줘야한다. 

이유는 간단하다.  습관이 되어야하는데 지금은 안걸어도되지만 우리가 만약에 송금이라는 서비스를 만들고 싶다.

송금이라는 서비스를 만들기 위해서는 계좌라는 테이블이 있을것이다. 계좌라는 테이블은 id money userId가 있을것이다. 1번에 만원이 있고 유저아이디 2번이다 

2번에는 3만원이 있고 유저아이디는 1번이 DB에있다고 생각해보자 , 송금을 하기 위해서는 서비스를 하나 만들었고 송금이라는 함수를 서비스 안에 만들어서 송금을 하기 위해서는 2가지를 해야하는데

첫번쨰는 2번 유저의 돈을 1번 유저에게 5천원을 송금할꺼기 때문에 그 결과는 5천원이 송금 되고나서 2번유저는 돈이 5천원이 되고 1번유저는 3만5천원이 되도록 바뀌어야한다.

그렇기 떄문에 2번유저와1번유저의 머니를 업데이트 해야한다! 

2번유저의 돈을 먼저 업데이트를 먼저해서 송금한 상태에서 1번유저의 머니를 업데이트 하는데 실패했다. 

그러면 3만 5천원으로 안나오고 3만원으로 보이니 5천원은 허공으로 날라갔다. 

지금 우리가 만든 서비스는 단순한 서비스여서 한번에 하나의 인서트밖에 없어서 사진 업로드에 여러가지 인서트와 업데이트가 섞여 있다고 생각해보면 이 하나를 두가지 로직의 하나의 송금 서비스가 되기 떄문에 하나의 트랜잭션(일의 최소 단위) 이라고 부른다. 

송금을 하기위해서는 최소 2가지 업데이트가 일어나야하는데 이런일이 발생하지 않기 위해서 @Transactional 을 달게 되는데 이것을 달게 되면 2가지 중에 하나라도 실패하면 전원다 롤백 시켜버린다. 

둘다 성공해야지 일의 최소 단위인 Transacion이 정상적으로 발동해서 커밋을한다! 

내가 먼가 데이터베이스의 변형을 일어나는일을 한다면 @Transactiona항상 걸어주는것이 좋은 습관이 될것이다. 

 

영속성 컨텍스트는 서비스가 끝나는 이 시점에 변경된 오브젝트르르 감지하고 안바뀌면 DB에 Flush 안하고 바뀌면 DB에 자동으로 FLUSH 한다! 저 감지가 연산이 계속들어간다....! 변경 감지되면 DB에 FLush 되면서 더티체킹이라고하는데

Transactional(readyOnly = true) 를 하면 바뀌었는지 안본다. 읽기전용 데이터라 변경감지를 아예 안하게된다. 

조금더 영속성 컨텍스트 JPA가 일을 좀더 적게 한다.

 


게시물 개수정보도 함께 가져오려면  너무쉽게 해결할수 있는데 

<li><a href=""> 게시물<span>${user.images.size()}</span>

이렇게 size함수를 사요하면된다