우당탕탕 개발일지

[SpringBoot] 4. JPA 적용하기 ( Repository 계층) 본문

Server

[SpringBoot] 4. JPA 적용하기 ( Repository 계층)

devchop 2025. 1. 6. 16:43

지금까지는 아래처럼, 문자열에 SQL 구문을 직접 적었다. 이런 방식은 여러가지 문제가 있다.

 public boolean isUserNotExist(int uid){
        String querySQL = "select * from user where id = ?";
       return jdbcTemplate.query(querySQL, (res,rowNum)->0,uid).isEmpty(); 
    }

 

  • 실수할 가능성이 많고, 실수를 인지하기 힘들다 . 문자열에 오타가 날 경우, 컴파일 시점이 아니라 런타임 시점에 발견되기 때문이다.
  • 특정 데이터베이스에 종속되게 된다. 상황에 따라 DB가 변경될 수 있는데, 만약 db가 변경될 경우, 모든 코드를 재작성해야한다.
  • 반복작업이 많아진다. CRUD 쿼리는 자주 사용하게되는데, 사용할때마다 작성해야한다.
  • 데이터베이스의 테이블과 객체는 패러다임이 다르다

JPA

이러한 불편한 점을 개선하기 위해 나온것이 JPA(Java Persistence API) 이다. 쉽게 말하면, Java의 객체와 SQL의 테이블을 매핑시켜서 유저가 직접 SQL문을 작성하지 않고 DB를 조작할 수 있게끔 도와주는 친구이다.


객체와 관계형 DB의 테이블을 짝지어 데이터를 영구적으로 저장할 수 있도록 도와주는 Java진영의 규칙이다. Java진영의 ORM이다. Hibernate이 JPA를 구현해놓았다. Hibernate은 내부적으로 JDBC를 사용한다.

 

User.java 를 생성하여 객체-테이블을 매핑할 수 있도록 정의해준다. @Id, @Column, @GeneratedValue 와 같은 어노테이션을 이용하여 DB의 테이블에 매핑시켜준다.

//User.java
package com.group.library_app.domain;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class User {
    @Id //primary key 로 간주
    @GeneratedValue(strategy= GenerationType.IDENTITY) //자동생성. identity == auto_increment
    private Long id ;

    @Column(nullable= false, length=25,name="name")
    private String name;

    @Column(nullable= true,name="age")
    private Integer age;

    protected User(){} //기본생성자가 필요함.
    public User(String name, Integer age){
        if(name == null || name.isBlank()){
            throw new IllegalArgumentException("invalid name : "+name);
        }
        this.name = name;
        this.age = age;
    }
}

 

 

application.yaml 파일에서 JPA 설정을 해준다.

##application.yaml
spring:
  datasource:
    url: "jdbc:mysql://localhost/library" ##java database connector 를 사용해 java 와 db를 연결
    username: "myname"
    password: "mypassword"
    driver-class-name: com.mysql.cj.jdbc.Driver #db접근시 사용할 프로그램
  jpa:
    hibernate:
      ddl-auto: none
    properties:
      hibernate:
        show_sql: true
        format_sql: true
        dialect: org.hibernate.dialect.MySQL8Dialect

 

각 옵션에 대한 설명은 간략히 한다. 나중에 필요할때 따로 찾아보도록하자. 

ddl-auto 옵션 : spring이 시작할때 db와 객체의 매핑이 다를 경우 어떻게 처리할지.
##create : 기존테이블이 있을 경우 삭제 후 재생성
##create-drop : 스프링이 종료될때 매핑된 테이블을 모두 제거
##update: 객체와 테이블이 다른 부분만 변경
##validate : 객체와 테이블이 동일한지 확인
##none : 별다른 조치없음.

 

show_sql옵션 :jpa를 사용해 db에 sql을 날릴 때 sql을 보여줄 것인지

format_sql 옵션 : sql을 보여줄때 이쁘게 포맷팅 할 것인지

dialect 옵션 : 다양한 종류의 db를 자동으로 해당문법으로 수정해주는 역할. sql마다 문법이 다름. 현재는 mysql8 에 맞춰달라는 의미이다.

 

 

UserRepository와 UserService 수정하기

직접 구현하던 UserRepository 에서, JpaRepository 를 상속받은 클래스로 변경한다.

//UserRepository.java
package com.group.library_app.domain.user;

import java.util.List;
import java.util.Optional;

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

public interface UserRepository extends JpaRepository<User,Long>{

    //함수명에 정확한 필드이름이 들어가야함
    public User findByName(String name);
    //public Optional<User> findByName(String name);
    public List<User> findAllByName(String name);
    public boolean existByName(String name);
    public int countByName(String name);

    public List<User> findAllByNameAndAge(String name,int age);
    public List<User> findAllByAgeBetween(int startAge, int endAge);
}

클래스가 굉장히 축약된 것을 알 수 있다. 기본으로 제공되는 save(), findById(), delete() 등 외에, 다양한 SQL구문을 확장시킬 수 있도록 함수를 제작할 수 있다. 더 많은 조합이 가능한데, Spring Data JPA 에서 정해놓은 네이밍 컨벤션 을 이용하여 적절한 SQL문을 작성할 수 있다.

 

UserService에서도 변화가 있다. userRepository 에서 제공하는 save(), delete() 등을 간편하게 가져오는것을 확인할 수 있다. 기본으로 제공되는 함수 + UserRepository 에서 확장한 함수 모두 사용가능하다.

//UserService.java
package com.group.library_app.service.user;
import java.util.List;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

import com.group.library_app.domain.user.User;
import com.group.library_app.domain.user.UserRepository;
import com.group.library_app.dto.user.request.UserCreateRequest;
import com.group.library_app.dto.user.request.UserUpdateRequest;
import com.group.library_app.dto.user.response.UserResponse;


//로직을 담당함.
@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository _repository){
        userRepository = _repository;
    }

    public void saveUser(UserCreateRequest request){
        userRepository.save(new User(request.getName(), request.getAge()));
    }
    public List<UserResponse> getUsers()
    {
       List<User> users =  userRepository.findAll();
       return users.stream().map(user -> new UserResponse(user)).collect(Collectors.toList());
    }

    public void updateUser(UserUpdateRequest request){

        User user =  userRepository.findById(request.getId()).orElseThrow(()->new IllegalArgumentException("invalid id"));
        user.UpdateName(request.getName());
        userRepository.save(user);
    }

    public void deleteUser(String name){

        User user =  userRepository.findByName(name);
        if(user == null) throw new IllegalArgumentException("invalid id");
        userRepository.delete(user);
    }
}