개발일기

JPA 본문

우리는 지금 jdbc -> jdbcTemplate으로 바꾸고 나서 반복해서 개발해야 하는 코드가 확줄았다. 

그런데 아직도 해결하지 못하는 것이 하나 있다. 아직도 sql은 개발자가 직접 작성해야 된다는 것이다. 

그런데 이제 우리가 JPA라는 기술을 사용하면 SQL쿼리도 자동으로 처리해준다. 

이러한 점때문에 개발 생산성을 크게 높일 수 있다. MemoryMemberRepository과 비슷하다. 마치 객체를 메모리에 넣듯이 JPA가 중간에서 DB의 SQL을 날리고 데이터를 DB를 통해서 가져오는 것은 JPA가 다 처리해준다. 

단순히 SQL을 만들어 주는 것을 넘어서서 JPA를 사용하면 우리가 이제 SQL문법보다는 객체중심으로 조금 더 많이 고민할 수 있다. 

SQL과 데이터베이스 중심의 설계에서 객체 중심의 설계로 패러다임을 바꿀 수 있는 것임. 

JPA는 Spring만큼 깊이가 있고 넓이가 있는 기술이다. 

Spring과 JPA는 비슷한 시기에 나왔다. 그래서 Spring이 JPA를 굉장히 많이 지원한다. 

사실 Spring에서 나온 관계형 데이터베이스와 관련된 것들은 JPA를 기본으로 깔고 가고 있다. 

plugins {
	id 'org.springframework.boot' version '2.7.3'
	id 'io.spring.dependency-management' version '1.0.13.RELEASE'
	id 'java'
}

group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

dependencies {
	//implementation 'org.springframework.boot:spring-boot-starter-jdbc'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	runtimeOnly 'com.h2database:h2'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

build.gradle을 이와 같이 설정을 해주었다. 이후에 라이브러리를 받았음. 

application에다가도 jpa와 관련된 설정을 추가해주어야 한다. 

spring.datasource.url = jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none

이와 같이 추개 해주었다. 이렇게 해주면 jpa가 날리는 sql을 볼 수 있다. 

ddl-auto=none <- JPA를 사용하면 회원객체를 보고 자기가 알아서 테이블도 다 만들어 버린다.

그런데 우리는 이미 테이블을 다 만들어 버렸고 만들어져 있는 것을 쓸것이기 때문에 테이블을 생성해주고 시작하는 기능은 꺼주었다.  

여기서 none -> create라고 바꿔주면 Table까지 만들어준다. 

JPA를 쓸려면 가장 먼저 라이브러리를 Mapping해주어야 한다.

JPA라는 것은 인터페이스만 제공이 되는 것이다.  구현체가 여러개 있는데 우리는 그중에 JPA인터페이스에서 Hibernate만 거의 사용할 것이다. 

JPA라는 것은 자바 진영의 표준 인터페이스이다. 구현은 여러 업체들이 하는 것이라고 생각하면 된다. 

각 업체마다 성능이 더좋거나 더 쓰기 편하거나 그런것이 있다. 

JPA는 객체랑 ORM이라는 기술이다. 

O : Object 

R : Relational (관계형 데이터)

M : Mapping 

 

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

매핑을 어떻게 하냐? 지금부터 한번 살펴보자. 

 

package hello.hellospring.domain;

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

@Entity
public class Member {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

이렇게 사용하면 이제부터 JPA가 관리하는 Entity가 되어진 것이다. 

그러고 나서 우리는 PK를 Mapping해주어야 한다. 

우리가 쿼리에 id를 넣는 것이 아니라 db에 값을 넣으면 db가 id를 자동으로 생성해주는 것을 identity라고 한다. 

oracle db같은 경우에는 sequence라고 하기도하고 테이블에서 채반할 수도 있고 아니면 내가 직접 넣어줄수도 있다.

그런데 db가 직접 넣어주는 것을 IDENTITY라고 함. 

 

만약에 db에 column명이 username이라면 @Column(name = "username") 이런식으로 매핑해주면 된다. 

이 어노테이션들이 갖지고 db에 있는 데이터베이스 명들과 매핑을 해주는 것이다. 

지금 같은 경우는 db의 테이블의 컬럼명과 필드 변수의 이름이 같기 때문에 안해주어도 된다. 

이제 이렇게 해주면 delect, select등등의 쿼리등을 매핑해준 것들과 사용할 수 있음. 

 

JPA는 EntityManager라는 것에 의해 모든게 동작을 한다. 

우리가 아까전에 data-jpa라는 라이브러리를 받았음. 이렇게 하면 spring-boot가 자동으로 Entity Manager라는 것을 생성을 해준다. 현재 데이터베이스랑 다 연결을 해서 만들어주었다. 그래서 우리는 만들어준 것을 그냥 Injection받으면 됨. 

 

Datasource들이랑 우리가 properties에서 설정을 해줬던 것들을 다 짬뽕을 해줘서 SpringBoot가 Entity Manger라는 것을 만들어준다. DataSource나 이런것을 내부에 다 들고 있어서 DB랑 통신하고 그런것을 내부에서 다 처리를 한다. 결론은 JPA를 쓸려면 EntityManager를 주입받아야 한다. 

package hello.hellospring.repository;

import hello.hellospring.domain.Member;

import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;

public class JpaMemberRepository implements MemberRepository{
    private final EntityManager em;

    public JpaMemberRepository(EntityManager em) {
        this.em = em;
    }

    @Override
    public Member save(Member member) {
        em.persist(member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        Member member = em.find(Member.class, id);
        return Optional.ofNullable(member);
    }

    @Override
    public Optional<Member> findByName(String name) {
        List<Member> result = em.createQuery("Select m from Member m where m.name = : name", Member.class)
                .setParameter("name",name)
                .getResultList();
        return result.stream().findAny();
    }

    @Override
    public List<Member> findAll() {
        return em.createQuery("select m from Member m", Member.class)
                .getResultList();
    }
}

em.persist <- 이렇게 되면 데이터베이스가 저장이 된다. 

이렇게 하면 JPA가 Insert query 다 만들어서 DB에 집어넣고 member에다가 setId까지 다 해준다.

find ByName같은 경우 약간 특별한 JPQL이라는 객체지향 쿼리 언어를 사용해야 한다. 

SQL이랑 똑같다.  

@Override
    public List<Member> findAll() {
        return em.createQuery("select m from Member m", Member.class)
                .getResultList();
    }

여기에서 select ~~ 가 JPA쿼리이다. 정확하게 Entity 대상으로 쿼리를 날리는 것이다. Member 엔티티를 select로 선택한다.  애는 m에서 member 객체 자체를 샐렉트함. 여기서 Member m이런식으로 되어 있는데 Member 뒤에는 as m이라는 것이 생략되어져 m으로 나와있고 이것은 별칭이다. 

이런식으로 PK기반이 아닌 나머지들은 JPQL이라는 것을 작성해주어야 한다. 

JPA기술을 Spring에 한번 감싸서 제공하는 기술이 있는데 이것이 바로 SpringDataJPA이다. 그래서 방금 라이브러리를 data jpa라는 것을 받은 것이다. 

이걸 사용하면 우리가 findByName, findAll에서도 JPQL을 사용하지 않아도 된다. 

JPA이 쓰면 주의 해야할 께 항상 @Transaction이 있어야 한다. 데이터를 저장하거나 변경할 때.. 

package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Optional;
@Transactional
public class MemberService {

    private final MemberRepository memberRepository;

    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    /**
     * 회원 가입
     * @param member
     * return
     *  */
    public Long join(Member member){
        //같은 이름이 있는 중복 회원X
        validateDuplicateMember(member);
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        memberRepository.findByName(member.getName()).ifPresent(m -> {
            throw new IllegalStateException("이미 존재하는 회원입니다.");
        });
    }

    /**
     * 전체 회원 조회
     */
    public List<Member> findMembers(){
        return memberRepository.findAll();
    }

    public Optional<Member> findOne(Long memberId){
        return memberRepository.findById(memberId);
    }
}

MemberService를 @Transaction 해주었다. 

join이 들어올 때 모든 데이터 변경이 Transaction안에서 실행이 되어야 한다. 

package hello.hellospring;

import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.JdbcTemplateMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

@Configuration
public class SpringConfig {
    private EntityManager em;
    @Autowired
    public SpringConfig(EntityManager em) {
        this.em = em;
    }
    //   private DataSource dataSource;
//    @Autowired
//    public SpringConfig(DataSource dataSource) {
//        this.dataSource = dataSource;
//    }

    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository()
    {
        // return new MemoryMemberRepository();
        // return new JdbcMemberRepository(dataSource);
        //return new JdbcTemplateMemberRepository(dataSource);
        return new JpaMemberRepository()
    }
}

spring config 파일을 다음과 같이 변경 하였음 test를 확인해보자.

이렇게 하면 테스트가 성공된 화면을 볼 수 있다.

큰 회사들도 JPA를 쓰고 있는 추세이다. 복잡한 쿼리도 가능하다. 

실무에서 잘 사용하려면 깊게 공부해야한다.  

'Spring > Spring tutorial - 코드로 배우는 스프링 (김영한)' 카테고리의 다른 글

AOP가 필요한 상황  (1) 2022.10.05
스프링 데이터 JPA  (1) 2022.10.05
스프링 JdbcTemplate  (0) 2022.09.30
스프링 통합 테스트  (1) 2022.09.30
순수 JDBC  (1) 2022.09.28