본문 바로가기
Back-End/🌱 Spring Boot (java)

[Spring 입문] 스프링부트와 MySQL연결하기 - JDBC Template (AWS이용)

by 코딩하는 동현😎 2023. 1. 21.

이전 포스트에 맞춰 실습하겠습니다! (VSCode on Gitpod.io WEB IDE)

https://konkukcodekat.tistory.com/entry/Spring-%EC%9E%85%EB%AC%B8-%EA%B3%84%EC%B8%B5-%EA%B5%AC%EC%A1%B0%EC%99%80-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84%EC%A3%BC%EC%9E%85-Dependency-Injection-%EC%8B%A4%EC%8A%B5

 

[Spring 입문] 계층 구조와 클래스 의존관계/주입 (Dependency Injection) 실습

입문자의 비즈니스 실습 요구 사항 정리 데이터 : 회원 ID , 이름 기능 : 회원 등록, 조회 아직 데이터저장소가 없다고 가정하고, 자바내부 자료구조를 이용해서 모의로 실습 해볼 것입니다. 서비

konkukcodekat.tistory.com


1. build.gradle 파일에 의존성 추가하기

jdbc와 mysql-connector 라이브러리를 build.gradle 파일에 아래와 같이 추가해줍니다. 

아래와 같이 추가해주고 동기화 시켜주면, gradle이 알아서 자바JDK 라이브러리들을 다운받아서 적용 시켜줍니다.

// 아래 3개 추가!!
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-jdbc'
	implementation 'mysql:mysql-connector-java'
}

 

(vscode) 어피치 오른쪽에 있는 동기화 버튼을 누르면 java Extention Pack이 있는한 자동으로 다운 받아질 것입니다.

IntelliJ는 자동으로 JDK가 다운 받아 집니다!


External Dependencys를 보시면 아래와 같이 mysql-connector과 starter-jdbc등의 라이브러리가 자동으로 설치된것을 확인할수 있습니다.

설치가 됐는지 이렇게 확인해주시면 좋습니다!


2. DataSource를 등록하기

 

1) DataSource란?

데이터베이스를 이용할때 jdbc를 사용하여 db에 접속하기 위해서는 드라이버를 로드하고 db에 접속하여 connection 객체를 받아와야 하는데, 이런식이면 db에 쿼리를 보낼때 마다 드라이버를 로드하고 커넥션을 생성하고 닫게되는데 이것이 성능을 낮추는 원인이 됩니다.(드라이버도 한번만 로드하면 되는데 불필요하게 여러번 로드하게 됩니다.)

이런 문제를 해결하기 위해 Connection Pool을 사용하는데 javax.sql.DataSource 를 사용하면 됩니다.

Connection Pool에는 여러개의 Connection 객체가 생성되어 운용되는데, 이를 직접 웹 애플리케이션에서 다루기 힘들기 때문에 DataSource라는 개념을 도입하여 사용합니다.

 


2) 데이터베이스 서버의 엔드포인트(링크) 가져오기

저의 경우는 개인컴퓨터로 띄우기 곤란해서 AWS로 데이터베이스를 원격으로 24시간 띄워놨습니다.

amazon Lightsail -> databases

https://lightsail.aws.amazon.com/ls/webapp/home/databases

 

https://lightsail.aws.amazon.com/ls/webapp/home/databases

 

lightsail.aws.amazon.com

Network security를 public mode로 하고, 어피치 우측에 Endpoint 주소를 복사하기면 됩니다!

mysql workbench로 띄워도 엔드포인트 주소가 항상 나옵니다.(자신의 컴퓨터로 올리면 localhost 입니다)


3) application.properties 파일을 생성하고 연결해주기

위와 같이 파일을 만들고 전에 복사한 엔드포인트를 아래와 같이 입력해줍니다.

 

resources/application.properties

#resources/application.properties
spring.datasource.url=jdbc:mysql://{엔드포인트}:{포트번호}/{데이터베이스}
spring.datasource.username={db에 나와있는 이름}
spring.datasource.password={db에서 내가 설정한 비번}

# jdbc:mysql://{엔드포인트}:{포트번호}/{데이터베이스}

port는 보통 3306을 체택하고, 비밀번호를 설정하지 않았다면 입력하지 않아도 됩니다.


데이터베이스를 당연히 미리 만들어주고, 테이블도 미리 만들어줘야합니다.

필자는 VSCODE로 아래 Extiension으로 DB를 조작했습니다.


3. DataSource 객체를 의존성 주입하기

 

스프링 빈에 대한 이해가 없다면 이 포스트를 미리 보시면 도움이 될것입니다. 

Config파일을 만들어서 자바코드로 등록하는 방법을 이용할 것 입니다.

https://konkukcodekat.tistory.com/entry/Spring-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88Bean%EA%B3%BC-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84-%EC%84%A4%EC%A0%95Dependency-Injection

 

[Spring 입문] 스프링 빈(Bean)과 의존관계 설정(Dependency Injection)

스프링 빈 (Spring Bean) 스프링 빈이란 스프링 컨테이너에서 관리되는 자바 객체를 의미합니다. 보통 저번에 만든 스프링 컨테이너 스프링 컨테이너는 스프링 빈의 생명 주기를 관리하고, 스프링

konkukcodekat.tistory.com


Config 생성자에 datasource를 바로 받아주고 전역변수 dataSource에 받아줍니다.

그리고 db를 다루는 JdbcRepository파일을 새로 생성해주고 주입시켜주면, repository가 DB를 바로 다룰수 있습니다.

 

SpringConfig.java

package example.boot;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import example.boot.repository.JdbcMemberRepository;
import example.boot.repository.MemberRepository;
// import example.boot.repository.MemoryMemberRepository;
import example.boot.service.MemberService;


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

4. JdbcRepository 예시

포스트 맨위에 이어서 한다는 실습을 구현한 모습입니다.

연동하는 법이기 때문에 코드 설명은 자세하지 않겠습니다.

jabc-template같은 경우에는 RowMapper라는 객체를 재정의해주고 오버라이딩을 해줘야합니다.

package example.boot.repository;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import javax.sql.DataSource;

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 example.boot.domain.Member;

// JDBC Template을 이용한 레포지토리 구현체
public class JdbcMemberRepository implements MemberRepository {

    // 선언
    private final JdbcTemplate jdbcTemplate;

    // DataSource 실시간으로 연결해주기

    @Autowired
    public JdbcMemberRepository(DataSource dataSource){
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public Member save(Member member) {
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
        jdbcInsert.withTableName("members").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 members where id=?",memberRowMapper(), id);
        return result.stream().findAny();
    }

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

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

    private RowMapper<Member> memberRowMapper(){
        return new RowMapper<Member>() {
            @Override
            public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return member;
            }
            
        };
    }
    
}

이해를 돕기 위한 MemberRepository 인터페이스 코드

package example.boot.repository;

import java.util.Optional;
import java.util.List;

import example.boot.domain.Member;

public interface MemberRepository {
    // 인터페이스란 기능에 대한 명세 집합
    // 공통적인 함수들을 정의하고 실질적인 repository에 상속 받는다

    // 멤버를 받으면 저장
    Member save(Member member);

    // id / name 으로 받고 member를 반환
    Optional<Member> findById(Long id);

    Optional<Member> findByName(String name);

    //여러 멤버들을 리스트에 담아서 묶음으로 반환
    List<Member> findAll();
}

 


5. JUnit 통합 테스트 케이스 예시

IntegrationTest 이름의 자바파일을 test폴더에 만듭니다.

@SpringBootTest 어노테이션을 붙이면 실제 스프링을 구동해서 스프링 컨테이너(빈)과 DB를 이용해서 테스트를 해줄수 있고,

@Transactional을 추가해주면, 테스트 시행후 DB를 롤백해서 테스트 하기 전 상태로 되돌려놔서, 테스트 DB에 영향 안가게 해줄 수 있습니다.

package example.boot.service;

import org.junit.jupiter.api.Assertions;
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 example.boot.domain.Member;
import example.boot.repository.MemberRepository;

// 스프링부트 컨테이너 이용한 테스트
@SpringBootTest
// 테스트 할때마다 DB 롤백해서 영향X
@Transactional
public class MemberServiceIntegrationTest {
    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    @Test
    public void 회원_가입(){
        //Given
        Member member = new Member();
        member.setName("spring1");
        //Then
        Long id = memberService.join(member);
        // When
        Assertions.assertEquals(member.getName(), memberService.findOne(id).get().getName());
    }

    @Test
    public void 중복_회원_예외(){
        // Given
        Member member1 = new Member();
        member1.setName("spring");
        Member member2 = new Member();
        member2.setName("spring");
        
        // When
        memberService.join(member1);

        // Then
        Exception e = Assertions.assertThrows(IllegalStateException.class, () ->  memberService.join(member2));
        Assertions.assertEquals(e.getMessage(), "already Exists");
    }
}

이해를 돕기위한 MemberService.java 원본

서비스파일이 repository를 다루는 파일이기 때문에 간접적으로 DB를 테스트 할 수 있게 됩니다.

package example.boot.service;

import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import example.boot.domain.Member;
import example.boot.repository.MemberRepository;

// @Service
public class MemberService {
    
    private MemberRepository memberRepository;

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

    // 회원 가입
    public Long join(Member member){
        // 같은 이름이 있는 중복 회원 X
        validateOverapMember(member);
        memberRepository.save(member);
        return member.getId();
    }

    // 중복된 회원이 있는지 검증해주는 함수
    public void validateOverapMember(Member member){
        // Optional 객체 이므로 ifPresent 함수를 이용해서 null 조건문 없이 작성 할 수 있습니다
        memberRepository.findByName(member.getName()).ifPresent(m -> {
            throw new IllegalStateException("already Exists");
        });
    }

    public List<Member> findAll() {
        return memberRepository.findAll();
    }

    public Optional<Member> findOne (Long id){
        return memberRepository.findById(id);
    }

}
반응형

댓글