[JPA/Hibernate]JPA를 이용해 귀찮은 쿼리문들을 퇴치해보자!(JPA를 이용한 CRUD, JPQL)

zzarbttoo·2021년 8월 4일
2

DB/Mybatis/JPA

목록 보기
2/7

안녕하세요 이전에 배치 서버를 구축해야 할 일이 생겨 JPA를 이용해 모듈을 만들었는데, 실제 운영환경에서 테이블 명이 계속해서 변하는(!)바람에 눈물을 머금고 Mybatis로 전환해야 했던 경험이 있습니다!
(JPA를 이용해 동적 쿼리를 사용하기 위해서는 다른 프레임워크를 공부해야하는 것)
그게 한이 되어 JPA를 공부해야겠다고 생각한것...

그래서 오늘은 JPA 기초와 동적 쿼리의 종류에 대해 간략하게 설명해볼까 해요!
일단 JPA에 대한 개념은 이렇게 알아두시면 됩니다!

JPA : Java 진영의 ORM 기술
ORM : 객체와 관계형 DB를 Mapping
Hibernate : JPA라는 기술을 구현해놓은 ORM 프레임워크

즉 JPA를 사용한다고 하지만 사실 저희는 hibernate를 사용하고 있는 것!
JPA를 이용해서 저희는 비즈니스 로직(java)에만 집중할 수 있는 것입니다
하지만 그렇다고 쿼리문을 몰라도 되느냐? 그건 절대 아니라는 것...🤷‍♀️
오히려 객체 지향과 쿼리문을 정확하게 이용해야 제대로 사용할 수 있다는 사실

일단 JPA 자체는 정적인 상황에서 사용하는 것을 권장하기 때문에 복잡한 쿼리와 동적 쿼리에 대한 문제가 발생하게 되는데요
그럴 때는 JPQL과 Querydsl을 사용할 것을 권장하고 있습니다 (출처)

JPQL : @Query 어노테이션을 사용해서 인터페이스에 바로 작성하고 끝
Querydsl : 동적 쿼리 발생 시는 필수적으로 사용 요함, custom repository 설정 필요

이번 글에서는 기초적인 JPA 를 이용한 CRUD 그리고 JPQL 간편 사용법(!)에 대해서 소개해보고
차후에 JPA를 이용해 복잡한 쿼리를 조작해보거나 Querydsl 을 이용해보도록 해보겠습니다

| JPA 설정

맨 처음 JPA 를 이용하기 위해 dependency를 추가했습니다!
일단 저는 Mysql 을 사용할 것이기 때문에 mysql dependency 도 추가해줬어요~
그리고 코드량을 줄이기 위해 lombok dependency도 추가했습니다


<!-- mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-jpa -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
 <!--lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

이후 application.properties 를 이용해 mysql 과 JPA 값들을 설정해줬어요

#db connection
spring.datasource.url=jdbc:mysql://localhost:3306/{DB이름}?serverTimezone=UTC&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password={비밀번호}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.batch.initialize-schema=ALWAYS

#jpa
spring.jpa.database=mysql
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.use_sql_comments=true
#절대 운영 환경에서는 false!
spring.jpa.generate-ddl=true 
  • DB 커넥션 정보를 설정해 Mysql에 연결될 수 있도록 했습니다
  • spring.jpa.database, spring.jpa.database-platform 을 설정해 JPA에서 mysql 방언(아 싸투리~)을 사용할 수 있도록 했습니다
  • spring.jpa.properties.hibernate. [ show_sql, formate_sql , use_sql_comments] 등을 통해 jpa가 실행될 때 쿼리문을 보여주도록 했어요(개발할 때만 true 설정 하시면 됩니다)
  • spring.jpa.generate-ddl = true 을 통해 Table create, drop 등 DDL 을 직접할 수 있도록 했습니다! (테이블이 있을 경우엔 Drop 하고 다시 create 합니다)

주의 할 점은 절대 절대 절대 운영환경에서는 이렇게 설정하면 안된다는 것! 무조건 false 를 해야 사고를 막을 수 있다고 합니다

관련 장애를 낸 썰을 호돌님이 풀어주셔서 재밌게(!!!!!) 이해할 수 있었습니다
호돌님은 spring.jpa.ddl-auto = create 를 설정하셨다고 하네요
😂

그리고 전 Table 을 직접 만들기 귀찮았기 때문에 쿼리를 실행해 테이블을 만들 수 있도록 설정하고자 했어요! 때문에 persistence.xml 파일이 필요했던 것!

intellij 기준 Project settings -> facets -> JPA(메뉴) -> +버튼 을 클릭 후 팝업창의 OK 버튼을 누르면

다음과 같이 META-INF 폴더에 persistence.xml 파일이 생기게 됩니다!

이후 Entity 테이블이 자동으로 생성되도록 하기 위해 다음과 같이 설정을 해줬습니다!

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
             version="3.0">
  <persistence-unit name="default">
    <properties>
      <property name="hibernate.hbm2ddl.auto" value="create"/>
      <property name="hibernate.show_sql" value="true"/>
    </properties>
  </persistence-unit>
</persistence>

간단한 예시를 들기 위해 Entity class인 Customer 을 만들어보았습니다!

package com.zzarbttoo.jpastudy.entity;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Getter
@Setter
@ToString
@Entity
@NoArgsConstructor
@Table(name = "CUSTOMER_TABLE")
public class Customer {

    @Id
    @GeneratedValue
    private int id;
    private String firstName;
    private String lastName;


    public Customer(int id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

}
  • 테이블과 mapping되는 Entity임을 표시하기 위해 상단에 @Entity를 표시했습니다
  • @Table(name = "테이블명")을 통해 생성될 테이블 명을 명시했습니다
  • 코드를 줄이기 위해 Lombok의 @Getter, @Setter, @NoArgsConstructor, @Tostring 을 사용했습니다

이후 프로젝트를 실행하면 @Entity annotation이 있는 Class의 경우
다음과 같이 Table이 create 가 되는 것을 볼 수 있고,
DBeaver을 이용해 테이블이 생성된 것을 확인할 수 있었습니다

| JPA 를 이용한 CRUD

그럼 기본적인 세팅이 끝났으니 다음으로 CRUD를 진행해보겠습니다! 일단 JPA CRUD를 진행하기 위해서는 JPA 실행을 위한 Repository를 생성해야합니다

@Repository
public interface JpaRepository extends org.springframework.data.jpa.repository.JpaRepository<Customer, Long>{

    public List<Customer> findAll(); //selectList

    public Customer findCustomerById(int id); //selectOne

    public List<Customer> findCustomersByFirstName(String firstName); //selectList

    public List<Customer> findCustomersByLastName(String lastName); //selectList

}
  • @Repository annotation을 설정해 Repository 임을 명시해줍니다
  • JpaRespository 를 상속받습니다 (JpaRepository<T, ID>)

이렇게 상속 설정을 하면 intellj 기준으로 entity 이름 + 조건을 합친 함수를 자동완성으로 만들어줍니다!
복잡한 select, insert, delete, update문을 정의할 수 있습니다!
자세한 사항은 reference 를 참고..

이제 main 함수에서 쿼리문들을 실행해보기 위해 다음과 같이 repository 를 선언했습니다!

@SpringBootApplication
public class JpastudyApplication {

    public static void main(String[] args) {
        //SpringApplication.run(JpastudyApplication.class, args);
        ConfigurableApplicationContext context = SpringApplication.run(JpastudyApplication.class, args);
        JpaRepository jpaRepository = context.getBean(JpaRepository.class);
}

저는 main함수에서 했지만, 다른 곳에서 할 때는 @Autowired 해서 Repository를 주입해서 사용하시면 됩니다!

| C(Insert)

먼저 Insert를 하기 위해 Entity 하나를 생성했어요!
이후 entity의 속성을 설정해줬습니다(setFirstName, setLastName)

        Customer tempCustomer = new Customer(); //entity
        tempCustomer.setFirstName("hello"); 
        tempCustomer.setLastName("insert");
        jpaRepository.save(tempCustomer);
  • save 함수를 사용할 때 값이 없으면 Insert, 있으면 Update가 되게 됩니다
  • save 함수는 JPA Repository 에 구현을 하지 않아도 됩니다!

이후 실행해보면, 위과 같이 쿼리문이 실행되는 것을 확인할 수 있고
(application.properties에서 JPA 실행시 로그가 찍히도록 했음)
Dbeaver을 이용해 값이 제대로 들어간 것을 확인할 수 있었습니다!

| R(Select)

다음은 Select! SelectList를 출력해보았습니다 (selectOne은 update나 delete 할 때 나옵니다)

List<Customer> selectAllCustomer = jpaRepository.findAll();
        for(Customer customer : selectAllCustomer) {
            log.info("select customers ::: " + customer.toString());
}
  • findAll() 은 JPA Repository 에 구현을 해야합니다
    이후 실행을 하면 위와 같이 결과가 나오는 것을 확인할 수 있었습니다!

| U(Update)

다음은 Update!

 //1. select
Customer firstCustomer = jpaRepository.findCustomerById(1);
log.info("firstCustomer ::: " + firstCustomer.toString());

//2. select 한 값 수정
firstCustomer.setFirstName("GGang");
jpaRepository.save(firstCustomer);
log.info("firstCustomer ::: " + jpaRepository.findCustomerById(1));
  • update 할 Entity를 find~ 함수를 이용해 select 해왔습니다
  • Entity의 세부 값을 바꾼 후 save 함수를 통해 저장했으며, 값이 이미 있기 때문에 insert 대신 update가 이루어졌습니다!

log로 출력한 결과 기존의 값이 바뀐 것을 확인할 수 있었습니다

| D(Delete)

마지막은 Delete!

//delete
Customer firstCustomer = jpaRepository.findCustomerById(1);
jpaRepository.delete(firstCustomer);
List<Customer> selectAfterDelete = jpaRepository.findAll();
for(Customer customer : selectAfterDelete) {
    log.info("select customers ::: " + customer.toString());
}
  • delete 할 Entity를 find~ 함수를 이용해 select를 해왔습니다
  • delete 함수를 이용해 Entity를 삭제했습니다

실행을 시키면 다음과 같이 쿼리가 실행되고

기존에 있던 값이 사라진 것을 확인할 수 있었습니다

| JPQL

JPQL은 살짝 복잡한 쿼리를 빠르게 작성할 수 있다는 장점이 있는데요
기존에 사용하던 JPA Repository 를 이용해 작성할 수 있습니다

저는 예시를 위해 id=3인 Entity를 출력하는 쿼리와, id 값에 따라 Entity를 정렬해서 출력하는 쿼리를 짜보았습니다

@Repository
public interface JpaRepository extends org.springframework.data.jpa.repository.JpaRepository<Customer, Long> , QuerydslPredicateExecutor<Customer> {

    //기존 
    public List<Customer> findAll();
    public Customer findCustomerById(int id);
    public List<Customer> findCustomersByFirstName(String firstName);
    public List<Customer> findCustomersByLastName(String lastName);

    //JPQL 
    @Query(" select c from Customer c where c.id = 3 ")
    public Customer findCustomerIdEqualsThree();

    @Query(" select c from Customer c order by c.id desc ")
    public List<Customer> sortedCustomerById();


}

쿼리 작성 시 Intellj에서 역시 자동완성을 지원해주는 것을 확인할 수 있었습니다 (갸꿀~)

  • id = 3인 Customer 출력
  • 정렬해서 Entity 출력

실행을 했을 때 잘 출력이 되는 것을 확인할 수 있었습니다!

오늘은 간편하게 JPA를 사용하는 방법을 다뤄봤는데 다음은 JOIN과 같은 복잡한 쿼리를 다루는 방법을
들고오도록 하겠습니다~

짜요~

profile
나는야 누워있는 개발머신

0개의 댓글