스프링

 

 

 

페이징 처리에 앞서프로젝트 최적화를 위헤 open-in-view: false 는 기본적으로 false 놓아야 하며,  지연로딩 전략을 사용한다.

@ManyToOne(fetch = FetchType.LAZY)

지연 전략시 다음과 같은 FetchType이 Lazy

[JPA] Lazy 로딩으로 인한 JSON 반환 오류 (No serializer found for class 가 나온다 

 org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer)

따라서. 

application.yml  다음과 같이 설정

spring
  jackson:
    serialization:
      fail-on-empty-beans: false  

 

 

 

1. PageableCustom  생성

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Slice;

import lombok.Getter;

@Getter
public class PageableCustom {

    private boolean first;
    private boolean last;
    private boolean hasNext;
    private int totalPages;
    private long totalElements;
    private int page;
    private int size;

    public PageableCustom() {
    }

    public PageableCustom(Page<?> page) {
        this.first = page.isFirst();
        this.last = page.isLast();
        this.hasNext = page.hasNext();
        this.totalPages = page.getTotalPages();
        this.totalElements = page.getTotalElements();
        this.page = page.getNumber() + 1;
        this.size = page.getSize();
    }

    public PageableCustom(Slice<?> slice) {
        this.first = slice.isFirst();
        this.last = slice.isLast();
        this.hasNext = slice.hasNext();
        this.page = slice.getNumber() + 1;
        this.size = slice.getSize();
    }
}

 

2.PageCustom  생성

import lombok.Getter;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.SliceImpl;

import java.io.Serializable;
import java.util.List;

@Getter
public class PageCustom<T> implements Serializable {
	private static final long serialVersionUID = 1L;

	private List<T> content;

    private PageableCustom pageableCustom;

    public PageCustom(List<T> content, Pageable pageable, Long total) {
        this.content = content;
        this.pageableCustom = new PageableCustom(new PageImpl<T>(content, pageable, total));
    }

    public PageCustom(List<T> content, Pageable pageable, boolean hasNext) {
        this.content = content;
        this.pageableCustom = new PageableCustom(new SliceImpl<T>(content, pageable, hasNext));
    }

}

 

=================================================  =================================================

 

3. 컨트롤 처리 예

import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.cos.photogramstart.config.auth.PrincipalDetails;
import com.cos.photogramstart.domain.image.ImageResDTO;
import com.cos.photogramstart.service.ImageService;
import com.cos.photogramstart.utils.PageCustom;
import com.cos.photogramstart.web.dto.CMRespDto;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@RestController
public class ImageApiController {

	private final ImageService imageService;
	
	@GetMapping("/api/image")
	public ResponseEntity<?> imageStory(@AuthenticationPrincipal PrincipalDetails principalDetails,
			@PageableDefault(size=3) Pageable pageable){
		PageCustom<ImageResDTO>	images=	imageService.이미지스토리(principalDetails.getUser().getId(), pageable);
		return ResponseEntity.status(HttpStatus.OK).body(new CMRespDto<>(1,"성공", images));
	}
	

}

 

 

4.서비스 

Image 엔티티를  반환처리하면 안 좋으므로 다음과 같이 ImageResDTO 커스텀 객체를 만든후 페이징 처리 한다.

Page<Image>  로 반환시킨후   커스텀  List<ImageResDTO> 에 데이터를 추가한후  PageCustom 반환 처리하면 된다.

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.cos.photogramstart.config.auth.PrincipalDetails;
import com.cos.photogramstart.domain.image.Image;
import com.cos.photogramstart.domain.image.ImageRepository;
import com.cos.photogramstart.domain.image.ImageResDTO;
import com.cos.photogramstart.utils.PageCustom;
import com.cos.photogramstart.web.dto.image.ImageUploadDto;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;

@Service
@RequiredArgsConstructor
@Log4j2
public class ImageService {

	private final ImageRepository imageRepository;
	
	
		@Transactional(readOnly = true) //영속성 컨텍스트 변경 감지를 해서, 더티체킹, flush 반영 x
	public PageCustom<ImageResDTO> 이미지스토리(long principalId, Pageable pageable){				
		/** 접속자만 구독 목록 가져오기 */
		List<Long>  ids =imageRepository.mySubscribes(principalId);		
		Page<Image> pageImages=imageRepository.mStroy(ids, pageable);
		
    	//Eager 전략일때는 영속성에서 데이터를 가져오지만 의 지연 전략으로 (FetchType.LAZY ) 인하여 user 객체  가져와야 한다.
		List<ImageResDTO> imageResDTOs=new ArrayList<ImageResDTO>();		
		for(Image image: pageImages) {
		   ImageResDTO imageResDTO=image.toImageResDTO();		  
		   imageResDTO.setUser(userRepository.findById(image.getUser().getId()).orElse(null));	
		   imageResDTOs.add(imageResDTO);
		}			
		return new PageCustom<ImageResDTO>(imageResDTOs, pageImages.getPageable(), pageImages.getTotalElements());
	}
	
}	

 

 

 

5. ImageRepository

import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface ImageRepository extends  JpaRepository<Image, Long> {

	@Query(value="select * from Image where userId = :userId" ,nativeQuery = true )
	List<Image> findAllByUserImages(@Param(value = "userId") long userId);


	//@Query(value="SELECT * FROM image WHERE userid IN (SELECT toUserId  FROM Subscribe  WHERE fromUserId =:principalId) ORDER BY id DESC ", nativeQuery = true)

	
	/**
	 * JPQL In 절 사용하기 
	 * @param ids
	 * @param pageable
	 * @return
	 */
	@Query(value="SELECT i FROM Image i  JOIN i.user u  WHERE u.id in (:ids)  ")
	Page<Image> mStroy(@Param(value = "ids") List<Long> ids, Pageable pageable);
		 

	/**
	 * 접속자의 구독목록 아이디만 가져오기
	 * @param principalId
	 * @return
	 */
    @Query(value="SELECT t.id  FROM Subscribe s  JOIN  s.fromUser u  JOIN s.toUser t   WHERE  u.id =:principalId   ")
    List<Long> mySubscribes(long principalId);
     
    
}

 

 

 

 

엔티티

Image  

import java.time.LocalDateTime;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;

import com.cos.photogramstart.domain.user.User;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@ToString(exclude = "user")
public class Image {
		
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private long id;
	
	private String caption; 

	private String originalFilename;

	private String postImageUrl; 
	
	@JoinColumn(name="userId")
	@ManyToOne(fetch = FetchType.LAZY)
	private User  user;
	
	private LocalDateTime createDate;
	
	@PrePersist
	public void createDate() {
		this.createDate=LocalDateTime.now();
	}
	
	public  ImageResDTO toImageResDTO() {
		return ImageResDTO.builder()
				.id(id)
				.caption(caption)
				.originalFilename(originalFilename)
				.postImageUrl(postImageUrl)
				.user(user)
				.userId(user.getId())
				.createDate(createDate)
				.build();
	}
	
	
}

 

 

 

 

ImageResDTO

@JsonIgnoreProperties({"images"}) 설정해 줘야 하는데 다음과 같은 영속성 오류가 발생하기 때문이다.

 

"trace": "org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: com.cos.photogramstart.domain.user.User.images, could not initialize proxy - no Session; nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize

 

유저 객체를 불러올때 다시 image 객체를 불러오는 반복된처처로   could not initialize proxy - no Session;  라는 것이다.

따라서, json 반환시에는  user 에서 images 를 무시하는  처리로 @JsonIgnoreProperties 처리를 해준다.

 

import java.time.LocalDateTime;

import com.cos.photogramstart.domain.user.User;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString(exclude = "user")
public class ImageResDTO { 
		
	private long id;
	private String caption; 

	private String originalFilename;

	private String postImageUrl; 

	@JsonIgnoreProperties({"images"})
	private User user;
	
	private long  userId; 
		
	private LocalDateTime createDate;
	

}

 

 

 

User

import java.time.LocalDateTime;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.PrePersist;

import com.cos.photogramstart.domain.image.Image;


import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;



@ToString(exclude = "images")
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Entity 	
public class User {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY) 
	private long id;
	
	@Column(nullable = false, length=20, unique = true)
	private String username;
	
	@Column(nullable = false)
	private String password;
	
	
	private String name;
	private String website; //웹 사이트
	private String bio; //자기소개
	
	@Column(unique = true, nullable = false)	
	private String email;
	
	private String phone;
	private String gender;
	
	private String profileImageUrl; //사진
	private String role; //권한
	

	@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
	private List<Image> images; //양방향 매핑
	
	
	private LocalDateTime createDate;
	
	@PrePersist  // 디비에 Insert 되기 직전에 실행
	public void createDate() {
		this.createDate=LocalDateTime.now();
	}
	

}

 

 

 

 

 

 

 

결과

{
    "code": 1,
    "message": "성공",
    "data": {
        "content": [
            {
                "id": 12,
                "caption": "홍길동 ",
                "originalFilename": "2312211906whiuzletyv_0.jpg",
                "postImageUrl": "1cdb123e-db89-42f7-b96a-44e80d14da43_2312211906whiuzletyv_0.jpg",
                "user": {
                    "id": 5,
                    "username": "ssar",
                    "password": "$2a$10$.NK35QvezqQoh3uvE5S0G.AmtILUd6upEcu4MUi0jm03z6fsW0Vry",
                    "name": "홍길동",
                    "website": null,
                    "bio": null,
                    "email": "ssar@gmail.com",
                    "phone": null,
                    "gender": null,
                    "profileImageUrl": null,
                    "role": "ROLE_USER",
                    "createDate": "2023-12-22T17:38:56.687979"
                },
                "userId": 5,
                "createDate": "2023-12-23T14:15:03.274058"
            },
            {
                "id": 11,
                "caption": "333",
                "originalFilename": "2312200136ptuoqyzasv_0.jpg",
                "postImageUrl": "87cc4ed0-de8c-485f-8b02-61b95bc0961a_2312200136ptuoqyzasv_0.jpg",
                "user": {
                    "id": 3,
                    "username": "test3",
                    "password": "$2a$10$qWtJO89AcvZKNFCg97dJN.AqbLmuc6two.D4s/knafkI5GoLxqoAq",
                    "name": "김민종",
                    "website": null,
                    "bio": null,
                    "email": "test3@gmail.com",
                    "phone": null,
                    "gender": null,
                    "profileImageUrl": null,
                    "role": "ROLE_USER",
                    "createDate": "2023-12-20T19:58:47.300206"
                },
                "userId": 3,
                "createDate": "2023-12-22T19:49:10.005323"
            },
            {
                "id": 10,
                "caption": "333",
                "originalFilename": "2312200136pqlhjryrdw_0.jpg",
                "postImageUrl": "17e59775-9854-4d85-b525-0f18b7ad1701_2312200136pqlhjryrdw_0.jpg",
                "user": {
                    "id": 3,
                    "username": "test3",
                    "password": "$2a$10$qWtJO89AcvZKNFCg97dJN.AqbLmuc6two.D4s/knafkI5GoLxqoAq",
                    "name": "김민종",
                    "website": null,
                    "bio": null,
                    "email": "test3@gmail.com",
                    "phone": null,
                    "gender": null,
                    "profileImageUrl": null,
                    "role": "ROLE_USER",
                    "createDate": "2023-12-20T19:58:47.300206"
                },
                "userId": 3,
                "createDate": "2023-12-22T19:49:01.636644"
            }
        ],
        "pageableCustom": {
            "first": true,
            "last": false,
            "hasNext": true,
            "totalPages": 2,
            "totalElements": 5,
            "page": 1,
            "size": 3
        }
    }
}

 

 

 

 

페이지 번호는 0 부

http://localhost:8080/api/image?page=0

http://localhost:8080/api/image?page=1

http://localhost:8080/api/image?page=2

 

 

소스

https://github.com/braverokmc79/EaszUp-Springboot-Photogram-Start

https://github.dev/braverokmc79/EaszUp-Springboot-Photogram-Start

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 1  라이트

댓글 ( 4)

댓글 남기기

작성