20220825 [Spring Boot]

Yeoonnii·2022년 8월 25일
0

TIL

목록 보기
12/52
post-thumbnail

물품서비스

👨‍🏫
CRUD Create(생성), Read(읽기), Update(갱신), Delete(삭제) 는 기본기능이다
하지만 Read(읽기) = 조회는 까다롭다!
전체 조회, 1개 조회, 이미지 포함 조회 등 조회 옵션에 따라 다양한 상황이 벌어지기 때문에 여러 방식의 조회 연습을 많이 해야하며, 이때 DB에서 원하는 데이터를 자유자재로 꺼내어 쓸 수 있어야 한다!

Item entity 생성

📁 entity / Item.java

  • @ToString( exclude = {"filedata"})
    ➡️ tostring 출력시 "filedata"는 출력하지 않는다
  • @Document(collection = "boot_item")
    ➡️ mongodb 컬렉션 생성
package com.example.entity;

import java.util.Date;

import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Transient;
import org.springframework.data.mongodb.core.mapping.Document;

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

@Getter
@Setter
@ToString( exclude = {"filedata"}) 
@NoArgsConstructor
@AllArgsConstructor
@Document(collection = "boot_item") 
public class Item {
    @Id
    private Long no = 0L;  //_id
    
    private String name = null;
    private String content = null;
    private Long price = 0L;
    private Long quantity = 0L;
    private Date regdate = null;
    
    private String filename = null;
    private Long filesize = 0L;
    private String filetype = null;  // 파일의 종류
    private byte[] filedata = null;  // 실제 파일 데이터는 byte 배열로 온다

    @Transient //임시변수 이미지 URL
    private String imageUrl = null;
}

물품등록(+이미지 첨부)

📁 service / ItemService.java

파일 생성시 상단에
@Service , @Controller, @RestController 등... ➡️ 클래스 별 업무 지칭
꼭 작성해줘야 한다! 그래야 어떤 역할을 하는지 인식 가능하다


@Service
public class ItemService {
    
    @Autowired MongoTemplate mongoTemplate;

    @Autowired CounterService counterService; // seq사용
    
// 등록하기
    public int insertItem(Item item){
        try {
            // 아이템번호(seq), 날짜 등록
            item.setNo( counterService.generateCounter("SEQ_ITEM_BOOT_NO") );
            item.setRegdate( new Date() );
            
            // 변수 만들지 않고 바로넣는게 간결하다
            // Item retitem = mongoTemplate.insert(item);
            if(mongoTemplate.insert(item) != null){
                return 1;
            }

            return 0;
        } catch (Exception e) {
            e.printStackTrace();
            return -1;
        }
    }

📁 controller / ItemController.java

...
@RestController // json형태로 반환
@RequestMapping(value = "/item")
public class ItemController {

    @Autowired
    ItemService iService;

   
    @Autowired
    ResourceLoader resourceLoader; // 리소스 접근위한 객체 생성 => 파일 읽어들일 수 있는 객체

// 127.0.0.1:8080/ROOT/item/insert.json
    // 물품등록
    @PostMapping(value = "/insert.json")
    public Map<String, Object> insertPost(@ModelAttribute Item item,
            @RequestParam(name = "file", required = false) MultipartFile file) throws IOException {
        if (file != null) { // 파일이 첨부되었을때
            // 수동으로 파일정보 4개 추가
            item.setFilename(file.getOriginalFilename());
            item.setFilesize(file.getSize());
            item.setFiledata(file.getBytes());
            item.setFiletype(file.getContentType());
        }
        System.out.println(item.toString());
        int ret = iService.insertItem(item);
        Map<String, Object> map = new HashMap<>();
        map.put("status", 0);
        if (ret == 1) {
            map.put("status", 200);
        }
        return map;
    }

@RestController
json형태로 반환해주는 컨트롤러 (rest전용)

required = false ➡️ 파일첨부 하지 않아도 업로드 가능하게 한다
파일 제외하고 업로드시 파일부분 데이터는 공백으로 비워진다

  • entity에서 이미지(file)에 대한 값은 filename , filesize , filetype , filedata 4가지로 설정 되어있지만 실질적으로 이미지 첨부시 파일 하나만 첨부한다.
  • 하나의 파일은 자동으로 위 4가지에 값에 데이터가 나누어 저장 될 수 없으므로 추가적인 작업이 이루어져야만 파일의 데이터를 저장 할 수 있다!
    ➡️ MultipartFile 사용하여 파일을 받은 후 데이터를 나누어 수동으로 넣어줘야 한다

💡 getInputStream은 파일 저장시 사용한다.
getInputStream 제외 나머지 4개 사용하여 파일 데이터 가져온다

🤯 오류!

➡️ throws IOException 추가하여 예외처리를 해주어야 한다

System.out.println(item.toString());출력 확인시 파일 정보 4가지가 출력되는데
filedatabyte타입의 정보가 저장되므로 굳이 알 필요가 없다!
📁 entity / Item.java 에서 @ToString( exclude = {"filedata"}) 입력하면 filedata 는 출력되지 않는다

💡 exclude에 출력을 원하지 않는 데이터를 적어주면
해당 데이터는 출력하지 않는다.


물품 1개 조회(+이미지)

📁 service / ItemService.java (이미지 제외한 데이터)

// 1개 조회(이미지 제외)
    public Item selectOneItem( Item item ){
        try {
            // MongoTemplate.findOne(Query query, Class<T> entityClass) : T
            Query query = new Query();
            query.addCriteria(Criteria.where("_id").is(item.getNo()));
            // 파일정보를 가져와도 이미지가 오는게 아니니 가져올 필요 없다
            query.fields().exclude("filedata", "filetype", "filename", "filesize");
            
            return mongoTemplate.findOne(query, Item.class);
            
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

query에서 조건 추가 ➡️ 아이디와 no일치

  • query.fields().exclude
    ➡️ 이미지 정보는 데이터로 오면 이미지로 쓸 수 없기 때문에 이미지 데이터 제외한 나머지 정보만 먼저 가져오고 추후에 이미지 데이터만 별도로 가져온다
query.addCriteria(Criteria.where("_id").is(item.getNo()));
            // 파일정보를 가져와도 이미지가 오는게 아니니 가져올 필요 없다
            query.fields().exclude("filedata", "filetype", "filename", "filesize");
  • exclude 사용시 (String field) 와 (String ... field)의 차이는
    전자는 한개의 필드만 넣을 수 있고 후자는 여러개의 필드를 넣을 수 있다는 점이다

📁 service / ItemService.java (1개 이미지 데이터만)

1개 조회(이미지 데이터만)
public Item selectOneItemImage( Item item ){
            try {
                // MongoTemplate.findOne(Query query, Class<T> entityClass) : T
                Query query = new Query();
                query.addCriteria(Criteria.where("_id").is(item.getNo()));
                // 파일정보만 가져온다
                query.fields().include("filedata", "filetype", "filename", "filesize");
                
                return mongoTemplate.findOne(query, Item.class);
                
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
  • 위의 selectOneItem에서 exclude ➡️ include 로 바꿔주면 된다
  • 물품조회 때 제외되었던 파일 정보들 filedata, filetype, filename, filesize 만 포함되어 리턴된다.

📁 controller / ItemController.java (1개 이미지 데이터만)

// 크롬에서 127.0.0.1:8080/ROOT/item/image?no=2 으로 바로 확인 가능
    // <img src="여기에 사용할 주소 생성하기">
    // 이미지 조회
    @GetMapping(value = "/image")
    public ResponseEntity<byte[]> selectOneImageGET(@ModelAttribute Item item) throws IOException {

        Item retItem = iService.selectOneItemImage(item);
        System.out.println(retItem); // 확인용

        HttpHeaders headers = new HttpHeaders();
        ResponseEntity<byte[]> response = null;
        if (retItem.getFilesize() > 0L) { // DB에 파일이 존재하는 경우

            // HttpHeaders.setContentType(MediaType mediaType) : void
            headers.setContentType(MediaType.parseMediaType(retItem.getFiletype()));
            response = new ResponseEntity<byte[]>(retItem.getFiledata(), headers, HttpStatus.OK);

        } else { // DB에 파일이 존재하지 않는경우, 파일을 읽어서 보여줌
            // classpath == resource

            InputStream stream = resourceLoader.getResource("classpath:/static/image/noimage.jpg").getInputStream();

            headers.setContentType(MediaType.IMAGE_JPEG);
            // stream.readAllBytes() => InputStream.readAllBytes() : byte[] 내가 원하는 byte배열이다
            // response = ResponseEntity<byte[]>(실제데이터, headers, HttpStatus status)
            response = new ResponseEntity<byte[]>(stream.readAllBytes(), headers, HttpStatus.OK);
        }
        return response;
  • parseMediaType 이용하여 String 타입 ➡️ media 타입에 넣어 사용
  • 리소스 접근위한 객체 ResourceLoader 생성 = 파일 읽어들일 수 있는 객체
    @Autowired
     ResourceLoader resourceLoader;
  • header에서 지정해준 content-type 을 크롬에서 인식해서 데이터를 읽어 표현한다
    [Content-Type:"image/jpeg"]
  • HttpHeaders headers = new HttpHeaders();
    ➡️ headers 지정시 if문 아래 각각 적는것 보다 밖에 빼줘서 쓰는게 간결하다 => 지역변수 변역변수

✔️ return시 두가지 상황이 있다.
1. DB에 파일이 존재하는 경우
2. DB에 파일이 존재하지 않는경우 ➡️ 기본이미지 파일을 읽어서 보여줄 것

🧪 각 상황별 return값을 지정해준다
1. DB에 파일이 존재하는 경우
ResponseEntity<byte[]> (실제데이터, headers, HttpStatus status) 사용하여 첨부된 파일의 데이터를 보낸다
2. DB에 파일이 존재하지 않는 경우
resource / static / image / noimage.jpg
➡️ 이미지 없으면 보여줄 기본이미지
실제데이터에 String형으로 기본이미지 위치 지정
ResourceLoader.getResource("classpath:/static/image/noimage.jpg").getInputStream();

ResponseEntity<byte[]> response = null;
        if(retItem.getFilesize() > 0L){ //DB에 파일이 존재하는 경우
// org.springframework.http.ResponseEntity.ResponseEntity(T body, MultiValueMap<String,String> headers, HttpStatus status)
            // response = new ResponseEntity<byte[]>(실제데이터, headers, HttpStatus status)
  • headers 생성시 원하는 파일타입만 지정도 가능하다!

📁 controller / ItemController.java (이미지 제외한 데이터)

    // 127.0.0.1:8080/ROOT/item/selectone.json?no=1 => params로 조회
    // 물품1개조회
    @GetMapping(value = "/selectone.json")
    public Map<String, Object> selectOneGET(HttpServletRequest request, @ModelAttribute Item item) {

        Map<String, Object> map = new HashMap<>();
        Item retitem = iService.selectOneItem(item);
        map.put("status", 0);
        if (retitem != null) {
            map.put("status", 200);
            map.put("data", retitem); // 파일정보를 제외한 Item의 데이터가 온다.
            map.put("imageurl", request.getContextPath() + "/item/image?no=" + item.getNo()); // 이미지 url을 가져온다
        }
        return map;
    }
  • 이미지 데이터 조회완성 후 이미지 제외한 데이터 조회이미지 url도 추가해 준다.
    이때 주소의 ROOT 부분을 바꾸면 imageurl도 변경해줘야 하는 문제 아래와 같이 해결!
 HttpServletRequest request
 map.put("imageurl", request.getContextPath() + "/item/image?no=" + item.getNo());

전체 물품 정렬, 페이지네이션

원래는 이미지데이터가 📁 entity / Item.java에 포함되어있지 않고 개별 엔티티로 왔으면 더 좋았을 것!
현재 ItemService 작성시 query로 매번 파일데이터를 제외하고 있어 비효율적이다
query.fields().exclude("filedata", "filetype", "filename", "filesize");

📁 service / ItemService.java

        // 정렬 , 페이지네이션
        public List<Item> selectListPageItem( int page ){
            try{
                Query query = new Query(); //정렬과 페이지네이션을 같이사용하는 쿼리!
                query.fields().exclude("filedata", "filetype", "filename", "filesize");
                Sort sort = Sort.by(Direction.DESC, "_id");

                // page는 0부터
                PageRequest pageable = PageRequest.of(page-1, 10, sort);
                query.with(pageable);

                return mongoTemplate.find(query, Item.class);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
  • Direction.DESC + ctrl + 클릭 ➡️ 거의 static형태이다
    어떠한 정보를 제공 받으려면 static형태이어야 하며 new 객체를 먼저 생성해서 찾아본다
    객체 생성시 static형태의 객체가 되며, 이때 정보를 제공받아 사용가능한 옵션들을 확인할 수 있다.

📁 controller / ItemController.java

    // 127.0.0.1:8080/ROOT/item/selectlistpage?no=2
    // 전체목록조회 + 정렬, 페이지네이션
    @GetMapping(value = "/selectlistpage.json")
   
    public Map<String, Object> selectlistPageGET(
            @RequestParam(name = "page", defaultValue = "1", required = false) int page) {

        Map<String, Object> map = new HashMap<>();
        List<Item> list = iService.selectListPageItem(page);
        map.put("status", 0);
        if (list != null) {
            map.put("status", 200);
            map.put("list", list);
        }
        return map;
    }
  • defaultValue = "1" ➡️ params 값을 입력 안하면 1로 세팅한다
  • required = 1 ➡️ 1이 안와도 된다. 안오면 default = 1로 세팅한다

물품 전체 조회(+이미지)

물품 전체 조회시 이미지 url은 📁 entity / Item.java 에 임시변수를 생성하여
이미지 url이 저장될 공간을 생성하고 📁 controller / ItemController.java 에서 반복문으로 만들어준다

@Transient //임시변수 이미지 URL
    private String imageUrl = null;

📁 service / ItemService.java (이미지 제외 전체 데이터)

// 전체 물품목록(이미지 제외한 전체 데이터)
        public List<Item> selectListItem(){
            try {
                Query query = new Query(); 
                query.fields().exclude("filedata", "filetype", "filename", "filesize");

                return mongoTemplate.find(query, Item.class);
            
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }

📁 controller / ItemController.java (이미지 포함한 전체 데이터)

    // 127.0.0.1:8080/ROOT/item/selectlist
    // 전체목록조회(+이미지url포함)
    @GetMapping(value = "/selectlist")
    public Map<String, Object> selectlistGET(HttpServletRequest request){
        Map<String, Object> map = new HashMap<>();
        List<Item> list = iService.selectListItem();

        map.put("status", 0);
        if(list != null){
            for(Item item : list){
                item.setImageUrl(request.getContextPath() + "/item/image?no=" + item.getNo());
            }
            map.put("status", 200);
            map.put("list", list);
        }
        return map;
    }

0개의 댓글