메타데이터 : 데이터를 위한 데이터
기능
컴파일러에게 코드 작성 문법 에러를 체크하도록 정보를 제공
ex) @Valid, @NotNull
소프트웨어 개발툴이 빌드나 배치 시, 코드를 자동으로 생성 할 수 있도록 정보를 제공
ex) @Getter, @NoArgsConstructor
실행 시, 특정 기능을 수행하도록 정보를 제공
ex) @Transactional, @ControllerAdvice
@Getter Annotation을 사용하지 않은 코드
public class ExampleDto {
private String title;
public String getTitle() {
return title;
}
}
@Getter Annotation을 사용한 코드
@Getter
public class ExampleDto {
private String title;
}
해당 코드는 위 코드와 동일하게 동작하며, 코드의 줄이 줄어드는 것 을 확인할 수 있다.
@Entity
class 위에 선언하여, 해당 class가 Entity 임을 나타내어 줍니다.
해당 class 에는 @Id 를 사용하여 Primary Key를 필수적으로 선택하여야 합니다.
@Id
class의 필드 위에 선언하여, 해당 필드가 해당 Entity 의 주요 키(Primary Key, PK)가 될 것 임을 지정해줍니다.
@GeneratedValue
PK 필드 값의 생성 전략을 설정해주기 위하여 사용합니다.
GenerationType 종류
TABLE
데이터베이스에 키 생성 전용 테이블을 하나 만들고 이를 사용하여 기본키를 생성합니다.
SEQUENCE
데이터베이스의 특별한 오브젝트 시퀀스를 사용하여 기본키를 생성합니다.
SEQUENCE : 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트
IDENTITY
기본키 생성을 데이터베이스에 위임합니다. 즉, 데이터베이스의 기본키 생성(번호 증가) 전략을 따라갑니다.
AUTO
JPA 구현체가 자동으로 생성 전략을 결정합니다.
gdsc.blog.domain.Post.java
@Data // Getter, Setter 생성
@NoArgsConstructor // 파라미터가 없는 기본 생성자를 생성해줍니다.
@AllArgsConstructor // 모든 필드 값을 파라미터로 받는 생성자를 생성해줍니다.
@Builder // builder 메서드를 자동 생성해줍니다.
@Entity // SpringContainer 에 Bean으로 등록해주는 @Component 를 포함하고 있습니다.
public class Post {
@Id // PK 를 해당 변수로 하겠다는 뜻.
@GeneratedValue(strategy = GenerationType.IDENTITY)
// 해당 데이터베이스 번호 증가 전략을 따라가겠다는 뜻.
private Long id;
private String title;
@Column(length = 5000) // 최대 길이 설정
private String content;
}
정의
특징
CrudRepository 를 상속받은 JpaRepository 를 상속받도록 함으로써 기본적인 동작(CRUD)이 모두 가능해진다.
JpaRepository는 어떤 엔티티를 메서드의 대상으로 할지를 아래의 키워드로 지정한다.
JpaRepository<대상으로 지정할 엔티티, 해당 엔티티의 PK의 타입>
위 사진은, JpaRepository 의 상속 관계를 보여준다.
JpaRepository
public inerface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID> {
...
void flush();
<S extends T> S saveAndFlush(S entity);
...
}
PagingAndSortingRepository
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
CrudRepository
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
Optional<T> findById(ID id);
boolean existsById(ID id);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> ids);
long count();
void deleteById(ID id);
void delete(T entity);
void deleteAllById(Iterable<? extends ID> ids);
void deleteAll(Iterable<? extends T> entities);
void deleteAll();
}
gdsc.blog.repository.PostRepository.java
public interface PostRepository extends JpaRepository<Post, Long> {
}
Repository 로부터 얻어온 데이터를 다시 가공 후, Controller 를 통해 사용자에게 보내준다.
사용자가 Controller 에게 보내온 요청 데이터를 가공하여, Repository를 통해 Database에 저장, 수정, 또는 삭제한다.
위 사진은, 전체적인 데이터 교환 흐름이다. 각 계층은 DTO 라는 객체를 통해서 데이터를 주고 받는다.
Entity class 와 형태가 거의 유사하지만, DTO class 를 추가로 생성하는 이유
DTO 코드
gdsc.blog.dto.post.WritePostReq.java
@Getter
@Builder
pubilc class WritePostReq {
// Swagger 문서에 명시하기 위한 Annotation
@ApiModelProperty(example = "게시글 제목")
private String title;
@ApiModelProperty(example = "게시글 내용")
private String content;
}
gdsc.blog.service.PostService.java
@Service // 해당 클래스를 Spring Container에 빈으로 등록 후, Spring MVC 서비스로 표시
@RequiredArgsConstructor // final 또는 @NotNull 이 붙은 필드의 생성자를 자동으로 생성해준다.
public class PostService {
// Spring Container에 싱글톤으로 생성되고 관리되는,
// PostRepository Bean 을 의존성 주입 받는다.
private final PostRepository postRepository;
}
PostService - 단건 생성
@Service
@RequiredArgsConstructor
public class PostService {
...
@Transactional // 해당 함수 종료 시, commit 또는 Rollback 수행 (트랜잭션 관리)
public Post save(WritePostReq writePostReq) {
Post post = Post.builder()
.title(writePostReq.getTitle())
.content(writePostReq.getContent()).build();
return postRepository.save(post);
}
}
PostService - 전체 조회
@Service
@RequiredArgsConstructor
public class PostService {
...
@Transactional(readOnly = true)
// JPA 변경감지(Database의 객체 필드값의 변경을 감지하는 내부 기능) Off
public List<Post> findAll() {
return postRepository.findAll();
}
}
PostService - 단건 조회
@Service
@RequiredArgsConstructor
public class PostService {
...
@Transactional(readOnly = true)
public Post findById(Long id) {
return postRepository.findById(id)
.orElseThrow(() -> new NoSuchElementException("id를 확인해주세요!!"));
}
}
PostService - 단건 수정
@Service
@RequiredArgsConstructor
public class PostService {
...
// 해당 서비스 함수 종료 시 => 트랜잭션 종료 시 => 영속화 되어있는 데이터를 DB로 갱신 (flush) => DB에 commit
@Transactional
public Post updateById(Long id, WritePostReq writePostReq) {
Post postEntity = postRepository.findById(id)
.orElseThrow(()->new NoSuchElementException("id를 확인해주세요!!"));
postEntity.setTitle(writePostReq.getTitle());
postEntity.setContent(writePostReq.getContent());
return postEntity;
}
}
영속성 컨텍스트
- Java (객체 지향 프로그래밍) 과 RDB (관계형 데이터베이스) 사이의 간극을 메우기 위해 ORM(Object Relational Mapping) 이라는 개념 등장
- ORM 에서는 영속성 컨텍스트 라는 일종의 논리적인 임시 저장소 공간을 통해 관계형 데이터를 객체 (Object)로 매핑하여 관리
- 즉, 영속화란 관계형 데이터를 영속성 컨텍스트에 임시적으로 저장 후, 객체로 매핑하여 관리하는 것
- 신규 엔티티 영속화, 데이터 캐싱, 변경 감지, 트랜잭션 쓰기 지연 등의 특징을 갖고 있음.
PostService - 단건 삭제
@Service
@RequiredArgsConstructor
public class PostService {
...
@Transactional
public String deleteById(Long id) {
try {
postRepository.deleteById(id);
} catch (EmptyResultDataAccessException e) {
throw new NoSuchElementException("id를 확인해주세요!!");
}
return "ok";
}
}
Repository 로부터 얻어온 데이터를 다시 가공하여, Controller 를 통해 사용자에게 보내준다.
사용자가 Controller 에게 보내온 데이터를 가공하여, Repository를 통해 Database 에 저장, 수정, 또는 삭제한다.
gdsc.blog.controller.PostController.java
gdsc/blog/controller/PostController.java
// final 또는 @NotNull 이 붙은 필드의 생성자를 자동으로 생성해준다.
@RequiredArgsConstructor
// CORS(교차 출처 자원 공유) 해결해주기.
@CrossOrigin(origins = "*", allowedHeaders = "*")
// 공통적인 url은 class에 @RequestMapping으로 설정해준다.
@RequestMapping("/post")
// @Controller + @ResponseBody (Java 객체를 HTTP 요청의 Body 내용으로 매핑하여 반환한다.)
@RestController
public class PostController {
private final PostService postService;
}
PostController - 단건 생성
...
public class PostController {
...
@ApiOperation(value = "게시글을 등록", notes = "게시글을 등록합니다.") // Swagger 설명 설정
@PostMapping("")
public ResponseEntity<Post> save(@RequestBody WritePostReq writePostReq) {
return new ResponseEntity<>(postService.save(writePostReq), HttpStatus.CREATED);
}
}
PostController - 전체 조회
...
public class PostController {
...
@ApiOperation(value = "게시글 전체 조회", notes = "게시글을 전체 조회합니다.")
@GetMapping("")
public ResponseEntity<List<Post>> findAll() {
return new ResponseEntity<>(postService.findAll(), HttpStatus.OK);
}
}
PostController - 단건 조회
...
public class PostController {
...
@ApiImplicitParam(name = "id", value = "게시글 아이디")
@ApiOperation(value = "게시글 단건 조회", notes = "게시글 id를 이용하여 단건 조회합니다.")
@GetMapping("/{id}")
public ResponseEntity<Post> findById(@PathVariable Long id) {
return new ResponseEntity<>(postService.findById(id), HttpStatus.OK);
}
}
PostController - 단건 수정
...
public class PostController {
...
@ApiImplicitParam(name = "id", value = "게시글 아이디")
@ApiOperation(value = "게시글 단건 수정", notes = "id에 해당하는 게시글을 수정합니다.")
@PutMapping("/{id}")
public ResponseEntity<Post> updateById(@PathVariable Long id, @RequestBody WritePostReq writePostReq) {
return new ResponseEntity<>(postService.updateById(id, writePostReq), HttpStatus.OK);
}
}
PostController - 단건 삭제
...
public class PostController {
...
@ApiImplicitParam(name = "id", value = "게시글 아이디")
@ApiOperation(value = "게시글 단건 삭제", notes = "id에 해당하는 게시글을 삭제합니다.")
@DeleteMapping("/{id}")
public ResponseEntity<String> deleteById(@PathVariable Long id) {
return new ResponseEntity<>(postService.deleteById(id), HttpStatus.OK);
}
}
@ControllerAdvice + @ResponseBody
예외 처리를 담당하는 @ControllerAdvice 와 HTTP Response Body 로 결과값을 전달하기 위한 @ResponseBody 가 합쳐진 Annotation 입니다.
@ControllerAdvice
클래스에 선언하게 되면, 모든 Controller 에 대하여 전역적으로 발생할 수 있는 예외를 잡아서 처리해주는 방식 입니다.
@ExceptionHandler
메서드 단에 선언하여, 특정 예외 클래스에 대한 예외 처리를 할 수 있습니다.
gdsc.blog.dto.ErrorResponse.java
@Getter
@Builder
public class ErrorResponse {
private String code;
private String message;
}
gdsc.blog.advice.GlobalExceptionHandler.java
@RestControllerAdvice
public class GlobalExceptionHadler {
// 특정 예외 클래스 발생시 실행되도록 설정
@ExceptionHandler({NoSuchElementException.class})
protected ResponseEntity<ErrorResponse> handleNoSuchElementFoundException(NoSuchElementException e) {
final ErrorResponse errorResponse = ErrorResponse.builder()
.code("Item Not Found")
.message(e.getMessage()).build();
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse);
}
}