본문 바로가기

Spring/Spring

Spring Boot 일단 따라쳐보는 일기장 앱 만들기 (Spring은 알지만 Spring boot는 처음일 때..)

반응형

Spring Boot 일기장 앱 만들기

스프링 부트를 왜 쓰는가에 대한 설명은 하지 않겠다.

불필요한 설정 다 재끼고 편리하고 생산성이 좋고 관리하기 좋으니까 쓰는 것이라 생각한다.


개발 환경

    - JDK1.8 , sts 3.9.2, 스프링부트 1.5.10 + (툴 - eclipse-oxygen)

Spring Boot project 순서

0. jdk 설치 및 이클립스 설치( + market place에서 sts설치)

1. 프로젝트 생성

프로젝트는 spring starter project로 생성한다. project명, package명을 적고, 빌드 툴로 Maven을 쓸지 gradle을 쓸지 정한다.

기타로 언어를 java, kotlin, groovy로 할지 정하면 된다.

패키지명 : com.apress.spring

프로젝트명 : spring-boot-journal

빌드 툴 : Maven

언어 : Java / 버전 : 8

next>를 누르면 위에 그림 처럼 초기 dependency를 설정할 수 있는 화면이 나온다.

필요한 것들을 체크하고 생성하면 끝.

(Web/Web , SQL/JPA, SQP/H2, Template Engines/Thymeleaf 이렇게 4개 체크)

이렇게 설정하면 pom.xml 파일에 알아서 dependency가 생성되어 있음. 

2. 코드 작성

[com.apress.spring.domain패키지의 Journal 클래스]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.apress.spring.domain;
 
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Transient;
 
 
@Entity
public class Journal {
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    private String title;
    private Date created;
    private String summary;
    
    @Transient
    private SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy");
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getTitle() {
        return title;
    }
 
    public void setTitle(String title) {
        this.title = title;
    }
 
    public Date getCreated() {
        return created;
    }
 
    public void setCreated(Date created) {
        this.created = created;
    }
 
    public String getSummary() {
        return summary;
    }
 
    public void setSummary(String summary) {
        this.summary = summary;
    }
    public String getCreatedAsShort() {
        return format.format(created);
    }
 
    @Override
    public String toString() {
        return "Journal [id=" + id + ", title=" + title + ", created=" + created + ", summary=" + summary + "]";
    }
 
    public Journal(String title, String summary, String date)throws ParseException {
        this.title = title;
        this.summary = summary;
        this.created = format.parse(date);
    }
    public Journal() {}
    
    
}
 
cs

Journal클래스는 DB에 저장 가능한 JPA엔티티이므로 @Entity, @Id, @GeneratedValue 작성. (뭔지 몰라도됨 일단)

@Transient를 붙이면 JPA 엔진이 값을 무시함. DB에 저장 안됨.

[com.apress.spring.repository패키지의 JournalRepository 인터페이스]

1
2
3
4
5
6
7
8
package com.apress.spring.repository;
 
import org.springframework.data.jpa.repository.JpaRepository;
 
import com.apress.spring.domain.Journal;
 
public interface JournalRepository extends JpaRepository<Journal,Long> { }
 
cs

JpaRepository 인터페이스를 확장해서 JPA 기술 구현,

스프링 데이터 레포지터리 엔진이 자동으로 인지하여 CRUD및 커스텀 메서드 구현 가능(몰라도 됨)

[com.apress.spring.web패키지의 JournalController클래스]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.apress.spring.web;
 
import java.util.List;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
 
import com.apress.spring.domain.Journal;
import com.apress.spring.repository.JournalRepository;
 
@Controller
public class JournalController {
    @Autowired
    JournalRepository repo;
    
    @RequestMapping("/")
    public String index(Model model) {
        //repo.findAll()은 jpaRepository를 상속한 인터페이스라고 부모가 물려준 메서드를 가지고 있음.
        model.addAttribute("journal", repo.findAll());
        return "index";
    }
    @RequestMapping(value = "/journal", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
    public @ResponseBody List<Journal> getJournal(){
        return repo.findAll();
    }
}
 
cs

인터페이스로 등록해놓은 JournalRepository를 DI함.

spring controller와 똑같다.

"/" 경로로 오면 model에 repo의 모든 정보를 담고 index.html로 보낸다.

"/journal" 경로로 오면 JSON형식으로 repo의 모든 정보를 보낸다.

[src/main/resources/templates디렉토리의 index.html]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://thymeleaf.org">
<head>
<meta charset="UTF-8"></meta>
<meta http-equiv="Content-Type" content="text/html"></meta>
<title>Journal</title>
<!-- 합쳐지고 최소화된 최신 CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css"></link>
 
<!-- 부가적인 테마 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css"></link>
 
<!-- 합쳐지고 최소화된 최신 자바스크립트 -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"></link>
<link rel="stylesheet" type="text/css" href="css/bootstrap-glyphicons.css"></link>
<link rel="stylesheet" type="text/css" href="css/styles.css"></link>
</head>
<body>
<div class="container">
    <h1>spring boot Journal</h1>
    <ul class="timeline">
        <!-- entry는 각 레코드, status는 순회 인덱스 -->
        <div th:each="entry,status : ${journal}">
            <li th:attr="class=${status.odd}?'timeline-inverted':''">
                <div class="tl-circ"></div>
                <div class="timeline-panel">
                    <div class="tl-heading">
                        <h4><span th:text="${entry.title}">제목</span></h4>
                        <p><small class="text-muted"><i class="glyphicon glyphicon-time"></i><span th:text="${entry.createdAsShort}">에 작성.</span></small></p>
                    </div>
                    <div class="tl-body">
                        <p><span th:text="${entry.summary}">요약</span></p>
                    </div>
                </div>
            </li>
        </div>
    </ul>
</div>
</body>
</html>
cs

bootstrap cdn을 먼저 부르고(사실 필요없음) css파일 3개 링크했다.

(css파일은 원작자의 링크가 죽어있어서 참고하는 책의 자료실에서 다운로드 했다. 없어도됨. 결과가 중요하기 때문.)

* 실제 코딩하면서 느낀 것인데 거의 모든 태그를 꼭 닫는 것이 중요함.

    <meta></meta>,<link></link>등.. 에러 유발 가능

[com.apress.spring패키지의 SpringBootJournalApplication 클래스]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.apress.spring;
 
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
 
import com.apress.spring.domain.Journal;
import com.apress.spring.repository.JournalRepository;
 
@SpringBootApplication
public class SpringBootJournalApplication {
 
    @Bean
    InitializingBean saveData(JournalRepository repo) {
        return () ->{
            repo.save(new Journal("스프링 부트 입문","스프링 공부 시작","02/06/2018"));
            repo.save(new Journal("스프링 프로젝트 샘플","스프링 샘플!","02/07/2018"));
            repo.save(new Journal("스프링 부트 구조","스프링 구조를 알아보자","02/08/2018"));
            repo.save(new Journal("스프링 부트 클라우드","클라우드요","02/09/2018"));
        };
        //InitializingBean은 스프링 엔진이 인스턴스 생성 후 초기화 할 때 항상 호출하는 특수 클래스
        //saveData는 스프링 시동 준비전에 실행
    }
    
    
    public static void main(String[] args) {
        SpringApplication.run(SpringBootJournalApplication.class, args);
    }
}
 
cs

@SpringBootApplication 애노테이션은 아주 중요하다.

@Configuration, @EnableAutoConfiguration, @ComponentScan 애노테이션이 뭉친 것이다.

이 애노테이션이 있어야 자동 구성이 가능하다.

@Bean 애노테이션은 Bean을 생성하고 InitializingBean은 스프링 엔진이 인스턴스를 생성 후 초기화할 때 항상 호출하는 특수클래스다.

따라서 saveData(JournalRepository repo)는 앱이 시동 준비를 마치기 전에 실행된다.

rep.save메서드를 통해서 자료 샘플을 입력한다.)


=> 프로젝트 우클릭> run as> Spring boot app


3. 결과



* 실행 TIP (?)

    - 톰캣을 따로 띄우는 기존 스프링에서는 애플리케이션 재시작하고 그러면 자동으로 기존 포트에 있는 서버를 죽였는데 따로 중지 안시키고 바로 프로젝트 run시키면 포트를 누가 쓰고있다고 나오면서 실행이 안된다.

(그렇다고 커맨드라인에서 netstat으로 찍어보면 포트는 안 쓰고 있는 것으로 나온다.)

    - 내장 톰캣 포트 변경하는 방법은 src/main/resources에 보면 application.properties라는 파일이 있다. 아무것도 적혀있지 않을 수 있는데 거기다가 "server,port = 8081" 이렇게 적어주면 내장 톰캣 포트가 변경된다. 나는 계속 바꿔서 8084까지 왔다...


* 작동 원리는 다음 포스트에서 공부하고 정확하게 파악할 것이다. (다른 책이나 블로그를 참조해서라도...)


참고 도서

    - 실전 스프링 부트 워크북, 펠리페구티에레스, 한빛미디어

반응형