부족한 점
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을 사용
- DBMS는 쿼리 프로세싱 성능이 좋지만, Spring data jpa는 역사가 짧기에 아직 성능이 안좋은 부분 발생
@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()
- update 작업
- 쿼리 실행: .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 |