[boostcourse] 3.10. Layered Architecture - BE (2)

0. 방명록 만들기 실습

- Spring JDBC를 이용한 DAO 작성

- Controller + Service + DAO

- 트랜잭션 처리

- Spring MVC에서 form 값 입력받기

- Spring MVC에서 redirect하기

- Controller에서 jsp에게 전달한 값을 JSTL과 EL을 이용해 출력하기

 

요구사항

요구사항 1

- 방명록 정보는 guestbook 테이블에 저장됨

- id는 자동으로 입력됨

- id, 이름, 내용, 등록일을 저장함

요구사항 2

- http://localhost:8080/guestbook 을 요청하면 자동으로 /guestbook/list로 리다이렉트 함

- 방명록이 없으면 건수는 0이 나오고, 아래에 방명록을 입력하는 form이 보여짐

요구사항 3

- 이름과 내용을 입력하고, 등록 버튼을 누르면 /guestbook/write URL로 입력한 값을 전달하여 저장함

- 값이 저장된 이후에는 /guestbook/list로 리다이렉트 됨

요구사항 4

- 입력한 한 건의 정보가 보여짐

- 방명록 내용과 폼 사이의 숫자는 방명록 페이지 링크. 방명록 5건당 1페이지로 설정함

요구사항 5

- 방명록이 6건 입력되면, 아래 페이지 수가 2건 보여짐

- 1페이지를 누르면 /guestbook/list?start=0 을 요청

- 2페이지를 누르면 /guestbook/list?start=5 을 요청

- /guestbook/list 는 /guestbook/list?start=0 와 결과가 같음

요구사항 6

- 방명록에 글을 쓰거나, 방명록의 글을 삭제할 때는 Log 테이블에 클라이언트의 id 주소, 등록/삭제 시간, 등록/삭제 정보를 DB에 저장함

- 사용하는 테이블은 Log

- id는 자동으로 입력되도록 함

 

테이블 및 다이어그램

 

1. 프로젝트 생성 및 설정 파일 생성

# pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.1.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>guestbook</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<name>guestbook</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-jasper</artifactId>
		</dependency>
		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
			<version>1.4</version>
		</dependency>
		<dependency>
			<groupId>jakarta.platform</groupId>
			<artifactId>jakarta.jakartaee-web-api</artifactId>
			<version>10.0.0</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>jakarta.servlet</groupId>
			<artifactId>jakarta.servlet-api</artifactId>
			<version>6.0.0</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>jakarta.servlet.jsp</groupId>
			<artifactId>jakarta.servlet.jsp-api</artifactId>
			<version>3.1.0</version>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>jakarta.servlet.jsp.jstl</groupId>
			<artifactId>jakarta.servlet.jsp.jstl-api</artifactId>
			<version>3.0.0</version>
		</dependency>
		<dependency>
			<groupId>org.glassfish.web</groupId>
			<artifactId>jakarta.servlet.jsp.jstl</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.tomcat</groupId>
			<artifactId>tomcat-jsp-api</artifactId>
			<version>9.0.10</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

 

# WebMvcContextConfiguration.java

package com.example.guestbook.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"com.example.guestbook.controller"})
public class WebMvcContextConfiguration implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/assets/**").addResourceLocations("classpath:/META-INF/resources/webjars/").setCachePeriod(31556926);
        registry.addResourceHandler("/css/**").addResourceLocations("/css/").setCachePeriod(31556926);
        registry.addResourceHandler("/img/**").addResourceLocations("/img/").setCachePeriod(31556926);
        registry.addResourceHandler("/js/**").addResourceLocations("/js/").setCachePeriod(31556926);
    }

    @Override
    public void addViewControllers(final ViewControllerRegistry registry) {
        System.out.println("addViewControllers 호출됨");
        registry.addViewController("/").setViewName("index");
    }

    @Bean
    public InternalResourceViewResolver getInternalResourceViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

}

 

# DBConfig.java

package com.example.guestbook.config;

import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;

import javax.sql.DataSource;

@Configuration
@EnableTransactionManagement
public class DBConfig implements TransactionManagementConfigurer {

    private String driverClassName = "com.mysql.cj.jdbc.Driver";
    private String url = "jdbc:mysql://localhost:3306/connectdb?useUnicode=true&characterEncoding=utf8";

    private String username = "connectuser";
    private String password = "connectuser";

    @Bean
    public DataSource dataSource() {

        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);

        return dataSource;
    }


    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return transactionManager();
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
}

 

# ApplicationConfig.java

package com.example.guestbook.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan(basePackages = {"com.example.guestbook.dao", "com.example.guestbook.service"})
@Import({DBConfig.class})
public class ApplicationConfig {
}

 

# web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app>
    <display-name>Spring JavaConfig Sample</display-name>

    <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
    </context-param>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.example.guestbook.config.ApplicationConfig</param-value>
    </context-param>
    <listner>   <!-- ApplicationConfig를 읽어들이기 위해. 특정한 이벤트가 일어났을 때 동작. Context가 로딩될 때 해당 클래스를 실행해줘 -->
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listner>

    <servlet>
        <servlet-name>mvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </init-param>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.example.guestbook.config.WebMvcContextConfiguration</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>mvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

- Layered Architecture 특징 상, 설정 파일을 분리함

- ApplicationConfig 를 읽어들이기 위해 ContextLoaderListener 사용

- run on Server, 서버가 올라가면, Context 가 로딩되면, Listener인 ContextLoaderListener를 실행해줘

- 이때 <context-param>에 등록된 정보를 참고함 -> ApplicationConfig 지정해둠

- <filter> : 요청이 수행되기 전, 응답이 나가기 전에 실행. 한글 인코딩 처리

 

# index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<%
    response.sendRedirect("guestbook/list");
%>

- "/" 접속 시 "/guestbook/list" 로 리다이렉트

 

2. DB에 테이블 생성

 

3. DTO 및 DAO 생성

3.1. DTO

# Guestbook.java

package com.example.guestbook.dto;

import java.util.Date;

public class Guestbook {

    private Long id;
    private String name;
    private String content;
    private Date regdate;

    public Guestbook(Long id, String name, String content, Date regdate) {
        this.id = id;
        this.name = name;
        this.content = content;
        this.regdate = regdate;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public Date getRegdate() {
        return regdate;
    }

    public void setRegdate(Date regdate) {
        this.regdate = regdate;
    }

    @Override
    public String toString() {
        return "Guestbook{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", content='" + content + '\'' +
                ", regdate=" + regdate +
                '}';
    }
}

 

# Log.java

package com.example.guestbook.dto;

import java.util.Date;

public class Log {

    private Long id;
    private String ip;
    private String method;
    private Date regdate;

    public Log() {}

    public Log(Long id, String ip, String method, Date regdate) {
        this.id = id;
        this.ip = ip;
        this.method = method;
        this.regdate = regdate;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getIp() {
        return ip;
    }

    public void setIp(String ip) {
        this.ip = ip;
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public Date getRegdate() {
        return regdate;
    }

    public void setRegdate(Date regdate) {
        this.regdate = regdate;
    }

    @Override
    public String toString() {
        return "Log{" +
                "id=" + id +
                ", ip='" + ip + '\'' +
                ", method='" + method + '\'' +
                ", regdate=" + regdate +
                '}';
    }
}

 

3.2. DAO

# LogDao.java

package com.example.guestbook.dao;

import com.example.guestbook.dto.Log;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;

import javax.sql.DataSource;
import java.util.List;

@Repository
public class LogDao {

    private NamedParameterJdbcTemplate jdbc;
    private SimpleJdbcInsert insertAction;
    private RowMapper<Log> rowMapper = BeanPropertyRowMapper.newInstance(Log.class);

    public LogDao(DataSource dataSource) {
        this.jdbc = new NamedParameterJdbcTemplate(dataSource);
        this.insertAction = new SimpleJdbcInsert(dataSource)
                .withTableName("log")
                .usingGeneratedKeyColumns("id");        // id가 자동으로 입력되도록
    }

    public Long insert(Log log) {
        SqlParameterSource params = new BeanPropertySqlParameterSource(log);
        return insertAction.executeAndReturnKey(params).longValue();        // 자동으로 생성된 id 값을 리턴하도록
    }


}

 

# GuestBoojDaoSqls.java

package com.example.guestbook.dao;

public class GuestbookDaoSqls {

    public static final String SELECT_PARCING = "SELECT id, name, content, regdate FROM guestbook ORDER BY id DESC limit :start, :limit";
    public static final String DELETE_BY_ID = "DELETE FROM guestbook WHERE id = :id";
    public static final String SELECT_COUNT = "SELECT count(*) FROM guestbook";
}

 

# GuestbookDao.java

package com.example.guestbook.dao;

import com.example.guestbook.dto.Guestbook;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;

import javax.sql.DataSource;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.example.guestbook.dao.GuestbookDaoSqls.*;

@Repository
public class GuestbookDao {

    private NamedParameterJdbcTemplate jdbc;
    private SimpleJdbcInsert insertAction;
    private RowMapper<Guestbook> rowMapper = BeanPropertyRowMapper.newInstance(Guestbook.class);

    public GuestbookDao(DataSource dataSource) {
        this.jdbc = new NamedParameterJdbcTemplate(dataSource);
        this.insertAction = new SimpleJdbcInsert(dataSource).withTableName("guestbook").usingGeneratedKeyColumns("id");
    }

    public List<Guestbook> selectAll(Integer start, Integer limit) {
        Map<String, Integer> params = new HashMap<>();
        params.put("start", start);
        params.put("limit", limit);

        return jdbc.query(SELECT_PARCING, params, rowMapper);
    }

    public Long insert(Guestbook guestbook) {
        SqlParameterSource params = new BeanPropertySqlParameterSource(guestbook);
        return insertAction.executeAndReturnKey(params).longValue();
    }

    public int deleteById(Long id) {
        Map<String, ?> params = Collections.singletonMap("id", id);
        return jdbc.update(DELETE_BY_ID, params);
    }

    public int selectCount() {
        return jdbc.queryForObject(SELECT_COUNT, Collections.emptyMap(), Integer.class);
    }
}

 

3.3. 테스트

# GuestbookDaoTest.java

package com.example.guestbook.dao;

import com.example.guestbook.config.ApplicationConfig;
import com.example.guestbook.dto.Guestbook;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Date;

public class GuestbookDaoTest {

    public static void main(String[] args) {

        ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);
        GuestbookDao guestbookDao = ac.getBean(GuestbookDao.class);

        Guestbook guestbook = new Guestbook();
        guestbook.setName("Jeon");
        guestbook.setContent("스프링 아자자");
        guestbook.setRegdate(new Date());
        Long id = guestbookDao.insert(guestbook);
        System.out.println("id: " + id);

    }
}

# LogDaoTest.java

package com.example.guestbook.dao;

import com.example.guestbook.config.ApplicationConfig;
import com.example.guestbook.dto.Log;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Date;

public class LogDaoTest {

    public static void main(String[] args) {

        ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);
        LogDao logDao = ac.getBean(LogDao.class);

        Log log = new Log();
        log.setIp("127.0.0.1");
        log.setMethod("insert");
        log.setRegdate(new Date());
        Long id = logDao.insert(log);
        System.out.println("id: " + id);

    }
}

 

4. Service 생성

# GuestbookService.java

package com.example.guestbook.service;

import com.example.guestbook.dto.Guestbook;

import java.util.List;

public interface GuestbookService {

    public static final Integer LIMIT = 5;
    public List<Guestbook> getGuestbooks(Integer start);
    public int deleteGuestbook(Long id, String ip);
    public Guestbook addGuestbook(Guestbook guestbook, String ip);
    public int getCount();
}

 

# GuestbookServiceImpl.java

package com.example.guestbook.service.impl;

import com.example.guestbook.dao.GuestbookDao;
import com.example.guestbook.dao.LogDao;
import com.example.guestbook.dto.Guestbook;
import com.example.guestbook.dto.Log;
import com.example.guestbook.service.GuestbookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;

@Service
public class GuestbookServiceImpl implements GuestbookService {

    @Autowired      // 알아서 GuestbookDao를 생성해서 주입해줌
    GuestbookDao guestbookDao;

    @Autowired
    LogDao logDao;

    @Override
    @Transactional      // 읽기만 하는 메소드인 경우 트랜잭션을 위해 사용 readOnly == true
    public List<Guestbook> getGuestbooks(Integer start) {
        List<Guestbook> list = guestbookDao.selectAll(start, GuestbookService.LIMIT);
        return list;
    }

    @Override
    @Transactional(readOnly = false)
    public int deleteGuestbook(Long id, String ip) {
        int deletedCount = guestbookDao.deleteById(id);
        Log log = new Log();
        log.setIp(ip);
        log.setMethod("delete");
        log.setRegdate(new Date());
        logDao.insert(log);

        return deletedCount;
    }

    @Override
    @Transactional(readOnly = false)
    public Guestbook addGuestbook(Guestbook guestbook, String ip) {
        guestbook.setRegdate(new Date());
        Long insertedId = guestbookDao.insert(guestbook);       // insert해서 자동으로 만들어진 id 값을 얻어옴
        guestbook.setId(insertedId);

        Log log = new Log();
        log.setIp(ip);
        log.setMethod("insert");
        log.setRegdate(new Date());
        logDao.insert(log);

        return guestbook;
    }

    @Override
    public int getCount() {
        return guestbookDao.selectCount();
    }
}

 

테스트

# GuestbookServiceTest.java

package com.example.guestbook.service.impl;

import com.example.guestbook.config.ApplicationConfig;
import com.example.guestbook.dto.Guestbook;
import com.example.guestbook.service.GuestbookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class GuestbookServiceTest {
    public static void main(String[] args) {

        ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);
        GuestbookService guestbookService = ac.getBean(GuestbookService.class);

        Guestbook guestbook = new Guestbook();
        guestbook.setName("soyeong");
        guestbook.setContent("안녕하세용");

        Guestbook result = guestbookService.addGuestbook(guestbook, "127.0.0.1");
        System.out.println(result);

    }
}

 

5. Controller 생성

# GuestbookController.java

package com.example.guestbook.controller;

import com.example.guestbook.dto.Guestbook;
import com.example.guestbook.service.GuestbookService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.ArrayList;
import java.util.List;

@Controller
public class GuestbookController {

    @Autowired      // Service를 사용하기 위해 주입
    GuestbookService guestbookService;

    @GetMapping("/list")
    public String list(@RequestParam(name = "start", required = false, defaultValue = "0") int start,
                       ModelMap model) {

        // start로 시작하는 방명록 목록 구하기
        List<Guestbook> list = guestbookService.getGuestbooks(start);

        // 전체 페이지 수 구하기
        int count = guestbookService.getCount();
        int pageCount = count / GuestbookService.LIMIT;
        if(count % GuestbookService.LIMIT > 0) pageCount++;

        // 페이지 수만큼 start의 값을 리스트로 저장
        // 페이지 수가 3이라면, 0, 5, 10 저장
        List<Integer> pageStartList = new ArrayList<>();
        for(int i = 0; i < pageCount; i++) {
            pageStartList.add(i * GuestbookService.LIMIT);
        }

        model.addAttribute("list", list);
        model.addAttribute("count", count);
        model.addAttribute("pageStartList", pageStartList);

        return "list";      // list.jsp로 넘겨줌
    }

    @PostMapping("/write")
    public String write(@ModelAttribute Guestbook guestbook, HttpServletRequest request) {
        String clientIp = request.getRemoteAddr();
//        System.out.println("clientIp: " + clientIp);
        guestbookService.addGuestbook(guestbook, clientIp);
        return "redirect:list";
    }
}

 

6. View 생성

# list.jsp

<%--
  Created by IntelliJ IDEA.
  User: jeonsoyeong
  Date: 2023/06/27
  Time: 6:47 PM
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>방명록 목록</title>
</head>
<body>
    <h1>방명록</h1>
    <br> 방명록 전체 수 : ${count }
    <br>
    <br>

    <c:forEach items="${list}" var="guestbook">
        ${guestbook.id} ${status.index}<br>
        ${guestbook.name} ${status.index}<br>
        ${guestbook.content} ${status.index}<br>
        ${guestbook.regdate} ${status.index}<br>
    </c:forEach>
    <br>

    <c:forEach items="${pageStartList}" var="pageIndex" varStatus="status">
        <a href="list?start=${pageIndex}">${status.index +1 }</a>&nbsp; &nbsp;
    </c:forEach>

    <br>
    <br>
    <form method="post" action="write">
        name : <input type="text" name="name"><br>
        <textarea name="content" cols="60" rows="6"></textarea>
        <br> <input type="submit" value="등록">
    </form>
</body>
</html>