
build.gradle파일에 JDBC, h2 데이터베이스 관련 라이브러리 추가implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
application.properties에 추가spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
MeberRepository가 하지만MemoryMemberRepository),JdbcMemberRepository)에 대한 차이DateSource는 데이터베이스 커넥션을 획득할 때 사용하는 객체DataSource를 생성하고,📂 JdbcMemberRepository
public class JdbcMemberRepository implements MemberRepository{
// 📌 DB에 붙으려면 DataSource가 필요 -> 스프링에게 주입 받아야 함
// 📌 application.properties의 내용을 스프링부트가 DataSource 생성 및 주입
private final DataSource dataSource;
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{
conn = getConnection();
pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
// parameterIndex 1은 SQL의 ?와 매칭
pstmt.setString(1, member.getName());
pstmt.executeUpdate(); // DB에 쿼리(insert)가 날라감
rs = pstmt.getGeneratedKeys(); // 방금 생성된 KEY(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);
// 조회는 executeUpdate()❌, executeQuery⭕
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 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);
}
}
@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);
}
}
// 📌 커넥션을 얻
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();
}
}
// 📌 dataSource.getConnection()❌, DataSourceUtils 권장 -> 트랜잭션 유지
// 📌 데이터베이스와 연결된 커넥션을 얻음
// 📌 진짜 데이터베이스와 연결되는 열린 소켓을 얻을 수 있음?**
private void close(Connection conn) throws SQLException{
DataSourceUtils.releaseConnection(conn, dataSource);
}
}
📂 SpringConfig
// 스프링이 application.properties를 보고 DataSource를 빈으로 자동 생성
@Configuration
public class SpringConfig {
private DataSource dataSource;
// DI, 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);
}
}
JdbcMemberRepository는 JDBC를 직접 사용하는 레포지토리 구현의 전형적인 패턴String sql = "select * from member where id = ?";
String으로 선언?를 넣고, 나중에 바인딩conn = getConnection();
DataSource로부터 커넥션을 가져옴DataSourceUtils.getConnection() 사용해야 함pstmt = conn.prepareStatement(sql);
pstmt.setLong(1, id);
PreparedStatement를 통해 SQL 실행 준비setXxx()로 파라미터 바인딩 (1은 ?의 인덱스)rs = pstmt.executeQuery(); // 조회
rs = pstmt.executeUpdate(); // 삽입, 수정, 삭제
if(rs.next()){
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
ResultSet을 객체로 변환하는 단계 (Manual ORM)getter 호출} catch (Exception e){
throw new IllegalStateException(e);
}
SQLException을 런타임 예외로 감싸서 던짐DataAccessException을 사용하는 경우도 많음close(conn, pstmt, rs);
ResultSet → Statement → ConnectionJdbcMemberRepository - Repository 계층DataSource객체가 필요한데,SpringConfig에서 설정된 DataSource가 전달됨SpringConfig - 설정 및 의존성 주입 관리MemberService → MemberRepository → DataSource 순서로 의존관계 주입application.properties - 설정파일application.properties를 기반으로 DataSource 객체가 빈으로 자동 생성JdbcMemberRepository와 MemberService가 빈으로 등록MemberService에서 memberRepository를 호출하면,DataSourceUtils를 통해 커넥션 풀을 관리하며,MemoryMemberRepository에서 JdbcMemberRepository로 갈아끼우기
