241106 RESTful (CRUD/문제)

241106 에이콘 아카데미 수업을 기반하여 작성되었음을 알립니다.

RESTful

겟/포스트 방식만 사용하는 것보다 싱글페이지어플리케이션을 구현하기 위해 레스트풀을 사용하면 쉽게 구현 가능하다 html/자바스크립트/css만으로도 구현 가능하다!

일반 html 요청과 레스트풀 요청으로 나누어서 컨트롤러 작성

자바스크립트, 함수를 잘 알면 새로운 기술이 나와도 금방 적응 가능하다~

RESTful 실습 (CRUD)

dependencies

Spring Web / Spring Boot DevTools / Spring Data JPA / Lombok / MariaDB Driver / Thymeleaf

application.properties

spring.application.name=sprweb35restful_mem
server.port=80
spring.thymeleaf.cache=false

spring.jpa.open-in-view=false

#mariadb server connect
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://127.0.0.1:3306/memberdb
spring.datasource.username=root
spring.datasource.password=1111
# jpa
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
logging.level.org.hibernate.type.descriptor.sql=trace
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect

 

2024-11-06T11:00:40.710+09:00  WARN 16496 --- [sprweb35restful_mem] [ restartedMain] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning

Spring Boot App 실행시 경고가 뜨는데 경고는 무시해도 무방하지만 안뜨게 해줄 수 있음.
해당 LAZY 로딩과 관련있음, 지연로딩을 막아줌, 필요할 때만 로딩되게 설정하는면 되는데 방법은 아래와 같다.

application.properties에 spring.jpa.open-in-view=false 추가 해주면 된다.

Bean

package pack.controller;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class MemBean {
	private int num;
	private String name;
	private String addr;
}

Entity

package pack.model;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class Mem {
	@Id
	private int num;
	private String name;
	private String addr;
}

Repository

package pack.model;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface MemCrudRepository extends JpaRepository<Mem, Integer> {
	// num 자동 증가 처리를 위한 코드 작성
	@Query(value = "select max(m.num) from Mem as m")
	int findByMaxNum();
	
	@Query(value = "select m from Mem as m where m.num=?1")
	Mem findByNum(String num);
}

DAO

package pack.model;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import pack.controller.MemBean;

@Repository
public class DataProcess {
	@Autowired
	private MemCrudRepository repository; // hikari pool 자동 지원
	
	// 전체 자료 읽기
	public List<Mem> getDataAll() {
		List<Mem> list = repository.findAll(); // 기본 메소드
		return list;
	}
	
	// 추가
	public String insert(MemBean bean) {
		// 가장 큰 번호를 구해 +1 하고 추가 시 사용 
		//int max = repository.findByMaxNum();
		
		// 입력한 번호 중복 확인(실습)
		try {
			Mem mem = repository.findById(bean.getNum()).get();
			return "이미 등록된 번호입니다.";
		} catch (Exception e) {
			// findById의 결과가 에러인 경우(등록 가능한 번호)
			try {
				Mem mem = new Mem(bean.getNum(), bean.getName(), bean.getAddr());
				repository.save(mem);
				return "success";
			} catch (Exception e2) {
				return "입력 자료 오류 : " + e2.getMessage();
			}
		}
	}
	
	// 수정/삭제를 위한 레코드 읽기
	public Mem getData(String num) {
		//Mem mem = repository.findById(bean.getNum()).get(); // 기본 제공 메소드
		Mem mem = repository.findByNum(num);
		return mem;
	}
	
	// 수정
	public String update(MemBean bean) {
		try {
			Mem mem = new Mem(bean.getNum(), bean.getName(), bean.getAddr());
			repository.save(mem);
			return "success";
		} catch (Exception e) {
			return "수정 작업 오류 : " + e.getMessage();
		}
	}
	
	// 삭제
	public String delete(int num) {
		try {
			repository.deleteById(num);
			return "success";
		} catch (Exception e) {
			return "삭제 작업 오류 : " + e.getMessage();
		}
	}
}

ViewController

package pack.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import pack.model.DataProcess;
import pack.model.Mem;

@Controller
@RequestMapping("/members")
public class MemViewController {
	@Autowired
	private DataProcess dataProcess;
	
	@GetMapping("/")
	public String index() {
		return "index";
	}
	
	@GetMapping("/list")
	public String list() {
		return "list";
	}
	
	@GetMapping("/new")
	public String insert() {
		return "insert";
	}
	
	@GetMapping("/update/{num}") // 수정할 대상 파일을 읽어 준 후 수정 페이지로 호출
	public String updateProcess(@PathVariable("num")String num, Model model) {
		Mem mem = dataProcess.getData(num);
		model.addAttribute("data", mem);
		return "update";
	}
}

Controller

package pack.controller;

import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import pack.model.DataProcess;
import pack.model.Mem;

@RestController // JSON을 반환하겠다는 의미
@RequestMapping("/api") // 아래 모든 메소드의 앤드포인트 경로에 /api를 기본 경로(prefix)로 설정, MemViewController와 구분
public class MemController {
	@Autowired
	private DataProcess dataProcess;
	
	@GetMapping("/members") // SELECT
	public List<Mem> listProcess() { // 전체 자료 읽기
		return dataProcess.getDataAll();
	}
	
	@PostMapping("/members") // INSERT
	public Map<String, Object> listProcess(@RequestBody MemBean bean) { // 자료 추가
		dataProcess.insert(bean);
		return Map.of("isSuccess", true);
	}
	
	@PutMapping("/members") // UPDATE
	public Map<String, Object> updateProcess(@RequestBody MemBean bean) { // 자료 수정
		dataProcess.update(bean);
		return Map.of("isSuccess", true);
	}
	
	@DeleteMapping("/members/{num}") // DELETE
	public Map<String, Object> deleteProcess(@PathVariable("num")int num) { // 자료 삭제
		dataProcess.delete(num);
		return Map.of("isSuccess", true);
	}
}

결과

Swagger로 GET 요청

Swagger로 POST 요청

Swagger로 PUT 요청

Swagger로 DELETE 요청


문제

싱글페이지로 CRUD 구현해보기!

1. CSS 적용해주기
2. 자바스크립트 파일 따로 만들기

에러

🚨 TypeError: products.forEach is not a function

해당 에러는 함수에 전달된 products 가 배열이 아닌 경우에 발생한다고 한다.

{ "products": [ { "dan": "999", "sang": "장갑", "su": "999", "code": "1" }, { "dan": "4444", "sang": "벙어리", "su": "4444", "code": "2" }, { "dan": "555", "sang": "지갑", "su": "555", "code": "3" }, { "dan": "99999", "sang": "커피", "su": "9999", "code": "4" }, { "dan": "5555", "sang": "안경", "su": "5555", "code": "5" } ] }
반환되는 데이터가 " products" 키에 배열이 포함된 객체로 넘어오고 있는 상태였다.

해당 코드를 아래처럼 변경하여 products의 배열로 접근하게 하여 에러 해결

.then(products => { const products = products; displayProduct(products);
                                                        ▼
.then(list => { const products = list.products; displayProduct(products);
🚨 The method getCode() in the type SangpumBean is not applicable for the arguments (int)

Bean으로 받은 데이터를 인자로 받을 수 있는 생성자가 엔티티에 없는 것을 확인!
그래서 엔티티에 롬복 어노테이션인 @AllArgsConstructor을 추가해주어 인자를 받을 수 있게해주었다.

 

🚨 스웨거로 확인 시 수정할 데이터를 잘 가져오지만폼 데이터를 읽어오지 못하는 것을 확인

객체들을 각각 만들어주어 데이터들을 담을 수 있게 해주었다.
const form = document.querySelector("#updateFrm");
const formData = new FormData(form);
                         ▼
const form = document.querySelector("#updateFrm");
const formData = new FormData(form);
                     
const code = formData.get("code");
const sang = formData.get("sang");
const su = formData.get("su");
const dan = formData.get("dan");

결과

피드백

CSS 좀 더 적용해보기!
상품 추가, 수정 완료 시 폼이 유지되는 점 고쳐보기!

'Study > Acorn' 카테고리의 다른 글

241108/241111 파일 업로드/다운로드  (0) 2024.11.08
241107 RESTful / AOP  (3) 2024.11.07
241105 RESTful (PUT/DELETE/문제)  (0) 2024.11.05
241105 RESTful (GET/POST)  (2) 2024.11.05
241104 AJAX (DB연동/문제)  (0) 2024.11.04