목차
0. 환경
- m1 macbook
- IntelliJ IDEA(m1) - 202102
- java 11(AdoptOpenJDK-11.0.11)
1. build.gradle
- jdbc, h2 데이터베이스 관련 라이브러리 추가합니다.
dependencies {
//자바가 db에 연결을 하기 위해선 jdbc 필요합니다.
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
//h2db 연결을 위한 클라이언트
runtimeOnly 'com.h2database:h2'
}
- 추가 후 import(Load Gradle changes)를 해줍니다.
2. H2DB 연결 정보 설정(application.properties)
- resources/application.properties
- application.properties 파일에 다음과 같이 스프링 부트 데이터베이스 연결 설정 추가해 줍니다.
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
[주의!]
- 스프링 부트 2.4부터는 spring.datasource.username=sa 를 꼭 추가해주어야 합니다.
- 그렇지 않으면 Wrong user name or password 오류가 발생합니다.
- 참고로 다음과 같이 마지막에 공백이 들어가면 같은 오류가 발생합니다.
- spring.datasource.username=sa 공백 주의, 공백은 모두 제거해야 한다.
3. Jdbc 리포지토리 구현
- hello/hellospring/repository/JdbcMemberRepository.java
- 메모리 기반의 저장소에서 H2DB로 변경을 위해 작성합니다. 따라서 MemberRepository 인터페이스를 두고 구현체로 작성합니다.
- JDBC API로 직접 코딩하는 것은 매우 과거의 이야기 입니다. 참고만 또는 복사해서 실행 정도만 해보고 넘어가는 게 좋습니다.
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;
}
@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);
pstmt.setString(1, member.getName());
pstmt.executeUpdate();
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 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);
}
}
/*
스프링에서 DataSourceUtils를 통해서 커넥션을 획득을 해야 합니다.
이유는 트랜잭션에 걸리지 않게 데이터 베이스 커넥션을 같게 유지를 시켜 줍니다.
닫을 때도 마찬가지로 DataSourceUtils를 사용해야 합니다.
*/
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. 스프링 설정 변경
- 메모리 기반의 저장소에서 H2DB로 변경을 위해 구현체를 바꿔줍니다.
- 객체지향 설계의 장점인 다형성을 활용할 수 있습니다.
- 인터페이스를 두고 쉽게 구현체를 변경함으로써 특별하게 코드를 수정할 필요가 사라집니다.
- 스프링은 이것을 편하게 구현 및 개발 할 수 있도록 스프링 컨테이너가 DI(의존성 주입)을 제공합니다.
- 스프링의 DI (Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스를 변경할 수 있습니다.
- 개팡-폐쇄 원칙(OCP)이 지켜졌습니다. (확장에는 열려있고, 수정에는 닫혀있다.)
[기존 설정 파일]
package hello.hellospring;
import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
//자바 코드로 직접 스프링 빈 등록하기
@Configuration
public class SpringConfig {
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();//구현체
}
}
[설정 파일 변경]
package hello.hellospring;
import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
//자바 코드로 직접 스프링 빈 등록하기
@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);//h2 jdbc 구현체
}
}
- DataSource는 데이터베이스 커넥션을 획득할 때 사용하는 객체입니다.
- 스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 DataSource를 생성하고 스프링 빈으로 만들어둡니다. 따라서 따로 빈 등록 없이 DI를 받을 수 있습니다.
- 스프링이 설정 파일을 보고 알아서 빈을 생성해줍니다.
5. 테스트
1. 회원 가입을 해봅니다.
2. 회원이 잘 등록되었는지 회원 목록을 클릭해봅니다.
3. H2 Web Console을 이용해 테이블에 잘 들어갔는지 확인해봅니다.
4. 스프링 부트 재시작 후에도 잘 저장이 되어있는지 위의 과정처럼 확인해봅니다.
6. SOLID (객체 지향 설계)
단일 책임 원칙 | SRP(Single responsibility principle) | 한 클래스는 단 하나의 책임만 가져야 한다. |
개방-폐쇄 원칙 | OCP(Open/closed principle) | 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다. |
리스코프 치환 원칙 | LSP(Liskov substitution principle) | 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어햐 한다. |
인터페이스 분리 원칙 | ISP(Interface segregation principle) | 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다. |
의존관계 역전 원칙 | DIP(Dependency inversion principle) | 프로그래머는 추상화에 의존해야지, 구체화에 의존하면 안된다. 의존성 주입은 이 원칙을 따르는 방법 중 하나다. |
'Backend > 코드로 배우는 스프링 부트' 카테고리의 다른 글
[코드로 배우는 스프링 부트] 11. 스프링 JdbcTemplate (0) | 2021.11.08 |
---|---|
[코드로 배우는 스프링 부트] 10. 스프링 통합 테스트 (0) | 2021.11.06 |
[코드로 배우는 스프링 부트] 8. H2DB 설치 (0) | 2021.11.04 |
[코드로 배우는 스프링 부트] 7. 웹 MVC 개발 (회원 웹 기능) (0) | 2021.11.03 |
[코드로 배우는 스프링 부트] 6. 스프링 빈과 의존관계 (의존성 주입 방식) (0) | 2021.11.02 |