현의 개발 블로그

JPA 활용한 User CRUD 개발 본문

스프링부트 실습/도서관리 웹 개발

JPA 활용한 User CRUD 개발

hyun2371 2023. 4. 17. 14:38

 

JDBC의 한계

앞에서는 SQL을 직접 사용해 DB에 접근하였다. 

이러한 방식은 다음과 같은 한계점이 존재한다.

 

1. SQL문을 직접 작성하기 때문에 오타가 생길 수 있다.

 

2. 반복 작업이 많아진다. 매번 INSERT, SELECT, UPDATE, DELETE 쿼리를 날려줘야 한다.

    또한, SELECT 쿼리를 작성할 때 필드를 하나씩 매핑해줘야 한다.

 

3. 특정 데이터베이스에 종속된다.

    SQL도 MySQL, MSSQL 등 문법이 조금씩 다른데, DB를 바꿔야 한다면 코드를 그에 맞게 수정해야 한다.

 

 

 

JPA(Java Persistence API) 정의

객체와 관계형 데이터베이스를 짝지어 데이터를 영구 저장하도록 정해진 규칙이다.

 

*Persistence: 영속성; 데이터를 생성한 프로그램이 종료되어도, 그 데이터는 영구적인 속성을 갖는다.

*객체와 데이터베이스의 테이블을 짝짓는 것을 ORM(Object-Relation Mapping)이라고 한다.

 

JPA는 규칙(interface)이고, Hibernate를 구현체로 많이 사용한다.

Hibernate는 내부적으로 JDBC를 포함한다.

 

 

 

JPA로 유저 CRUD 개발하기

CRUD는 Create, Read, Update, Delete를 의미한다.

이전에 유저 CRUD API를 JDBC로 작성했다면, 이번에는 JPA를 활용할 것이다.

JPA를 사용하기 위해 application.yml에 해당 코드를 작성한다.

spring:	
	jpa:
	  hibernate:
	    ddl-auto: none
	  properties:
	    hibernate:
	      show_sql: true
	      format_sql: true
	      dialect: org.hibernate.dialect.MySQL8Dialect

dialect는 데이터베이스 방언을 의미한다.

 

 

유저 엔티티 클래스 생성

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {
    @Id @GeneratedValue
    private Long id;

    @Column(nullable = false, length = 20)
    private String name;
    private Integer age;
}

- @Entity

   객체와 테이블을 매핑해준다. 스프링이 User객체와 user 테이블을 같은 것으로 간주한다.

 

- @Id

   해당 필드를 기본키로 간주한다.

 

- @GeneratedValue

    다양한 옵션이 있는데 default는 AUTO로, 각 DB 종류에 맞는 생성 전략을 적용해준다.

 

- @Column

   name, nullable, length 등의 옵션을 부여할 수 있다.  여기서는 null을 허용하지 않고, 길이를 20으로 제한했다.

 

테이블과 매핑된 객체는 파라미터가 없는 기본 생성자가 꼭 필요하다.

@NoArgsConstructor는 인수 없는 생성자를 자동 생성하는데, protected 접근 권한을 따로 부여했다.

 

 

 

유저 생성

UserRepository 인터페이스를 만드는데 JpaRepository를 상속받도록 하자

테이블 매핑 객체인 User와 유저 테이블 id인 Long 타입을 각각 적어줘야 한다.

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

 

UserRepository 안에는 save 기능이 내장되어 있다. User 객체를 만들어 save()를 호출하면 DB에 저장된다.

public void saveUser(UserCreaterRequest request){
   userRepository.save(new User(request.getName(), request.getAge());
}

 

 

유저 조회

public List<UserResponse> getUsers(){
    List<User> users = userRepository.findAll();
    return users.stream()  //User → UserResponse
            .map(UserResponse::new)
            .collect(Collectors.toList());
}

List <User>를 받아 List<UserResponse> 로 변경하는 코드이다.

* UserResponse::new는 new UserResponse(user.getId(), user.getName(), user.getAge())와 동일하다.

 

 

 

유저 업데이트

id를 통해 User를 가져와 User 유무를 확인하고, User가 있다면 데이터를 수정한다.

public void updateUser(UserUpdateRequest request){
    User user = userRepository.findById(request.getId())
    			.orElseThrow(IllegalArgumentException::new);
     user.updateName(request.getName());
     userRepository.save(user);
}

findById() 호출 시 Optional<User>가 반환된다.

optional.orElseThrow()를 통해 User가 없으면 예외를 던진다.

 

객체를 업데이트해주고 save 메서드를 호출한다.  그러면 변경 감지가 발생해 자동으로 update sql이 날아간다.

 

 

 

유저 삭제

public void deleteUser(String name){
    User user = userRepository.findByName(name);
    if (user == null) {
       throw new IllegalArgumentException();
    }
    userRepository.delete(user);
}

findByName 사용하기 위해서, UserRepository 인터페이스에 메서드 시그니처를 작성해줘야 한다.

함수 이름만 작성하면 SQL이 자동으로 작성된다.

 

find라고 작성하면 1개의 데이터를 가져오고, by 뒤에 붙는 필드 이름으로 where문이 작성된다.

findByName은 select * from user where name =? 와 동일하다.

 

 

 

 

JPA 쿼리 작성법

find   단일 조회. 반환 타입은 객체 또는 Optional<타입>이 된다.
findAll   다중 조회. 반환 타입은 List<타입> 이다.
exists 쿼리 결과가 존재하는지 확인한다. 반환 타입은 boolean이다.
count   SQL 결과 개수를 센다. 반환 타입은 long이다.

 

By 뒤에는 필드 이름이 들어가는데 필드들을 And, Or로 조합할 수 있다.

예를 들어 findAllByNameOrAge는 select * from user name =? or age=? 과 같다.

 

이외에도 다양한 조건을 활용할 수 있다.

GreaterThan 초과
GreaterThanEqual 이상
LessThan 미만
LessThanEqual 이하
Between 사이에
StartsWith ~로 시작하는
EndsWith ~로 끝나는

 

 

트랜잭션

트랜잭션은 쪼갤 수 없는 업무의 최소 단위이다.

원자성(Atomicity)은 트랜잭션의 성질 ACID 중 하나이다.

트랜잭션과 관련된 일은 모두 실행되든지 모두 실행되지 않도록 하든지 보장하는 특성이다. (All or Nothing)

예를 들어 쇼핑몰 주문 시 결제는 되었는데, 구매 기록이 저장되지 않았다면 문제이므로 원자성이 필요하다.

 

서비스 메서드가 시작할 때 트랜잭션을 시작해

서비스 메서드 로직이 성공하면 commit이 되고, 도중에 문제가 생기면 rollback 되도록 해야 한다.

 

이는 대상 메서드에 @Transactional를 붙여 적용할 수 있다.

@Transactional
public void saveUser(UserCreaterRequest request){
   userRepository.save(new User(request.getName(), request.getAge());
}

데이터 변경이 없고, 조회 기능만 있을 때는 readOnly = true 옵션을 적용할 수 있다.

 

 

 

 

이렇게 유저 CRUD API 개발을 완료하고, JPA 쿼리와 트랜잭션도 살펴보았다.

다음 글에서 본격적으로 도서 관리 API 개발을 할 것이다.

 

 

JPA로 도서관리 API 개발하기

책 생성 API 개발 API 명세서 HTTP Method POST HTTP Path /book HTTP Body {"name" : String} 결과 반환 X book 테이블 설계하기 create table book ( id bigint auto_increment, name varchar(255), primary key (id) ); @Column length 기본값이 25

hyun-keepdeving.tistory.com

 

 


참고

https://www.inflearn.com/course/%EC%9E%90%EB%B0%94-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%84%9C%EB%B2%84%EA%B0%9C%EB%B0%9C-%EC%98%AC%EC%9D%B8%EC%9B%90

 

자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인

Java와 Spring Boot, JPA, MySQL, AWS를 이용해 서버를 개발하고 배포합니다. 웹 애플리케이션을 개발하며 서버 개발에 필요한 배경지식과 이론, 다양한 기술들을 모두 학습할 뿐 아니라, 다양한 옵션들

www.inflearn.com

https://www.joinc.co.kr/w/man/12/ACID

Comments