개발자의 오르막

Spring Boot 기반 REST API 설계 (화면에서 DB까지) 본문

SpringFrameWork/SpringBoot

Spring Boot 기반 REST API 설계 (화면에서 DB까지)

계단 2019. 9. 27. 17:01

* https://www.youtube.com/watch?v=Xb0OEOiZ3Rg 참조

 

# RestFul 개념

- URI 는 정보의 자원을 표현해야 하며 자원에 대한 행위는 HTTP Method

  (GET, POST, PUT, DELETE)로 표현해야 한다.

- URI는 정보의 자원을 표현해야 한다. (자원 이름은 동사보다는 명사를 사용)

- URI 는 자원을 표현하는데 중점을 둬야 한다.

  ex) DELETE / emp / 1

  자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE 등)로 표현,

  사원 정보를 가져올 때는 GET, 추가는 POST, 수정은 PUT, 삭제는 DELETE

 

# 스프링 부트 + RESTFUL 웹 서비스 기본설정

- RESTful 웹 서비스는 JSON, XML 및 기타 미디어 유형을 생성하고 활용할 수 있다.

- Spring 또는 Spring boot 기반의 RESTful 웹 서비스를 만들려면 @RestController로

  스프링 컨트롤러를 만들어야 한다.

- Spring Boot 애플리케이션에서 RESTful 웹 서비스를 사용하려면 빌드 파일에

  spring-boot-starter-web을 포함시켜야 한다.

 

- 위의 dependency 는 기본적으로 Jackson JSON 라이브러리를 가지고 온다.

  Spring Boot REST 는 클래스패스에서 jackson-databind를 감지하기 때문에 기본적으로

  JSON 응답을 제공한다.

 

- Spring Boot REST 에서 XML 응답을 지원하려면 spring-boot-starter-web 과 함께

  jackson-dataformat - xml 라이브러리를 제공해야 한다.

 

 


그럼 이제 RESTFUL API 실습을 진행하겠다.

 

먼저 사용할 도메인 클래스와 레파지토리를 만든다.

 

# member.class (도메인)

@Getter
@Setter
@Entity
@Table(name = "tb_members")
@EqualsAndHashCode(of = "id")
@ToString
public class Member {
	
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	@Column(nullable = false, unique = true)
	private String uemail;
	
	private String upw;
	
	private String uname;
	
	private String uphone;
	
	private boolean uphoneCheck;
	
	private String uaddress1;
	
	private String uaddress2;
	
	private int ugrade;
 
	private String upicture;
	
	@Temporal(TemporalType.TIMESTAMP)
	private Date regdate;
	
	@Temporal(TemporalType.TIMESTAMP)
	private Date joinDate;
	
	@Temporal(TemporalType.TIMESTAMP)
	private Date updatedate;
	
	@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
	@JoinColumn(name = "member")
	private List<MemberRole> roles;
	
}

 

# memberRepository

 

# service

  
public interface MemberService {

	Member findById(Long id);
	
	void deleteById(Long id);
	
	Member save(Member member);
	
	void updateById(Long id, Member member);
}

# serviceImpl

@Log
@Service
public class MemberServiceImpl implements MemberService{

	@Autowired
	MemberRepository memberRepo;
	
	@Override
	public Member findById(Long id) {
		Member member = memberRepo.findById(id).orElseThrow(() -> 
		new ResourceNotFoundException("Member", "id", id));
		return member;
	}

	@Override
	public void deleteById(Long id) {
		memberRepo.deleteById(id);
	}

	@Override
	public Member save(Member member) {
		memberRepo.save(member);
		return member;
	}

	@Override
	public void updateById(Long id, Member member) {
		Member m = memberRepo.findById(id).orElseThrow(() -> new ResourceNotFoundException("Member", "id", id));
		log.info("MEMBER: " + member);
		log.info("M: " + m);
		String encryptPw = BCrypt.hashpw(member.getUpw(),BCrypt.gensalt());
		
		
		m.setUemail(member.getUemail());
		m.setUpw(encryptPw);
		m.setUphone(member.getUphone());
		m.setUaddress1(member.getUaddress1());
		m.setUaddress2(member.getUaddress2());
		
		memberRepo.save(m);
	}

}

즉  MemberServiceImpl 에서는 밑단에서 데이터를 어떻게 가공하고 DB에 저장할 지를 만든다.

MemberRepository에서 JpaRepository에서 사용할 기본적인 메소드는 다 오버라이드 하고,

Service에서는 내가 커스텀할 메소드들만 정의하고, 이를 impl에서 구체화한다.

 

그럼 가장 중요한 컨트롤러!!

 

# MemberController

@Log
@Controller
@RequestMapping("/join/*")
public class MemberController {
	
	@Autowired
	MemberRepository memberRepository;
	
	@GetMapping("/memberJoin")
	public void memberJoin() {
		log.info("MEMBERJOIN======================");
	}
	
	@GetMapping("/memberUpdate")
	public void memberUpdate() {
		log.info("MEMBERUPDATE======================");
	}

}

제일 헷갈렸던 부분이다.. 지금 내가 짠 구조가 스프링부트 JPA와 맞는지는 잘 모르겠다.

CRUD 방법은 다양하기 때문에, REST 방식의 CRUD 가 아닐 때, JPA 메소드가 컨트롤러에

작성되던 예제를 처음에 많이 풀었었다.

 

하지만 REST 방식의 CRUD를 해보고 싶었고, REST 컨트롤과 구분되서 작성해봤다..

(혼자 하니까 뭐가 맞는 건지 모르겠음... ㅠㅠ)

 

 

# RestMemberController

@Log
@RestController
@RequestMapping("/member/*")
public class RestMemberController {
	
	@Autowired
	MemberService memberService;
	
	// 사원 입력
	@PostMapping
	public ResponseEntity<Member> save(@RequestBody Member member) {
		String encryptPw = BCrypt.hashpw(member.getUpw(),BCrypt.gensalt());
		member.setUpw(encryptPw);
		    
		MemberRole role = new MemberRole();
		role.setRoleName("BASIC");
		    
		member.setRoles(Arrays.asList(role));
		
		return new ResponseEntity<Member>(memberService.save(member), HttpStatus.OK);
	}
	
	
		@Transactional
		@PutMapping(value = "/{id}", produces = { MediaType.APPLICATION_JSON_VALUE })
		public ResponseEntity<Member> updateMember(@PathVariable("id") Long id, @RequestBody Member member) {
			log.info("putMapping ==================================" + id);
			memberService.updateById(id, member);
			return new ResponseEntity<Member>(member, HttpStatus.OK);
		}
		
		@Transactional
		@DeleteMapping(value = "/{id}", produces = { MediaType.APPLICATION_JSON_VALUE })
		public ResponseEntity<Void> deleteEmp(@PathVariable("id") Long id) {
			memberService.deleteById(id);
			return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
		}
	
}

RESTMemberController 와 기존 컨트롤러와 구분되니 드디어 이해하기 편해졌다.

url 을 어떻게 줘야 하는지, 그냥 뒤에 붙일까 생각하다가, 

동적으로 CRUD 작업이 들어가는 건 그냥 RESTController 에 몰기 시작했다...

 

제일 중요한건 /member/{id} 의 형태로 URL을 클라이언트에서 받기만 하면

제대로 동작한다는 사실이다.

 

보통 이때 REST 동작을 확인하는 구글 폼이 있지만, 나는 화면에서 나오는 것을 보고 싶기

때문에 이번엔 어떤 방식으로 url 을 넘기고, method 를 어떻게 넘겨줘야 하는지 헷갈리기 시작했다.

 

REST 는 (GET / POST / PUT / DELETE) 로 이루어져 있기 때문에, 해당 url 과 메소드를 잘 넘겨줘야

작동한다.

 

# 화면

각각 인풋 타임의 name을 정하는 건 당연한 부분이고,

여기서 받은 정보를 jQuery로 ajax를 활용해 정보를 보냈다.

 

# Script 부분

var Memberupdate = function(obj){
			console.log("update......");
			
			$.ajax({
				type:'put',
				url:'/member/' + obj.id,
				dataType:'json',
				data: JSON.stringify(obj),
				contentType: "application/json",
				beforeSend : function(xhr){
					xhr.setRequestHeader(obj.csrf.headerName, obj.csrf.token);
				},
				contentType: "application/json",
				success:location.href="/"
			});
		};
  
  
  $("#btn_modify").on("click", function(){
		
		var id = [[${#authentication.principal.member.id}]];
		
		var uemail = $("input[name='uemail']").val();
		
		var upw = $("input[name='upw']").val();
		
		var uphone = $("input[name='uphone']").val();
		
		var uaddress1 = $("input[name='uaddress1']").val();
		
		var uaddress2 = $("input[name='uaddress2']").val();
		
		var csrf= JSON.parse('[[${_csrf}]]');
		
		var obj = {id:id, uemail:uemail, upw:upw, uphone:uphone, uaddress1:uaddress1, uaddress2:uaddress2, csrf:csrf};
		Memberupdate(obj);
		
	});

스크립트도 제대로 배우지 않고 구글링만으로 짜다보니 뭐가 뭔지 모르겠다.. ㅠㅠ

그냥 동작하는 거에 의의를 두고 있긴 한데, 실력이 늘고나면 리펙토링을 자주 해야겠다.

 

type 부분으로 method 를 하고,

csrf 빼먹지말고.. 성공했을 때 어떤 자바스크립트 함수를 사용할건지 지정만 하면

그래도 원하는 동작들을 한다.. 

 

 

처음 책으로 배웠을 때는 여러가지 방법을 소개받다 보니 책에 소개된 방법만으로 프로젝트 하나를

온전히 하기에는 역시 무리가 있다.. ㅠㅠ 그래서 인강도 보고, 책도 보고, youtube, 구글링 등 다양한 걸

시도해보고 맞는 걸 찾는 게 생각보다 시간이 오래걸려 멘붕이다.. 

 

화이팅화이팅!! 좀 여유가 생기면, 하나하나 차근 포스팅을 정리할 생각이다. 

 

 

 

* 참고

- @NoArgsConstructor : 기본 생성자 만들어 주세요.

- @ AllArgsConstructor : 모든 파라미터 생성자 만들어주세요.

 

 


# 저장하면 자동 import 시켜주는 속성창 ( import 대상이 1개만 있을 때 적용 )

 

 

Comments