티스토리 뷰

자, 지난시간 JPA를 위해 DB도 만들고 셋팅도 하고 Table도 만들고 이것저것 했다.

이제 Repository와 Service 를 만들어서 user 테이블의 정보를 가져오는 API를 만드는 것이 이번 포스팅의 목적이다.


자, 우리가 순차적으로 작업을 진행할 필요가 있는데 무엇을 먼저 작업하면 좋을지 하는 거다.

우리는 user테이블에서 데이터를 가져와서 API를 이용해 클라이언트에게 전달할 건데 가장 필요한게 무엇일까?


대부분의 개발자들이 먼저 하는 작업이 무엇이냐하면 Controller먼저 만든다. 일단 요청을 해야 뭔가를 얻어올 수 있다고 생각해서다. 하지만 뭔가를 얻어온다는 말에 속에 간과하고 있는게 한가지 있다. 그것이 바로 "뭔가" 라고 하는 단어다.

"뭔가"를 얻어오려면 그 "뭔가"를 담을 수 있는 통이 필요한데 그것이 있어야 비로소 DB에서 데이터를 가져오고, 그 데이터를 가공할 수도 있다. 한마디로 "여자친구에게 바라는 점이 무엇인지 묻기전에 여자친구가 있는지 부터 물어보는게 예의"인것 처럼 말이다.



이런 이유로 일단 엔티티(Entity)라는걸 만들어 볼거다. 이 엔티티라는게 무어냐 물어본다면 사전적인 의미로는 "실체"라고도 하고 "객체"라고도 하는데 개발에서도 이 의미와 별반 다르지 않은 "어떤 데이터의 실질적인 정의" 라고 생각하면 되겠다.



그래서 지금부터 Entity클래스를 만들건데 Lombok 라이브러리를 아주 적극 활용하겠다.


아, 뭔가 클래스를 새로만들땐 Package부터 만들어주는게 인지상정이다. 이 Entity 클래스는 "com.example.sample.data.entity"에 User라는 클래스명으로 만들도록 하겠다.


package com.example.sample.data.entity;

import java.sql.Timestamp;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import lombok.Getter;
import lombok.Setter;

@Entity
@Table(name = "user")
@Setter
@Getter
public class User {

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private @Column(name = "uid") Long uid;
	private @Column(name = "email") String email;
	private @Column(name = "password") String password;
	private @Column(name = "nick") String nick;
	
	@CreationTimestamp
	private @Column(name = "cdate") Timestamp cdate;
	
	@UpdateTimestamp
	private @Column(name = "udate") Timestamp udate;
}


이렇게 만들면 Entity는 완성이 되었다. 음, 여기서 가장 중요한 부분은 @Column 어노테이션인데 이 Column 어노테이션에서는 DB의 실제 컬럼명을 입력해주면 된다. 그리고 @Id 어노테이션을 PK(Prime Key: 기본키)를 의미하며 GerneratedValue 어노테이션을 통해서 이 PK값을 어떻게 입력할 것인지 정의할 수 있다.


GenerationType.AUTO 

특정 DB에 맞게 자동 선택 

GenerationType.IDENTITY 

DB의 Identity 컬럼을 이용 

GenerationType.SEQUENCE 

DB의 시퀀스 컬럼을 이용 

GenerationType.TABLE

유일성이 보장된 데이터베이스 테이블을 이용 


위의 내용으로 정의하는데 조금 더 자세한 이야기는 링크를 따라가 공부해보도록 하자.

(http://jsaver.tistory.com/entry/Id%EC%99%80-GeneratedValue-%EC%95%A0%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98)


그리고 별도로 @CreationTimestamp의 경우엔 데이터를 Insert해줄때 자동으로 시간을 입력해주고, @UpdateTimestamp는 Update해줄때 자동으로 시간을 입력해준다.


어노테이션의 의미를 대충 정리했고 Entity도 만들었다면 이제 Controller부터 만들어보도록하자. 이 Controller를 만든다는 것은 Restful API를 만든다는 이야기고 이 Restful API는 기본적인 기능규칙에 의해서 만들어진다고 생각하면 처음 공부할 때 편하다.


대부분(?)의 웹서비스는 CRUD를 기초로 만들어져있다.(C - Create, R - Read, U - Update, D - Delete를 의미한다.) UI의 구성도 API 구조도 이 CRUD를 기초로 만들면 일단 큰 골격에서 크게 벗어나지 않는다.

이런 내용을 Restful APIs에서는 Request method로 구분해서 처리를 하는데 아래와 같이 정리한다.


Method 

수행작업 

GET 

목록 혹은 항목을 조회할 때 (Read)

POST 

항목을 등록할 때 (Create)

PUT 

항목을 수정할 때 (Update)

DELETE 

항목을 삭제할 때(Delete)


이 항목을 어디서 정의하느냐 하면 우리가 만들 Controller에서 RequestMapping 어노테이션에서 정의한다.

이제부터 나는 위의 내용을 토대로 UserController라는 클래스를 com.example.sample.controller 패키지에 만들도록 하겠다.


UserController.java


package com.example.sample.controller;

import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.sample.model.ApiResponseMessage;
import com.example.sample.data.entity.User;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@RestController
@RequestMapping(value = "/user")
@Api(value = "UserController", description = "사용자 관련 API",basePath = "/user")
public class UserController {


	@RequestMapping(value = "", method = RequestMethod.GET)
	@ApiOperation(value = "사용자 목록 조회", notes = "사용자 목록을 조회하는 API.")
	public List<User> getUserList(){
		return null;
	}

	@RequestMapping(value = "/{uid}", method = RequestMethod.GET)
	@ApiOperation(value = "사용자 정보 조회", notes = "사용자의 정보를 조회하는 API. User entity 클래스의 uid값을 기준으로 데이터를 가져온다.")
	public User getUser( @PathVariable("uid") Long uid ){
		return null;
	}

	@RequestMapping(value = "", method = RequestMethod.POST)
	@ApiOperation(value = "사용자 정보 등록", notes = "사용자 정보를 저장하는 API. User entity 클래스로 데이터를 저장한다.")
	public ResponseEntity<ApiResponseMessage> insertUser( User user ){
		ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", "");
		ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK);
		
		return response;
	}
	
	@RequestMapping(value = "", method = RequestMethod.PUT)
	@ApiOperation(value = "사용자 정보 수정", notes = "사용자 정보를 수정하는 API. User entity 클래스로 데이터를 수정한다.
이때엔 정보를 등록할 때와는 다르게 uid 값을 함깨 보내줘야한다.") public ResponseEntity<ApiResponseMessage> updateUser( User user ){ ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", ""); ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK); return response; } @RequestMapping(value = "/{uid}", method = RequestMethod.DELETE) @ApiOperation(value = "사용자 정보 삭제", notes = "사용자 정보를 삭제하는 API. User entity 클래스의 uid 값으로 데이터를 삭제한다.") public ResponseEntity<ApiResponseMessage> deleteUser( @PathVariable("uid") Long uid ){ ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", ""); ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK); return response; } }


처음에 개발에 들어갈때는 일단 뭔가 기능을 구현하는 것보다 전체적으로 구조를 잡아 나가는 것이 중요하다. 이 과정을 통해 개발이 진행되면 전체적을 기능의 목록들이 나열되게 되고 위와 같이 아무 기능은 없으나 앞으로 개발해야하는 기능의 목록들을 나열하면 프로젝트의 큰 틀이 눈에 들어오게된다.


일단 위와 같이 Controller 클래스를 짰다면 이제 Service 인터페이스 클래스를 만들어보도록 하자. 

인터페이스 클래스가 뭔지 모르는 사람은 검색을 좀 해서 개념을 잡고 돌아오거나 혹은 나중에(언제가 될지 모르겠지만) 진행할 인터페이스와 추상화 클래스에 대한 설명으로 포스팅하는 것을 기다리도록. 이번 "프로젝트 시작하기" 시리즈의 목적은 "내가 뭘 좀 모르지만 일단 프로젝트를 진행해야겠는데 뭘 어떻게하는지 알려주는" 것에 있다. 그러므로 자세하게 설명하고 싶은 마음은 있으나 자세하게 설명했다가는 이 포스팅을 끝내지 못할 수도 있으니 일단 너그러운 마음으로 넘어가도록 하자.


나는 com.example.sample.service 패키지에 UserService 인터페이스 클래스를 만들거다. 


UserService.java


package com.example.sample.service;

import java.util.List;

import com.example.sample.data.entity.User;

public interface UserService {
	/**
	 * 사용자 목록 조회
	 * @return
	 */
	public List<User> selectUserList();
	
	/**
	 * 사용자 조회
	 * @param uid
	 * @return
	 */
	public User selectUser(Long uid);
	
	/**
	 * 사용자 등록
	 * @param user
	 */
	public void insertUser(User user);
	
	/**
	 * 사용자 정보 수정
	 * @param user
	 */
	public void updateUser(User user);
	
	/**
	 * 사용자 삭제
	 * @param uid
	 */
	public void deleteUser(Long uid);
}


인터페이스 클래스를 만들었다면 이제 구현체도 만들어야지.

com.example.sample.service.impl 패키지에 UserServiceImpl 이라는 클래스를 만들어 방금 만든 UserService 인터페이스 클래스의 구현체를 만들건데 아래의 이미지를 따라서 만들면 작업하기가 편하다.


클래스의 이름을 적고 Interfaces 항목 오른쪽에 있는 Add라는 버튼을 클릭한다.



여기에서 우리가 방금 만든 UserService를 검색하면 Matching Items에 항목이 나타날텐데 이 때 Package정보를 잘 보고 선택한 다음 OK버튼을 클릭한다.



위의 상태가 되었으면 Finish버튼을 클릭한다. 그러고 나면 알아서 인터페이스 클래스의 내용을 토대로 메서드들을 만들어준다.




그럼 일단 UserService에 대한 아주 기본적인 내용이 만들어졌으니 UserController 클래스에 약간(?)의 수정을 하도록 하자.



package com.example.sample.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.sample.model.ApiResponseMessage;
import com.example.sample.data.entity.User;
import com.example.sample.service.UserService;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@RestController
@RequestMapping(value = "/user")
@Api(value = "UserController", description = "사용자 관련 API",basePath = "/user")
public class UserController {

	@Autowired
	UserService userService;
	

	@RequestMapping(value = "", method = RequestMethod.GET)
	@ApiOperation(value = "사용자 목록 조회", notes = "사용자 목록을 조회하는 API.")
	public List<User> getUserList(){
		return userService.selectUserList();
	}

	@RequestMapping(value = "/{uid}", method = RequestMethod.GET)
	@ApiOperation(value = "사용자 정보 조회", notes = "사용자의 정보를 조회하는 API. User entity 클래스의 uid값을 기준으로 데이터를 가져온다.")
	public User getUser( @PathVariable("uid") Long uid ){
		return userService.selectUser(uid);
	}

	@RequestMapping(value = "", method = RequestMethod.POST)
	@ApiOperation(value = "사용자 정보 등록", notes = "사용자 정보를 저장하는 API. User entity 클래스로 데이터를 저장한다.")
	public ResponseEntity<apiresponsemessage> insertUser( User user ){
		ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", "");
		ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK);
		
		try {
			userService.insertUser(user);
		} catch(Exception ex){
			message = new ApiResponseMessage("Failed", "사용자 등록에 실패하였습니다.", "ERROR00001", "Fail to registration for user information.");
			response = new ResponseEntity<apiresponsemessage>(message, HttpStatus.INTERNAL_SERVER_ERROR);
		}
		
		return response;
	}
	
	@RequestMapping(value = "", method = RequestMethod.PUT)
	@ApiOperation(value = "사용자 정보 수정", notes = "사용자 정보를 수정하는 API. User entity 클래스로 데이터를 수정한다.
이때엔 정보를 등록할 때와는 다르게 uid 값을 함깨 보내줘야한다.") public ResponseEntity<ApiResponseMessage> updateUser( User user ){ ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", ""); ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK); try { userService.updateUser(user); } catch(Exception ex){ message = new ApiResponseMessage("Failed", "사용자 정보 수정에 실패하였습니다.", "ERROR00002", "Fail to update for user information."); response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR); } return response; } @RequestMapping(value = "/{uid}", method = RequestMethod.DELETE) @ApiOperation(value = "사용자 정보 삭제", notes = "사용자 정보를 삭제하는 API. User entity 클래스의 uid 값으로 데이터를 삭제한다.") public ResponseEntity<ApiResponsemessage> deleteUser( @PathVariable("uid") Long uid ){ ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", ""); ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK); try { userService.deleteUser(uid); } catch(Exception ex){ message = new ApiResponseMessage("Failed", "사용자 정보 삭제에 실패하였습니다.", "ERROR00003", "Fail to remove for user information."); response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR); } return response; } }


뭐가 많이 변했는데 데이터를 등록하고 수정하고 삭제하는 부분의 코드가 조금(?) 많이 늘었고 Autowired라고 해서 UserService라는 인터페이스 클래스를 자동으로 주입하게끔 해놨다.

UserServiceImpl 클래스도 아니고 왜 인터페이스 클래스를 가져다 놓은걸까.


일단 조금 이따가 UserServiceImpl 클래스에 @Service라고 하는 어노테이션을 박아넣을텐데 이 Service 어노테이션은 자신이 어떤 인터페이스클래스를 구현했는지 판단하여 자동으로 @Autowired의 인터페이스 객체에 구현체를 넣어 사용할 수 있게 해준다. 뭔 소린지 모르겠다고? 당연하지, 이걸 이해하기 위해서는 @Autowired와  @Service, @Repository 와 Interface, Abstract의 차이점과 도데체 Interface가 뭐고 그와 같이 등장하는 Abstract(추상화) 클래스들은 무엇이며 이걸 이용해 할 수 있는 일은 무엇인지, 왜 이용해야하는지 등등등등 알아야할게 너무 많거든.

어떤 한 개념을 이해하기 위해 수반되는 배경지식의 양이 기하급수적으로 늘어나는데 그걸 이 포스팅 한장으로 배우기엔 설명하는 나도, 배우는 당신도 힘들다. 솔직히 Interface 클래스 하나만 배운다고 해도 아마 하나의 카테고리로 시리즈 포스팅을 해야할 걸.

그러니 잘 모르더라도 따라가도록 하자. 배경지식들은 차차 배운다고 생각하고. 지금 중요한건 사이트를 만드는거지 왜 Interface 클래스를 가져와 쓰는게 아니니까.


일단 이렇게 만들었으면 Repository 인터페이스 클래스를 만들건데 여기서 아주 아주 아주 JPA-Hibernate의 큰 장점이 등장하게 된다.

일단 만들고 나서 설명해주겠다.


Package: com.example.sample.data.repository

Class : UserRepository

Class Type : Interface

package com.example.sample.data.repository;

import org.springframework.data.repository.CrudRepository;

import com.example.sample.data.entity.User;

public interface UserRepository extends CrudRepository<User, Long>{

}


끝이다.

더이상 뭘 하지 않아도 된다.

어찌된 영문이냐고?


일단 우리는 최초에 "spring-boot-starter-data-jpa" 라이브러리를 pom.xml에 추가한걸 기억해야한다. 그래서 우리가 JPA의 기능을 이용할 수 있는 거니까. 그리고 JPA의 기능을 오늘 배우는게 가장 중요하다. 기 기능들을 잘 이용하면 생산성이 비약적으로 높아지기 때문이다. 생산성이 높아진다는 이야기는 곧 야근을 덜 할 수 있다는 이야기다.


일단 JPA의 기능은 3가지로 요약할 수 있다.

  • 메소드의 이름으로 쿼리 생성
  • 메소드의 이름으로 JPA NamedQuery 호출
  • @Query 어노테이션을 사용해서 Repository Interface에 쿼리를 직접 정의


이렇게 3가지를 이야기 한다. 그 중에서도 첫번째 기능이 중요하다. 두번째 기능이나 세번째 기능은 뭐 인터페이스 클래스에다가 쿼리를 직접 입력할 수 있다는건데 이건 생산성하고 별로 상관없는 이야기니까 패스.

메소드의 이름으로 쿼리를 생성한다는 이야기가 무엇이냐면 이런거다. JPA가 미리 정의해 놓은 메소드 명명 규칙이 있는데 그 규칙을 따라 인터페이스 클래스에 메소드를 선언하면 서버를 기동하거나 컴파일 단계에서 Spring Framework에서 자동으로 구현체를 만들어 준다.

이게 얼마나 중요한 내용이냐면 예전에 Mybatis를 사용했을 때나 혹은 이런 ORM 관련 라이브러리가 없었을 적에는 코드에 직접 SQL 문을 작성해야했다.


SELECT
    *
FROM
    user
WHERE
    email LIKE '%test%'

이런 단순한 쿼리문 부터 아주 복잡한 쿼리문까지 만들었어야 했다.  그리고 메서드를 만들고 그걸 동작하게하고.. 뭐 암튼 할일이 엄청 많았는데 JPA를 이용하면 인터페이스 클래스에 아래와 같이 코딩하면 된다.


package com.example.sample.data.repository;

import java.util.List;

import org.springframework.data.repository.CrudRepository;

import com.example.sample.data.entity.User;

public interface UserRepository extends CrudRepository<User, Long>{
	List<User> findByEmail(String email);

}


위와같이 코딩을 하면 그냥 email 컬럼을 검색해서 목록으로 결과물을 뱉어낸다. 쿼리는 어떻게 하냐고? 짤 필요없다. 그냥 JPA가 알아서 만들어준다. 언제 만들어주느냐면 서버가 기동될때, 코드가 컴파일 될 때 자동으로 SQL 문을 짜고 DB와 연동해 데이터 가져올 구현체를 생성해서 어플리케이션에 등록해준다.

앞으로 Query몰라도 어플리케이션을 만들 수 있다. 물론 복잡하고 Join도 해야하고 막 그런건 어떻게 하느냐고 물어보시는 분들 계신데 그건 나중에 QueryDSL라는걸 공부할 때 마저 하는걸로 하고, 오늘은 이 메소드 네이밍 규칙을 알아가도록 하자.


KeywordSampleJPQL snippet

And

findByLastnameAndFirstname

… where x.lastname = ?1 and x.firstname = ?2

Or

findByLastnameOrFirstname

… where x.lastname = ?1 or x.firstname = ?2

Is,Equals

findByFirstname,findByFirstnameIs,findByFirstnameEquals

… where x.firstname = 1?

Between

findByStartDateBetween

… where x.startDate between 1? and ?2

LessThan

findByAgeLessThan

… where x.age < ?1

LessThanEqual

findByAgeLessThanEqual

… where x.age ⇐ ?1

GreaterThan

findByAgeGreaterThan

… where x.age > ?1

GreaterThanEqual

findByAgeGreaterThanEqual

… where x.age >= ?1

After

findByStartDateAfter

… where x.startDate > ?1

Before

findByStartDateBefore

… where x.startDate < ?1

IsNull

findByAgeIsNull

… where x.age is null

IsNotNull,NotNull

findByAge(Is)NotNull

… where x.age not null

Like

findByFirstnameLike

… where x.firstname like ?1

NotLike

findByFirstnameNotLike

… where x.firstname not like ?1

StartingWith

findByFirstnameStartingWith

… where x.firstname like ?1(parameter bound with appended %)

EndingWith

findByFirstnameEndingWith

… where x.firstname like ?1(parameter bound with prepended %)

Containing

findByFirstnameContaining

… where x.firstname like ?1(parameter bound wrapped in %)

OrderBy

findByAgeOrderByLastnameDesc

… where x.age = ?1 order by x.lastname desc

Not

findByLastnameNot

… where x.lastname <> ?1

In

findByAgeIn(Collection<Age> ages)

… where x.age in ?1

NotIn

findByAgeNotIn(Collection<Age> age)

… where x.age not in ?1

True

findByActiveTrue()

… where x.active = true

False

findByActiveFalse()

… where x.active = false

IgnoreCase

findByFirstnameIgnoreCase

… where UPPER(x.firstame) = UPPER(?1)

출처는 https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation

간혹 이런분들이 있다. "저는 조건이 좀 까다로와서요" 라면서 조건들을 나열하는 분들. 그런 분들을 위해 조건들을 아래와 같이 나열하는 것도 가능하다.


package com.example.sample.data.repository;

import java.sql.Timestamp;
import java.util.List;

import org.springframework.data.repository.CrudRepository;

import com.example.sample.data.entity.User;

public interface UserRepository extends CrudRepository<User, Long>{
	List<User> findByEmailLikeOrickLikeAndCdateAfter(String email, String nick, Timestamp cdate);
}


아, 그리고 이러한 규칙을 통해 만들어진 CrudRepository 인터페이스 클래스를 이용하면 기본적인 메소드들을 제공해서 개발자로 하여금 아무것도 코딩하지 않더라도 기능을 구현해준다. 정말이다. 아무것도 안해도 된다. 지원하는 기능은 아래와 같다.


count()개수 확인
delete()1건 또는 여러 건 삭제
deleteAll()모두 삭제
exists()ID 있는지 확인
findAll()전체 또는 ID목록으로 조회
findOne()ID로 조회
save()1건 또는 여러 건 저장, 존재하는 데이터는 수정

막 중구난방으로 설명이 진행되서 정신이 없을텐데 Repository에 대해 이제부터 정리를 좀 하겠다.


1. 데이터의 흐름

데이터의 흐름은 사용자의 요청 -> 컨트롤러(Controller) -> 서비스(Service) -> 리포지토리(Repository) -> DB 방향으로 Request(요청) 절차가 수행되고 이와 반대의 흐름으로 Response(응답) 절차를 수행하게 된다.


2. Controller

사용자의 요청과 응답을 직접적으로 처리하는 단계로 요청 내용을 정리하고 그에 맞는 응답을 할 수 있도록 서비스 메소드를 호출한다.


3. Service

컨트롤러에서 요청하는 데이터를 Repository의 메소드를 호출해 정제하고 정리해서 컨트롤러에 전달한다.

이때 Transaction을 정의하고 DB 커넥션 풀을 적절하게 관리할 수 있도록 설정한다. Transaction을 적절하게 설정하게 되면 데이터 처리중 문제가 발생했을 때 RollBack을 할 수 있다.


2. Repository

우리는 JPA 라이브러리를 이용하고 있어서 인터페이스클래스안에 네이밍을 맞춰서 메소드를 선언해놓으면 Spring boot가 기동할때 자동으로 구현체를 만들어서 붙여준다. 구현체를 만드느라 시간 낭비할 필요없다. 클라이언트의 요구사항이 매우 난해하여 굳이 뭔가를 해야할 때는 나중에 QueryDSL이라는 것을 통해 포스팅을 하겠다.



이제 좀 정리가 되는가? 그러면 UserController, UserService, UserServiceImpl, UserRepository 코드의 완성형을 아래에 쭉 나열해놓겠다.


Package: com.example.sample.controller

Class : UserController

Class Type : Class


package com.example.sample.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.example.sample.data.entity.User;
import com.example.sample.model.ApiResponseMessage;
import com.example.sample.service.UserService;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

@RestController
@RequestMapping(value = "/user")
@Api(value = "UserController", description = "사용자 관련 API",basePath = "/user")
public class UserController {

	@Autowired
	UserService userService;
	

	@RequestMapping(value = "", method = RequestMethod.GET)
	@ApiOperation(value = "사용자 목록 조회", notes = "사용자 목록을 조회하는 API.")
	public List<User> getUserList(){
		return userService.selectUserList();
	}

	@RequestMapping(value = "/{uid}", method = RequestMethod.GET)
	@ApiOperation(value = "사용자 정보 조회", notes = "사용자의 정보를 조회하는 API. User entity 클래스의 uid값을 기준으로 데이터를 가져온다.")
	public User getUser( @PathVariable("uid") Long uid ){
		return userService.selectUser(uid);
	}

	@RequestMapping(value = "", method = RequestMethod.POST)
	@ApiOperation(value = "사용자 정보 등록", notes = "사용자 정보를 저장하는 API. User entity 클래스로 데이터를 저장한다.")
	public ResponseEntity<ApiResponseMessage> insertUser( User user ){
		ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", "");
		ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK);
		
		try {
			userService.insertUser(user);
		} catch(Exception ex){
			message = new ApiResponseMessage("Failed", "사용자 등록에 실패하였습니다.", "ERROR00001", "Fail to registration for user information.");
			response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR);
		}
		
		return response;
	}
	
	@RequestMapping(value = "", method = RequestMethod.PUT)
	@ApiOperation(value = "사용자 정보 수정", notes = "사용자 정보를 수정하는 API. User entity 클래스로 데이터를 수정한다.
이때엔 정보를 등록할 때와는 다르게 uid 값을 함깨 보내줘야한다.") public ResponseEntity<ApiResponseMessage> updateUser( User user ){ ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", ""); ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK); try { userService.updateUser(user); } catch(Exception ex){ message = new ApiResponseMessage("Failed", "사용자 정보 수정에 실패하였습니다.", "ERROR00002", "Fail to update for user information."); response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR); } return response; } @RequestMapping(value = "/{uid}", method = RequestMethod.DELETE) @ApiOperation(value = "사용자 정보 삭제", notes = "사용자 정보를 삭제하는 API. User entity 클래스의 uid 값으로 데이터를 삭제한다.") public ResponseEntity<ApiResponseMessage> deleteUser( @PathVariable("uid") Long uid ){ ApiResponseMessage message = new ApiResponseMessage("Success", "등록되었습니다.", "", ""); ResponseEntity<ApiResponseMessage> response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.OK); try { userService.deleteUser(uid); } catch(Exception ex){ message = new ApiResponseMessage("Failed", "사용자 정보 삭제에 실패하였습니다.", "ERROR00003", "Fail to remove for user information."); response = new ResponseEntity<ApiResponseMessage>(message, HttpStatus.INTERNAL_SERVER_ERROR); } return response; } }


Package: com.example.sample.service

Class : UserService

Class Type : Interface


package com.example.sample.service;

import java.util.List;

import com.example.sample.data.entity.User;

public interface UserService {
	/**
	 * 사용자 목록 조회
	 * @return
	 */
	public List<User> selectUserList();
	
	/**
	 * 사용자 조회
	 * @param uid
	 * @return
	 */
	public User selectUser(Long uid);
	
	/**
	 * 사용자 등록
	 * @param user
	 */
	public void insertUser(User user);
	
	/**
	 * 사용자 정보 수정
	 * @param user
	 */
	public void updateUser(User user);
	
	/**
	 * 사용자 삭제
	 * @param uid
	 */
	public void deleteUser(Long uid);
}


Package: com.example.sample.data.service.impl

Class : UserServiceImpl

Class Type : Class


package com.example.sample.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.example.sample.data.entity.User;
import com.example.sample.data.repository.UserRepository;
import com.example.sample.service.UserService;
import com.google.common.collect.ImmutableList;

@Service
public class UserServiceImpl implements UserService {

	@Autowired
	UserRepository userRepository;
	
	@Override
	@Transactional(readOnly = true, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
	public List<User> selectUserList() {
		return ImmutableList.copyOf(userRepository.findAll());
	}

	@Override
	@Transactional(readOnly = true, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
	public User selectUser(Long uid) {
		return userRepository.findOne(uid);
	}

	@Override
	@Transactional(readOnly = false, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
	public void insertUser(User user) {
		userRepository.save(user);
	}

	@Override
	@Transactional(readOnly = false, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
	public void updateUser(User user) {
		userRepository.save(user);
	}

	@Override
	@Transactional(readOnly = false, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
	public void deleteUser(Long uid) {
		userRepository.delete(uid);

	}
}


Package: com.example.sample.data.data.repository

Class : UserRepository

Class Type : Interface


package com.example.sample.data.repository;

import org.springframework.data.repository.CrudRepository;

import com.example.sample.data.entity.User;

public interface UserRepository extends CrudRepository<User, Long>{}


이렇게 위와같이 코드를 작성하고 어플리케이션을 기동하고 난 뒤에 localhost:8080/swagger-ui.html 로 가보자. 아무 화면도 안나오는 사람들이 간혹 있는데 크롬브라우저 기준으로 컨트롤 + 쉬프트 + R 키를 누르면(맥OS에선 커멘드 + 쉬프트 + R) 이런 화면이 나올거다.



새로 추가된 UserController 가 보이는가? 그러면 저 타이틀을 클릭해보자.


이렇게 API들 목록이 나열된다.


아직 우리가 POST랑 Update랑 준비된게 없으니 일단 처음 "[GET] /user" API를 클릭해보자.

그리고 조금 아래부분에 Try it out!이라는 버튼을 누른다.


그러면 갑자기 STS의 콘솔창에서 자동으로 쿼리를 만들어 던진다.



그리고 가져온 데이터는 브라우저에 표시된다.




우리가 DB에 넣어줬던 그 데이터다.


그러면 [GET] /user/{uid} 를 호출해볼까



uid 값을 1로 만들어서 던지면 uid가 1로 등록되었던 데이터가 Response Body에 응답되었다는것을 확인할 수 있다.

이때 자동으로 생성된 SQL을 보면


이렇게 자동으로 쿼리문도 만들어지게 된다.


이로써 아주 기초적인 Restful APIs 프로젝트를 시작했다. 이제 우리는 웹 서비스의 기초 모듈들을 개발하면서 발생할 수 있는 이슈들에 대해서 차차 공부해가도록 하자.


이상 여기까지.


댓글
댓글쓰기 폼