✔️ 학습 목표
✔️ 스프링 부트, JPA 전체적인 동작 과정
🛠️ RestController vs Controller
controller
는 주로 view (화면) return이 주 목적RestController
는 view가 필요없고 API만 지원하는 서비스에서 주로 사용
기능 | METHOD | 리소스(앤드포인트) | PATH | 인풋데이터 | 인풋 데이터 타입 | 아웃풋 데이터 | 아웃풋 데이터 타입 |
---|---|---|---|---|---|---|---|
사진 목록 조회 | GET | /content | [{uid:(String), path:(String), contents:(String)}] | json list | |||
사진 목록 작성 | POST | /content | picture:(file), title:(String), password:(String) | multipart/form | {path:(String)} | json | |
사진 목록 수정 | PUT | /content | /{uid} | picture:(file), title:(String), password:(String) | multipart/form | {path:(String)} | json |
사진 목록 삭제 | DELETE | /content | /{uid} | password:(String) | json | void |
✔️ 준비할 것
사전 학습에 적혀있는 방법으로도 환경 세팅을 할 수 있지만, spring initializer를 사용했다.
Sprnig Web
, Lombok
, Spring Data JPA
, MySQL Driver
, Spring Boot DevTools
✔️ 현재 디렉터리 구조
✔️ 문제에서 제공하는 프론트(화면) index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>crud test</title>
</head>
<body>
<div id="app">
<input type="text" id="title" ref="title" /><br>
<input type="file" id="picture" ref="picture" accept="image/png, image/jpeg" /><br>
<input type="password" id="password" ref="password" /> <br>
<button v-on:click="post" >Posting!</button>
<hr>
<div
v-for="list in lists"
v-bind:id="list.uid"
>
{{list.uid}}
<img v-bind:src=list.path width="100%"/>
<input type="text" v-model=list.title ref="tt" /><br>
<input type="file" v-bind:ref=list.uid accept="image/png, image/jpeg" /><br>
<button v-on:click="update(list)" >update</button>
<button v-on:click="del(list)" >delete</button>
<hr>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/4.0.1/mustache.min.js"></script>
<script type="text/javascript" >
new Vue({
el: '#app',
data: {
file: null
, lists: []
, path: ''
, status: ''
},
methods: {
post: function () {
var formData = new FormData();
formData.append("picture", this.$refs.picture.files[0] );
console.log("files : " + this.$refs.picture.files[0])
formData.append("title", this.$refs.title.value );
formData.append("password", this.$refs.password.value );
axios.post(
'/content'
, formData
, { headers: { 'Content-Type': 'multipart/form-data' } }
).then(response => {
var result = response.data;
// 결과 출력
for(prop in result) {
if(prop === "Error") alert(result[prop]);
} this.getList();
}).catch(error => {
console.log(error);
}); } ,update: function (list) {
var upPasswd = prompt("Enter the password for update.");
if (!upPasswd){
alert("password를 입력해주세요.");
return;
} var formData = new FormData();
formData.append("picture", this.$refs[list.uid][0].files[0]);
formData.append("title", list.title );
formData.append("password", upPasswd );
axios.put(
'/content/'+list.uid
, formData
, { headers: { 'Content-Type': 'multipart/form-data' } }
).then(response => {
var result = response.data;
// 결과 출력
for(prop in result) {
if(prop === "Error") alert(result[prop]);
} // console.log(response.data);
this.getList();
}).catch(error => {
console.log(error);
}); } , del: function (list) {
var delPasswd = prompt("Enter the password for delete.");
if (!delPasswd){
alert("password를 입력해주세요.");
return;
} axios.delete('/content/'+list.uid
,{ data: { password: delPasswd }}
).then(response =>{
// var result = response.data;
// // // 결과 출력
this.getList()
} ).catch(error =>
console.log(error)
); } , getList: function () {
axios.get('/content').then(response => {
this.lists = response.data
response.data.forEach(element => {
// console.log(element.path);
});
}).catch(error =>
console.log(error)
); } } , mounted: function () {
this.$nextTick(function () {
this.getList();
})
}
})</script>
</script>
</body>
</html>
✔️ resources/application.yml
spring:
datasource: url: jdbc:mysql://localhost:3306/studytest?serverTimezone=UTC&useUniCode=yes&characterEncoding=UTF-8
username: ssafy
password: ssafy
jpa:
hibernate: ddl-auto: update
properties:
hibernate: show_sql: true
format_sql: true
database-platform: org.hibernate.dialect.MySQL8Dialect
✔️ Req. 2-1 사진 목록 조회
로직 : 정의되어진 Repository를 사용하여 DB를 조회를 해온 뒤에 프론트단에 필요한 정보들을 값을 셋팅해서 돌려준다.
@Slf4j
@RestController
@AllArgsConstructor
@RequestMapping("/content")
public class InstagramRestController {
@Autowired
ContentRepository contentRepository;
// Req. 2-1 사진 목록 조회 기능
@GetMapping
public List<Map<String, Object>> list(){
List<Map<String, Object>> result = new ArrayList<>();
List<InstagramContent> list = contentRepository.findTop1000ByOrderByUidDesc();
for (InstagramContent instagramContent : list) {
Map<String, Object> map = new HashMap<>();
log.info("uid : " + instagramContent.getUid() + " path : " + instagramContent.getPath()
+ " title : " + instagramContent.getTitle());
map.put("uid",instagramContent.getUid());
map.put("path",instagramContent.getPath());
map.put("title",instagramContent.getTitle());
result.add(map);
}
return result;
}
InstagramContent
@Entity
@NoArgsConstructor
@Getter
@Setter
@ToString
public class InstagramContent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int uid;
private String path;
private String title;
private String password;
@Builder
public InstagramContent(String path, String title, String password) {
this.path = path;
this.title = title;
this.password = password;
}}
ContentRepository
public interface ContentRepository extends JpaRepository<InstagramContent, Integer> {
public List<InstagramContent> findTop1000ByOrderByUidDesc();
}
✔️ Req. 2-2 사진 목록 작성
로직
// 2-2 사진 목록 작성 기능
@PostMapping
public Map<String, String> post(
@RequestPart("picture") MultipartFile pic,
@RequestParam("title") String title,
@RequestParam("password") String password) throws IOException {
String path = System.getProperty("user.dir");
File file = new File(path + "/src/main/resources/static/" + pic.getOriginalFilename());
if(pic.isEmpty()){
log.info("파일 저장");
// 파일을 저장한다.
return Map.of("Error", "사진을 입력해주세요.");
}// log.info("password : " + password);
if(password.isEmpty()){
return Map.of("Error", "password를 입력해주세요.");
}
log.info("pic : " + pic.getOriginalFilename());
log.info("path와 title, password 입니다 : " + path + " " + title + " " + password);
// transferTo 메소드를 이용하여 multipartFile에 담겨있는 파일을 해당 경로에 담는다.
pic.transferTo(file);
// builder를 사용하지 않은 소스 vs builder를 사용한 소스
// contentRepository.save(new InstagramContent(pic.getOriginalFilename(), title, password)); // 데이터 저장
contentRepository.save(InstagramContent.builder()
.password(password)
.path(file.getName())
.title(title)
.build());
return Map.of("path", file.getName());
}
✏️ builder
build()
를 통해 빌더를 작동 시켜 객체를 생성한다.
✔️ Req. 2-3 사진 목록 수정
로직
// 2-3 사진 목록 수정
@PutMapping("/{uid}")
public Map<String, String> update(
@PathVariable int uid, @RequestPart("picture") MultipartFile pic,
@RequestParam("title")String title, @RequestParam("password") String password
)throws IOException{
InstagramContent content = contentRepository.findById(uid).get();
// 비밀번호가 다르다면, 에러로 처리
if(!password.equals(content.getPassword())){
return Map.of("Error","비밀번호 잘못 입력하셨습니다.");
}
// 만약에 조회한 파일이 존재한다면
if(!pic.isEmpty()){
// 현재 경로
String path = System.getProperty("user.dir");
File file = new File(path + "/src/main/resources/static/" + pic.getOriginalFilename());
// static에 이미지 파일 등록
pic.transferTo(file);
// log.info("name : " + file.getName());
// 이미지 파일이름 수정
content.setPath(file.getName());
// title 수정
content.setTitle(title);
}
// 현재 결과 저장
contentRepository.save(content);
return Map.of("path", content.getPath());
}
✔️ Req. 2-4 글 삭제 기능
로직
// 2-4 글 삭제
@DeleteMapping("/{uid}")
public void delete(@PathVariable int uid, @RequestBody Map<String, Object> body){
// 현재 입력된 비밀번호가 db에 저장된 id에 해당된 비밀번호와 같다면
if(body.get("password").toString().equals(contentRepository.findById(uid).get().getPassword())){
contentRepository.deleteById(uid);
}}
@RequestPart
에 어떠한 형식으로 데이터를 넣어야 할지 모르겠다.
이전에 추가했던 사진들은 화면에 잘 나타나지만, 새롭게 추가했던 사진은 화면에 잘 나타지 않습니다.
이유로는
이에 대한 오류를 찾고 있습니다.
controller -> service -> dao -> mybatis(직접 sql 작성)
controller -> service -> repository, entity -> db
2학기 프로젝트에 이를 적용해볼 예정이며, 이에 더 나아가 Filter, Interceptor, AOP
동작과정을 적용해보며 만들어볼 것입니다.