Spring/Spring boot 입문

[스프링 부트] 스프링 DB 접근 기술(5) JPA - 스프링 입문 강의 / 인프런

mopipi 2022. 8. 30. 22:33
반응형

5. 스프링 DB 접근 기술


5) JPA

JPA: 기존 반복 코드 + 기본 SQL 대신 자동으로 처리 해줌


  • JPA 사용 이유
    • JdbcTemplate 의 업그레이드 버전 ∴ 작성 코드 더 간단해짐 (직접 작성하던 SQL 상당 부분 대체)
    • SQL, 데이터 중심 설계 → 객체 중심 설계로 전환 가능 (객체만 신경쓰기 가능)
    • 개발 생산성 증대 (객체를 JPA에 넣으면, 중간에서 DB에 SQL날리고 Data 가져오는 것들을 JPA가 다 처리해줌)

1. 설정 추가/ 수정

  1) build.gradle에 dependency 추가/수정

//  implementation 'org.springframework.boot:spring-boot-starter-jdbc'
   implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
   runtimeOnly 'com.h2database:h2'
   testImplementation('org.springframework.boot:spring-boot-starter-test') {
      exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
   }

  2) application.properties 수정

spring.jpa.show-sql=true
//jpa와 관련된 sql을 볼 수 있음
spring.jpa.hibernate.ddl-auto=none
//jpa 사용시 객체(member)를 보고 테이블도 자동으로 생성함 - 자동 생성 기능 off해주기
//반대로 테이블 자동생성을 원한다면 none -> create

다 받아졌다면 외부 라이브러리에 2가지 라이브러리가 들어와야 한다. 

 

♘ JPA가 그래서 뭔데?
  1) 표준인터페이스 자체를 말하는 것. 따라서 인터페이스만 제공이 됨
    ⇒ 구현체가 추가적으로 필요함 (구현 기술은 업체별로 다양하다) - 주로 hibernate만 사용
    - 따라서 hibernate는 jpa의 구현체이다. 
  2) 객체 + ORM 기술
    - ORM : Object + Relational + Mapping [객체와 Relational Database의 테이블을 매핑한다]
    - 어떻게 매핑하는데?? ⇒ Annotation 사용함

 

2. Entity Mapping 해주기

import javax.persistence.Entity;

@Entity
public class Member {
   @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;
   @Column(name = "username")
   private String name;
}
  • Member Class에 Annotation을 추가해 줌 ➤ Member == JPA가 관리하는 Entity가 됨
  • @Id(=PK) @GeneratedValue(strategy = GenerationType.IDENTITY)
    • PK : ID(=PK)는 DB에서 자동으로 생성해 줌
    • 이렇게 DB에 값을 넣으면 자동으로 생성해주는 것을 IDENTITY strategy라고 함
  • @Column(name = "username")
    • DB에 있던 Column명을 username과 mapping 되게 함
위 @Id ... , @Column Annotation을 통해 DB와 매핑을 하는 것. ⇒ 요 정보들을 이용해 JPA가 SQL문을 만들어 날림

 

3. Repository 생성 (JpaMemberRepository)

public class JpaMemberRepository implements MemberRepository{

    private final EntityManager em;
    public JpaMemberRepository(EntityManager em) {
        this.em = em;
    }
  • EntityManager
    • JPA는 EntityManager로 모든 동작을 수행함 (스프링 부트가 현재 DB와 연결해서 자동으로 생성해 줌)
    • 그러므로 이미 만들어진 EntityManager를 Injection만 하면 됨! (= JPA를 사용하려면 EntityManager를 주입 받아야 한다)

1) save 

@Override
public Member save(Member member) {
    //사실 이게 핵심
    em.persist(member);
    //구색 맞추기 위해 return
    return member;
}
  • JPA가 Insert 쿼리 스스로 만들어서 DB에 넣고, member에 setId까지 해줌

2) findById

@Override
public Optional<Member> findById(Long id) {
    Member member = em.find(Member.class, id);
    return Optional.ofNullable(member);
}
  • em.find(A, B) : 조회할 타입(A), 식별자(B)를 요소로 넣으면, 해당 식별자가 있는지 조회 가능
  • Optional로 반환해야 하므로 ofNullable 붙여줌

 

3) findAll

@Override
public List<Member> findAll() {
    List<Member> result = em.createQuery("select m from Member m", Member.class)
            .getResultList();
    return result;
}
  •  PK 기반 외의 것들을 조회하기 위해선, jpql라는 객체지향쿼리를 사용해야 함
  • "select m from Member (as) m" : 테이블이 아닌 엔티티(객체)를 대상으로 쿼리문을 날림
    • select 하는 것도 member 객체 자체를 select 하는 것 >> 추가적 매핑 필요 없음!

4) findByName

@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();
}
  • 마찬가지로 PK가 아닌 값을 조회하므로 객체지향쿼리를 사용
  • 비교값을 ? 대신 :name을 사용
    • setParameter로 식별자 셋팅해줌
추후에 등장할 "Spring Data JPA" (JPA + Spring 진화 최최최종.ver)를 사용하면, 위에 두 경우에서도 jpql 안 짜도 됨ㅎ

 

+) MemberService 수정

JPA를 사용하기 위해선 Service 계층에 @Transactional 추가 필수!!!
- JPA는 data 변경이 발생하는 경우(ex. join),  Transactional 안에서 수행 돼야 함
- 해당 클래스의 method 실행 시 Transaction 시작 >>> method 정상 종료시에만 commit (아니믄 롤백함)

 

4. 테스트 하기

1) SpringConfig 수정 - DI 삽입

private EntityManager em;
@Autowired
public SpringConfig(EntityManager em) {
    this.em = em;
}
...
@Bean
public MemberRepository memberRepository() {
   return new JpaMemberRepository(em);
}

 

2) 테스트 코드 돌려보기 - MemberServiceIntegrationTest

 

이제 테스트만 돌리면 되겠군! 하고 돌렸는데...


우리 좋았잖아 왜이래

왜 때문에 오류 발생?... 쿼리 세팅을 잘못한 것 같다.

알고보니 문제는 JPA 엔티티를 매핑하는 과정에서 발생했다!

Column "Member_username" not found >> 테이블을 보니 column명으로 Name은 있지만 username은 없다. 

Column Annotation을 추가해서 발생한 문제같다. 삭제했더니 아래와 같이 잘 통과한다. 

(강의 복습하다 전말을 알게되었다. column명이 username일 경우에 대한 추가설명에서 어노테이션이 등장했던 것...^^ 정신 체리~~~) → Annotation만 잘 활용하면 코드는 그대로 가져가되 DB만 갈아끼울수도 있겠군!


- jpa 인터페이스에선 기본적으로  hibernate라는 구현체가 사용됨을 알 수 있다

- select 쿼리, insert 쿼리가 동작함을 알 수 있다.

- 이때 insert 쿼리에서 id는 default 값으로 넣음 (DB에서 id 자동 생성해서 넣음)

 

 

+) JPA 기초 강의도 추가적으로 꼭꼭!!! 듣자 (이걸로는 택도 없다 ㅡ.ㅡ)

 

JPA에서 제일 중요한 것 = 객체/테이블을 잘 설계해서, 정확하고 명확하게 매핑하는 것!

 

반응형