안녕하세요 이전에 배치 서버를 구축해야 할 일이 생겨 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 를 이용하기 위해 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
주의 할 점은 절대 절대 절대 운영환경에서는 이렇게 설정하면 안된다는 것! 무조건 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;
}
}
이후 프로젝트를 실행하면 @Entity annotation이 있는 Class의 경우
다음과 같이 Table이 create 가 되는 것을 볼 수 있고,
DBeaver을 이용해 테이블이 생성된 것을 확인할 수 있었습니다
@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
}
이렇게 상속 설정을 하면 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를 주입해서 사용하시면 됩니다!
먼저 Insert를 하기 위해 Entity 하나를 생성했어요!
이후 entity의 속성을 설정해줬습니다(setFirstName, setLastName)
Customer tempCustomer = new Customer(); //entity
tempCustomer.setFirstName("hello");
tempCustomer.setLastName("insert");
jpaRepository.save(tempCustomer);
이후 실행해보면, 위과 같이 쿼리문이 실행되는 것을 확인할 수 있고
(application.properties에서 JPA 실행시 로그가 찍히도록 했음)
Dbeaver을 이용해 값이 제대로 들어간 것을 확인할 수 있었습니다!
다음은 Select! SelectList를 출력해보았습니다 (selectOne은 update나 delete 할 때 나옵니다)
List<Customer> selectAllCustomer = jpaRepository.findAll();
for(Customer customer : selectAllCustomer) {
log.info("select customers ::: " + customer.toString());
}
다음은 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));
log로 출력한 결과 기존의 값이 바뀐 것을 확인할 수 있었습니다
마지막은 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());
}
실행을 시키면 다음과 같이 쿼리가 실행되고
기존에 있던 값이 사라진 것을 확인할 수 있었습니다
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에서 역시 자동완성을 지원해주는 것을 확인할 수 있었습니다 (갸꿀~)
실행을 했을 때 잘 출력이 되는 것을 확인할 수 있었습니다!
오늘은 간편하게 JPA를 사용하는 방법을 다뤄봤는데 다음은 JOIN과 같은 복잡한 쿼리를 다루는 방법을
들고오도록 하겠습니다~
짜요~