아직 스프링이 익숙하지 않아서, 연습 겸 간단하게 게시판 기능을 구현해봤다.
MySQL을 연결하는 것까지.
깃허브 주소: https://github.com/SihyeonHong/demo
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.0'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'mysql:mysql-connector-java:8.0.33'
}
tasks.named('test') {
useJUnitPlatform()
}
사용한 라이브러리는 Thymeleaf, Spring Web, 그리고 JPA.
데이터베이스는 MySQL 사용했다.
Could not find mysql:mysql-connector-java:.
dependencies {
...
implementation 'mysql:mysql-connector-java'
runtimeOnly 'mysql:mysql-connector-java:8.0.33'
}
MySQL의 버전을 제대로 입력하지 않아서 발생한 오류.
...라고 해서 밑에 이렇게 한 줄을 추가했더니 이번에는 mysql이 두 번 등장하고 있다고 문제가 됐다.
그래서 찾아봤다.
MySQL Connector/J의 경우,
implementation
설정을 사용하면 MySQL Connector/J 라이브러리의 클래스를 코드에서 사용할 수 있으며, 이를 컴파일하고 실행할 수 있습니다. 그러나runtimeOnly
를 사용하면 코드에서 클래스를 사용할 수 없지만 코드를 실행할 때 클래스가 포함됩니다. 이는 일반적으로 클래스가 반영을 통해서만 접근되거나 코드가 사용하는 프레임워크에 의해 필요로 하는 경우에 사용됩니다. Spring Boot 애플리케이션의 맥락에서는 애플리케이션 코드가 커넥터 라이브러리의 클래스를 직접 사용하므로implementation
을 사용하는 것이 더 합리적입니다.
(ChatGPT)
runtimeOnly로 했을 때도 내 간단한 게시판 기능은 잘 돌아가긴 했는데, implementation을 쓰는 게 맞는 것 같아서 바꿨다.
spring.datasource.url=jdbc:mysql://localhost:3306/postdemo
spring.datasource.username=root
spring.datasource.password=1234
spring.jpa.hibernate.ddl-auto=update
postdemo는 schema의 이름.
schema는 MySQL에 따로 들어가서 만들어 줬다.
마지막 줄은 Hibernate가 schema를 자동으로 생성하거나 변경하는 방식을 지정한다. update
로 설정하면, 엔티티 클래스의 변경 사항을 schema에 반영한다.
package com.example.demo.model;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String title;
private String content;
public Post() {
}
public Post(String title, String content) {
this.title = title;
this.content = content;
}
// getter and setter ...
}
id
, title
, content
가 column들. @Entitiy
annotation 달고, id 필드에 @Id
달아서 id를 primary key로 정의. @GeneratedValue(strategy = GenerationType.AUTO)
: id 자동 생성을 데이터베이스에 위임. public
또는 protected
이어야 한다. package com.example.demo.repository;
import com.example.demo.model.Post;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
}
JpaRepository<Post, Long>
: Post
타입의 Entity를 대상으로 데이터 접근 작업을 할 것이며, 그 Entity의 ID 필드가 Long
타입이다. import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class PostService {
private List<Post> posts = new ArrayList<>();
@Autowired
private PostRepository postRepository;
public List<Post> getAllPosts() {
return postRepository.findAll();
}
public void addPost(Post post) {
postRepository.save(post);
}
}
findAll()
: 모든 데이터 조회 save(post)
: post가 새로운 entity면 create, 이미 존재하는 entity면 update. findById(ID id)
, deleteById(ID id)
, count()
, existsById(ID id)
등 있는데 그때그때 찾아 쓰자. package com.example.demo.controller;
import com.example.demo.model.Post;
import com.example.demo.service.PostService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.List;
@Controller
public class PostController {
@Autowired
private PostService postService;
@GetMapping("/posts")
public String getAllPosts(Model model) {
List<Post> posts = postService.getAllPosts();
model.addAttribute("posts", posts);
return "posts";
}
@GetMapping("/posts/new")
public String getNewPostForm(Model model) {
model.addAttribute("post", new Post() );
return "postForm";
}
@PostMapping("/posts")
public String addPost(@ModelAttribute Post post) {
postService.addPost(post);
return "redirect:/posts";
}
}
@Autowired
: Spring bean 주입받아 와라. new post()
해야 해서 Post에 빈 생성자가 필요했다. package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@SpringBootApplication
: 이게 붙어있는 메인 클래스를 기준으로 하위 패키지들을 스캔한다.@Repository
, @Service
, @Controller
등은 Spring의 Stereotype이므로, 저 annotation 붙어있으면 자동으로 Spring Bean으로 등록된다.
posts.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Simple Board</title>
</head>
<body>
<h1>Post List</h1>
<table>
<tr>
<th>ID</th>
<th>Title</th>
<th>Content</th>
</tr>
<tr th:each="post : ${posts}">
<td th:text="${post.id}"></td>
<td th:text="${post.title}"></td>
<td th:text="${post.content}"></td>
</tr>
</table>
</body>
</html>
postForm.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>New Post</title>
</head>
<body>
<h1>New Post</h1>
<form action="#" th:action="@{/posts}" th:object="${post}" method="post">
<p>Title: <input type="text" th:field="*{title}" /></p>
<p>Content: <textarea th:field="*{content}"></textarea></p>
<p><input type="submit" value="Submit" /></p>
</form>
</body>
</html>