ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 부트 - 회원 관리 예제 실습 (순수 JDBC로 H2 데이터베이스 연동)
    Spring Framework 2022. 4. 24. 00:01
    728x90

     

    1. build.gradle에 jdbc와 h2 데이터베이스 의존성을 추가합니다.

    dependencies {
    	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    	implementation 'org.springframework.boot:spring-boot-starter-web'
    	implementation 'org.springframework.boot:spring-boot-starter-jdbc' //추가됨
    	runtimeOnly 'com.h2database:h2' //추가됨
    	testImplementation 'org.springframework.boot:spring-boot-starter-test'
    }

     

    2. src/main/resources/application.properties 에 다음과 같은 코드를 추가합니다.

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

     

    이렇게 하면 데이터베이스에 접근하기 위한 준비는 끝나게 됩니다.

     

    3. hello.hellospring.repository 패키지에 JdbcMemberRepository 클래스를 생성합니다.

    package hello.hellospring.repository;
    
    import java.sql.Connection;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Optional;
    
    import org.springframework.jdbc.datasource.DataSourceUtils;
    import javax.sql.DataSource;
    
    import hello.hellospring.domain.Member;
    
    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 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);
    	}
    }

    만약 org.springframework.jdbc.datasources.DatasourceUtils 쪽에 에러가 발생한다면 build.gradle 파일 우클릭 -> gradle -> refresh gradle project를 클릭하시면 정상적으로 작동할 것입니다.

     

    DB를 연결하고 sql Query를 실행하고 close 하는데 try-catch가 많고 코드가 복잡해 보입니다.

    그냥 순수 jdbc는 이렇게 동작하는구나라고 생각하고 넘어가겠습니다.

     

    4. hello.hellospring.service 패키지의 SpringConfig.java 파일을 수정합니다.

    package hello.hellospring.service;
    
    import javax.sql.DataSource;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import hello.hellospring.repository.JdbcMemberRepository;
    import hello.hellospring.repository.MemberRepository;
    import hello.hellospring.repository.MemoryMemberRepository;
    
    @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() {
    		//return new MemoryMemberRepository();
    		return new JdbcMemberRepository(dataSource);
    	}
    	
    }

    private DataSource dataSource를 선언하여 생성자로 주입하면 스프링이 자동적으로 application.properties를 읽고 dataSource를 만들어 할당해줍니다.

     

    이후에 MemoryMemberRepository()를 JdbcMemberRepository(dataSoruce)로만 변경해준다면 끝입니다.

    SpringConfig 파일만 수정함으로써 끝입니다.

    인터페이스의 다형성 그리고 스프링의 DI를 이용하여 이렇게 편리하게 애플리케이션을 변경할 수 있습니다.

    즉 객체지향에서 중요한 개방-폐쇄 원칙(OCP) = 확장에는 열려있고, 수정, 변경에는 닫혀있는 원칙을 지킬 수 있습니다.

     

    이후에 코드를 실행시키고 회원 목록을 조회해 보면 에러가 발생할 수 있습니다.

    만약 스프링 부트 2.4부터는 application.properties에 spring.datasource.username=sa를 추가해주시면 정상적으로 DB에 저장된 회원 목록을 조회할 수 있고 DB에 회원등록을 진행할 수 있습니다.

     

    5. 서버 실행 후 localhost:8080에 접속하여 동작확인

    댓글

Designed by Tistory.