순수 JDBC

hellospring.SpringConfig - 스프링 설정 변경

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

DataSource는 데이터베이스 커넥션을 획득할 때 사용하는 객체다. 스프링 부트는 데이터베이스 커넥션 정보를 바탕으로 DataSource를 생성하고 스프링 빈으로 만들어둔다.

Untitled

Untitled

개방-폐쇄 원칙(OCP, Open-Closed Principle)

확장에는 열려있고, 수정, 변경에는 닫혀있다.

스프링의 DI (Dependencies Injection)을 사용하면 기존 코드를 전혀 손대지 않고, 설정만으로 구현 클래스를 변경할 수 있다.

스프링 통합 테스트

service.MemberServiceIntegrationTest

@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

순수 Jdbc와 동일한 환경설정.

스프링 JdbcTemplate과 MyBatis 같은 라이브러리는 JDBC API에서 본 반복 코드를 대부분 제거해주지만 SQL은 직접 작성해야 한다.

hellospring.SpringConfig - 스프링 설정 변경

@Configuration  //코드로 직접 스프링 빈 등록하기. 코드 수정이 유리하다.
public class SpringConfig {

    private final DataSource dataSource;

    @Autowired
    public SpringConfig(DataSource dataSource){
        this.dataSource=dataSource;
    }

    private final MemberRepository memberRepository;

    @Autowired
    public SpringConfig(MemberRepository memberRepository){
        this.memberRepository = memberRepository;
    }

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository);
    }

    @Bean
    public MemberRepository memberRepository() {
        //return new MemoryMemberRepository();
        //return new JdbcMemberRepository(dataSource);
        return new jdbcTemplateMemberRepository(dataSource);
    }
}

JPA

repository.JpaMemberRepository

public class JpaMemberRepository implements MemberRepository{

    private final EntityManager em;

    public JpaMemberRepository(EntityManager em){
        this.em = em;
    }
    @Override
    public Member save(Member member) {
        em.persist(member); //insert 쿼리 수행. setter도 알아서 수행
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        Member member = em.find(Member.class, id); //pk를 통해 조회하는 쿼리 날림.
        return Optional.ofNullable(member);
    }

    @Override
    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();
    }

    @Override
    public List<Member> findAll() {           //jpql. 객체 대상으로 쿼리를 날림. m:객체 자체를 선택.
        List<Member> result = em.createQuery("select m from Member m", Member.class)
                .getResultList();
        return result;
    }
}

hellospring.SpringConfig

@Configuration  //코드로 직접 스프링 빈 등록하기. 코드 수정이 유리하다.
public class SpringConfig {

    private EntityManager em;

    @Autowired
    public SpringConfig(EntityManager em) {this.em=em;}

    private final MemberRepository memberRepository;

    @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를 통한 모든 데이터 변경은 트랜잭션 안에서 실행해야 한다.

스프링 데이터 JPA

Untitled

Untitled

기본 CRUD 기능도 스프링 데이터 JPA가 모두 제공.

개발자는 핵심 비즈니스로직을 개발하는데, 집중할 수 있다.

repository.SpringDataJpaMemberRepository

JpaRepository가 해당 인터페이스의 구현체를 자동으로 생성한다.

@Primary  //해당 빈을 우선적으로 주입함.
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {

		//JPQL: select m from Member m where m.id = ? 쿼리를 날림. 메서드 이름 규칙을 지켜야 한다.
    @Override
    Optional<Member> findByName(String name);
}

hellospring.SpringConfig

@Configuration  //코드로 직접 스프링 빈 등록하기. 코드 수정이 유리하다.
public class SpringConfig {

    private final MemberRepository memberRepository;

    @Autowired   //SpringDataJpaMemberRepository의 구현체가 주입됨.
    public SpringConfig(MemberRepository memberRepository){
        this.memberRepository = memberRepository;
    }

    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository);
    }
}

스프링 데이터 JPA가 자동으로 SpringDataJpaMemberRepository의 구현체를 생성해서 스프링 빈에 등록됨.