본문 바로가기

WINK-(Web & App)/Spring Boot 스터디

[2025 1학기 스프링 부트 스터디] 정다은 #6주

반응형

노트북을 이제서야 새로 장만 햇습니다.

 

 

h2 database를 설치하고 파일을 만들어줘야합니다.

처음 연결 이후에는 jdbc:h2:tcp://localhost/~/test로 변경해서 접속해야 합니당

이후에는 sql 문법을 이용해서 조회할 수 있습니다. 

drop table if exists member CASCADE;
create table member
( id bigint generated by default as identity,
 name varchar(255),
 primary key (id)
);

 

Jdbc

즉 Java Database Connectivity  : java 어플리케이션에서 데이터 베이스에 접근 및 조작하기 위한 표준 api

 

옛날에는 개발할 때 데이터베이스 연결, 수정, 조회 등 모든 작업을 직접 개발해야했는데 그때 사용된 것이 jdbc.

요즘에는 jpa(java persistence api)가 많이 활용된다.

 

jdbc 회면 repository

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 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);
 }
}

 

 

jdbc 멤버 repository

package hello.hellospring;
import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.JdbcTemplateMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.repository.MemoryMemberRepository;import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class SpringConfig {
 private final DataSource dataSource;
 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);
 }
}

 

 

구현 클래스 추가 이미지

스프링 설정 이미지

 

스프링 통합 테스트

 

테스트 디렉토리 -> MemberServiceIntegrationTest 클래스 생성 및 코드 작성

package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

@SpringBootTest
@Transactional

class MemberServiceIntegrationTest {
 @Autowired MemberService memberService;
 @Autowired MemberRepository memberRepository;
 @Test
 public void 회원가입() throws Exception {
 //Given
 Member member = new Member();
 member.setName("hello");
 //When
 Long saveId = memberService.join(member); //Then
 Member findMember = memberRepository.findById(saveId).get();
 assertEquals(member.getName(), findMember.getName());
 }
 @Test
 public void 중복_회원_예외() throws Exception {
 //Given
 Member member1 = new Member();
 member1.setName("spring");
 Member member2 = new Member();
 member2.setName("spring");
 //When
 memberService.join(member1);
 IllegalStateException e = assertThrows(IllegalStateException.class,
 () -> memberService.join(member2));//예외가 발생해야 한다.
 assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
 }
}

 

스프링 jdbc template

spring jdbc template은 순수 jdbc의 단점을 해결하는데 도움을 줍니다

(순수 jdbc의 단점 : 반복 코드, 예외 처리, 리소스 관리 등등)

순수 jdbc는 반복적인 작업이 진행되는데 여기서는 오류가 생길 수도 있고 불필요한 코드들을 줄일 필요가 있음

 

레포지토리 -> jdbcTemplateMemberRepository 클래스 생성

package hello.hellospring.repository;import hello.hellospring.domain.Member;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class JdbcTemplateMemberRepository implements MemberRepository {
 private final JdbcTemplate jdbcTemplate;
 public JdbcTemplateMemberRepository(DataSource dataSource) {
 jdbcTemplate = new JdbcTemplate(dataSource);
 }
 @Override
 public Member save(Member member) {
 SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
 jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
 Map<String, Object> parameters = new HashMap<>();
 parameters.put("name", member.getName());
 Number key = jdbcInsert.executeAndReturnKey(new
MapSqlParameterSource(parameters));
 member.setId(key.longValue());
 return member;
 }
 @Override
 public Optional<Member> findById(Long id) {
 List<Member> result = jdbcTemplate.query("select * from member where id 
= ?", memberRowMapper(), id);
 return result.stream().findAny();
 } @Override
 public List<Member> findAll() {
 return jdbcTemplate.query("select * from member", memberRowMapper());
 }
 @Override
 public Optional<Member> findByName(String name) {
 List<Member> result = jdbcTemplate.query("select * from member where 
name = ?", memberRowMapper(), name);
 return result.stream().findAny();
 }
 private RowMapper<Member> memberRowMapper() {
 return (rs, rowNum) -> {
 Member member = new Member();
 member.setId(rs.getLong("id"));
 member.setName(rs.getString("name"));
 return member;
 };
 }
}

 

jpa

jpa는 기존의 반복 코드 + 기본적인 sql도 jpa가 직접 만들어서 실행해주기 때문에 개발 생산성이 높아집니당

 

jpa mapping

package hello.hellospring.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Member {
 @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;
 private String name;
 public Long getId() {
 return id;
 }
 public void setId(Long id) {
 this.id = id;
 }
 public String getName() { return name;
 }
 public void setName(String name) {
 this.name = name;
 }
}

 

회원 레포

package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
public class JpaMemberRepository implements MemberRepository {
 private final EntityManager em;
 public JpaMemberRepository(EntityManager em) {
 this.em = em;
 }
 public Member save(Member member) {
 em.persist(member);
 return member;
 }
 public Optional<Member> findById(Long id) {
 Member member = em.find(Member.class, id);
 return Optional.ofNullable(member);
 }
 public List<Member> findAll() {
 return em.createQuery("select m from Member m", Member.class)
 .getResultList();
 } public Optional<Member> findByName(String name) {
 List<Member> result = em.createQuery("select m from Member m where 
m.name = :name", Member.class)
 .setParameter("name", name)
 .getResultList();
 return result.stream().findAny();
 }
}

 

스프링 데이터 jpa

 

스프링부트와 jpa를 사용했을 때 개발 생산성이 눈에 띄게 증가합니다

스프링 데이터 jpa를 이용하면 구현체 작성 없이 인터페이스만을 작성해서 개발을 완료할 수 있습니다.

 

springDataJpaMemberRepository

package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, 
Long>, MemberRepository {
 Optional<Member> findByName(String name);
}

스프링 설정변경 (스프링 데이터 jpa 회원 레포지토리를 사용하게 변경)

package hello.hellospring;
import hello.hellospring.repository.*;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SpringConfig {
 private final MemberRepository memberRepository;
 public SpringConfig(MemberRepository memberRepository) {
 this.memberRepository = memberRepository;
 }
 
 @Bean
 public MemberService memberService() {
 return new MemberService(memberRepository);
 }
}
반응형