Post

Spring - 5 스프링 DB접근 기술 (2)

해당 자료는 인프런 김영한님의 스프링 입문 - 코드로 배우는 스프링 부트, 웹MVC, DB 접근 기술 강의 노트입니다.


1. Spring JDBC template

JdbcTemplate이란 JDBC API에서 본 ‘connect()’와 같은 반복 코드를 줄여주는 역할을 햊부니다. 하지만 SQL은 직접 작성해야합니다.
SQL은 뒤에 작성할 JPA를 통해 줄여나갈 수 있다고 합니다.

repository package에서 JdbcTemplateMemberRespository라는 자바파일을 만들어줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.springframework.beans.factory.annotation.Autowired;
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 jdbcTemaplate;

    //spring이 자동으로 datsoruce를 injection 해줌.
    @Autowired
    public JdbcTemplateMemberRepository(DataSource dataSource){
        jdbcTemaplate = new JdbcTemplate(dataSource);
    }

    //감 잡기용.
    @Override
    public Member save(Member member) {
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemaplate);
        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 = jdbcTemaplate.query("select * from member where id = ?",memberRowMapper(),id);
        return result.stream().findAny();
    }

    @Override
    public Optional<Member> findByName(String name) {
        List<Member> result = jdbcTemaplate.query("select * from member where name = ?", memberRowMapper(), name);
        return result.stream().findAny();
    }

    @Override
    public List<Member> findAll() {
        return jdbcTemaplate.query("select * from member",memberRowMapper());
    }

    private RowMapper<Member> memberRowMapper(){
        return (rs, rowNum) -> {

            Member member = new Member();
            member.setId(rs.getLong("id"));;
            member.setName(rs.getString("name"));;
            return member;
        };
    }
}

위와같이 코드를 작성해준 다음 전에 작성했던 SpringConfig에서 스프링이 알 수 있게 다음과 같이 코드를 작성합니다.

1
2
3
4
5
6
 @Bean
    public MemberRepository memberRepositroy(){
        //return new MemoryMemberRepository();
        //return new JdbcMemberRepository(dataSoruce);
        return new JdbcTemplateMemberRepository(dataSoruce);
    }

테스트를 실행해서 오류가 뜨지 않으면 성공입니다.


2. JPA

SQL쿼리도 JPA가 자동으로 처리하여 개발 생산성을 크게 늘려주는 기술 –> 객체 중심의 설계로 패러다임을 전환할 수 있다고 합니다.

마이바티스에 비해 JPA는 해외적으로 비교도 안 될 정도로 많이 사용하고 있다.

먼저 jpa,h2 데이터베이스 관련 라이브러리를 추가하기 위해 build.gradle의 코드를 바꿔줍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
plugins {
	id 'org.springframework.boot' version '2.4.1'
	id 'io.spring.dependency-management' version '1.0.10.RELEASE'
	id 'java'
}

group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

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'
	//jdbc,jpa 포함
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	runtimeOnly 'com.h2database:h2'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
	useJUnitPlatform()
}

그 후, apllication.properties에서

1
2
3
4
5
6
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa

spring.jpa.show-sql=true 
spring.jpa.hibernate.ddl-auto=none

로 밑의 2줄을 추가해줍니다.
spring.jpa.show-sql=true 는 jpa가 생성하는 SQL을 출력합니다.
spring.jpa.hibernate.ddl-auto=none은 jpa가 자동으로 테이블을 생성하는 기능을 none을 통해 해당 기능을 끕니다. 왜냐하면 이미 만들어진 테이블을 갖고 테스트를 진행하기 때문입니다.

예전에 작성했던 domain/Member.java 파일을 수정해줘야합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package hello.hellospring.domain;

import javax.persistence.*;

//jpa가 관리하는 entity가 되는 것.
@Entity
public class Member {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;//시스템이 정하는 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;
    }
}

그리고 repository pakage에서 JpaMemberRepository.java를 만들어주고 다음 코드를 넣습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package hello.hellospring.repository;

import hello.hellospring.domain.Member;
import org.hibernate.type.EntityType;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.swing.text.html.Option;
import java.util.List;
import java.util.Optional;


public class JpaMemberRepository implements MemberRepository{

    //주입받아야한다.
    private final EntityManager em;

    public JpaMemberRepository(EntityManager em){
        this.em = em;
    }

    @Override
    public Member save(Member member) {
        //영속적으로 저장한다. jpa가 insert query만들어서 db에 집어넣는다.
        //혁식적
        em.persist(member);
        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        //조회할 타입이랑 식별자를 넣어줌.
        Member member = em.find(Member.class,id);
        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() {
        //멤버 entity 대상으로 쿼리를 보냄.
        //select문을 보면 m entity 객체 자체를 select함.
        return em.createQuery("select m from Member m", Member.class).getResultList();

    }
}

마지막으로 MemberSerivce.java파일인 서비스계층에 트랜잭션을 추가해줘야합니다.

1
2
3
@Transactional
public class MemberService {~~
}

@Transactional을 추가해줍니다.

  • 스프링은 해당 클래스의 메서드를 실행할 때 트랜잭션을 시작하고, 메서드가 정상 종료되면 트랜잭션을 커밋합니다. 만약 런타임 예외가 발생하면 롤백합니다.
  • JPA를 통한 모든 데이터 변경은 트랜잭션 안에서 실행되어야 합니다.

마지막으로 JPA를 사용하도록 스프링 설정을 변경해야합니다.
SpringConfig.java파일에서

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package hello.hellospring.service;

import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.JdbcTemplateMemberRepository;
import hello.hellospring.repository.JpaMemberRepository;
import hello.hellospring.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.persistence.EntityManager;
import javax.sql.DataSource;

@Configuration
public class SpringConfig {

    private EntityManager em;

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

    /*
    private final DataSource dataSoruce;

    @Autowired
    public SpringConfig(DataSource dataSource){
        this.dataSoruce = dataSource;
    }
     */
    @Bean
    public MemberService memberService(){
        return new MemberService(memberRepositroy());
    }

    @Bean
    public MemberRepository memberRepositroy(){
        //return new MemoryMemberRepository();
        //return new JdbcMemberRepository(dataSoruce);
        //return new JdbcTemplateMemberRepository(dataSoruce);
        return new JpaMemberRepository(em);
    }
}

이처럼 Jpa를 사용할 수 있게 객체 생성, Bean에 담아줍니다. 테스트를 해보면
처럼 insert ~ 문구가 뜨면 테스트가 진행된 것이고

만약 값이 들어가는 지 눈으로 확인하고 싶다면 전에 작성한 MemberServiceIntegrationTest.java 파일에서 회원가입()위에

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
~~~
    //중복 에러가 뜨면 DB를 비워주자. test용 DB를 구축
    @Test
    //DB에 반영하도록 함.
    //@Commit
    void 회원가입() {
        //given 무언가 주어졌을 때
        Member member = new Member();
        member.setName("spring");
        //when 어떤 상황에서
        Long saveId = memberService.join(member);
        //then 무언가 나와야한다.
        Member findMember = memberService.findOne(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }
~~~

@Commit이란 것을 달아주면 DB에 직접 반영됩니다. 하지만 나중에 데이터를 지워줘야합니다.

This post is licensed under CC BY 4.0 by the author.