본문 바로가기
Spring/개념

[스프링입문] CH06. 스프링 DB 접근 기술

by MINNI_ 2021. 8. 13.

1. H2 데이터 베이스 설치

  • 데이터 베이스 파일 생성
    - JDBC URL을 jdbc:h2:~/test 으로 최초 접속

    - ~/test.mv.db 파일 생성확인
    - 이 후 JDBC URL : jdbc:h2:tcp://localhost/~/test
  • 테이블 생성
    - Long(java) → bigint(db)
    - generate by deault as identity : null 값 일 때, 자동으로 id값 채워짐
drop table if exists member CASCADE;
create table member
(
   id bigint generated by default as identity,  /* null 값 일 때, 자동으로 id값 채워짐 */
   name varchar(255),
   primary key (id)
);

2. 순수 JDBC

  • 기능
    - db에 연동하여 저장
  • H2 데이터베이스 라이브러리 추가
# bundle.gradle
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
  • 스프링 부트 H2 데이터베이스 연결 설정 추가
# application.properties
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
  • JDBC 리포지토리 생성 및 구현
  • 스프링 설정 변경
    • DataSource
      - 데이터 베이스 커넥션을 획득할 때 사용하는 객체

      - 스프링이 설정파일(application.properties)를 보고 자체적으로 DataSource를 생성하고 스프링 빈으로 만듦
    • 개방-폐쇄 원칙(OCP, Open-Closed Principle)
      - 확장(=기능 추가)에는 열려 있고, (전체 코드) 수정/변경에는 닫혀있다.
    • 스프링의 DI 사용 → 기존 코드 수정 없이, 설정만으로 구현 클래스 변경 가능
    • DB에 저장 → 스프링 서버 다시 실행해도 데이터 안전하게 저장

memory리포지토리와 연결 끊고, jdbc 리포지토리와 서비스 연결

# SpringConfig
@Configuration
public class SpringConfig {

    // 스프링이 설정파일(application.properties)를 보고 자체적으로 DataSource를 생성하고 스프링 빈으로 만듦 -> DI 가능
    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);
    }
}

3. 스프링 통합 테스트

  • 기능
    - 스프링 컨테이너와 DB까지 연결한 통합 테스트
  • @SpringBootTest
    - 스프링 컨테이너와 테스트 함께 실행 = 스프링이 테스트 할 때 사용
  • @Transactional
    - 테스트케이스에 해당 애노테이션 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 완료 후에 항상 롤백하여, DB에 데이터가 반영되지 않게 함
    - 각 테스트 메소드마다 모두 적용됨
  • @Commit
    - 롤백되지 않고 커밋 ⇒ DB에 데이터 반영
# 회원 서비스 스프링 통합 테스트
@SpringBootTest // 스프릥이 테스트 할 때 사용, 진짜 스프링을 띄워서 테스트 실행
@Transactional    // 동작 검증 후에 롤백해서 db에 데이터 반영되지 않게 하는 것
                    /* 테스트에 붙이면, 테스트 실행전에 트랜잭션을 실행하고 db에 데이터를 넣은 후에
                    테스트 끝나면 롤백하여 db에 데이터 반영 안됨 */
class MemberServiceIntegrationTest {

    @Autowired  // 테스트는 편한 방식으로 진행해도 되므로 -> 필드 주입
    MemberService memberService;
    @Autowired
    MemberRepository memberRepository;

    @Test
    void 회원가입() {   // test는 이름 한글로 변경 가능
        //given : 이런 상황이 주어졌을 때
        Member member = new Member();
        member.setName("hello");

        //when : 이것을 실행했을 때
        Long saveId = memberService.join(member);

        //then : 결과가 이렇게 나와야 함
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }
}

4. 스프링 JdbcTemplate

  • 기능
    - 스프링 JdbcTemplate과 MyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드를 대부분 제거, SQL은 직접 작성해야함 → 순수 jdbc 보다 심플
# JdbcTemplate을 사용하기 위한 스프링 설정 변경
# SpringConfig
@Bean
 public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
 return new JdbcTemplateMemberRepository(dataSource);
 }
# 스프링 JdbcTemplate 회원 리포지토리
public class JdbcTemplateMemberRepository implements MemberRepository{

    private final JdbcTemplate jdbcTemplate;    // 의존성 주입 받을 수 있는 것 x

//    @Autowired  // 생성자가 1개 => @Autowired 생략 가능    // 스프링이 dataSource 주입
    public JdbcTemplateMemberRepository(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public Member save(Member member) {
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
        jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("name", member.getName());
        Number key = jdbcInsert.executeAndReturnKey(new
                MapSqlParameterSource(parameters));
        member.setId(key.longValue());
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        List<Member> result = jdbcTemplate.query("select * from member where id=?", memberRowMapper(), id);
        return result.stream().findAny();
    }

    @Override
    public Optional<Member> findByName(String name) {
        List<Member> result = jdbcTemplate.query("select * from member where name=?", memberRowMapper(), name);
        return result.stream().findAny();
    }

    @Override
    public List<Member> findAll() {
        return jdbcTemplate.query("select * from member", memberRowMapper());
    }

    private RowMapper<Member> memberRowMapper(){
        return (rs, rowNum) -> {
            Member member = new Member();
            member.setId(rs.getLong("id"));
            member.setName(rs.getString("name"));
            return member;
        };
    }
}

5. JPA

  • 기능
    - JPA는 기존의 반복 코드는 뿐만아니라, 기본적인 SQL도 JPA가 직접 만들어 실행
    - JPA 사용 → SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임 전환
  • build.gradle 파일에 JPA 관련 라이브러리 추가
    - spring-boot-starter-data-jpa : 내부에 jdbc관련 라이브러리 포함하므로, implementation 'org.springframework.boot:spring-boot-starter-jdbc 제거
# build.gradle
//implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

 

  • 스프링 부트에 JPA 설정 추가 : application.properties
    • JPA는 인터페이스만 제공, hibernate는 구현체
    • JPA는 객체와 orm(object-relational database-mapping) 기술 
    • spring.jpa.show-sql=true
      - JPA가 생성하는 SQL 출력
    • spring.jpa.hibernate.ddl-auto=none
      - JPA는 테이블을 자동으로 생성하는 기능 제공하는데 none을 사용하면, 해당 기능을 끈다. (기존의 우리가 만든 테이블 사용)
      - create 사용하면, 엔티티 정보를 바탕으로 테이블 자동 생성해준다.
# application.properties
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none

 

  • JPA 엔티티 매핑
    • @Entity
      - JPA가 관리하는 엔티티가 됨
    • @GeneratedValue(strategy = GenerationType.IDENTITY)
      - 쿼리에 id값을 직접 넣는 것이 아니라, db에 다른 필드 값을 넣으면 자동으로 해당 id 값 생성
    • @Column(name="username")
      - db의 컬럼명(username)과 해당 필드이름(name)이 다를 때 매핑

# MemberService
@Entity // jpa가 관리하는 엔티티가 됨
public class Member {
    @Id // pk 매핑
    /* strategy = GenerationType.IDENTITY
    쿼리에 id값을 직접 넣는 것이 아니라, db에 다른 필드 값을 넣으면 자동으로 해당 id 값 생성 */
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
//    @Column(name="username")    // db의 컬럼명(username)과 해당 필드이름(name)이 다를 때 매핑
    private String name;
	...
}

 

  • JPA 회원 리포지토리
    • EntityManager
      - JPA가 모든 동작 수행할 때 필요한 것, JPA에 관련 모든 정보를 가지고 있음
      - JPA 라이브러리 설정하면, 스프링부트가 자동으로 DB에 연결해서 EntityManager 생성
    • em.persist(member)
      - JPA가 쿼리 만들어서 DB에 집어넣고 setId까지 모든 것을 실행
    • em.find(Member.class, id)
      - em.find(조회할 타입, PK)하면, 해당 아이디 조회
    • List<Member> result = em.createQuery("select m from Member m where m.name=:name", Member.class)
                      .setParameter("name", name)
                      .getResultList();

      - PK가 아닌 다른 필드의 값을 조회 할 때, JPQL 객체지향 쿼리 언어 사용하여 조회
      - JPQL : 테이블이 아닌 객체(엔티티)를 대상으로 쿼리를 날리면, SQL로 변역
               : select 할 때, 객체 자체를 조회(id, name 이런식 아님)
    • ctrl + alt + n : 인라인
public class JpaMemberRepository implements MemberRepository{

    private final EntityManager em; // jpa는 EntityManager로 모든 동작 수행함
    // build.gradle 에서 jpa 라이브러리 설정하면, 스프링부트가 자동으로 db에 연결해서 EntityManager 생성해줌
    // 만든것을 DI 받으면 됨

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

    @Override
    public Member save(Member member) {
        // 이렇게 만들면, jpa가 쿼리만들어서 db에 집어넣고 setId까지 모든 것을 실행
        em.persist(member); // persist : 영구저장하다
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        Member member = em.find(Member.class, id);  // em.find(조회할 타입, PK)하면 조회됨
        return Optional.ofNullable(member);
    }

    @Override
    public Optional<Member> findByName(String name) {
        // 위에 id는 pk이니까 find쓰는데, name은 아니므로 아래와 같이 특별한 jpql 객체지향 쿼리언어 사용해야 함
        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() {
        // jpql : 테이블이 아닌 객체(엔티티)를 대상으로 쿼리를 날리면, sql로 번역이 됨
        // select 할 때 객체 자체를 조회 ( id, name 이런식 아님)
        return em.createQuery("select m from Member m", Member.class)
                .getResultList();
    }
}

 

  • 서비스 계층에 트랜잭션 추가
    - JPA를 사용하려면, 데이터를 저장하고 변경하는 곳에 항상 @Transactional 필요
    - JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행되어야 함
@Transactional
public class MemberService {}

 

  • JPA를 사용하도록 스프링 설정 변경
# SpringConfig
@Bean
public MemberRepository memberRepository() {
  // return new MemoryMemberRepository();
  // return new JdbcMemberRepository(dataSource);
  // return new JdbcTemplateMemberRepository(dataSource);
   return new JpaMemberRepository(em);
}

6. 스프링 데이터 JPA

  • 기능
    - 리포지토리에 구현 클래스 없이 인터페이스만으로 개발
    - 기본 CRUD 기능 제공
    - JPA를 편리하게 사용하도록 도와주는 기술
  • 스프링 데이터 JPA 회원 리포지토리
    - SpringDataJpaMemberRepository 인터페이스가 JpaRepository를 상속받으면, 해당 인터페이스의 구현체를 자동으로 만들어 스프링 빈에 자동으로 등록함
    - 스프링 데이터 JPA가 SpringDataJpaMemberRepository를 스프링 빈으로 자동 등록
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
// 해당 인터페이스가 JpaRepository를 상속받으면, 구현체를 자동으로 만들어 스프링 빈에 자동으로 등록함

    // findBy()의 규칙을 통해 ()를 조회하는 JPQL를 제공하고 SQL로 번역됨
    // JPQL : select m from Member m where m.name=?
    @Override
    Optional<Member> findByName(String name);
}

 

  • 스프링 데이터 JPA 회원 리포지토리를 위한 스프링 설정 변경
private final MemberRepository memberRepository;

@Autowired  // JpaRepository가 인터페이스 구현체를 만들고 스프링 빈에 등록 -> 주입 가능
public SpringConfig(MemberRepository memberRepository) {
   this.memberRepository = memberRepository;
}

@Bean   // 스프링 빈 등록
public MemberService memberService(){
//   return new MemberService(memberRepository());
     return new MemberService(memberRepository);
}

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

 

  • 스프링 데이터 JPA 제공 기능
    • 인터페이스를 통한 기본적인 CRUD 기능
    • findByName(), findByEmail() 처럼 메서드 이름만으로 조회 기능
      - id가 아닌 다른 컬럼을 통한 조회는 JpaRepository에 공통적으로 들어있지 않음
      ⇒ [해결법] : 스프링데이터JPA에서 Optional findByName(String name); 와 같은 추상 메소드 생성

      ⇒ findBy()의 규칙을 통해 ()를 조회하는 JPQL를 제공하고 SQL로 번역됨
          → JPQL : select m from Member m where m.name=?
    • 페이징 기능
참고
: 실무 → JPA, 스프링 데이터 JPA 사용
: 복잡한 동적 쿼리는 Querydsl 라이브러리 사용 → 자바코드로 안전하게 작성 & 동적쿼리 작성 편리
: 해결하기 어려운 쿼리 → JPA가 제공하는 네이티브 쿼리를 사용 OR 스프링 JdbcTemplate 사용

 


[ 출처 ]

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard

 

[무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 - 인프런 | 강의

스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., 스프링 학습 첫 길잡이! 개발 공부의 길을 잃지 않도록 도와드립니다. 📣 확인해주세

www.inflearn.com

댓글