Back-End/Inflearn 강의

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

yeonx 2022. 5. 16. 15:26
728x90
  • H2 데이터베이스 설치
  • 순수 Jdbc
  • 스프링 JdbcTemplate
  • JPA
  • 스프링 데이터 JPA

 

H2 데이터베이스 설치

> https://www.h2database.com/html/download-archive.html

 

Archive Downloads

 

www.h2database.com

- h2 접속 시 경로 : ip대신 localhost라고 입력

- JDBC URL은 jdbc:h2:tcp://localhost/~/test : 파일에 직접 접근하는 것이 아닌 소켓을 통한 접근

 

순수 JDBC

고대의 선배님들이 쓰던 방법(편하게 들으면 됨)

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-jdbc' 
runtimeOnly  'com.h2database:h2'
implementation 'org.springframework.boot:spring-boot-starter-jdbc' : 자바는 기본적으로 db와 붙으려면 jdbc 드라이버가 꼭 있어야 함
runtimeOnly  'com.h2database:h2' : db와 붙을 때 데이터베이스가 제공하는 client가 필요함

 

resources/application.properties

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=orgh2.Driver
spring.datasource.username=sa
데이터 베이스에 접근하기 위한 준비

 

JdbcMemberRepository.java

 private final DataSource dataSource;

 public JdbcMemberRepository(DataSource dataSource) { //generate 단축키 : Alt+Insert
     this.dataSource = dataSource;
 }
DB에 붙으려면 dataSource가 필요
dataSource를 스프링에게 주입받아야 함. -> getConnection으로 커넥션도 받아야 함
@Override
    public Member save(Member member) {
        String sql = "insert into member(name) values(?)";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection(); //커넥션을 가져옴
            pstmt = conn.prepareStatement(sql,
                    Statement.RETURN_GENERATED_KEYS); //DB 아이디 인덱스 Z
            pstmt.setString(1, member.getName()); // 1 : 쿼리의 ?와 매칭이됨
            pstmt.executeUpdate(); // DB에 실제 쿼리가 이때 날아감
            rs = pstmt.getGeneratedKeys(); //ID 1,2 .. 자동으로 들어가는거
            if (rs.next()) {
                member.setId(rs.getLong(1));
            } else {
                throw new SQLException("id 조회 실패");
            }
            return member;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs); 
        }
    }
어차피 사용하지 않을 것이기 때문에 알아만 두면 됨.
@Override
    public Optional<Member> findById(Long id) { //조회
        String sql = "select * from member where id = ?"; //쿼리 날리기
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setLong(1, id);
            rs = pstmt.executeQuery(); //조회는 execute쿼리임
            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return Optional.of(member);
            } else {
                return Optional.empty();
            }
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }

 

 

springConfig.java

@Configuration
public class SpringConfig {
 private final DataSource dataSource;
 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);
  }
}
memberRepository만 변경

 

 

구현 클래스 추가 이미지
스프링 설정 이미지

개방-폐쇄 원칙(OCP, Open-Closed Principle)
 - 확장에는 열려이고 수정, 변경에는 닫혀있다.
스프링의 DI(Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스를 변경할 수 있다.
회원을 등록하고 DB에 결과가 잘 입력되는지 확인하자
데이터를 DB에 저장하므로 스프링 서버를 다시 실행해도 데이터가 안전하게 저장된다.

 

스프링 통합 테스트

스프링 컨테이너와 DB까지 연결한 통합 테스트를 진행해보자

 

@SpringBootTest

스프링 컨테이너와 테스트를 함께 실행한다.

 

@Transactional

테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백한다. 이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다.

 

스프링 JdbcTemplate

  • 스프링 JdbcTemplate과 MyBatis 같은 라이브러리는 JDBC API에서 본 코드를 대부분 제거해준다. 하지만 SQL은 직접 작성해야 한다.
  • 실무에서도 많이 사용

JdbcTemplateMemberRepository.java

private final JdbcTemplate jdbcTemplate;

@Autowired // 생성자가 하나일때 Autowired 생략 가능
public JdbcTemplateMemberRepository(DataSource dataSource) { //datasource 인젝션
    this.jdbcTemplate = new JdbcTemplate(dataSource);
}​
private RowMapper<Member> memberRowMapper(){
    return (rs, rowNum) -> { //객체 생성
        Member member=new Member();
        member.setId(rs.getLong("id"));
        member.setName(rs.getString("name"));
        return member;
    };
}

Jdbc 매뉴얼 참고하면 사용 방법 잘 나와있음

 

 

JPA

  • JPA는 기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
  • JPA를 사용하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환할 수 있다.
  • JPA를 사용하면 개발 생산성을 크게 높일 수 있다.

build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    //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'
    }
}
spring-boot-starter-data-jpa는 내부에 jdbc 관련 라이브러리를 포함한다. 따라서 jdbc는 제거해도 된다.
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
show-sql : JPA가 생성하는 SQL을 출력한다.
ddl-auto : JPA는 테이블을 자동으로 생성하는 기능을 제공하는데 none을 사용하면 해당 기능을 끈다.
- create를 사용하면 엔티티 정보를 바탕으로 테이블도 직접 생성해줌

 

domainMember.java@Entity 매핑

@Entity
public class Member {

     @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
     // Id는 pk임 id는 자동으로 번호가 생성되니 IDEMTITY라고 함
     
     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 리포지토리

public class JpaMemberRepository implements MemberRepository {

    private final EntityManager em; //Jpa는 EntityManager 주입 받아야 사용 ㄱㄴ

    public JpaMemberRepository(EntityManager em) {
        this.em = em;//em이라고 많이 씀
    }

    @Override
    public Member save(Member member) {
        em.persist(member); //영구 저장하다 ( 이렇게 하면 jpa가 inser 쿼리 다 만들어줌)
        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();
    }
}

 

Service에 @Transactional 추가하기

import org.springframework.transaction.annotation.Transactional
@Transactional
public class MemberService {}

JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 함

 

- Test로 할 때 DB로 반영하고 싶으면 @Commit 쓰면 됨

 

스프링 데이터 JPA

public interface SpringDataJpaMemberRepository extends JpaRepository<Member,Long>, MemberRepository {
 	Optional<Member> findByName(String name);
}
interface가 interface를 받을 때는 extends를 씀

  • 인터페이스를 통한 기본적은 CRUD
  • findByName(), findByEmail()처럼 메서드 이름만으로 조회 기능 제공
  • 페이징 기능 자동 제공

참고한 강의 : 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