반응형

이전에 Jdbc를 해보고, JdbcTemplate를 써봤는데,

Jdbc에서 JdbcTemplate로 바꿔보니, 개발해야하는 반복적인 코드가 확 줄었던 경험을 해보았다.

그런데, 아직도 해결이 안되는 것이 하나가 있다.

"SQL은 결국 개발자가 직접 작성을 해야 한다는 것"

이 문제는 해결할 수 없을까?

JPA라는 기술을 사용하면 SQL쿼리도 JPA가 자동으로 처리를 해준다.

JPA를 사용하면 개발 생산성을 크게 높일 수 있다.
마치 우리가 MemoryMemberRepository를 사용했던 것처럼
객체를 메모리에 넣듯이 JPA에 집어넣으면,
JPA가 중간에서 DB에 SQL날리고, DB를 통해서 데이터를 가져오는 것을 JPA가 처리해준다.

JPA가 단순히 SQL을 만들어 주는 것을 넘어서서,
JPA를 사용하면, SQL보다 객체중심으로 고민 할 수 가 있게 된다.

즉, JPA를 사옹하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있다.
그래서, JPA를 사용하면, 개발 생산성을 크게 높일 수 있다.

 


 

우선, JPA를 사용하려면 build.gradle 파일에 JPA, h2데이터베이스 관련 라이브러리를 추가해야 한다.

spring-boot-starter-data-jpa

에는 내부에 jdbc 관련 라이브러리를 포함한다.
따라서 기존에 작성해둔 jdbc 라이브러리는 제거해도 된다.

 

그리고 이번에는 스프링 부트에 JPA 설정을 추가해줘야 한다.

spring.jpa.show-sql=true

이렇게 true로 하면 JPA가 날리는 SQL을 볼 수가 있다.

spring.jpa.hibernate.ddl-auto=none

jpa를 사용하면, 객체를 보고 jpa가 테이블도 다 만들어준다.
그런데, 우리는 이미 테이블을 만들어놓았고, 만들어진것을 사용할 것이기 때문에, 자동으로 테이블을 생성해주는 기능은 none으로 끄고 시작하도록 하겠다. (create로 하면 자동생성)

build.gradle reload로 라이브러리 로딩이 끝나면,

jpa라이브러리와 hibernate라이브러리 로딩이 되어있어야 한다.

 

JPA라는 것은 인터페이스다.
구현체로 hibernate 등의 구현기술이 있는 것이다.

그중에서 우리는 거의 JPA인터페이스와 hibernate 만 거의 쓴다고 보면 된다.

JPA라는 것은 Java진영의 표준 인터페이스이다.

구현은 여러 업체들이 하는 것이다.
각 업체마다 성능이 더 좋거나, 쓰기편하거나 등의 장단점이 있는 것이다.

 

 JPA는 객체와 ORM이라는 기술로 정의할 수 있다.
 O는 Object, R은 Relational, M은 Mapping이다.

즉, 객체와 관계형 데이터베이스의 테이블을 매핑한다는 뜻이다.

그래서, JPA를 사용하려면 먼저 엔티티라는 것을 매핑해야한다.

매핑은 어노테이션으로 한다.

 

Member 클래스에 작성해보자.

@Entity라고 적어주면,
"아! 이제 이것은 JPA가 관리하는 엔티티구나!" 가 되는 것이다.
즉, @Entity를 달아서 엔티티라고 알려주는 것이다.

그리고 Primary Key도 매핑해줘야하는데,

아래 DB살펴보자.

DB에 데이터를 저장하면 ID가 generate된다.
쿼리를 잘 보면, 쿼리에 id를 넣는 것이 아니라,
DB에 값을 넣으면, DB가 ID를 자동으로 생성해 주는 것을 뭐라고하냐면, "Identity 전략"이라고 한다.

그래서, 이렇게

@Id @GeneratedValue(strategy = GenerationType.IDENTITY)


라고 해줘야 한다.
이렇게 DB가 알아서 생성해 주는 것은 IDENTITY라고 한다.

지금은 name이 컬럼명도 name이라서 그대로 하면 되지만,

만약에 DB의 컬럼 명이 username이면, @Column(name = "username") 이렇게 해주면 매핑된다.

이렇게 애너테이션을 가지고 데이터베이스와 매핑을 하는 것이다.
그렇게 해놓으면, 이제 이 정보들을 가지고 sql쿼리문을 만들 수 있는 것이다.
JPA가 이러한 방식으로 동작하는 것이다.

 

이제 JPA로 Repository를 만들어 보자.

JpaMemberRepository 라는 이름으로 클래스를 만들고,
MemberRepository를 implements 했다.

 

private final EntityManager em;

를 입력하고 Constructor Method를 해주었다.

JPA는 EntityManager라는 것으로 모든게 동작한다.

아까전에  build.gradle에서 data-jpa 라이브러리를 받았는데, 그렇게 하면, properties정보등을 전부 취합하여
스프링 부트가 자동으로 EntityManager라는 것을 생성을 해준다.

현재 DB랑 연결까지 전부해서 EntityManager를 생성해준다.

그래서 우리는 만들어진 EntityManager를 Injection 받으면 된다.

 

아무튼, 결론은,  JPA를 사용하려면,
EntityManager라는 것을 인젝션 받아야 한다.

 

 

save를 구현해보자.

EntityManager를 줄여서 em이라고 많이 사용하는데,

em.persist(member);

를 해준다. persist가 영속하다, 영구저장하다라는 뜻이다.
안에는 member를 넣고, return member를 해준다.

이렇게만 하면 save()가 끝난다.

JPA가 Insert쿼리를 다 만들어서 DB에 집어넣고,
ID까지 member에다 setId 까지 해준다.

 

이번에는 findById를 구현해보자.

em.find(Member.class, id)

em.find라고 있는데, 매개변수로는 조회할 타입과, 식별자Primary Key만 넣어주면 조회가 된다.
그러면 SELECT문이 나가는 것이다.

그런데 return을 해야하는데,
Optional로 반환을 하기 때문에 값이 없을 수도 있기 때문에 

return Optional.ofNullable(member);

이렇게 작성한다.

 

이번에는 fndAll을 먼저 작성해보자.
앞서 작성한, findId는 id가 primary key였다.

name은 pk가 아니다. 따라서
findAll과 findByName를 작성할 때는 조금 다르게 작성한다.

이 두 위치의 값이 같으면, inline을(cmd + opt + n) 사용할 수 있다.

(맥 기준 ctrl+t 로 검색할 수 있다.)

inline을 사용하면 이렇게 바뀐다.

어쨋든, 위 코드가 jpql이라는 쿼리 언어인데,
우리가 보통 테이블을 대상으로 sql을 날리는데,
jpql은 테이블 대상이 아니라, 객체를 대상으로 sql을 날리는 것이다.
정확하게는 Entity를 대상으로 쿼리를 날리는 것이다.

return em.createQuery("select m from Member m", Member.class)
        .getResultList();

"Member Entity를 조회해!" 라고 하는데,
select의 대상이 m이다.

원래 SQL이면, *이라고 하거나, m.id, m.name 으로 할 텐데말이다.

return em.createQuery("select m from Member m", Member.class)
        .getResultList();

jqpl이 적용된 이 코드에서는
m 엔티티 객체 자체를 select하는 것이다.
이렇게 해서 조회를 하게 된다.

 

이번에는 findByName을 작성해보자.

List<Member> result = em.createQuery("select m from Member m where m.name = :name", Member.class)
        .setParameter("name", name)
        .getResultList();

findByName 도 jpql이라는 것을 짜야한다. 지금은 Member.class로 조회해야 한다.
그리고, findByName의 결과를 1개만 찾는다고 했으므로,
stream을 사용해서 반환하면 된다.

 

단건을 찍어서 찾는.. 예를 들면 저장하고, 조회하고 업데이트 하는 것은 sql을 짤 필요가 없는데,

findByName이나, findAll 처럼
여러개의 리스트를 가지고 돌릴 때는, PrimaryKey기반이 아닌 나머지 들은 jpql이라는 것을 작성해줘야 한다.

 

그리고, JPA를 사용하려면 항상 트랜잭션이 있어야 한다. (JPA로 데이터를 저장하거나 변경하려면..)

그래서 MemberService계층에 @Transactional 을 작성 해줘야 한다.다.org.springframework.transaction.annotation.Transactional 를 사용하자.
스프링은 해당 클래스의 메서드를 실행할 때 트랜잭션을 시작하고, 메서드가 정상 종료되면 트랜잭션을 커밋한다.
만약 런타임 예외가 발생하면 롤백한다. JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.

 

이제 JPA를 사용하도록 스프링 설정을 변경해보자.

원래 스펙에서는

@PersistenceContext 이렇게 받아야 하는데,

private EntityManager em;

@Autowired
public SpringConfig(EntityManager em) {
    this.em = em;
}

스프링에서는 이렇게 작성해도 DI를 해준다.

자!! 이제 돌려보자.

전에 만들어둔 스프링 통합테스트를 돌려보자.

테스트에 통과했다.

로그를 보면, JPA는 인터페이스고, 쿼리가 Hibernate 구현체가 사용 된 것을 볼 수 있다.
결국 DB에는 SQL이 나가야 하는 것이다.

 

 

 

※ 참고
JPA기술을 스프링에 담아서 감싸서 제공하는 기술이 있다.
그것이 바로 SpringDataJPA 기술이다.

SpringDataJPA를 사용하면, findByName이나 findAll 같이 PrimaryKey 기반이 아닌 것들도 jpql쿼리를 작성하지 않아도 된다.

이것에 대해서는 다음시간에 공부해보자.

반응형