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는 Interface다.
JpaRepository<Test, Long>을 상속받아주자.
// TestRepository.java
public interface TestRepository extends JpaRepository<Test, Long> {
}
지금은 추가할 내용이 없다.
Service, Controller을 작성한 후 다시 돌아오자.
Repository를 Controller에서 직접 접근하지 않게 service를 만들자.
@Service 어노테이션을 사용하여 Service임을 명시하자.
// TestService.java
@RequiredArgsConstructor
@Service
public class TestService {
private final TestRepository testRepository;
}
지금은 기본 틀만 만들어놓은 상황이다. Controller을 작성하고 다시 돌아오자.
직접 요청받는 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);
}
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/