순수 JDBC
객체 지향의 가장 큰 장점 : 다형성(인터페이스 구현체를 바꾸면서 기존 코드 변경 없이 바꿀 수 있다!)
Last updated
객체 지향의 가장 큰 장점 : 다형성(인터페이스 구현체를 바꾸면서 기존 코드 변경 없이 바꿀 수 있다!)
Last updated
웹 서버와 DB를 연결해서 데이터를 저장해본다!
자바는 기본적으로 DB랑 붙으려면 JDBC가 꼭 있어야한다!
회원을 저장하는 역할은 MemberRepository가 하지만, memory에 할지 JDBC에 할지는 다르게 구현 가능하다! (implements MemberRepository에 커서를 두고 option enter 누르면 메서드들 오버라이드 자동완성된다!)
이처럼 스프링의 DI를 사용하면 기존코드를 전혀 손대지 않고, 설정을 바꾸는 것만으로 구현 클래스를 변경할 수 있다!
이것을 개방 폐쇄 원칙(OCP;Open-Closed Principle) 이라고 한다. 확장에는 열려있고, 수정에는 닫혀있다.
다형성의 개념을 잘 활용하면 애플리케이션 수정없이 가능하다! 객체지향의 가장 큰 장점은 이와 같이 인터페이스에서 구현체를 바꾸면서도 기존 코드 변경 없이 바꿀 수 있는 것이다.
JdbcMemberRepository.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를 빈에 등록해준다!
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를 넣어준다!
}
}