👨🏫
CRUDCreate(생성), Read(읽기), Update(갱신), Delete(삭제)
는 기본기능이다
하지만Read(읽기)
=조회
는 까다롭다!
전체 조회, 1개 조회, 이미지 포함 조회 등 조회 옵션에 따라 다양한 상황이 벌어지기 때문에 여러 방식의 조회 연습을 많이 해야하며, 이때 DB에서 원하는 데이터를 자유자재로 꺼내어 쓸 수 있어야 한다!
@ToString( exclude = {"filedata"})
"filedata"
는 출력하지 않는다@Document(collection = "boot_item")
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 , @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;
}
}
...
@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가지가 출력되는데
filedata
는byte타입
의 정보가 저장되므로 굳이 알 필요가 없다!
📁 entity / Item.java 에서@ToString
에( exclude = {"filedata"})
입력하면filedata
는 출력되지 않는다💡
exclude
에 출력을 원하지 않는 데이터를 적어주면
해당 데이터는 출력하지 않는다.
// 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)의 차이는
전자는 한개의 필드만 넣을 수 있고 후자는 여러개의 필드를 넣을 수 있다는 점이다
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
만 포함되어 리턴된다.
// 크롬에서 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 생성시 원하는 파일타입만 지정도 가능하다!
// 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");
// 정렬 , 페이지네이션
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
형태의 객체가 되며, 이때 정보를 제공받아 사용가능한 옵션들을 확인할 수 있다.
// 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;
// 전체 물품목록(이미지 제외한 전체 데이터)
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;
}
}
// 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;
}