mojo's Blog

웹 계층 개발 - (1) 본문

Spring

웹 계층 개발 - (1)

_mojo_ 2022. 9. 6. 22:24

 

홈 화면과 레이아웃

 

template 에 html 을 다음과 같이 설정한다.

 

  • fragment
    • bodyHeader.html
    • footer.html
    • header.html
  • home.html

 

home.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header">
    <title>Hello</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>

<body>
<div class="container">
    <div th:replace="fragments/bodyHeader :: bodyHeader" />
    <div class="jumbotron">
        <h1>HELLO SHOP</h1>
        <p class="lead">회원 기능</p>
        <p>
            <a class="btn btn-lg btn-secondary" href="/members/new">회원 가입</a>
            <a class="btn btn-lg btn-secondary" href="/members">회원 목록</a>
        </p>
        <p class="lead">상품 기능</p>
        <p>
            <a class="btn btn-lg btn-dark" href="/items/new">상품 등록</a>
            <a class="btn btn-lg btn-dark" href="/items">상품 목록</a>
        </p>
        <p class="lead">주문 기능</p>
        <p>
            <a class="btn btn-lg btn-info" href="/order">상품 주문</a>
            <a class="btn btn-lg btn-info" href="/orders">주문 내역</a>
        </p>
    </div>
    <div th:replace="fragments/footer :: footer" />
</div> <!-- /container -->
</body>
</html>

 

fragments.header.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="header">
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-
to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="/css/bootstrap.min.css" integrity="sha384-
ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
          crossorigin="anonymous">
    <!-- Custom styles for this template -->
    <link href="/css/jumbotron-narrow.css" rel="stylesheet">
    <title>Hello, world!</title>
</head>

 

fragments.footer.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div class="footer" th:fragment="footer">
    <p>&copy; Hello Shop V2</p>
</div>

 

fragments.bodyHeader.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<div class="header" th:fragment="bodyHeader">
    <ul class="nav nav-pills pull-right">
        <li><a href="/">Home</a></li>
    </ul>
    <a href="/"><h3 class="text-muted">HELLO SHOP</h3></a>
</div>

 

실행 결과

 

밋밋한 결과가 나타난 것을 볼 수 있다.

이러한 밋밋함을 해결할 수 있도록 Bootstrap 사이트에 가서 Compiled CSS and JS 를 다운한다.

 

Download · Bootstrap (getbootstrap.com)

 

Download

Download Bootstrap to get the compiled CSS and JavaScript, source code, or include it with your favorite package managers like npm, RubyGems, and more.

getbootstrap.com

 

 

다운받으면 css, js 가 생긴다.

이걸 복사해서 resources 아래에 static 에 css, js 를 붙여넣기 한다.

 

 

이제 실행해보면 아까보다는 괜찮은 레이아웃이 나타나는 것을 볼 수 있다.

 

 

여기서 가운데 정렬을 해서 좀 더 이쁘게 만들어보도록 하자.

css 폴더에 jumbotron-narrow.css 를 추가하고 다음과 같이 작성한다.

/* Space out content a bit */
body {
  padding-top: 20px;
  padding-bottom: 20px;
}
/* Everything but the jumbotron gets side spacing for mobile first views */
.header,
.marketing,
.footer {
  padding-left: 15px;
  padding-right: 15px;
}
/* Custom page header */
.header {
  border-bottom: 1px solid #e5e5e5;
}
/* Make the masthead heading the same height as the navigation */
.header h3 {
  margin-top: 0;
  margin-bottom: 0;
  line-height: 40px;
  padding-bottom: 19px;
}
/* Custom page footer */
.footer {
  padding-top: 19px;
  color: #777;
border-top: 1px solid #e5e5e5;
}
/* Customize container */
@media (min-width: 768px) {
.container {
  max-width: 730px;
}
}
.container-narrow > hr {
margin: 30px 0;
}
/* Main marketing message and sign up button */
.jumbotron {
text-align: center;
border-bottom: 1px solid #e5e5e5;
}
.jumbotron .btn {
font-size: 21px;
padding: 14px 24px;
}
/* Supporting marketing content */
.marketing {
margin: 40px 0;
}
.marketing p + h4 {
margin-top: 28px;
}
/* Responsive: Portrait tablets and up */
@media screen and (min-width: 768px) {
/* Remove the padding we set earlier */
.header,
.marketing,
.footer {
padding-left: 0;
padding-right: 0;
}
/* Space out the masthead */
.header {
margin-bottom: 30px;
}
/* Remove the bottom border on the jumbotron for visual effect */
.jumbotron {
border-bottom: 0;
}
}

 

 

깔끔하게 정렬된 것을 확인할 수 있다.

 

회원 등록

 

회원가입 화면 설계를 해보도록 하자.

우선, 회원의 이름, 도시, 거리, 우편번호를 입력할 수 있도록 돕는 클래스를 생성하고자 한다.

 

MemberForm 클래스

@Getter @Setter
public class MemberForm {

    @NotEmpty(message = "회원 이름은 필수 입니다")
    private String name;
    private String city;
    private String street;
    private String zipcode;
}

 

@NotEmpty 를 사용하면 name 이 없을 때 오류가 발생하게 된다.

@NotEmpty 를 쓰려면 의존성을 추가해줘야 한다.

build.gradle 에 가서 다음을 추가해주자.

dependencies {
   implementation 'org.springframework.boot:spring-boot-starter-validation'
   ...
}

 

html 은 templates 아래에 members 디렉터리를 만들고 createMemberForm.html 을 생성한다.

createMemberForm.html 은 아래와 같이 작성한다.

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<style>
    .fieldError {
        border-color: #bd2130;
    }
</style>
<body>
<div class="container">
    <div th:replace="fragments/bodyHeader :: bodyHeader"/>
    <form role="form" action="/members/new" th:object="${memberForm}"
          method="post">
        <div class="form-group">
            <label th:for="name">이름</label>
            <input type="text" th:field="*{name}" class="form-control"
                   placeholder="이름을 입력하세요"
                   th:class="${#fields.hasErrors('name')}? 'form-control fieldError' : 'form-control'">
            <p th:if="${#fields.hasErrors('name')}"
               th:errors="*{name}">Incorrect date</p>
        </div>
        <div class="form-group">
            <label th:for="city">도시</label>
            <input type="text" th:field="*{city}" class="form-control" placeholder="도시를 입력하세요">
        </div>
        <div class="form-group">
            <label th:for="street">거리</label>
            <input type="text" th:field="*{street}" class="form-control"
                   placeholder="거리를 입력하세요">
        </div>
        <div class="form-group">
            <label th:for="zipcode">우편번호</label>
            <input type="text" th:field="*{zipcode}" class="form-control"
                   placeholder="우편번호를 입력하세요">
        </div>
        <button type="submit" class="btn btn-primary">Submit</button>
    </form>
    <br/>
    <div th:replace="fragments/footer :: footer" />
</div> <!-- /container -->
</body>
</html>

 

이제 컨트롤러를 구현하여 회원가입 버튼을 누르면 /members/new 로 이동하게 되는데 위에서 만든

createMemberForm.html 으로 이동할 수 있도록 구현한다.

@Controller
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;

    @GetMapping("/members/new")
    public String createForm(Model model) {
        model.addAttribute("memberForm", new MemberForm());
        return "members/createMemberForm";
    }
}

 

 

정상적으로 화면이 나타난 것을 볼 수 있다.

 

이번엔 컨트롤러에 이름 및 주소를 입력하고 전송버튼을 누르면 디비에 반영되면서 동시에 홈 화면으로

리다이렉션할 수 있도록 코드를 구현해보도록 하자.

@PostMapping("/members/new")
public String create(@Valid MemberForm form) {
    Address address = new Address(form.getCity(), form.getStreet(), form.getZipcode());
    Member member = new Member();

    member.setName(form.getName());
    member.setAddress(address);

    memberService.join(member);
    return "redirect:/";
}

 

 

정상적으로 들어가는 것을 확인할 수 있다.

 

이번엔 회원가입 화면에서 아무것도 입력하지 않은채 전송버튼을 누를 경우 똑같은 화면에 다시 진입할 수 있도록

하면서 "회원 이름은 필수입니다" 라는 문구가 뜰 수 있도록 컨트롤러를 바꿔보자.

@PostMapping("/members/new")
public String create(@Valid MemberForm form, BindingResult result) {
    if (result.hasErrors()) {
        return "members/createMemberForm";
    }
    Address address = new Address(form.getCity(), form.getStreet(), form.getZipcode());
    Member member = new Member();

    member.setName(form.getName());
    member.setAddress(address);

    memberService.join(member);
    return "redirect:/";
}

 

BindingResult 객체를 통해 에러가 발견될 경우 다시 회원가입 화면으로 이동할 수 있도록 한 것이다.

 

 

여기서 "회원 이름은 필수 입니다" 라는 문구가 떴는데 이는 MemberForm 에서 이미 지정하였다.

@Getter @Setter
public class MemberForm {

    @NotEmpty(message = "회원 이름은 필수 입니다")
    private String name;
    ...
}

 

위와 같이 @NotEmpty 을 통해 이름을 입력하지 않을 경우 에러가 발생되는데 그 때 message 를

위와 같이 설정하였기 때문이다.

 

 

회원 목록 조회

 

이번엔 회원 목록 버튼을 누르면 회원 정보들이 뜨는 것을 구현해보도록 하자.

우선 회원 정보들이 뜰 수 있도록 하는 html 을 생성해야 한다.

members 패키지 아래에 memberList.html 을 아래와 같이 생성한다.

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<body>
<div class="container">
    <div th:replace="fragments/bodyHeader :: bodyHeader" />
    <div>
        <table class="table table-striped">
            <thead>
            <tr>
                <th>#</th>
                <th>이름</th>
                <th>도시</th>
                <th>주소</th>
                <th>우편번호</th>
            </tr>
            </thead>
            <tbody>
            <tr th:each="member : ${members}">
                <td th:text="${member.id}"></td>
                <td th:text="${member.name}"></td>
                <td th:text="${member.address?.city}"></td>
                <td th:text="${member.address?.street}"></td>
                <td th:text="${member.address?.zipcode}"></td>
            </tr>
            </tbody>
        </table>
    </div>
    <div th:replace="fragments/footer :: footer" />
</div> <!-- /container -->
</body>
</html>

 

그리고 컨트롤러에서 현재 데이터베이스에 저장된 멤버 전체 정보가 리스트로 받아서 html 에 for - loop 을 통해

각 멤버들의 정보들을 띄울 수 있도록 한다.

@GetMapping("/members")
public String list(Model model) {
    List<Member> members = memberService.findMembers();
    model.addAttribute("members", members);
    return "members/memberList";
}

 

 

위와 같은 경우는 회원 정보를 입력하지 않아서 빈 경우이다.

회원 정보를 여러개 등록하면 아래와 같이 나타나게 된다.

 

 

 

상품 등록

 

이번엔 상품 등록버튼을 누르면 상품 등록이 되도록 해보자.

우선 상품은 책에 대한 상품만 등록할 수 있도록 하는 화면을 구성하려고 한다.

따라서, 책을 담을 수 있도록 돕는 클래스 BookForm 을 생성한다.

@Getter @Setter
public class BookForm {

    private Long id;
    private String name;
    private int price;
    private int stockQuantity;

    private String author;
    private String isbn;
}

 

그리고 상품이 등록될 수 있는 html 을 생성한다.

items 패키지를 생성하고 createItemForm.html 을 아래와 같이 작성하여 넣는다.

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<body>
<div class="container">
    <div th:replace="fragments/bodyHeader :: bodyHeader"/>
    <form th:action="@{/items/new}" th:object="${form}" method="post">
        <div class="form-group">
            <label th:for="name">상품명</label>
            <input type="text" th:field="*{name}" class="form-control"
                   placeholder="이름을 입력하세요">
        </div>
        <div class="form-group">
            <label th:for="price">가격</label>
            <input type="number" th:field="*{price}" class="form-control"
                   placeholder="가격을 입력하세요">
        </div>
        <div class="form-group">
            <label th:for="stockQuantity">수량</label>
            <input type="number" th:field="*{stockQuantity}" class="form-control" placeholder="수량을 입력하세요">
        </div>
        <div class="form-group">
            <label th:for="author">저자</label>
            <input type="text" th:field="*{author}" class="form-control"
                   placeholder="저자를 입력하세요">
        </div>
        <div class="form-group">
            <label th:for="isbn">ISBN</label>
            <input type="text" th:field="*{isbn}" class="form-control"
                   placeholder="ISBN을 입력하세요">
        </div>
        <button type="submit" class="btn btn-primary">Submit</button>
    </form>
    <br/>
    <div th:replace="fragments/footer :: footer" />
</div> <!-- /container -->
</body>
</html>

 

마지막으로 ItemController 클래스를 생성한다.

@Controller
@RequiredArgsConstructor
public class ItemController {

    private final ItemService itemService;

    @GetMapping("/items/new")
    public String createForm(Model model) {
        model.addAttribute("form", new BookForm());
        return "items/createItemForm";
    }

    @PostMapping("/items/new")
    public String create(BookForm form) {
        Book book = new Book();
        book.setName(form.getName());
        book.setPrice(form.getPrice());
        book.setStockQuantity(form.getStockQuantity());
        book.setAuthor(form.getAuthor());
        book.setIsbn(form.getIsbn());

        itemService.saveItem(book);
        return "redirect:/";
    }
}

 

 

정상적으로 데이터베이스에 반영되는 것을 볼 수 있다.

'Spring' 카테고리의 다른 글

웹 계층 개발 - (2)  (0) 2022.09.08
주문 도메인 개발  (0) 2022.09.06
상품 도메인 개발  (0) 2022.09.02
회원 도메인 개발  (1) 2022.09.02
애플리케이션 구현 준비  (2) 2022.09.02
Comments