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

 

'Spring > 기본 구성' 카테고리의 다른 글

+4. DTO를 record type으로 설정  (0) 2024.02.06
7. Entity 클래스  (0) 2024.01.18
6. ORM 개념 with. Spring Data JPA  (0) 2024.01.18
5. Service 클래스  (0) 2024.01.18
4. Controller 클래스 with. DTO  (0) 2024.01.18