Spring/기본 구성

8. Repository 클래스 with. JDBC, JPQL(JPA), QueryDSL

초코chip 2024. 1. 18. 14:50

부족한 점

JPQL의 문법(SQL과 차이점), 3가지 DB접근 방식의 Join 연산

 

리포지터리 클래스

  • 정의/목적 : DB 테이블에 대한 CRUD 작업을 하기 위한 클래스
  • 특징 : 다양한 DB접근 방법이 존재
    • JDBC Template
    • JPQL (권장)
    • QueryDSL (권장)

 

JDBC Template

참고 사이트: https://code-lab1.tistory.com/277

 

[Spring] JdbcTemplate이란? JdbcTemplate 사용법, RowMapper란?

JdbcTemplate이란? JdbcTemplate은 JDBC 코어 패키지의 중앙 클래스로 JDBC의 사용을 단순화하고 일반적인 오류를 방지하는데 도움이 된다. 개발자가 JDBC를 직접 사용할 때 발생하는 다음과 같은 반복 작

code-lab1.tistory.com

 

개념

  • 정의: JDBC 기술을 쉽게 사용할 수 있도록 도와주는 클래스
  • 목적: JDBC 기술을 통해 DB접근을 하는 경우에 사용

 

의존성

  • Spring data jdbc 의존성 사용
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'

 

Repository 클래스 구성

기본적으로 JdbcTemplate 객체를 가져와서 작업 진행

 

클래스 애너테이션

@Repository

  • 정의/목적: 해당 클래스가 리포지터리 클래스임을 알리는 애너테이션
@Repository
@NoArgsConstructor
public class Repository {
    private final JdbcTemplate jdbcTemplate;
 	...   
}

 

RowMapper 클래스

  • 정의/목적: JDBC 반환 결과(ResultSet)를 객체로 변경해주는 클래스
  • 사용 방법: 람다식을 이용하여 구현
  • 예시:
    private RowMapper<Club> clubRowMapper() {
        return (rs, rowNum) -> {
          Club club = new Club();
          club.setId(rs.getInt("id"));
          club.setName(rs.getString("name"));
          club.setCategory(rs.getString("category"));

          return club;
        };

 

DB 조회 작업(Select)

다중 조회

query() 메서드

  • 정의/목적: 여러개의 행을 조회할 때 사용하는 메서드
  • 사용 방법: SQL(필수), rowMapper(필수), SQL에 들어갈 인자들
  • 예시 :
    public List<Club> getAllClubs(String name){
    	String sql = "select id, name, category from club where name = ?";
        List<Club> clubs = jdbcTemplate.query(sql, clubRowMapper(), name);
        return clubs;
    }

 

 

단일 조회

queryForObject() 메서드

  • 정의/목적: 단일행을 조회할 때 사용하는 메서드
  • 사용 방법: SQL(필수), rowMapper(필수), SQL에 들어갈 인자들
  • 예시: @@@queryForObject() 메서드의 결과가 Optional로 되는지 한번 테스트 해봥@@@
    public Optional<Club> getClub(int id){
    	String sql = "select id, name, category from club where id= ?";
        Optional<Club> club = jdbcTemplate.queryForObject(sql, clubRowMapper(), id);
        return club;
    }

 

 

query() 메서드

  • query() 메서드를 통해서도 단일 조회 가능
  • 사용 방법: 반환 결과인 List<> 객체에 .stream().findAny() 메서드 추가
  • 예시:
    public Optional<Club> getClub(int id){
    	String sql = "select id, name, category from club where id= ?";
        List<Club> club = jdbcTemplate.query(sql, clubRowMapper(), id);
        return club.stream().findAny();
    }

 

DB 변경 작업(Insert, Update, Delete)

update() 메서드

  • 정의/목적: DB변경 작업을 할 때 사용하는 메서드
  • 사용 방법: SQL(필수), SQL에 들어갈 인자들
  • 예시:
	//삽입(INSERT)
    public int addClub(int id, String name, String category){
        String query = "insert into club values(?,?,?)";

        return jdbcTemplate.update(query, id, name, category);
    }

	//삭제(DELETE)
    public int deleteClub(int id){
        String query = "delete from club where id = ?";

        return jdbcTemplate.update(query, id);
    }

 

 

JPQL

@@@@ 아직 JPQL과 SQL 문법의 차이점은 잘 모름 조사 더 필요 @@@@

개념

Java Persistence Query Language

  • 정의: 자바 객체(엔티티)를 다루는 객체지향 쿼리
    • Spirng Data JPA를 사용
  • 특징:
    • DBMS에 독립적
    • JPQL쿼리를 SQL로 변환하여 DB조회
  • DB접근 레이어
    • Spring Data JPA -> JPA -> HIBERNATE -> JDBC -> DB

 

쿼리 메소드

  • 정의: Spring Data Jpa에서 쿼리를 생성하는 방법
  • 종류:
    • 메소드 이름을 기반으로 쿼리 생성
      • 단점: 컬럼 이름이 변경되면, 메소드 이름또한 변경해줘야함
    • @Query 애너테이션을 통해 직접 JPQL 쿼리 생성 (권장)

 

의존성

  • Spring-data-jpa 의존성 사용
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

 

 

Repository 클래스 구성

상속 준비

아래 2가지 처리를 진행

  • interface 선언: 리파지토리를 인터페이스로 선언
  • JpaRepository 상속: 기본적으로 JpaRepository 인터페이스를 상속받아 사용
    • 제네릭은 <엔티티 타입, 식별자 타입> 으로 지정
public interface Repository extends JpaRepository<Entity, Long> { }

 

DB 조회 작업(Select)

@Query 애너테이션 + JPQL

  • value 속성: 여기에 JPQL 구문을 작성하면 됨
  • JPQL 파라미터 바인딩:
    • 변수 이름(권장): ':이름'
    • 위치: '?1'
  • 조회 결과: java.util 패키지의 클래스 활용
    • 단일행 조회: 옵셔널 -  Optional<>
    • 다중행 조회: 컬렉션 - List<>
    @Query(value = "from Student where id = :id")
    public Optional<Student> findStudentById(Long id); //단일행 조회

    @Query(value = "from Student where name = :name")
    public List<Student> findStudentByName(String name); //다중행 조회

 

 

메소드 이름 기반

  • findBy<컬럼이름>: 이때 컬럼 이름의 첫글자는 대문자로 설정
    Optional<RefreshToken> findByUserId(Long userID);
    Optional<RefreshToken> findByRefreshToken(String refreshToken);

DB 변경 작업(Insert, Update, Delete)

@Query 애너테이션 + JPQL

  • value 속성: 여기에 JPQL 구문을 작성하면 됨
  • JPQL 파라미터 바인딩:
    • 변수 이름(권장): ':이름'
    • 위치: '?1'
  • 동시성 제어 설정: DB변경을 발생시키는 것에는 동시성 제어가 필요함
    • @Modifying, @Transactional 애너테이션 추가
    • nativeQuery = true: SQL로 처리
    • nativeQuery = false: JPQL로 처리
	//수정
    @Modifying
    @Transactional
    @Query(value = "update Student set address = ?2 where id = ?1", nativeQuery = true)
    public void updateStudentAddress(Long id, String address);

	//삭제
    @Modifying
    @Transactional
    @Query(value = "delete from Student where id = ?1", nativeQuery = true)
    public void deleteStudentById(Long id);

	//삽입
    @Modifying
    @Transactional
    @Query(value = "insert into Student(id, name, address, did) values(?1, ?2, ?3, ?4)", nativeQuery = true)
    public void insertStudent(Long id, String name, String address, Long did);

 

 

메소드 이름 기반

@@@ 한번 직접 테스트 해보자 @@@ 그리고 위 수정과 관련된 애너테이션도 필요가 없는지 gpt는 필요가 없다네

  • 삽입(insert): save() - 이때, 인자는 엔티티를 전달
  • 삭제(Delete): deleteBy<컬럼이름> - 이때, 컬럼 이름의 첫글자는 대문자로 설정
  • 업데이트(update): update<바꿀컬럼>By<조건컬럼>  - 이때, 컬럼 이름의 첫글자는 대문자로 설정
//추가
public void save(Entity entity);
    
//삭제
//id를 기반으로 삭제
public void deleteById(Long id);

//수정
???

 

 

Join 연산

@@@ 얘도 더 조사 필요 @@@

  • join fetch
    • DBMS는 쿼리 프로세싱 성능이 좋지만, Spring data jpa는 역사가 짧기에 아직 성능이 안좋은 부분 발생
      • 예시) N+1 문제
      • 따라서, fetch join을 사용
    @Query(value = "select s from Student s join fetch s.dept")
    public List<Student> findAll();

 

 

QueryDSL

개념

  • 정의: 정적 타입의 SQL 쿼리 생성해주는 것

 

정적 vs 동적 쿼리

  • 정적 쿼리: 상황에 관계없이 SQL 구조가 동일한 것
  • 동적 쿼리: 상황에 따라 SQL 구조가 바뀌는 것

 

vs JPQL

  • 쿼리를 문자열이 아닌 자바 코드 형태로 작성 가능
  • 컴파일 시점에서 문법 오류를 확인 가능
  • 동적 쿼리 문제를 깔끔하게 처리 가능

그래서 복잡한 쿼리나 동적 쿼리를 사용하는 경우는 QueryDSL, 그 외의 경우는 JPQL방식을 사용하자!

 

의존성

	implementation "com.querydsl:querydsl-jpa"
	implementation "com.querydsl:querydsl-apt"

 

... build.gradle 맨 밑부분에 추가

def querydslDir = "$buildDir/generated/querydsl"

querydsl{
	library = "com.querydsl:querydsl-apt"
	jpa = true
	querydslSourcesDir = querydslDir
}

sourceSets {
	main.java.srcDir querydslDir
}

compileQuerydsl{
	options.annotationProcessorPath = configurations.querydsl
}

configurations {
	querydsl.extendsFrom compileClasspath
}

 

 

설정 파일

config패키지에 UnivQuerydslConfiguraion.java파일 추가

@Configuration
public class UnivQuerydslConfiguration {
    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}

 

Repository 클래스 구성

JPQL방식과 동일하지만, @Query애노테이션 사용x

public interface StudentRepository extends JpaRepository<Student, Long> {
    public Optional<Student> findStudentById(Long id);

    public List<Student> findStudentByName(String name);

    @Modifying
    @Transactional
    public void updateStudentAddress(Long id, String address);

    @Modifying
    @Transactional
    public void deleteStudentById(Long id);

    public List<Student> findAll();

}

 

 

RepositoryImpl 클래스

QueryDsl은 커스텀 리포지토리를 따로 생성해줘야 한다.

 

상속 준비

아래 4가치 처리를 진행

  • @Repository 애너테이션 사용
  • QuerydslRepositorySupport 상속
  • JPAQueryFactory 객체 주입
  • 생성자 작성: super() 메서드를 통해 부모 클래스 생성자 호출
    • 제네릭 타입은 엔티티 클래스
@Repository
public class StudentRepositoryImpl extends QuerydslRepositorySupport {
    private final JPAQueryFactory jpaQueryFactory;
    
    public StudentRepositoryImpl(JPAQueryFactory jpaQueryFactory) {
        super(Student.class);
        this.jpaQueryFactory = jpaQueryFactory;
    }
 
 	...
}

 

DB 조회 작업(Select)

  • 쿼리 작성: 체인 메서드를 통해 작성
    • .select()
    • .from()
    • .where()
  • 쿼리 결과: 아래 메서드를 통해 처리
    • fetchOne(): 단일행 조회 -> 반환값은 Entity or Opitonal<>
    • fetch(): 다중행 조회 -> 반환값은 List<>
    public Student findStudentById(Long id) { // id로 조회
        return jpaQueryFactory
                .selectFrom(student)
                .where(student.id.eq(id))
                .fetchOne();
    }

    public List<Student> findStudentByName(String name){
        return jpaQueryFactory
                .selectFrom(student)
                .where(student.name.eq(name))
                .fetch();
    }

 

DB 변경 작업(Insert, Update, Delete)

  • 쿼리 작성: 체인 메서드를 통해 작성
    • update 작업
      • .update()
      • .set()
      • .where()
    • delete 작업
      • .delete()
      • .where()
  • 쿼리 실행: .execute() 메서드를 통해 쿼리 실행
  • 쿼리 결과: 영향 받은 행의 개수 반환 
    public void updateStudentAddress(Long id, String address){
        long execute = jpaQueryFactory
                .update(student)
                .set(student.address, address)
                .where(student.id.eq(id))
                .execute();
    }

    public void deleteStudentById(Long id){
        long execute = jpaQueryFactory
                .delete(student)
                .where(student.id.eq(id))
                .execute();
    }

 

Join 연산

    public List<Student> findAll(){
        return jpaQueryFactory
                .select(student)
                .distinct()
                .from(student)
                .innerJoin(student.dept, dept)
                .fetchJoin().fetch();
    }

 

 

더 자세한 내용

진짜 JPA의 마스터가 되고싶다! 하면 김영한님의 강의가 나쁘지 않을 것으로 판단

(나중에 취업하고 공부해보도록 하자)

https://www.inflearn.com/course/ORM-JPA-Basic

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 - 인프런

현업에서 실제로 JPA로 개발을 하고 있습니다. 그런 입장에서보면 지금 작성하고 있는 코드들이 어떻게 작동하는지 이해하는데 큰 도움을 주는 강의입니다. 다음은 제가 느낀 이 강의의 장점들

www.inflearn.com