스프링부트 입문기 #2 - 데이터베이스 연결

HanSH·2023년 7월 2일
0

SpringBoot 삽질기

목록 보기
4/12

MySQL - Spring Boot 연결

MySQL은 이미 이곳에서 삽질해서 연결해놨다.
이제 이를 사용해볼 차례이다.

먼저, 데이터베이스를 연결해보자.

// build.gradle
dependencies {
	...
    runtimeOnly 'com.mysql:mysql-connector-j' # mysql connector
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa' # jpa
    ...
}
// application.properties
# MySQL 설정
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# DB Source URL 설정
spring.datasource.url=jdbc:mysql://localhost:3307/spring?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul


# DB 사용자 이름 설정
spring.datasource.username=root

# DB 사용자이름에 대한 암호 설정
spring.datasource.password=

# true 설정 시, JPA 쿼리문 확인 가능
spring.jpa.show-sql=true

# DDL(create, alter, drop) 정의 시, DB의 고유 기능을 사용할 수 있음.
spring.jpa.hibernate.ddl-auto=update 

# JPA의 구현체인 Hibernate가 동작하면서, 발생한 SQL의 가독성을 높여줌.
spring.jpa.properties.hibernate.format_sql=true
build.gradle은 작성 후아이콘이 뜰 것이다.

코끼리(?) 아이콘을 눌러 gradle을 최신화해주자.

기존에는 mplementation 'mysql:mysql-connector-java'였으나, 언제부터인지 runtimeOnly 'com.mysql:mysql-connector-j'이걸로 바뀐듯하다.

테스트 데이터베이스를 하나 만들어보자.
DBeaver를 열어 아래와 같은 방법으로 데이터베이스를 하나 만들자.

database name은 spring.datasource.url=jdbc:mysql://<HOST>:<PORT>/<DB_NAME> 여기서 정한 데이터베이스 이름으로 만들자.

이후, DemoApplicationTests.java 파일을 JUnit으로 실행하면...

결과
"C:\Program Files\Java\jdk-20\bin\java.exe" -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 
  
  ...

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.1)

2023-07-02T21:23:26.956+09:00  INFO 26816 --- [           main] com.example.demo.DemoApplicationTests    : Starting DemoApplicationTests using Java 20.0.1 with PID 26816 (started by Han in C:\Users\Han\Desktop\demo)
2023-07-02T21:23:26.958+09:00  INFO 26816 --- [           main] com.example.demo.DemoApplicationTests    : No active profile set, falling back to 1 default profile: "default"
2023-07-02T21:23:27.608+09:00  INFO 26816 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2023-07-02T21:23:27.631+09:00  INFO 26816 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 14 ms. Found 0 JPA repository interfaces.
2023-07-02T21:23:28.069+09:00  INFO 26816 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2023-07-02T21:23:28.138+09:00  INFO 26816 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 6.2.5.Final
2023-07-02T21:23:28.140+09:00  INFO 26816 --- [           main] org.hibernate.cfg.Environment            : HHH000406: Using bytecode reflection optimizer
2023-07-02T21:23:28.344+09:00  INFO 26816 --- [           main] o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
2023-07-02T21:23:28.476+09:00  INFO 26816 --- [           main] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
2023-07-02T21:23:28.491+09:00  INFO 26816 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2023-07-02T21:23:28.641+09:00  INFO 26816 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@52354202
2023-07-02T21:23:28.644+09:00  INFO 26816 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2023-07-02T21:23:28.814+09:00  INFO 26816 --- [           main] o.h.b.i.BytecodeProviderInitiator        : HHH000021: Bytecode provider name : bytebuddy
2023-07-02T21:23:29.036+09:00  INFO 26816 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2023-07-02T21:23:29.042+09:00  INFO 26816 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2023-07-02T21:23:29.193+09:00  WARN 26816 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2023-07-02T21:23:29.713+09:00  INFO 26816 --- [           main] com.example.demo.DemoApplicationTests    : Started DemoApplicationTests in 3.076 seconds (process running for 4.021)
Java HotSpot(TM) 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
2023-07-02T21:23:30.361+09:00  INFO 26816 --- [ionShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2023-07-02T21:23:30.362+09:00  INFO 26816 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2023-07-02T21:23:30.373+09:00  INFO 26816 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

종료 코드 0(으)로 완료된 프로세스

짜잔! 정상적으로 실행됨을 볼 수 있다.

이제 데이터베이스에 값을 넣어보자.

스프링부트 데이터 처리 과정

도식도는 대략 위와 같다.
각각은 아래와 같이 정의할 수 있다.

  • controller
    요청이 실제 들어오는 경로. @Controller 어노테이션을 사용해 controller임을 명시.
    @[HTTP]Mapping("/{PATH}")을 이용해 REST 요청을 처리하게 된다.

  • service
    요청이 들어오고, 해당 요청을 처리하는 과정. @Service 어노테이션을 사용해 service임을 명시.
    controller에서 전부 처리해도 되지만, 코드 가독성 및 요청 추가 시 어려움이 있어 Service에 데이터 처리 로직을 구현한다.

  • repository
    데이터베이스에 접근할 수 있는 메소드들을 모아둔 Interface. JpaRepository<T, ID_TYPE>을 상속받는다.
    데이터베이스 CRUD를 담당한다.

  • entity
    데이터베이스에 저장된 실제 테이블.

위의 흐름도를 바탕으로 제작하도록 하자.

테이블 생성

테이블은 @Entity 어노테이션으로 만들 수 있다.
Test.java 파일을 만들고 아래와 같이 작성하자.

// Test.java
@Data
@Entity
public class Test {
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;
}

이후 DemoApplication을 실행하면...

짜잔! 테이블이 생성되었다!

Repository 생성

일단 Repository를 만들자. Repository는 Interface다.
JpaRepository<Test, Long>을 상속받아주자.

// TestRepository.java
public interface TestRepository extends JpaRepository<Test, Long> {
}

지금은 추가할 내용이 없다.
Service, Controller을 작성한 후 다시 돌아오자.

Service 생성

Repository를 Controller에서 직접 접근하지 않게 service를 만들자.
@Service 어노테이션을 사용하여 Service임을 명시하자.

// TestService.java
@RequiredArgsConstructor
@Service
public class TestService {
    private final TestRepository testRepository;
}

지금은 기본 틀만 만들어놓은 상황이다. Controller을 작성하고 다시 돌아오자.

Controller 생성/CRUD

직접 요청받는 controller을 만들자.

// TestController.java
@Controller
@RequiredArgsConstructor
@RestController
@RequestMapping("/test")
public class TestController {
    private final TestService testService;
}

기본 경로로 localhost:8080/test를 갖는 컨트롤러를 만들었다.
기본적인 CRUD를 처리하는 코드를 추가해보자.

// TestController.java
    @GetMapping("")
    public List<Test> getAllTests() {
        return testService.getAll();
    }

    @GetMapping("/{id}")
    public Test getTest(@PathVariable("id") Long id) {
        return testService.findById(id);
    }

    @PostMapping("")
    public void postTest(@RequestBody TestRequest test) {
        testService.push(test);
    }

    @PutMapping("/{id}")
    public void putTest(@PathVariable("id") Long id, @RequestBody TestRequest test) {
        testService.modify(id, test);
    }

    @DeleteMapping("/{id}")
    public void deleteTest(@PathVariable("id") Long id) {
        testService.delete(id);
    }

Service 처리

Controller에서 getAll, findById, push, modify, delete 메소드를 작성하였다.
이를 Service에서 추가하자.

// TestService.java
    public List<Test> getAll() {
        return testRepository.findAll();
    }

    public Test findById(Long id) {
        Optional<Test> testOptional = testRepository.findById(id);
        if (testOptional.isPresent()) {
            return testOptional.get();
        }
        return null;
    }

    public void push(TestRequest test) {
        Test t = new Test();
        t.setName(test.getName());
        testRepository.save(t);
    }

    public void modify(Long id, TestRequest test) {
        Optional<Test> testOptional = testRepository.findById(id);
        if (testOptional.isPresent()) {
            Test t = testOptional.get();
            t.setName(test.getName());
            testRepository.save(t);
        }
    }

    public void delete(Long id) {
        testRepository.deleteById(id);
    }

Repository에서는 findById, findAll, deleteById를 지원하지만 findByName은 지원하지 않는다.
이 경우 사용자가 직접 만들어줘야한다.

명명 규칙은 JPA Query Creation을 참고하자.

쿼리 추가

이제 쿼리를 추가해보자.

// TestRepository.java
    Optional<Test> findByName(String name);

Optional로 반환하는것을 잊지 말자. 값이 없는 경우, 값이 들어가지 않는다.
또한 error도 발생시키니 단일 find의 경우 Optional을 사용하자.

실 서비스

자, 이제 데이터를 추가하는 로직도 다 만들어놨으니 데이터를 추가, 삭제, 변경하는 것을 해보자.
추가 및 변경하는 과정에서, Test Entity를 바로 사용하는것은 애매하다 싶어 TestRequest 클래스를 하나 생성하였다.

//TestRequest.java
@Getter
@NoArgsConstructor
public class TestRequest {
    private String name;
}

Postman을 사용하여 진행하였다.

  • getAllTests

    현재 데이터가 아무것드 들어가있지 않아 빈 배열이 반환되었다.

  • postTest

    값이 제대로 들어감을 볼 수 있다.

  • modifyTest

    값이 정확하게 수정되는것을 볼 수 있다.

  • getTest

    getById가 제대로 동작하여 해당 id를 가진 entity의 값이 반환됨을 알 수 있다.
    이 경우, Response 객체를 만들어 반환하는 방식을 사용해야할 것 같다.

  • deleteTest

    제대로 제거된 것을 볼 수 있다.

이로서 기초적인 CRUD를 작성하고, 서비스 할 수 있게 되었다.

출처

그림 아이콘 - https://www.iconhunt.site/

profile
저는 말하는 싹 난 감자입니다

0개의 댓글