본문 바로가기

Backend/SpringBoot

SpringBoot + JPA + Thymeleaf로 간단한 CRUD + Travis CI 사용해보기 프로젝트

SpringBoot + JPA + Thymeleaf로 간단한 CRUD + Travis CI 사용해보기 프로젝트

SpringBoot & Thymeleaf & JPA (& H2 & lombok) 예제를 사용한 아주 간단한 예제를 찾다가 괜찮은 CR 기능의 예제가 있어서 삭제 기능(D)까지 추가하여 포스팅을 하게 되었다. (*업데이트(U) 기능은 없다.)

원본 링크는 맨 아래 참고 문서에서 확인이 가능하다.

또한, 소스코드는 깃허브(shinsunyoung/springboot_study_CRUD)에서 확인이 가능하다.

개발환경

  • IntelliJ IDEA Ultimate
  • JAVA JDK 1.8
  • SpringBoot 1.5.10.RELEASE
  • Gradle

프로젝트 구조

controller, model, repository, service, templates 폴더를 사용할 예정이다.

  • controller : 사용자에게 입력을 받고 돌려주는 처리를 하는 부분
  • model : 엔티티 클래스도메인 객체(JPA)가 들어가는 부분
  • repository : 데이터 베이스가 연결되는 부분 (DAO)
  • service : 비즈니스 로직이 들어가는 부분

프로젝트 생성

File - Project - Spring Initalizr를 선택해준다.

Group 이름과 Artifact 이름을 원하는대로 지정해준다.

Type는 Gradle로 지정해준다. (기본값은 Maven이기 때문에 꼭 바꿔줘야 한다.)

gradle과 maven의 차이점은 Maven vs Gradle에서 확인할 수 있다.

  • Core
    • Lombok : setter, getter를 자동으로 생성해주는 라이브러리
  • Web
    • Web : 웹 프로젝트를 하기 위해 필요한 것
  • Template Engines
    • Thymeleaf : 뷰 단을 구성하는 템플릿 엔진
  • SQL
    • JPA : RDB에 접근하기 위한 표준 ORM 기술
    • H2 : SpringBoot에서 제공해주는 가상 DB

프로젝트 이름과 저장할 위치를 설정해준 후, Finsh 버튼을 눌러 프로젝트를 생성해준다.

File-Settings-Build, Execution, Deployment-Annotation Processors-Enable annotation processing을 체크해준다. 체크하지 않으면 lombok에서 오류가 난다.

Gradle 살펴보기

build.gradle

...

dependencies {
    compile group: 'org.apache.ws.xmlschema', name: 'xmlschema', version: '2.0.3', ext: 'pom'
    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'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

아까 체크했던 것들(lombok, web, thymeleaf, jpa, h2)이 dependencies에 자동으로 추가가 된 것을 확인할 수 있다.

깃허브 프로젝트 생성 & 커밋

후에 빌드, 테스트 자동화(CI)를 위해 깃허브 프로젝트를 생성하고 커밋하도록 한다.

깃허브 프로젝트 생성 혹은 커밋하는 방법을 모르면 깃(Git)에 대한 간단한 설명부터 레파지토리 생성 후 커밋, 푸쉬하기까지를 참고하면 된다.

Model

springcrud/model/Person.java

import lombok.Getter;
import lombok.Setter;

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

@Getter // 1
@Setter // 2
@Entity // 3
public class Person{
    @Id // 4
    @GeneratedValue // 5
    private Long id;

    @Column // 6
    private String name;

    @Column
    private String password;

}

JPA와 Lombok을 사용해 Entity 클래스를 만들고 getter/setter를 자동으로 만들고 있다.

① : Lombok. getter 메소드를 만들어준다. 사용하지 않을 시 getId, getName, getPassword … 이런 식으로 하나하나 생성해주어야 한다.

② : Lombok. setter 메소드를 만들어준다. 역시 사용하지 않을 시 하나하나 생성해주어야 한다. (setId, setName, setPassword)

③ : JPA. 엔티티 클래스임을 정의하기 위해 사용한다.

④ : JPA. PK(Primary Key)에 매핑하기 위해 사용한다.

⑤ : JPA, PK키의 자동 생성 전략을 명시한다.

⑥ : JPA, 필드를 컬럼에 매핑하기 위해 사용한다.

JPA의 사용법이 제대로 이해가 안 간다면 JPA 사용법 (JpaRepository)←를 참고하면 될 것 같다.

매개변수가 3개밖에 없어 빌더 패턴을 사용하지 않았는데, 매개변수가 많아지면 빌더 패턴을 사용해도 좋을 것 같다.

Repository

springcrud/repository/PersonRepository.java

import com.shinsunyoung.springbootcrud.model.Person;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PersonRepository extends JpaRepository<Person, Long> {

}

model을 작성한 뒤에 작성해야 하는 것은 repository이다.

기본적인 CRUD 사용이 가능한 JpaRepository를 상속한 후 클래스 이름과 PK의 값을 넘겨주면 된다.

Service

springcrud/service/PersonService.java

import com.shinsunyoung.springbootcrud.model.Person;

import java.util.List;

public interface PersonService {
    Person createPerson(Person person);

      void deletePerson(Long id);

    List<Person> getAllPersons();
}

추가를 위한 createPerson,

삭제를 위한 deletePerson,

리스트를 읽어오기 위한 getAllPerson를 인터페이스에 정의한다.

아직 몸체가 없으므로 몸체를 붙여줄 인터페이스를 만들어준다.

springcrud/service/PersonServiceImp.java

import com.shinsunyoung.springbootcrud.model.Person;
import com.shinsunyoung.springbootcrud.repository.PersonRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class PersonServiceImp implements PersonService {

    @Autowired // 1
    private PersonRepository personRepository;

    @Override
    public Person createPerson(Person person) {
        return personRepository.save(person); // 2
    }

    @Override
    public void deletePerson(Long id) {
        personRepository.delete(id); // 3
    }

    @Override
    public List<Person> getAllPersons() {
        return personRepository.findAll(); // 4
    }

}

아까 repository에서 상속한 JpaRepository의 기능인 save(), findAll(), delete()를 사용했다.

① : personRepository를 외부에서 찾아 주입 시켜준다. 외부의 personRepository를 여기에서 사용할 수 있게 된다.

② : 주어진 엔티티인 Person을 저장한다.

③ : 해당 id값을 가진 엔티티를 삭제한다.

④ : 모든 엔티티를 반환한다.

Controller

springcrud/controller/PersonController

import com.shinsunyoung.springbootcrud.model.Person;
import com.shinsunyoung.springbootcrud.service.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;


@Controller // 1
public class PersonController {
@Autowired
private final PersonService personService;
public PersonController(PersonService personService) {
this.personService = personService;
}

@RequestMapping(value = "") // 2 public String homePage(Model model) { model.addAttribute("person", new Person()); // 3 return "makePerson"; // 4 } @RequestMapping(value = "/person") public String getPagePerson(Model model) { model.addAttribute("persons", personService.getAllPersons()); // 5 return "result"; } @RequestMapping(value = "/person", method = RequestMethod.POST) // 6 public String addPagePerson(@ModelAttribute Person person, Model model) { // 7 personService.createPerson(person); model.addAttribute("persons", personService.getAllPersons()); return "result"; } @RequestMapping(value = "/person/delete/{id}") public String deletePagePerson(@PathVariable Long id) { // 8 personService.deletePerson(id); return "redirect:/person"; // 9 } }

사용자에게 요청을 받으면 그에 해당하는 응답을 주는 controller를 구성했다.

① : Controller를 사용하면 return 타입에는 resource/templates에 있는 파일 이름을 리턴해준다. 그리고 그 파일을 읽어와서 뿌려주게 된다.

② : value 안에 있는 주소로 요청이 오면 아래 메소드(homePage)를 실행시킨다.

③ : 템플릿에 변수명과 변수에 담길 값을 넘겨준다. 즉, makePerson에는 person이라는 변수명으로 사용을 한다.

https://lopicit.tistory.com/224

④ : resource/templates/makePerson을 뷰단에 뿌려준다.

⑤ : 모든 정보를 가져오는 가져오는 getAllPersons 메소드를 실행한 값을 persons라는 변수에 넣어준다.

⑥ : POST로 받은 /person에 대한 처리를 해준다. (GET 형식은 위의 메소드)

⑦ : @ModelAttribute - 서버로 넘어오는 값(쿼리 스트링 또는 POST 데이터 같은)을 특정 객체에 반환해준다. 즉, POST 데이터를 Person이라는 객체에 넘겨준다.

⑧ : value 값에 있는 {id} 값를 가져와 변수에 넣어준다.

⑨ : 페이지를 새로 고침해주는 것이다. return 값에는 주소가 들어가야 한다. (템플릿 파일 이름이 아니다.)

View

Tutorial: Using Thymeleaf <- 타임리프 공식 문서이다. 영어를 읽기 싫을 때는

thymeleaf (server-side template engine) 사용법 정리 - 1 ~ thymeleaf (server-side template engine) 사용법 정리 - 2를 참고했다.

resource/templates/makePerson.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title> SpringBoot : makePerson </title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<h1>사람 추가하기(Create)</h1>
<form action="#" th:action="@{/person}" th:object="${person}" method="post">
    <p>Name : <input type="text" th:field="*{name}"/></p>
    <p>Password : <input type="password" th:field="*{password}"/></p>
    <p><input type="submit" value="추가하기"/> <input type="reset" value="새로 입력하기"/></p>
</form>
</body>
</html>

resource/templates/result.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title> SpringBoot : result </title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<h2>리스트 보기(Read)</h2>

<div th:if="${not #lists.isEmpty(persons)}">
    <table class="glyphicon glyphicon-calendar">
        <tr>
            <th>Id</th>
            <th>Name</th>
            <th>Password</th>
            <th>delete</th>
        </tr>
        <tr th:each="person : ${persons}">
            <td th:text="${person.id}"></td>
            <td th:text="${person.name}"></td>
            <td th:text="${person.password}"></td>
            <td><a href="/person" th:href="@{'person/delete/' + ${person.id}}">삭제</a></td>
        </tr>
    </table>
    <hr/>
</div>
<a href="/"><button>새로 만들기</button></a>

</body>
</html>

여기까지 했으면 Spring을 시작하면 된다.

localhost:8080 (혹은 다른 포트 번호로 설정했으면 그 포트 번호로 진입하면 된다.)에 접속하면

이런 폼이 뜨면 성공이다.

추가도 정상적으로 된다.

삭제도 정상적으로 잘 된다.

Travis CI 이용해서 빌드, 테스트 자동화해보기

추가로 CI까지 해보고 싶은 욕심이 생겨서 CI를 해보기로 했다.

깃허브 계정과 레파지토리만 있으면 쉽게 따라 할 수 있다.

Travis CI - Test and Deploy Your Code with Confidence 에 접속한다.

Sign Up을 눌러 깃허브와 연동한다.

아까 생성해둔 레파지토리 이름을 검색해서 활성화해준다.

(반드시 public이어야한다.)

들어가면 화면이 이렇게 되어있다.

프로젝트에 .travis.yml이라는 새 파일을 생성해준다.

# 자바 버전
language: java
jdk:
  - openjdk8

# master 브랜치에 푸시 했을 때만 실행
branches:
  only:
    - master

# Travis CI 서버의 Home
cache:
  directories:
    - '$HOME/.m2/repository'
    - '$HOME/.gradle'

script: "./gradlew clean build"

# CI 실행 완료시 메일로 알람
notifications:
  email:
    recipients:
      - << 메일 주소 >>

그리고 위 코드를 써준다.

그 후 commit - build를 하면

잘 돌아간다.

잠시 기다리면 위 사진과 같이 잘 돌아갔다는 표시를 볼 수 있다.

혹시 위 사진 같은 오류가 나면 Travis.yml ./gradlew : Permission denied를 참고하면 된다. (gradlew 권한 문제)

여기까지가 CI이다. 배포 자동화 CD는 아직 제대로 모르기 때문에 알게 되면 정리해서 포스팅할 예정이다.

마치며

스프링부트를 잘 아는 상태가 아니여서 부족한 부분이 있을수도 있습니다.
이 부분은 지적해주시면 진심으로 감사드리겠습니다!

참고 문헌

Making a Spring Boot & Thymeleaf CRUD Application

원본 링크입니다.

6) 스프링부트로 웹 서비스 출시하기 - 6. TravisCI & AWS CodeDeploy로 배포 자동화 구축하기

CI 참고 링크입니다.