y-seo
y-seo의 딩코 기록들
y-seo
  • 분류 전체보기 (174)
    • Computer Science (49)
      • Database Design & Query Lan.. (10)
      • Network Security (16)
      • Software Engineering (6)
      • Computer Network (17)
    • Spring (50)
      • Spring-Basic (11)
      • SpringBoot-AWS (7)
      • SpringBoot&JPA (22)
      • 토비의 스프링 (3)
      • + α (7)
    • Cloud (22)
      • AWS (4)
      • GCP (1)
      • ElasticSearch (17)
    • Test (3)
    • Project (4)
    • Algorithm (24)
      • 개념 (9)
      • 문제풀이 (15)
    • AI (3)
      • About (2)
      • AIDU ez (1)
    • Error (4)
    • ETC (1)
    • Review (8)
    • IT (5)
      • SQLD (4)
      • ADsP (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

최근 글

최근 댓글

전체 방문자
오늘
어제

태그

  • 김영한
  • 알고리즘
  • 파이썬
  • JPA
  • 보안
  • 스프링부트
  • java
  • 인프런
  • 네트워크
  • 알기 쉬운 정보보호개론 3판
  • Python
  • springboot
  • 백준
  • algorithm
  • 자바
  • 컴퓨터 네트워킹 하향식 접근
  • Spring
  • 네트워크보안
  • baekjoon
  • 스프링

티스토리

hELLO · Designed By 정상우.
y-seo

y-seo의 딩코 기록들

[스프링 입문] Section6. 스프링 DB 접근 기술
Spring/Spring-Basic

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

2023. 5. 8. 12:43

  • 기존에는 메모리에 저장하는 방식
  • 실무에서는 데이터베이스에 데이터들을 저장하여 관리
  • 스프링이 JDBC template 기술 제공
    • 애플리케이션에서 데이터베이스로 SQL을 편리하게 날릴 수 있음 
  • JPA를 쓰면 객체를 바로 DB에 쿼리 없이 저장 가능
  • 스프링 데이터 JPA : JPA를 편리하게 쓸 수 있도록 한 번 감싼 기능 

 

 H2 데이터베이스 설치

  • https://www.h2database.com/html/download-archive.html 에서 1.4.200 버전 설치

1. 데이터 베이스 파일 만들기

  • JDBC URL : 내 파일 경로
  • 이후에 파일로 접근하면 애플리케이션과 웹 콘솔이 동시에 접근이 안될 수 있음
  • 이후부터는 JDBC URL : jdbc:h2:tcp://localhost/~/test
    • 파일 직접 접근이 아닌 톰캣을 통한 접근

2. 테이블 생성

  • Java에서 Long이 bigint 타입
  • generated by default as identity : null 값이 들어올 경우 DB가 자동으로 값을 채워줌

3. 조회

4. 등록

5. 테이블 관리를 위해 파일 추가

 

 순수 JDBC

  • 애플리케이션과 DB 연동 => 애플리케이션에서 저장하는 것을 DB에 insert/select query를 날려 넣고 빼기
  • 20년 전에 사용했던 방법 .. 

1. build.gradle 에 jdbc, h2 데이터베이스 관련 라이브러리 추가

dependencies {
   implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
   implementation 'org.springframework.boot:spring-boot-starter-web'
   testImplementation 'org.springframework.boot:spring-boot-starter-test'
   implementation 'org.springframework.boot:spring-boot-starter-jdbc' //이거랑
   runtimeOnly 'com.h2database:h2' //이거

}

2. resources/application.properties 에 스프링 부트 데이터베이스 연결 설정 추가

spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa

3. .../src/main/java/hello/hellospring/repository/JdbcMemberRepository.java 추가

package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import org.springframework.jdbc.datasource.DataSourceUtils;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.sql.*;
import java.util.Optional;

public class JdbcMemberRepository implements MemberRepository {
    private final DataSource dataSource; //DB에 붙을 때 필요한 것
    public JdbcMemberRepository(DataSource dataSource) { //주입 받아야
        this.dataSource = dataSource;
    }
    @Override
    public Member save(Member member) {
        String sql = "insert into member(name) values(?)";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try { //Exception이 많아서 try-catch문을 잘 사용해야
            conn = getConnection();
            pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); //문장합성
            pstmt.setString(1, member.getName());
            pstmt.executeUpdate(); //DB에 쿼리가 날라감
            rs = pstmt.getGeneratedKeys();
            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();
            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);
        }
    }
    @Override
    public List<Member> findAll() { //모두 조회
        String sql = "select * from member";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            rs = pstmt.executeQuery();
            List<Member> members = new ArrayList<>();
            while(rs.next()) { //값이 있으면 루프를 돌며 반환
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                members.add(member);
            }
            return members;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }
    @Override
    public Optional<Member> findByName(String name) {
        String sql = "select * from member where name = ?";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, name);
            rs = pstmt.executeQuery();
            if(rs.next()) {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return Optional.of(member);
            }
            return Optional.empty();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }
    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource);
    }
    private void close(Connection conn, PreparedStatement pstmt, ResultSet rs)
    {
        try {
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (pstmt != null) {
                pstmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (conn != null) {
                close(conn);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    private void close(Connection conn) throws SQLException {
        DataSourceUtils.releaseConnection(conn, dataSource);
    }
}

4. SpringConfig.java 수정

@Configuration
public class SpringConfig {
    private final 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);
    }
}

5. RUN 결과

 

  • 스프링 컨테이너는 자바의 다형성을 지원하여 객체지향적인 설계 가능
  • 스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 DataSource를 생성하고 스프링 빈으로 만들어둠, 그래서 DI를 받을 수 있음

 

  • 구현 클래스 추가 이미지
    • MemeberService는 MemberRepository를 의존
    • MemberRepository는 구현체로 MemmoryMemberRepository와 JdbcMemberRepository가 있음

`

  • 스프링 설정 이미지
    • 구현체만 바뀌어 다른 수정 없이 JDBC로 작동

 

  • 개방-폐쇄 원칙(OCP, Open-Closed Principle)
    • 확장에는 열려있고, 수정, 변경에는 닫혀있다
  • 스프링의 DI (Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스 변경 가능

 

  • 결론 : 데이터를 DB에 저장하므로 스프링 서버를 다시 실행해도 데이터가 안전하게 저장된다

 

 스프링 통합 테스트

  • 스프링을 올리고 DB 연동한 통합 테스트 진행

1. .../src/test/java/hello/hellospring/service/MemeberServiceIntegrationTest.java 추가

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {
    @Autowired
    MemberService memberService;
    @Autowired
    MemberRepository memberRepository;
    @Test
    public void 회원가입() throws Exception {
        //Given
        Member member = new Member();
        member.setName("hello");
        //When
        Long saveId = memberService.join(member);
        //Then
        Member findMember = memberRepository.findById(saveId).get();
        assertEquals(member.getName(), findMember.getName());
    }
    @Test
    public void 중복_회원_예외() throws Exception {
        //Given
        Member member1 = new Member();
        member1.setName("spring");
        Member member2 = new Member();
        member2.setName("spring");
        //When
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class,
                () -> memberService.join(member2));//예외가 발생해야 한다.
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
    }
}
  • @SpringBootTest : 스프링 컨테이너와 테스트를 함께 실행
  • @Transactional : 테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백 => 이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않음
  • 단위 테스트를 잘 만드는 것이 좋은 테스트

 

 스프링 JdbcTemplate

  • 순수 Jdbc와 동일한 환경설정
  • 스프링 JdbcTemplate과 MyBatis 같은 라이브러리 : JDBC API에서 본 반복 코드를 대부분 제거해줌, 그러나 SQL은 직접 작성해야 한다

1. .../src/main/java/hello/hellospring/repository/JdbcTemplateMemberRepository.java 추가

public class JdbcTemplateMemberRepository implements MemberRepository {
    private final JdbcTemplate jdbcTemplate; //Jdbc Template 쓰기
    public JdbcTemplateMemberRepository(DataSource dataSource) { //DataSource 필요
        jdbcTemplate = new JdbcTemplate(dataSource); //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 List<Member> findAll() {
        return jdbcTemplate.query("select * from member", memberRowMapper());
    }
    @Override
    public Optional<Member> findByName(String name) {
        List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name);
        return result.stream().findAny();
    }
    private RowMapper<Member> memberRowMapper() { //결과를 매핑
        return (rs, rowNum) -> { //람다식으로
            Member member = new Member();
            member.setId(rs.getLong("id"));
            member.setName(rs.getString("name"));
            return member;
        };
    }
}

 

저작자표시 (새창열림)

'Spring > Spring-Basic' 카테고리의 다른 글

[스프링 핵심 원리] Section2. 스프링 핵심 원리 이해1 - 예제 만들기  (0) 2023.09.05
[스프링 핵심 원리] Section1. 객체 지향 설계와 스프링  (0) 2023.07.11
[스프링 입문] Section5. 회원 관리 예제 - 웹 MVC 개발  (0) 2023.05.08
[스프링 입문] Section4. 스프링 빈과 의존관계  (0) 2023.05.07
[스프링 입문] Section3. 회원 관리 예제 - 백엔드 개발  (0) 2023.04.03
    'Spring/Spring-Basic' 카테고리의 다른 글
    • [스프링 핵심 원리] Section2. 스프링 핵심 원리 이해1 - 예제 만들기
    • [스프링 핵심 원리] Section1. 객체 지향 설계와 스프링
    • [스프링 입문] Section5. 회원 관리 예제 - 웹 MVC 개발
    • [스프링 입문] Section4. 스프링 빈과 의존관계
    y-seo
    y-seo

    티스토리툴바