> For the complete documentation index, see [llms.txt](https://heunnajo.gitbook.io/spring/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://heunnajo.gitbook.io/spring/db/jdbc.md).

# 순수 JDBC

웹 서버와 DB를 연결해서 데이터를 저장해본다!

자바는 기본적으로 DB랑 붙으려면 JDBC가 꼭 있어야한다!

회원을 저장하는 역할은 MemberRepository가 하지만, memory에 할지 JDBC에 할지는 다르게 구현 가능하다!\
(implements MemberRepository에 커서를 두고 option enter 누르면 메서드들 오버라이드 자동완성된다!)

![MemberService의 의존관계와 MemberRepository 인터페이스](/files/-MdLbbkVxvfRnOtHIMu7)

이처럼 스프링의 DI를 사용하면 기존코드를 전혀 손대지 않고, 설정을 바꾸는 것만으로 구현 클래스를 변경할 수 있다!

이것을 개방 폐쇄 원칙(OCP;Open-Closed Principle) 이라고 한다.\
확장에는 열려있고, 수정에는 닫혀있다.

다형성의 개념을 잘 활용하면 애플리케이션 수정없이 가능하다!\
객체지향의 가장 큰 장점은 이와 같이 인터페이스에서 구현체를 바꾸면서도 기존 코드 변경 없이 바꿀 수 있는 것이다.

**JdbcMemberRepository.java 클래스**

```java
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.jdbc.datasource.DataSourceUtils;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;

import java.util.List;
import java.util.Optional;
public class JdbcMemberRepository implements MemberRepository {
    private final DataSource dataSource;
    public JdbcMemberRepository(DataSource dataSource) {
        this.dataSource = dataSource;
        //여기서 dataSource.getConnectionn()을 할 수도 있지만 이러면 많은 커넥션들이 생기기 때문에 하지 않는다!
    }
    @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에 insert할 때 데이터행의 인덱스를 얻는다!
            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();//없으면 empty().
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            close(conn, pstmt, rs);
        }
    }
    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource);//사용할 일 없지만 편하게 들어라.
        //DataSourceUtils  통해서만 getConnection()을 얻어야 한다! DB 트랜잭션. 똑같은 커넥션으로 유지해줘야함.
        //닫을 때도 마찬가지로 DataSourceUtils를 통해서 release해줘야한다!
    }
    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);
    } }
```

**변경된 Assembly(SpringConfig.java)**\
새로 구현한 JdbcMemberRepository를 빈에 등록해준다!

```java
package hello.hellospring.service;

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

import javax.sql.DataSource;

//memberService, memberRepository 생성해서 스프링빈에 등록하고, 후자를 전자에 연결시켜준다~!
@Configuration
public class SpringConfig {
    private DataSource dataSource;
    @Autowired
    public SpringConfig(DataSource dataSource){
        this.dataSource = dataSource;
    }
    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepository());
    }
    @Bean
    public MemberRepository memberRepository(){//인터페이스(MemberRepository)
        //return new MemoryMemberRepository();//구현체(MemoryMemberRepository)
        return new JdbcMemberRepository(dataSource);//스프링 컨테이너에 등록된 dataSource를 넣어준다!
    }
}

```
