SLF4j 특징

 

SLF4j(Simple Log Facade for Java) 는 사용자가 원하는 로깅 프레임워크(log4j, logback, 등)으로 변경하여 사용 할수 있도록 추상화하는 기능을 제공한다.

 

 

1. gralde  추가 


    compile("org.slf4j:slf4j-api:1.7.7")
    compile('ch.qos.logback:logback-classic:1.1.2')

 

2. logback.xml 파일을 src/main/resource 아래 생성 후 아래 내용을 추가해준다. 

<?xml version="1.0" encoding="UTF-8"?>
<!-- 30초마다 설정 파일의 변경을 확인한다. 파일이 변경되면 다시 로딩한다 -->
<configuration scan="true" scanPeriod="30 seconds">

    <!-- 외부 설정파일을 사용할 수 있다. -->
    <property resource="resource.properties"/>

    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern> %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${APP_HOME}/sujemall-webapp.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 파일이 하루에 한개씩 생성된다 -->
            <fileNamePattern>sujemall-webapp.%d{yyyy-MM-dd}.log</fileNamePattern>

            <!-- maxHIstory 설정은 위 부분에 롤링 정책에 따라 적용되 된다고 보면된다.
             위 설정데로 라면 30일이 지난 파일은 삭제가 된다.-->
            <maxHistory>30</maxHistory>
        </rollingPolicy>

        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
        </encoder>
    </appender>

    <logger name="org.springframework" level="info"/>
    <logger name="org.hibernate" level="debug"/>
    <logger name="com.sujemall.webapp" level="debug"/>
    <if condition='property("RUN_MODE").equals("SERVICE")'>
        <then>
            <!-- 설정파일에 RUN_MODE 가 SERVICE로 정의되어 있으면 로깅 레벨을 INFO로 지정 -->
            <root level="info">
                <appender-ref ref="console"/>
                <appender-ref ref="FILE"/>
            </root>
        </then>
        <!-- 설정파일에 RUN_MODE 가 SERVICE로 정의되어 있지 않으면  로깅 레벨을 DEBUG 지정 -->
        <else>
            <root level="debug">
                <appender-ref ref="console"/>
                <appender-ref ref="FILE"/>
            </root>
        </else>
    </if>
</configuration>

3. Anotation을 이용하여 Log찍어보기 

 

@Slf4j
public class App {
    public static void main(String[] args) {
        log.debug("test {}","asdf");
        log.debug("test {}","asdf");
        log.debug("test {}","asdf");
    }
}
    • 영속성 컨텍스트란 무엇인가.
        • 실제 DB 저장하는 것이 아니라 
        • 엔티티를 "영속컨텍스트에" 저장한다는 의미이다.

          • JPA 이해하는데 가장 중요한영어
          • "엔티티를 영구 저장하는 환경"이라는 의미를 가진다.
          • EntityManager.persist(entity)
          • 매니저(EntityManager)를 통해 영속성 컨텍스트(persistence)에 접근한다.
        • 비영속 컨텍스트
          • 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
          • JPA 관리하지 않는 상태
      //객체를 생성한 상태(비영속)
      
      Admin admin = new Admin();
      
      admin.setId("member1");
      
      admin.setUsername("회원1");
      
      //영속상태
      em.persist()

       

       

      • 준영속 (detached)
        • 영속성 컨텍스트에 저장되었다가 분리된 상태 
          //
          회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
        • em.detach()
      Admin admin = em.find(Admin.class , 101L);
      admin.setName("AAAAAA");
                  
      //JPA에서 관리하지 않는다. 
      em.detach(admin);
      
      //영속성 컨테스트의 내용을 전부 초기화 
      em.clear();

       

      • 그렇다면 영속성 속성을 클리어하고 다시 조회 하게 어떻게 될까 ? 
        • 아래의 예시를 보면 "em.clear" 또는 em.detach를 사용시에는 영속성관리를 하지 않게 됨으로 
        • admin 과 admin1은 1차캐시된 데이터를 가져오지 않으므로  false가 나오게 된다. 

       

                  Admin admin = em.find(Admin.class , 101L);
                  admin.setName("AAAAAA");
      
                  //JPA에서 관리하지 않는다.
                  em.detach(admin);
      
                  em.clear();
      
                  Admin admin1 = em.find(Admin.class,101L);
      
                  System.out.println(admin == admin1);

       

       

      • 영속이란 
        • 실제 영속컨테스트가 관리하는 1차캐시에 데이터가 없으면 DB에서 데이터를 가져와 캐시에 담아 JPA가 관리하는 것 
      • 데이터의 동일성
        • JPA "영속엔티티" 동일성을 보장해준다.
        • 1 캐시로 반복가능한 읽기(RePEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공
      • 엔티티 수정 감지
        • JPA 변경감지 기능이 있어서 "엔티티" 변경
        • JPA 자바의 Collection 변경하듯이 "값만 변경했지만update 되었다 .

                  Admin admin = em.find(Admin.class,101L);

                  admin .setName("나승후1111");

       









      package study.ExDate;


      import java.util.Calendar;

      import java.util.Date;

      import java.util.Locale;

      import java.util.TimeZone;


      /**

       * @author admin

       *

       */

      public class UtilCalendar {


      private static Calendar cal = Calendar.getInstance();


      /**

      * 현재 연월일 시분초 밀리세컨드까지 가져오는 메소드

      */

      public static void getSamDate() {


      disDate();


      }


      public static void getSetDate() {


      Date date = new Date();

      // 5초 추가

      date.setTime(5000);

      // 현재시간에 5초로 시간 설정

      cal.setTime(date);

      // 년

      disDate();


      }


      /**

      * 지역을 찾아서 영국과의 시간차를 빼기위한 메소드

      */

      public static void getDateLocale() {

      Locale lo = Locale.getDefault();


      System.out.println(cal.get(Calendar.YEAR));

      System.out.println(cal.get(Calendar.MONTH) + 1);

      System.out.println(cal.get(Calendar.DATE) + "일");

      System.out.println(cal.get(Calendar.AM_PM) + "오전오후");

      if (lo.getCountry().equals("KR") && lo.getLanguage().equals("ko")) {

      System.out.println(cal.get(Calendar.HOUR) - 9 + "시간");

      } else {

      System.out.println(cal.get(Calendar.HOUR) + "시간");

      }

      System.out.println(cal.get(Calendar.MINUTE) + "분");

      System.out.println(cal.get(Calendar.SECOND) + "초");

      System.out.println(cal.get(Calendar.MILLISECOND) + "세컨드");

      System.out.println(cal.getTimeInMillis() + "");


      }


      public static void getUkDate() {

      // 다시 객체 생성

      cal = Calendar.getInstance();


      TimeZone zone = TimeZone.getTimeZone("Europe/London");


      cal.setTimeZone(zone);


      disDate();


      }


      public static void useContryDate() {


      String[] local = TimeZone.getAvailableIDs();


      for (String s : local) {

      System.out.println(s);

      }


      }


      public static void disDate() {


      // 년

      System.out.println(cal.get(Calendar.YEAR));

      System.out.println(cal.get(Calendar.MONTH) + 1);

      System.out.println(cal.get(Calendar.DATE) + "일");

      System.out.println(cal.get(Calendar.AM_PM) + "오전오후");

      System.out.println(cal.get(Calendar.HOUR) + "시간");

      System.out.println(cal.get(Calendar.MINUTE) + "분");

      System.out.println(cal.get(Calendar.SECOND) + "초");

      System.out.println(cal.get(Calendar.MILLISECOND) + "세컨드");

      System.out.println(cal.getTimeInMillis() + "");

      }


      }








      AOP 관점지향 프로그래밍 


      Aspect Oriented Programming(관점 지향 프로그래밍): 객체 지향 프로그래밍을 보완하는 

      개념으로 메소드나 클래스를 관점에 따라 분리시켜서 구현하는 프로그래밍 방법

      하나의 메소드에 비지니스 로직을 수행하는 문장과 공통으로 사용하는 ㅁ눈장이 같이 존재할 때 이를 분리해서 구현하기 위한 프로그래밍 방식 

      관점지향 프로그래밍이라고 하는데 spring에서는 공통으로 사용하는 문장을 별도의 클래스에 작성해서 분리를 할 수 있는 기능을 제공합니다.

      실행될 때 코드를 합쳐서 하나의 proxy 객체를 만들어서 실행합니다. 




      **AOP 적용

      AOP: 하나의 메소드에 비지니스 로직을 수행하는 문장과 공통으로 사용하는 문장이 같이 존재할 때 이를 분리해서 구현하기 위한 프로그래밍 방식

      관점 지향 프로그래밍이라고 하는데 spring에서는 공통으로 사용하는 문장을 별도의 클래스에 작성해서 분리를 할 수 있는 기능을 제공합니다.

      실행 될 때 코드를 합쳐서 하나의 proxy 객체를 만들어서 실행합니다.


      =>Dao 클래스의 메소드가 호출될 때의 시간을 매일 파일에 기록하는 AOP


      1.advice로 사용될 클래스를 생성

      =>kr.co.pk.advice.LoggingAdvice


      package kr.co.pk.advice;


      import java.io.FileOutputStream;

      import java.io.PrintWriter;

      import java.util.Calendar;


      import org.aspectj.lang.ProceedingJoinPoint;

      import org.aspectj.lang.annotation.Around;

      import org.aspectj.lang.annotation.Aspect;

      import org.springframework.stereotype.Component;


      //객체를 자동으로 생성하기 위한 어노테이션

      @Component

      //Advice 클래스로 만들기 위한 어노테이션

      @Aspect

      public class LoggingAdvice {


      //advice로 수행될 메소드

      //pointcut 작성

      //접근지정자는 public 다음 *은 모든 리턴 타입

      //kr.co.pk.. 은 kr.co.pk 패키지 안에 있는 모든

      //*Dao 는 Dao로 끝나는 클래스 .* 은 메소드 이름이 무엇이든지

      //(..)은 매개변수 개수에 상관없이

      @Around("execution(public * kr.co.pk..*Dao.*(..))")

      public Object invoke(ProceedingJoinPoint joinPoint)

      throws Throwable{

      //pointcut으로 설정된 메소드가 호출되기 전에 수행할 내용

      //메소드 이름 가져오기

      String methodName = joinPoint.getSignature().toLongString();

      //현재 시간 만들기

      Calendar cal = Calendar.getInstance();

      java.util.Date date = new java.util.Date(

      cal.getTimeInMillis());

      //파일에 문자열 기록하기 - 파일이 존재하면 이어쓰기

      FileOutputStream fos = 

      new FileOutputStream("d:\\log.txt", true);

      //문자열을 기록할 수 있는 클래스의 객체 만들기

      PrintWriter pw = new PrintWriter(fos);

      //파일에 기록

      pw.println(methodName + " " + date.toString() + "\n");

      pw.flush();

      pw.close();

      Object obj = joinPoint.proceed();

      //pointcut으로 설정된 메소드가 호출 된 후에 수행할 내용

      return obj;

      }

      }


      2.pom.xml 파일에 aop를 사용하기 위한 의존성 라이브러리를 추가

      <dependency>

      <groupId>org.aspectj</groupId>

      <artifactId>aspectjweaver</artifactId>

      <version>1.8.8</version>

      </dependency>


      <dependency>

      <groupId>org.springframework</groupId>

      <artifactId>spring-aop</artifactId>

      <version>${org.springframework-version}</version>

      </dependency>


      <dependency>

      <groupId>org.springframework</groupId>

      <artifactId>spring-aspects</artifactId>

      <version>${org.springframework-version}</version>

      </dependency>


      3.servlet-context.xml 파일에 aop 네임스페이스를 추가하고 어노테이션으로 설정한 aop를 사용할 수 있는 태그를 추가

      <!-- 어노테이션으로 만든 AOP 적용 -->

      <aop:aspectj-autoproxy />



      **인터셉터 

      게시글쓰기, 상세보기, 수정, 삭제 등은 로그인 되어 있지 않은 유저는 작업을 수행할 수 없도록 하고자 하는 경우 이러한 코드를 Controller, Service, Dao 등에 작성하는 것은 이상합니다.

      스프링에서는 Interceptor 나 AOP 등을 이용해서 구현 할 수 있습니다.

      Interceptor는 URL을 설정해서 URL 요청이 왔을 때 메소드를 호출하도록 할 수 있습니다.


      1. 로그인이 되어 있지 않으면 로그인 페이지로 이동시키고 로그인을 하면 작업을 할 수 있는 페이지로 이동하도록 만들어봅시다. 


      => Interceptor : url 요청이 왔을 때 Controller 보내기 전이나 Controller가 작업한 후에 수행할 내용을 작성할 수 있는 Spring이

      제공하는 기능 

      =>HandlerInterceptor 인터페이스나 HandlerInterceptorAdapter 클래스를 상속받는 클래스를 만들어서 메소드를 재정의 하고 

      dispatcher-servlet(Servlet-Context.xml)에 interceptors 태그를 이용해서 설정하면 됩니다.

      => HandlerInterceptorAdapter인터페이스는 모든 메소드가 추상메소드라서 전부 재정의 해야하고 

      HandlerInterceptorAdapter 클래스는 모든 메소드가 내용이 없는 상태로 구현되어 있어서 필요한 메소드만 재정의 하면 되는데 

      메소드 이름을 기억하기 어려우므로 인터페이스를 이용합시;다. 



      2.HandlerInterceptorAdapter를 implements 한 클래스 안의 재정의 된 메소드 설명 

      => com.seunghoo.na.AuthenticationInerceptor


      1. preHandle 메소드 : Controller가 처리하기 전에 호출되는 메소드 


      2. postHandle 메소드 : Controller가 사용자의 요청을 정상적으로 처리하고 난 후 호출되는 메소드 


      3. afterCompletion메소드 :  Controller에서 예외 발생여부에 상관없이 호출되는 메소드 



      3.HandlerInterceptorAdapter를 implements 한 클래스를 생성 

      @Component

      @Override

      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)

      throws Exception {


      // 로그인을 확인하기 위해서 session 가져오기

      HttpSession session = request.getSession();

      // 로그인 정보는 session의 user 속성에 저장되어 있습니다.

      if (session.getAttribute("user") == null) {

      // 사용자의 요청을 session에 dest라는 속성에 저장

      // 로그인이 되면 원래의 요청을 처리하기 위해서

      // 클라이언트 요청 전체 주소

      String requestURI = request.getRequestURI();

      // 현재 프로젝트 경로 가져오기

      String contextPath = request.getContextPath();

      String uri = requestURI.substring(contextPath.length() + 1);

      // 주소 뒤에 파라미터를 가져오기

      String query = request.getQueryString();

      System.out.println("쿼리"+query);

      System.out.println("URI"+uri);

      // 실제 주소만들기

      if (query == null || query.equals("null")) {

      query = "";

      } else {

      query = "?" + query;

      }

      //세션에 주소 저장하기 

      session.setAttribute("dest", uri+query);

      //세션에 메시지 저장하기 

      session.setAttribute("msg","로그인을 하셔야 이용할 수 있는 서비스 입니다.");


      response.sendRedirect(contextPath + "/user/login");

      return false;

      }

      // 로그인된 경우에는 Controller가 처리합니다.


      return true;

      }

      4. UserController 에서 로그인을 처리하는 메소드를 수정 

      => 이전에는 무조건 시작 페이지로 가도록 되어 있었지만 요청이 있는 경우 그페이지로 이동하도록 코드를 수정 

      session.setAttribute("user", user);

      //이전 요청을 가져오기 

      Object dest = session.getAttribute("dest");

      //이전 요청이 없으면 시작페이지로 이동 

      if(dest==null) {

      return "redirect:/";

      //이전 요청이 있으면 그 페이지로 이동 

      }else {

      return "redirect:/"+dest.toString();

      }

      **게시물 삭제

      1.detail.jsp 파일에 삭제를 위한 UI 와 이벤트를 작성

      =>jquery ui의 dialog 기능을 이용

      1)삭제 버튼(deletebtn)을 눌렀을 때 수행되는 코드가 있으면 제거


      2)파일의 하단에 대화상자로 사용할 내용 만들기

      <c:if test="${user.email == vo.email}">

      <link rel="stylesheet"

      href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">

      <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>


      <div id="dialog-confirm" title="정말로 삭제?" style="display: none">

      <p>삭제하시면 복구할 수 없습니다. 그래도 삭제하실 건가요?</p>

      </div>


      <script>

      //삭제 버튼을 눌렀을 때 처리

      document.getElementById("deletebtn").addEventListener(

      "click", function(){

      $("#dialog-confirm").dialog({

            resizable: false,

            height: "auto",

            width: 400,

            modal: true,

            buttons: {

              "삭제": function() {

                $(this).dialog("close");

                location.href="delete?bno=${vo.bno}";

              },

              "취소": function() {

                $(this).dialog("close");

              }

            }

          });

      });

      </script>

      </c:if>


      2.board.xml 파일에 게시글을 삭제하는 SQL을 작성

      <!-- 게시글 삭제를 위한 SQL -->

      <delete id="delete" parameterType="java.lang.Integer">

      delete from springboard

      where bno=#{bno}

      </delete>


      3.BoardDao 클래스에 게시글을 삭제하는 메소드를 생성

      //글번호에 해당하는 데이터를 삭제를 하는 메소드

      public void delete(int bno) {

      sqlSession.delete("board.delete", bno);

      }


      4.BoardService 인터페이스에 게시글을 삭제하는 메소드를 선언

      //게시글 삭제를 처리해 줄 메소드를 선언

      public void delete(HttpServletRequest request);


      5.BoardServiceImpl 클래스에 게시글을 삭제하는 메소드를 구현

      @Override

      public void delete(HttpServletRequest request) {

      //파라미터 읽기

      String bno = request.getParameter("bno");

      //Dao 메소드 호출

      boardDao.delete(Integer.parseInt(bno));

      }


      6.Controller 에 게시물 삭제를 처리해주는 메소드 생성 


      @RequestMapping(value = "board/delete", method = RequestMethod.GET)

      public String delete(HttpServletRequest request, RedirectAttributes attr) {

      boardService.delete(request);

      attr.addFlashAttribute("msg", "삭제");

      return "redirect:list";

      }

















       게시글 수정 

      상세보기를 작업할 때는 글번호를 가지고 이미 데이터를 찾아오는 방법이 있기 때문에 Service부터작업하면 됩니다.


      1. BoardService 인터페이스에 게시글을 가져와서 수정보기에 사용할 메소드스를 선언 

      //게시물 수정 보기를 위한 메소드 

      public Board updateView(HttpServletRequest request);


      2. BoardServiceImpl 클래스에 게시글을 가져와 수정보기에 사용할 메소드를 구현 


      @Override

      public Board updateView(HttpServletRequest request) {

      String bno = request.getParameter("bno");

      return boardDao.detail(Integer.parseInt(bno));}

      3. BoardController 클래스에 게시글을 가져와서 수정보기 화면에 출력하는 메소드를 구현 

      @RequestMapping(value = "board/update", method=RequestMethod.GET)

      public String update(HttpServletRequest request ,Model model){

      Board board = boardService.detail(request);

      model.addAttribute("vo", board);

      return "board/update";


      }


      4.Board디렉토리에 update.jsp 파일을 만들고 수정화면을 작성 

      <%@ page language="java" contentType="text/html; charset=UTF-8"

      pageEncoding="UTF-8"%>

      <%@include file="../include/header.jsp"%>

      <section class="content">

      <div class="box-header">

      <h3 class="box-title">게시판 수정</h3>

      </div>


      <form role="form" method="post">

      <!-- 데이터 수정을 할 때 기본키의 값이 있어야 해서 필요하고

      작업이 끝나고 결과 페이지로 이동할 때

      상세보기로 이동하려면 글번호가 필요합니다. -->

      <input type="hidden" name="bno" value="${vo.bno}" />

      <div class="box-body">

      <div class="form-group">

      <label>제목</label> <input type="text" name='title'

      class="form-control" value="${vo.title}">

      </div>

      <div class="form-group">

      <label>내용</label>

      <textarea class="form-control" name="content" rows="5">${vo.content}</textarea>

      </div>


      <div class="form-group">

      <label>작성자</label> <input type="text" name="nickname"

      value="${user.nickname}" class="form-control" readonly="readonly">

      </div>

      </div>


      <div class="box-footer">

      <button type="submit" class="btn btn-primary">작성완료</button>

      </div>

      </form>

      </section>

      <%@include file="../include/footer.jsp"%>


      5. board.xml 파일에 게시글 수정을 위한 SQL 작성 

      <update id="update" parameterType="Board">


      update springboard

      set

      title=#{title}, content=#{content}, regdate=sysdate

      where bno=#{bno}


      </update>

      6. BoardDao 클래스에 게시글 수정을 위한 메소드 생성 

      public void update(Board board) {

      sqlSession.update("board.update",board);

      }


      7.게시글 수정을 처리해 줄 메소드를 선언 

      public void update(HttpServletRequest request);


      8.BoardServiceImpl 클래스에 게시글 수정을 처리해 줄 메소드를 구현 

      @Override

      public void update(HttpServletRequest request) {

      // 파라미터 읽기

      // 파라미터를 이용해서 수행할 작업이 있으면 수행

      String title = request.getParameter("title");

      String content = request.getParameter("content");

      String ip = request.getRemoteAddr();

      String bno = request.getParameter("bno");


      // Dao 메소드를 호출

      Board board = new Board();

      board.setIp(ip);

      board.setContent(content);

      board.setTitle(title);

      board.setBno(Integer.parseInt(bno));

      boardDao.update(board);


      }


      9. BoardController에서 게시물 수정을 처리해줄 메소드  

      @RequestMapping(value = "board/update", method=RequestMethod.POST)

      public String update(HttpServletRequest request , RedirectAttributes attr){

      boardService.update(request);

      attr.addFlashAttribute("msg","게시글 수정");

      return "redirect:list";


      }

       **상세보기 

      =>게시판에서는 글 제목을 클릭하면 상세보기를 하도록 만듭니다.

      =>상세보기를 할 때는 기본키의 값을 파라미터로 넘겨주도록 링크를 작성 

      =>상세보기는 2개의 sql을 실행해야 합니다. 

      => 글번호에 해당하는 데이터의 조회수를 1증가 

      => 글번호에 해당하는 데이터를 가져와야 합니다.

      <a href="링크? 이름=기본키값">제목</a>





      1. list.jsp 파일에서 제목을 출력하는 부분을 링크를 추가 

      <a href="detail?bno=${vo.bno}">${vo.title}</a>



      2. 글번호 에 해당하는 데이터를 가져와야합니다. 

      <!-- 상세보기를 할때 글 번호에 해당하는 데이터의 조회수를 1증가시켜주는 sql -->

      <update id="updatecnt" parameterType="java.lang.Integer">


      update springboard

      set readcnt = readcnt +1

      where bno=#{bno}


      </update>

      3.상세보기를 할 때 글번호에 해당하는 데이터를 가져오는 sql 


      <select id="detail" parameterType="java.lang.Integer" resultType="Board">

      select * 

      from springboard

      where bno = #{bno}

      </select>



      3. BoardDao 클래스에 글번호에 해당하는 데이터의 조회수를 1증가시켜 주는 

      메소드와 글번호에 해당하는 데이터를 가져오는 메소드를 생성 


      public void updatecnt(int bno){

      sqlSession.update("board.updatecnt",bno);

      }

      //글번호에 해당하는 데이터를 가져오는 메소드 

      public Board detail(int bno) {

      return sqlSession.selectOne("board.detail",bno);

      }


      4. BoardService 인터페이스에 상세보기를 처리할 메소드를 선언 

      public Board detail(HttpServletRequest request) ;



      5.BoardServiceImpl 클래스에 상세보기를 처리할 메소드를 구현 


      @Override

      public Board detail(HttpServletRequest request) {

      //파라미터 읽기 

      //Integer.parsint를 호출했을 때 예외가 생기는 경우 

      //=> null이 리턴되서 정수로 변환을 못하는 경우 

      //=> 숫자 뒤에 공백이 포함되 정수로 변환을 못하는 경우 

      //NumberFormatException이 발생하면 Integer.parseint에 대입한 데이터를 먼저 출력해서 확인 

      String bno  = request.getParameter("bno");

      //조회수 1증가시키는 메소드 호출 

      boardDao.updatecnt(Integer.parseInt(bno));

      //데이터 가져오는 메소드를 호출해서 리턴 

      return boardDao.detail(Integer.parseInt(bno));

      }


      5.BoardController에 상세보기 요청을 처리하는 메소드를 구현 

      @Reqeustparam(value = "board/detail", method=Reqeustparam.GET)

      public String detail(HttpServletRequest request ,Model model){

      Board board = boardService.detail(request);

      model.addAttribute("board", board);

      return "board/detail";


      }



      7.views.board 디렉토리에 detail.jsp 파일을 만들고 출력 

      <%@ page language="java" contentType="text/html; charset=UTF-8"

          pageEncoding="UTF-8"%>

      <!DOCTYPE html>

      <html>

      <head>

      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

      <title>상세보기</title>

      </head>

      <body>

      ${vo}

      <%@ include file="../include/header.jsp" %>


      <section class="content">

      <div class="box">

      <div class="box-header">

      <h3 class="box-title">상세보기</h3>

      </div>

      <div class="box-body">

      <div class="form-group">

      <label>제목</label>

      <input type="text" name="title"

      class="form-control" value="${vo.title}"

      readonly="readonly" />

      </div>

      <div class="form-group">

      <label>내용</label>

      <textarea name="content" rows="5"

      readonly="readonly" class="form-control">${vo.content}</textarea>

      </div>

      <div class="form-group">

      <label>작성자</label>

      <input type="text" 

      class="form-control" value="${vo.nickname}"

      readonly="readonly" />

      </div>

      </div>

      <div class="box-footer">

      <button class="btn btn-success" id="mainbtn">메인</button>

      <c:if test = "${user.email == vo.email}">

      <button class="btn btn-warning" id="updatebtn">수정</button>

      <button class="btn btn-danger" id="deletebtn">삭제</button>

      </c:if>

      <button class="btn btn-primary" id="listbtn">목록</button>

      </div>

      </div>

      </section>

      <%@ include file="../include/footer.jsp" %>

      <script>

      //메인 버튼을 눌렀을 때 처리

      document.getElementById("mainbtn").addEventListener("click", function(){

      location.href="../";

      });

      //목록 버튼을 눌렀을 때 처리

      document.getElementById("listbtn").addEventListener("click", function(){

      location.href="list";

      });

      <c:if test = "${user.email == vo.email}">

      //삭제 버튼을 눌렀을 때 처리

      document.getElementById("deletebtn").addEventListener("click", function(){

      location.href="delete?bno=" + ${vo.bno};

      });

      //수정 버튼을 눌렀을 때 처리

      document.getElementById("updatebtn").addEventListener("click", function(){

      location.href="update?bno=" + ${vo.bno};

      });

      </c:if>

      </script>

      </body>

      </html>


      + Recent posts