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

[Spring 입문] 스프링부트 JPA를 이용해서 MySQL 연결하기 (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 파일에 의존성 추가하기

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

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

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'mysql:mysql-connector-java'
}

 

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

IntelliJ는 자동으로 JDK가 다운 받아 집니다! (스프링 2.7.7 버전기준)


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

spring.datasource.url=jdbc:mysql://{엔드포인트}:{포트번호}/{데이터베이스}
spring.datasource.username={DB에 설정한 사용자 이름}
spring.datasource.password={DB에 설정한 비번}

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

# 실행할때 콘솔창에 SQL문을 띄워줌
spring.jpa.show-sql=true
# 테이블등이 없을때 자동으로 생성할지 여부
# none : 생성X , create : 생성해줌
spring.jpa.hibernate.ddl-auto=none

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


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

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


3. 자바 객체를 DB스키마와 연동하기

이전 포스트에서 domain/Member.java에 멤버객체를 정의를 해줬었습니다.

그 객체에 아래와 같은 몇몇 어노테이션을 추가해주면 완성입니다.

  • @Entity: JPA를 사용할 클래스를 명시하며, 테이블과 매핑하는 역할을 합니다.
  • @Id : 기본키(Primary Key)라는 것을 명시하는 역할을 합니다.
  • @GeneratedValue: 키값의 자동생성 전략을 설정할 수 있습니다. (default: GenerationType.AUTO)
    • 이 실습에서는 MySQL에서 AUTO_INCREMENT를 사용하므로, GenetationType.IDENTITY를 사용합니다.
  • @Column: 컬럼의 속성값을 추가로 부여 할 수 있습니다.

 

객체의 이름과 속성값이 다르면 @Entity(name="테이블이름")와 @Column(name="칼럼이름")를 붙여줘서 연결할 수 있습니다.

 

domain/Member.java - members 테이블과 연결

package example.boot.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity(name = "members")
public class Member {

    @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String name;

    public Long getId(){
        return this.id;
    }

    public void setId(Long id){
        this.id = id;
    }

    public String getName(){
        return this.name;
    }

    public void setName(String name){
        this.name = name;
    }
}

4. EntityManager 객체를 의존성 주입하기

JPA에서는 dataSource를 내장하고 있는 EntityManager가 전부 DB를 관리합니다.

JpaMemberRepository파일을 새로 만들고 여기서 DB조작 코드를 입력할 것입니다.

그러면 JpaMemberRepository는 EntityManager가 필요하므로 생성자를 통해 주입시켜줍니다.

 

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

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.persistence.EntityManager;
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.JpaMemberRepository;
import example.boot.repository.MemberRepository;
// import example.boot.repository.MemoryMemberRepository;
import example.boot.service.MemberService;


@Configuration
public class SpringConfig {
    
    private final DataSource dataSource;
    //JPA EntityManager추가
    public 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 JpaMemberRepository(em);
    }
    
}

4. JpaMemberRepository 예시

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

DB연동하는 법에대한 포스트이기 때문에 코드 설명은 주석말곤 자세하게 하지 않겠습니다.

package example.boot.repository;

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

import javax.persistence.EntityManager;

import example.boot.domain.Member;

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

    @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) {
        // PK 즉 필수 키가 아니면 중복과 부재의 문제가 있으므로 쿼리를 작성해야함
        List<Member> result = em.createQuery("select m from members m where m.name = :name",Member.class)
                            .setParameter("name", name)
                            .getResultList();
        return result.stream().findAny();
        
    }

    @Override
    public List<Member> findAll() {
        return em.createQuery("select m from members m",Member.class)
                .getResultList();
    }
    
}

이해를 돕기 위한 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);
    }

}
반응형

댓글