프로그래밍/Java

IntelliJ 이용하여 스프링 부트 + JPA + Mysql + thymleaf 게시판 CRUD

소행성왕자 2023. 8. 23. 10:01

IntelliJ IDEA를 사용하여 스프링 부트(Spring Boot) 기반의 웹 애플리케이션을 개발하며, JPA(Java Persistence API)를 통해 MySQL 데이터베이스를 사용하고 Thymeleaf를 사용하여 간단한 게시판의 CRUD(Create, Read, Update, Delete) 기능을 구현하는 과정을 설명합니다. 이 프로젝트를 만들기 위한 단계별 가이드입니다.

전제 조건:

 - IntelliJ IDEA 설치
 - JDK 11 설치
 - 스프링 부트 프로젝트 생성 및 설정
 - MySQL 데이터베이스 설치 및 설정

IntelliJ IDEA로 스프링 부트 프로젝트 생성

IntelliJ IDEA를 열고 "File" 메뉴에서 "New"를 선택한 다음 "Project"를 클릭합니다.
"Spring Initializr"를 선택하고 프로젝트 설정을 진행합니다.


프로젝트 유형: Gradle 선택

언어: Java 선택

패키징 : War 선택

자바 버전 : 11 선택


의존성 추가: "Spring Boot DevTools", "Spring Web", "Lombok", "Thymeleaf", "Spring Data JPA", "MySQL Driver" 추가


"Next"를 클릭하고 프로젝트 디렉토리 및 이름을 설정한 다음 "Finish"를 클릭하여 프로젝트를 생성합니다.

출력결과

게시판 기본 기능

  • list 
  • read
  • write
  • update
  • delete
  • search (type, keyword)

build.gradle

plugins {
    id 'java'
    id 'war'
    id 'org.springframework.boot' version '2.7.14'
    id 'io.spring.dependency-management' version '1.1.2'
}

group = 'com.naya'
version = '0.0.1-SNAPSHOT'

java {
    sourceCompatibility = '11'
}

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'com.mysql:mysql-connector-j'
    runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
    annotationProcessor 'org.projectlombok:lombok'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

resources/application.yml

server:
  address: localhost
  port: 8080

spring:
  jpa:
    show-sql: true
    database: mysql
    properties:
      hibernate:
        ddl-auto: create
        format_sql: true

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/dbname?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Seoul
    username: username
    password: password

  thymeleaf:
    cache: false
    prefix: file:src/main/resources/templates/
    suffix: .html
  devtools:
    restart:
      enabled: true

프로젝트 구조

controller/BbsController

package com.naya.shopjpa.controller;

import com.naya.shopjpa.model.Bbs;
import com.naya.shopjpa.service.BbsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

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

@Controller
@RequestMapping("/bbs")
public class BbsController {

    @Autowired
    private BbsService bbsService;

    @GetMapping("/list")
    public String bbsList(Model model) {
        List<Bbs> bbs = bbsService.getList();
        model.addAttribute("bbs", bbs);
        return "bbs/list";
    }

    @PostMapping("/search")
    public String bbsSearchList(Model model, @RequestParam String type, @RequestParam String keyword) {
        List<Bbs> bbs = bbsService.getSearchList(type, keyword);
        model.addAttribute("bbs", bbs);
        return "bbs/list";
    }

    @GetMapping("/{id}")
    public String bbsRead(Model model, @PathVariable Long id) {
        Optional<Bbs> item = bbsService.getById(id);
        model.addAttribute("item", item);
        return "bbs/read";
    }

    @GetMapping("/write")
    public String bbsWriteForm() {
        return "bbs/write";
    }

    @PostMapping("/write")
    public String bbsWrite(Model model, Bbs bbs) {
        bbsService.addBbs(bbs);
        return "redirect:/bbs/list";
    }

    @GetMapping("/remove/{id}")
    public String bbsRemove(Bbs bbs, @PathVariable Long id) {
        bbsService.deleteBbs(id);
        return "redirect:/bbs/list";
    }

    @GetMapping("/update/{id}")
    public String bbsUpdateForm(Model model, @PathVariable Long id) {
        Optional<Bbs> item = bbsService.getById(id);
        model.addAttribute("item", item);
        return "bbs/update";
    }

    @PostMapping("/update")
    public String bbsUpdate(Bbs bbs) {
        System.out.println(bbs.toString());
        bbsService.updateBbs(bbs);
        return "redirect:/bbs/list";
    }


}

게시판 list /  search list

위 컨트롤러는 /bbs 경로를 기반으로 다양한 HTTP 요청을 처리하고, 서비스 클래스인 BbsService를 사용하여 데이터를 관리합니다.

@Controller 및 @RequestMapping("/bbs")
클래스에 @Controller 어노테이션을 붙여서 스프링에게 이 클래스가 컨트롤러임을 알립니다.
@RequestMapping("/bbs")는 이 컨트롤러의 모든 메소드에 적용되는 기본 URL 경로를 정의합니다.
@Autowired private BbsService bbsService;
BbsService 인터페이스를 구현한 BbsService 빈을 주입받습니다.
@GetMapping("/list")
HTTP GET 요청을 처리하는 메소드로, /bbs/list 경로로 요청이 오면 실행됩니다.
bbsService.getList()를 호출하여 게시글 목록을 가져온 다음, Thymeleaf 모델에 bbs라는 이름으로 추가합니다.
@PostMapping("/search")
HTTP POST 요청을 처리하는 메소드로, /bbs/search 경로로 요청이 오면 실행됩니다.
@RequestParam을 사용하여 type, keyword라는 파라미터를 받아옵니다.
bbsService.getSearchList(type,keyword)를 호출하여 검색 결과를 가져온 다음, Thymeleaf 모델에 bbs라는 이름으로 추가합니다.

게시판 read

@GetMapping("/{id}")
HTTP GET 요청을 처리하는 메소드로, /bbs/{id} 경로로 요청이 오면 실행됩니다. {id}는 게시글의 고유한 식별자입니다.
@PathVariable을 사용하여 id라는 경로 변수를 받아옵니다. URL 에서 받아옵니다.
bbsService.getById(id)를 호출하여 해당 게시글을 가져온 다음, Thymeleaf 모델에 item이라는 이름으로 추가합니다.

게시판 write

@GetMapping("/write")
HTTP GET 요청을 처리하는 메소드로, /bbs/write 경로로 요청이 오면 실행됩니다.
게시글 작성 폼을 표시하는 페이지로 리다이렉션합니다.
@PostMapping("/write")
HTTP POST 요청을 처리하는 메소드로, /bbs/write 경로로 요청이 오면 실행됩니다.
Bbs 객체를 파라미터로 받아서 게시글을 작성하고, 작성된 게시글을 목록 페이지로 리다이렉션합니다.

게시판 delete

@GetMapping("/remove/{id}")
HTTP GET 요청을 처리하는 메소드로, /bbs/remove/{id} 경로로 요청이 오면 실행됩니다.
@PathVariable을 사용하여 id라는 경로 변수를 받아옵니다.
해당 id에 해당하는 게시글을 삭제하고, 게시글 목록 페이지로 리다이렉션합니다.

게시판 update

@GetMapping("/update/{id}")
HTTP GET 요청을 처리하는 메소드로, /bbs/update/{id} 경로로 요청이 오면 실행됩니다.
@PathVariable을 사용하여 id라는 경로 변수를 받아옵니다.
해당 id에 해당하는 게시글을 수정하기 위한 폼 페이지로 이동합니다.
@PostMapping("/update")
HTTP POST 요청을 처리하는 메소드로, /bbs/update 경로로 요청이 오면 실행됩니다.
수정된 게시글 정보를 저장하고, 게시글 목록 페이지로 리다이렉션합니다.


search/BbsSearch.java

package com.naya.shopjpa.service;

import com.naya.shopjpa.model.Bbs;

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

public interface BbsService {
    List<Bbs> getList();

    List<Bbs> getSearchList(String type, String keyword);

    Optional<Bbs> getById(Long id);

    boolean addBbs(Bbs bbs);

    void deleteBbs(Long id);

    boolean updateBbs(Bbs bbs);


}

위 코드는 BbsService라는 인터페이스를 정의한 것으로, Spring Boot 애플리케이션에서 사용되는 게시글 서비스의 메서드를 정의하고 있습니다. 이 인터페이스를 구현한 클래스는 게시글 관련 기능을 구현하게 됩니다.

List<Bbs> getList(): 게시글 목록을 가져오는 메서드입니다. 이 메서드는 모든 게시글을 가져와서 리스트로 반환합니다.

List<Bbs> getSearchList(String type, String keyword): 특정 키워드를 포함하는 게시글 목록을 가져오는 메서드입니다. 검색 기능을 위해 type과 키워드를 입력받고, 해당 키워드를 포함하는 게시글들을 리스트로 반환합니다.

Optional<Bbs> getById(Long id): 게시글의 고유한 식별자(ID)를 기반으로 게시글을 가져오는 메서드입니다. ID를 입력받고 해당 ID에 해당하는 게시글을 Optional 객체로 반환합니다. Optional은 값이 없을 수도 있는 경우를 다룰 때 사용됩니다.

boolean addBbs(Bbs bbs): 새로운 게시글을 추가하는 메서드입니다. Bbs 객체를 입력받아서 데이터베이스에 저장하고, 성공 여부를 불리언으로 반환합니다.

void deleteBbs(Long id): 게시글을 삭제하는 메서드입니다. 삭제할 게시글의 ID를 입력받아 해당 게시글을 삭제합니다.

boolean updateBbs(Bbs bbs): 게시글을 수정하는 메서드입니다. 수정할 게시글 정보를 입력받아 데이터베이스에서 해당 게시글을 찾아 수정하고, 성공 여부를 불리언으로 반환합니다.

이 BbsService 인터페이스를 구현한 클래스는 이러한 메서드들을 구현하여 실제 게시글 서비스를 제공하게 됩니다. 이렇게 인터페이스와 구현 클래스를 분리함으로써 코드의 유지보수와 확장성을 높일 수 있습니다.

service/BbsServiceImpl.java

package com.naya.shopjpa.service;

import com.naya.shopjpa.model.Bbs;
import com.naya.shopjpa.repository.BbsRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

@Service
public class BbsServiceImpl implements BbsService {

    @Autowired
    private BbsRepository bbsRepository;

    @Transactional
    public List<Bbs> getList() {
        return bbsRepository.findAll();
    }

    @Transactional
    public List<Bbs> getSearchList(String type, String keyword) {
        if ("title".equals(type)) {
            return bbsRepository.findByTitleContaining(keyword);
        } else if ("name".equals(type)) {
            return bbsRepository.findByNameContaining(keyword);
        } else if ("content".equals(type)) {
            return bbsRepository.findByContentContaining(keyword);
        } else {
            // 지원하지 않는 검색 유형인 경우 빈 리스트 반환
            return Collections.emptyList();
        }
    }

    @Transactional
    public Optional<Bbs> getById(Long id) {
        return bbsRepository.findById(id);
    }

    @Transactional
    public boolean addBbs(Bbs bbs) {
        return bbsRepository.save(bbs) != null;
    }

    @Transactional
    public void deleteBbs(Long id) {
        bbsRepository.deleteById(id);
    }

    @Transactional
    public boolean updateBbs(Bbs bbs) {
        return bbsRepository.save(bbs) != null;
    }

}

위 코드는 BbsService 인터페이스를 구현한 BbsServiceImpl 클래스입니다. 이 클래스는 게시글 서비스의 실제 구현을 담당합니다.

아래에서 코드의 주요 내용을 설명하겠습니다.

@Service 어노테이션: 이 어노테이션은 스프링 프레임워크에게 해당 클래스가 서비스 클래스임을 알려줍니다. 이것은 컴포넌트 스캔을 통해 스프링 애플리케이션 컨텍스트에 빈으로 등록되게 합니다.

@Autowired 어노테이션: 이 어노테이션은 BbsRepository 인스턴스를 자동으로 주입하도록 스프링에게 알려줍니다. BbsRepository는 JPA를 사용하여 데이터베이스와 상호 작용하기 위한 인터페이스입니다.

@Transactional 어노테이션: 이 어노테이션은 각각의 메서드가 트랜잭션 내에서 실행되어야 함을 나타냅니다. 이것은 메서드 내에서 일어나는 모든 데이터베이스 작업을 하나의 트랜잭션으로 묶어줍니다.

getList(): 이 메서드는 모든 게시글을 데이터베이스에서 조회하고 리스트로 반환합니다. bbsRepository.findAll() 메서드를 사용하여 모든 게시글을 가져옵니다.

getSearchList(String keyword): 이 메서드는 특정 키워드를 포함하는 게시글을 검색하고 리스트로 반환합니다. findByTitleContaining(keyword) 메서드는 제목에 주어진 키워드가 포함된 게시글을 검색합니다.

getById(Long id): 이 메서드는 게시글의 고유 ID를 기반으로 게시글을 조회합니다. bbsRepository.findById(id)를 사용하여 해당 ID의 게시글을 가져옵니다.

addBbs(Bbs bbs): 새로운 게시글을 추가하는 메서드입니다. bbsRepository.save(bbs)를 사용하여 게시글을 데이터베이스에 저장하고 저장 성공 여부를 반환합니다.

deleteBbs(Long id): 게시글을 삭제하는 메서드입니다. bbsRepository.deleteById(id)를 사용하여 주어진 ID의 게시글을 삭제합니다.

updateBbs(Bbs bbs): 게시글을 수정하는 메서드입니다. bbsRepository.save(bbs)를 사용하여 게시글을 데이터베이스에 저장하고 저장 성공 여부를 반환합니다.

이 클래스는 BbsService 인터페이스를 구현하여 게시글 서비스의 핵심 로직을 구현하고, 데이터베이스와의 상호 작용을 BbsRepository를 통해 수행합니다.

repository/BbsRepository.java

package com.naya.shopjpa.repository;

import com.naya.shopjpa.model.Bbs;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;

public interface BbsRepository extends JpaRepository<Bbs, Long> {
    List<Bbs> findByTitleContaining(String keyword);

    List<Bbs> findByNameContaining(String keyword);

    List<Bbs> findByContentContaining(String keyword);
}

위 코드는 Spring Data JPA에서 제공하는 인터페이스인 JpaRepository를 확장하여 만든 BbsRepository 인터페이스입니다. 이 인터페이스는 데이터베이스 작업을 위한 CRUD(Create, Read, Update, Delete) 메서드를 제공하는데, 여기서는 Bbs 엔티티와 관련된 데이터베이스 작업을 할 수 있는 메서드를 선언하고 있습니다.

JpaRepository<Bbs, Long>: Bbs 엔티티와 기본 키인 Long 형식의 Id 필드를 사용하는 JpaRepository를 정의합니다.
그리고 아래와 같이 몇 가지 메서드를 선언하고 있습니다.

List<Bbs> findByTitleContaining(String keyword): title 필드에서 keyword를 포함하는 모든 레코드를 검색합니다.

List<Bbs> findByNameContaining(String keyword): name 필드에서 keyword를 포함하는 모든 레코드를 검색합니다.

List<Bbs> findByContentContaining(String keyword): content 필드에서 keyword를 포함하는 모든 레코드를 검색합니다.

이렇게 선언된 메서드들은 Spring Data JPA가 자동으로 해당 쿼리를 생성하고 실행할 수 있도록 도와주며, 개발자는 간단한 메서드 선언으로 데이터베이스 작업을 수행할 수 있습니다.

model/Bbs.java

package com.naya.shopjpa.model;

import lombok.Data;

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Data
@Table(name = "bbs")
public class Bbs {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long idx;
    private String title;
    private String content;
    private String name;

    @Column(name = "reg_date", updatable = false)
    private LocalDateTime reg_date;

    @Column(name = "update_date")
    private LocalDateTime update_date;

    @PrePersist
    protected void onCreate() {
        reg_date = update_date = LocalDateTime.now();
    }

    @PreUpdate
    protected void onUpdate() {
        update_date = LocalDateTime.now();
    }

    @Override
    public String toString() {
        return "Bbs{" +
                "idx=" + idx +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", name='" + name + '\'' +
                ", reg_date=" + reg_date +
                ", update_date=" + update_date +
                '}';
    }
}

위 코드는 JPA(Java Persistence API)를 사용하여 데이터베이스와 상호작용하는 엔티티 클래스인 Bbs 클래스를 정의한 것입니다. 아래는 코드의 주요 부분을 설명한 것입니다.

@Entity: 엔티티 클래스임을 나타내는 애노테이션입니다. JPA가 이 클래스를 데이터베이스 테이블과 매핑합니다.

@Data: Lombok 라이브러리에서 제공하는 애노테이션으로, getter, setter, toString, equals, hashCode 등의 메서드를 자동으로 생성합니다.

@Table(name = "bbs"): 해당 엔티티를 매핑할 데이터베이스 테이블의 이름을 지정합니다.

@Id: 해당 필드를 엔티티의 기본 키(primary key)로 사용한다는 것을 나타냅니다.

@GeneratedValue(strategy = GenerationType.IDENTITY): 기본 키 값을 자동으로 생성하는 전략을 지정합니다. 여기서는 데이터베이스의 자동 증가(auto-increment) 값을 사용하도록 설정했습니다.

@Column(name = "reg_date", updatable = false): 데이터베이스 컬럼과 매핑되는 필드에 대한 설정입니다. updatable = false로 설정하여 업데이트 시에는 해당 필드의 값을 변경하지 않도록 했습니다.

@PrePersist와 @PreUpdate: 엔티티가 저장 또는 업데이트되기 전에 실행되는 메서드입니다. onCreate 메서드는 엔티티가 생성될 때(INSERT) 호출되며, onUpdate 메서드는 엔티티가 업데이트될 때(UPDATE) 호출됩니다. 여기서는 reg_date를 생성 시점의 현재 날짜와 시간으로 설정하고, update_date는 업데이트 시점의 현재 날짜와 시간으로 설정합니다.

@Override toString(): 객체를 문자열로 표현하기 위한 메서드를 재정의합니다. 이 메서드는 객체의 정보를 문자열로 반환합니다.

이렇게 정의된 Bbs 클래스는 JPA를 사용하여 데이터베이스와 상호작용하며, reg_date와 update_date 필드를 자동으로 관리하고, toString() 메서드를 통해 객체의 내용을 문자열로 표현할 수 있습니다.