[Spring] JPA사용하기

jsieon97·2023년 3월 2일

[Spring] BackEnd

목록 보기
8/11

현재의 Repository의 문제점

public boolean isUserNotExist(long id) {
        String readSql = "SELECT * FROM user WHERE id = ?";
        return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, id).isEmpty();
    }
  1. 다음의 메소드에서 readSql문의 쿼리문에 오타가 나도 단순 String에 불과하기 때문에 문제없이 컴파일되어 실행되고 이 메소드를 실행하는 요청이 왔을 때 오류가 발생한다 즉 컴파일시점이 아닌 런타인시점에 오류가 발생한다.
  2. MySQL 기준으로 쿼리문이 작성되어 다른 데이터베이스와 호환이 되지않는다.
    즉 특정 데이터베이스에 종속되게 된다.
  3. 반복되는 쿼리가 많아지게 된다. 테이블을 하나 만들때마다 CRUD코드가 계속 필요하게 된다.
  4. 데이터베이스의 테이블과 객체는 패러다임이 다르다. 객체는 양방향을 가르키지만 데이터베이스는 단방향을 가르킨다. 또 객체의 상속관계를 데이터베이스로 표현하기에는 어려운 부분이 존재한다.

이를 해결하기 위해 JPA를 사용할 수 있다

JPA(Java Persistence API)란?

자바진영의 ORM(Object-Relational Mapping)
객체와 테이블을 짝지어 데이터를 영구적으로 저장할 수 있도록 정해진 Java규칙

이를 구현하여 코드로 작성한 것이 HIBERNATE

JPA 세팅하기

User테이블에 대응되는 Entity Class를 만들어 사용해보겠습니다.

User테이블(MySQL)

CREATE TABLE IF NOT EXISTS user
(
    id   bigint AUTO_INCREMENT,
    name varchar(25),
    age  int,
    primary key (id)
);

Entity Class

@Id

이 필드를 primary key로 간주한다.

@GeneratedValue

primary key는 자동 생성되는 값이다. GenerationType.IDENTITY은 MySQL의 AUTO_INCREMENT속성에 대응된다.
다른 DB를 사용해도 자동으로 속성을 맞춰준다.

@Column

객체필드와 Table필드를 매핑한다 null여부, 길이 제한, DB에서의 이름 등등을 속성으로 지정한다. 객체필드의 컬럼명과 테이블의 컬럼명이 같으면 따로 DB에서의 이름을 지정하지 않아도 된다.

application.yml에 다음을 추가

spring:
  jpa:
    hibernate:
      ddl-auto: none
    properties:
      hibernate:
        show_sql: true
        format_sql: true
        dialect: org.hibernate.dialect.MySQL8Dialect
  • ddl-auto : 스프링이 시작할 때 DB에 있는 테이블을 어떻게 처리할지
    - create : 기존 테이블이 있다면 삭제 후 재생성
    - create-drop : 스프링이 종료될 때 테이블을 모두 제거
    - update : 객체와 테이블이 다른 부분만 변경
    - validate : 객체와 테이블이 동인한지 확인
    - none : 별다른 조치를 하지 않는다.
  • show_sql : JPA를 사용해 DB에 SQL을 날릴때 SQL을 보여줄 것인가
  • format_sql : SQL을 보여줄 때 포맷팅 여부
  • dialect : SQL문이 어떤 DB기준으로 표기될 것인가.

JPA를 사용하도록 리팩토링

기존 코드

// UserServiceV1.java

import com.group.libraryapp.dto.user.request.UserCreateRequest;
import com.group.libraryapp.dto.user.request.UserUpdateRequest;
import com.group.libraryapp.dto.user.response.UserResponse;
import com.group.libraryapp.repository.user.UserJdbcRepository;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserServiceV1 {

    private final UserJdbcRepository userJdbcRepository;

    public UserServiceV1(UserJdbcRepository userJdbcRepository) {
        this.userJdbcRepository = userJdbcRepository;
    }

    public void updateUser(UserUpdateRequest request) {
        if (userJdbcRepository.isUserNotExist(request.getId())) {
            throw new IllegalArgumentException();
        }

        userJdbcRepository.updateUserName(request.getName(), request.getId());
    }


    public void saveUser(UserCreateRequest request) {
        userJdbcRepository.saveUser(request.getName(), request.getAge());
    }

    public List<UserResponse> getUsers() {
        return userJdbcRepository.getUsers();
    }
}
// UserJdbcRepository.java

import com.group.libraryapp.dto.user.response.UserResponse;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import java.util.List;


@Repository
public class UserJdbcRepository {

    private final JdbcTemplate jdbcTemplate;

    public UserJdbcRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void updateUserName(String name, long id) {
        String sql = "UPDATE user SET name = ? WHERE id = ?";
        jdbcTemplate.update(sql, name, id);
    }

    public boolean isUserNotExist(Long id){
        String readSql = "SELECT * FROM user WHERE name = ?";
        return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, id).isEmpty();
    }

    public boolean isUserNotExist(String name) {
        String readSql = "SELECT * FROM user WHERE name = ?";
        return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, name).isEmpty();
    }

    public void saveUser(String name, Integer age) {
        String sql = "INSERT INTO  user (name, age) VALUES(?, ?)";
        jdbcTemplate.update(sql, name, age);
    }

    public List<UserResponse> getUsers() {
        String sql = "SELECT * FROM user";
        return jdbcTemplate.query(sql, (rs, rowNum) -> {
            long id = rs.getLong("id");
            String name = rs.getString("name");
            int age = rs.getInt("age");
            return new UserResponse(id, name, age);
        });
    }
}

JPA로 리팩토링하기

JpaRepository를 상속받는 UserRepository 인터페이스를 생성한다.

// UserRepository.java

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

public interface UserRepository extends JpaRepository<User, Long> {}
// UserResponse.java
// User객체를 받는 생성자를 만들어 코드를 간편하게 바꾸도록한다.

import com.group.libraryapp.domain.user.User;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;

@Getter
@AllArgsConstructor
public class UserResponse {
    private long id;
    private String name;
    private Integer age;

    public UserResponse(User user) {
        this.id = user.getId();
        this.name = user.getName();
        this.age = user.getAge();
    }

    public UserResponse(long id, User user) {
        this.id = id;
        this.name = user.getName();
        this.age = user.getAge();
    }
}
// UserServiceV2.java

import com.group.libraryapp.domain.user.User;
import com.group.libraryapp.domain.user.UserRepository;
import com.group.libraryapp.dto.user.request.UserCreateRequest;
import com.group.libraryapp.dto.user.request.UserUpdateRequest;
import com.group.libraryapp.dto.user.response.UserResponse;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class UserServiceV2 {

    private final UserRepository userRepository;

    public UserServiceV2(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

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

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

    public void updateUser(UserUpdateRequest request){
        // SELECT * FROM user WHERE id = ?;
        // Optional<User>
        User user = userRepository.findById(request.getId())
                .orElseThrow(IllegalArgumentException::new);

        user.updateName(request.getName());
        userRepository.save(user);
    }

}

UserResponse에 생성자를 추가하므로써
UserResponse::new가
user -> new UserResponse(user.getId(), user.getName(), user.getAge()) 코드를 대체할 수 있게되었다.

Spring Data JPA를 활용해 다양한 쿼리 작성

위 Jpa에서는 findById처럼 Id값으로만 대상을 찾고있다. 다른 값으로 원하는 대상을 찾도록 수정해보자

// UserRepository.java

public interface UserRepository extends JpaRepository<User, Long> {

    User findByName(String name);
}

findByName?
find는 하나만 찾습니다 By뒤에 붙는 필드이름으로 SELECT쿼리의 WHERE문이 작성됩니다. 위의 경우엔 "SELECT * FROM user WHERE name = ?"가 됩니다.

인터페이스의 위 처럼 객체에 맞는 값을 찾는 메소드를 작성한다.

이를 이용해 유저를 삭제하는 코드를 작성해보자

// UserServiceV2.java

public void deleteUser(String name) {
        User user = userRepository.findByName(name);
        if(user == null) { // 유저 존재 여부
            throw new IllegalArgumentException();
        }

        userRepository.delete(user);
    }

그외의 다양한 JPA 쿼리

By앞에 들어갈 수 있는 구절

find : 1건을 가져온다. 반환타입은 객체가 될수도 Optional<타입>일 수도있다.
findAll : 쿼리의 결과물이 N개인 경우 사용. List<타입> 반환
exists : 쿼리 결과가 존재하는지 확인. 반환 타입은 boolean
count : SQL 결과의 개수를 센다. 반환 타입은 long.

각 구절은 And나 Or로 조합할 수 있다.

// SELECT * FROM user WHERE name = ? AND age = ?;
List<User> findAllByNameAndAge(String name, int age);

By뒤에 들어갈 수 있는 구절

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

// SELECT * FROM user WHERE age BETWEEN ? AND ?;
List<User> findAllByAgeBetween(int startAge, int endAge);
profile
개발자로써 성장하는 방법

0개의 댓글