h2 데이터베이스 설치
https://www.h2database.com/html/main.html
H2 Database Engine
H2 Database Engine Welcome to H2, the Java SQL database. The main features of H2 are: Very fast, open source, JDBC API Embedded and server modes; in-memory databases Browser based Console application Small footprint: around 2.5 MB jar file size Supp
www.h2database.com
여기에 들어가서 설치를 해준다!
접근권한을 주고 실행시키면
이렇게 뜬다
테이블을 생성해보자! sql문을 치고 실행하면 member 테이블이 생성된 것을 확인할 수 있다.
순수 JDBC
build.gradle에 jdbc, h2 데이터베이스관련 라이브러리를 추가한다.
스프링부트 데이터베이스 연결 설정도 추가한다. (스프링부트 2.4부터는 spring.datasource.username=sa를 꼭 추가해주어야한다!! 공복 모두 제거)
Jdbc 리포지토리를 구현하고 스프링 설정도 변경해주면 (코드 생략)
이렇게 회원가입한 회원들이 DB콘솔에 잘 들어가있는 것을 확인할 수 있다.
개방-폐쇄 원칙(OCP, Open-Closed Principle) : 확장에는 열려있고, 수정, 변경에는 닫혀있다.
스프링의 DI (Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클 래스를 변경할 수 있다.
스프링 통합 테스트
스프링 컨테이너와 DB까지 연결한 통합 테스트 진행!!
package hello.hello_spring.service;
import hello.hello_spring.domain.Member;
import hello.hello_spring.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("이미 존재하는 회원입니다.");
}
}
@SpringBootTest` : 스프링 컨테이너와 테스트를 함께 실행한다.
@Transactional` : 테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테 스트 완료 후에 항상 롤백한다. 이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다.
스프링 JdbcTemplate
JdbcTemplate은 JDBC API에서 반복 코드를 대부분 제거해준다. 하지만 SQL은 직접 작성해야한다 (-> JPA)
JdbcTemplate 회원 리포지토리 코드!
package hello.hello_spring.repository;
import hello.hello_spring.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;
};
}
}
이걸 사용하도록 config도 잊지말고 변경
return new JdbcTemplateMemberRepository(dataSource);
JPA
왜 JPA를 쓰는가?!!
JPA는 기존의 반복 코드는 물론이고, 기본적인 SQL도 JPA가 직접 만들어서 실행해준다.
JPA를 사용하면, SQL과 데이터 중심의 설계에서 객체 중심의 설계로 패러다임을 전환을 할 수 있다.
JPA를 사용하면 개발 생산성을 크게 높일 수 있다.
일단 build.gradle에 라이브러리를 추가하자 (spring-boot-starter-data-jpa는 내부에 jdbc 관련 라이브러리를 포함한다.)
스프링 부트에 JPA 설정을 추가해주기 위해서 아래 두 줄을 사용하자
spring.jpa.show-sql=true //JPA가 생성하는 SQL 출력
spring.jpa.hibernate.ddl-auto=none //JPA는 테이블을 자동으로 생성하는 기능 제공, none 은 해당 기능 끄기, create는 엔티티 정보를 바탕으로 테이블도 직접 생성해줌
JPA 엔티티 매핑, 회원 리포지토리, 스프링 설정 변경을 모두 해준다!
package hello.hello_spring.domain;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.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.hello_spring.repository;
import hello.hello_spring.domain.Member;
import jakarta.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();
}
}
package hello.hello_spring;
import hello.hello_spring.repository.*;
import hello.hello_spring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import jakarta.persistence.EntityManager;
import javax.sql.DataSource;
@Configuration
public class SpringConfig {
private final DataSource dataSource;
private final EntityManager em;
public SpringConfig(DataSource dataSource, EntityManager em) {
this.dataSource = dataSource;
this.em = em;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
//return new MemoryMemberRepository();
//return new JdbcMemberRepository(dataSource);
//return new JdbcTemplateMemberRepository(dataSource);
return new JpaMemberRepository(em);
}
}
하지만 자꾸 JPA관련 클래스를 못 찾는다는 오류가 떴다..
알고보니 Spring Boot 3.x 부터는 JPA 관련 패키지가 바뀌었다고 한다!
// 변경 전
import javax.persistence.EntityManager;
// 변경 후
import jakarta.persistence.EntityManager;
import를 모두 jakarta로 바꿔줌
해결되었다!
테스트도 잘 통과했다.
스프링 데이터 JPA
스프링 데이터 JPA를 사용하면 리포지토리에 구현 클래스 없이 인터페이스 만으로 개발을 끝낼 수 있다고 한다. 기본 CRUD 기능도 스프링데이터 JPA가 모두 제공한다는데 아무튼 엄청 좋은 건가보다
package hello.hello_spring.repository;
import hello.hello_spring.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.hello_spring;
import hello.hello_spring.repository.*;
import hello.hello_spring.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);
}
}
스프링 설정도 변경해준다.
스프링 데이터 JPA가 SpringDataJpaMemberRepository를 스프링 빈으로 자동 등록해준다!
스프링 데이터 JPA는
인터페이스를 통한 기본적인 CRUD도 제공해주고
findByName(), findByEmail()처럼 메서드 이름 만으로 조회 기능을 제공해주고
페이징 기능도 자동 제공해준다.
우와~
실무에서는 JPA랑 스프링 데이터 JPA를 기본으로 사용하고 복잡한 동적 쿼리는 Querydsl이라는 라이브러리를 사용한다고 한다.
'WINK-(Web & App) > Spring Boot 스터디' 카테고리의 다른 글
[2025 1학기 스프링 부트 스터디] #여민호 6주차 (0) | 2025.05.20 |
---|---|
[2025 1학기 스프링 부트 스터디] 남윤찬 #6주차 (0) | 2025.05.19 |
[2025 1학기 스프링부트 스터디] 고윤정 #6주차 (2) | 2025.05.18 |
[2025 1학기 스프링 부트 스터디] 이상래 #6주차 (0) | 2025.05.18 |
[2025 1학기 스프링 부트 스터디] 정다은 #6주 (1) | 2025.05.18 |