ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Back End] Layered Architecture and Rest Controller
    Web Development/부스트코스 - Back-End(Java) 2020. 4. 6. 14:56

    ✔️ Controller의 중복요소

    ◾ 웹 페이지는 중복으로 개발되는 요소가 존재한다. ex) 웹 페이지 어디서나 보이는 로그인 상태 정보

    ◾ 2개의 URL이 있다. 두 URL은 다르지만, 각 URL에 해당하는 웹 페이지를 보여주기 위해서 중복으로 실행되는 부분이 존재한다. 이 중복을 없애려면 어떻게 해야할까? 중복되는 부분을 별도의 객체 또는 메서드로 분리하면된다.

    ◾ 예를들어, 쇼핑몰에서 '문의 게시판'에서도 '회원 정보'를 보여주고, '상품 목록 보기'에서도 '회원정보'를 보여줘야 한다면 회원정보를 읽어오는 코드는 어떻게 해야할까?

    ◾ '문의 게시판'을 보여주는 컨트롤러와 '상품 목록 보기'를 보여주는 보여주는 컨트롤러를 각각 별도로 구현한다.

    '회원 정보'를 보여주는 것을 별도의 객체로 만들고 각 컨트롤러는 이 객체를 이용하면 된다.

    컨트롤러에서 중복적으로 호출되는 부분들을 별도의 Service 객체에서 구현하도록하고 컨트롤러들을 Service 객체를 사용하도록 해주면 된다. 

    ◾ Service에서도 DB 작업시 중복되는 코드가 있는데, 이것도 역시 별도의 객체나 메서드로 구현한다. ( Repository Layer = dao )

     


    ✔️서비스( Service ) 객체

    ◾ 서비스 객체는 보통 업무와 관련된 메서드를 가지고 있다. 이를 비지니스 로직이라고 한다. 

    ◾ 보통 하나의 비지니스 로직은 하나의 트랜잭션으로 동작한다. 

     

    컨트롤러와 서비스

    ◾ 위 그림을 보면 컨트롤러 1,3은 상품 Service 객체를 각각 구현하는 것이 아니라, 가져다 쓴다. 

     


    ✔️ 트랜잭션 ( Transaction )

    ◾ 하나의 논리적인 작업을 의미한다. 트랜잭션은 다음과 같은 4가지 특성이 있다.

    ◾ 원자성 ( Atomicity ) : 트랜잭션은 DB에 완전히 반영되거나 ( commit ), DB에 아예 반영되지 않거나 해야한다. ( rollback)

    ◾ 일관성 ( Consistency ) : 트랜잭션의 작업 처리 결과가 항상 일관성이 있어야 한다.

    ◾ 독립성 ( Isolation ) : 어떤 트랜잭션이 다른 트랜잭션의 연산을 끼어들 수 없다.

    ◾ 지속성 ( Durability ) : 트랜잭션이 성공적으로 완료됐을 경우, 결과는 영구적으로 반영되어야 한다.

    ◾ JDBC는 auto commit 설정이 기본적으로 되어있다. ( Connection 객체의 파라미터 조정으로 commit 메서드로 commit을 할 수 있음 )

    ◾ Spring JDBC에서는 Spring Java Config 파일에서 @EnableTransactionManagement 어노테시션을 사용해 드랜잭션을 활성화할 수 있다.

     


    ✔️서비스( Service )의 중복요소

    서비스 객체들은 비지니스 로직을 가지고 있다. 하나의 비즈니스 로직은 하나의 트랜잭션 단위로 작업이 처리가 된다.

    ◾ 하나의 트랜잭션은 여러개의 DB 작업이 수행될 수 있다. 이 때 비지니스 메서드마다 중복되는 기능을 호출할 수 있다.

    ◾ 서비스에서의 중복요소도 별도의 객체나 메서드로 분리해서 사용하면 중복을 없앨 수 있다.

     

     


    ✔️ 레이어드 아키텍쳐 ( Layered Architecture ) 

     

    레이어드 아키텍쳐

    ◾ Persentation Layer : 컨트롤러 객체가 동작 한다.

    ◾ Service Layer : 비지니스 메서드를 가지고 있는 서비스 객체가 동작한다.

    ◾ Repository Layer : DB 작업을 수행한다. 

    Service 객체는 Repository Layer에 있는 DAO 객체를 사용한다. 

    Persentation Layer 은 웹, 앱, 윈도우 프로그램 등으로 만들 수 있다. 이 때 Service Layer, Repository Layer를 재사용할 수 있다. -> 재사용, 유지보수 측면에서 Presentation Layer와 Service, Repository Layer 설정 파일을 분리하는 것이 좋다.

     

     


    ✔️ 설정의 분리

    Spring 설정 파일을 프리젠테이션 레이어쪽과 나머지를 분리할 수 있다.

    web.xml 파일에서 프리젠테이션 레이어에 대한 스프링 설정은 DispathcerServlet이 읽도록 하고, 그 외의 설정은 ContextLoaderListener를 통해서 읽도록 한다.

    ContextLoaderListener와 DispatcherServlet은 각각 ApplicationContext를 생성하는데, ContextLoaderListener가 생성하는 ApplicationContext가 root컨텍스트가 되고 DispatcherServlet이 생성한 인스턴스는 root컨텍스트를 부모로 하는 자식 컨텍스트가 된다. 참고로, 자식 컨텍스트들은 root컨텍스트의 설정 빈을 사용할 수 있다. ( 상속 )

     


    ✔️ 레이어드 아키텍쳐 실습

    1️⃣ 설정( Configuration )

    💾 WebMvcContextConfiguration.java : Spring DispatcherServlet 설정 클래스

    더보기
    package kr.or.connect.guestbook.config;
    
    import javax.sql.DataSource;
    
    import org.apache.commons.dbcp2.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;
    
    @Configuration
    @EnableTransactionManagement //트랜잭션과 관련된 설정을 자동으로 해주는 애노테이션
    public class DBConfig implements TransactionManagementConfigurer {
        private String driverClassName = "com.mysql.jdbc.Driver";
        private String url = "jdbc:mysql://localhost:3306/connectdb?useUnicode=true&characterEncoding=utf8";
        private String username = "root";
        private String password = "0000";
    
        @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 transactionManger();
        }
    
        @Bean
        public PlatformTransactionManager transactionManger() {
            return new DataSourceTransactionManager(dataSource());
        }
    }

    💾 DBConfig.java :  DB 관련 설정 클래스

    더보기
    package kr.or.connect.guestbook.config;
    
    import javax.sql.DataSource;
    
    import org.apache.commons.dbcp2.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;
    
    //DB 설정 클래스. TransactionManagementConfigurer인터페이스를 구현한다.
    @Configuration
    @EnableTransactionManagement //트랜잭션과 관련된 설정을 자동으로 해주는 애노테이션
    public class DBConfig implements TransactionManagementConfigurer {
        private String driverClassName = "com.mysql.jdbc.Driver";
        private String url = "jdbc:mysql://localhost:3306/connectdb?useUnicode=true&characterEncoding=utf8";
        private String username = "root";
        private String password = "0000";
    
        //DB연결을 위한 dataSouce 등록
        @Bean
        public DataSource dataSource() {
            BasicDataSource dataSource = new BasicDataSource();
            dataSource.setDriverClassName(driverClassName);
            dataSource.setUrl(url);
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            return dataSource;
        }
    
        // 트랜잭션을 처리할 transactionManger을 반환하는 메서드
        @Override
        public PlatformTransactionManager annotationDrivenTransactionManager() {
            return transactionManger();
        }
    
        @Bean
        public PlatformTransactionManager transactionManger() {
            return new DataSourceTransactionManager(dataSource());
        }
    }

    💾 ApplicationConfig.java :  스프링 IoC/DI 컨네이너 설정을 위한 클래스 (dao, service 컴포넌트스캔 )

    더보기
    package kr.or.connect.guestbook.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    
    @Configuration
    @ComponentScan(basePackages = { "kr.or.connect.guestbook.dao",  "kr.or.connect.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>
    		<!-- AnnotationConfigWebApplicationContext 사용 설정 -->
    		<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext
    		</param-value>
    	</context-param>
    	
    	<context-param>
    	    <!-- ApplicationConfig.java를 참고하도록  설정 -->
    		<param-name>contextConfigLocation</param-name>
    		<param-value>kr.or.connect.guestbook.config.ApplicationConfig
    		</param-value>
    	</context-param>
    	
    	<listener> 
    	    <!-- ContextLoaderListener을 통해 service layer와 repository layer을 설정!! -->
    		<!-- ContextLoaderListener는 context-param을 참고한다.-->
    		<listener-class>org.springframework.web.context.ContextLoaderListener
    		</listener-class>
    	</listener>
    
    	<servlet>
    		<servlet-name>mvc</servlet-name>
    		<!-- 모든요청을 DispatcherServlet이 받는다. (front servlet으로 등록하는 것, presentaition layer 설정!!) -->
    		<servlet-class>org.springframework.web.servlet.DispatcherServlet 
    		</servlet-class>
    		<init-param>
    			<param-name>contextClass</param-name>
    			<!-- AnnotationConfigWebApplicationContext 사용 설정 -->
    			<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext
    			</param-value>
    		</init-param>
    		<init-param>
    			<param-name>contextConfigLocation</param-name>
    			<!-- 디스패처 설정 클래스 등록. WebMvcContextConfiguration.java를 참고해서 설정해라! -->
    			<param-value>kr.or.connect.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>
    		<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>

    2️⃣ Repository Layer 구현

    💾 Guestbook.java : Guestbook DTO

    더보기
    package kr.or.connect.guestbook.dto;
    
    import java.util.Date;
    
    public class Guestbook {
        private Long id;
        private String name;
        private String content;
        private Date 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 : Log DTO

    더보기
    package kr.or.connect.guestbook.dto;
    
    import java.util.Date;
    
    public class Log {
        private Long id;
        private String ip;
        private String method;
        private Date 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 + "]";
        }
    }

    💾 GuestbookDao.java : Guestbook DAO

    더보기
    package kr.or.connect.guestbook.dao;
    
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.sql.DataSource;
    
    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 kr.or.connect.guestbook.dto.Guestbook;
    
    import static kr.or.connect.guestbook.dao.GuestbookDaoSqls.*;
    
    //DAO에는 @Respasitory 어노테이션을 붙여준다.
    @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_PAGING, 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);
            }
    }

    💾 GuestbookDaoSqls.java

    더보기
    package kr.or.connect.guestbook.dao;
    
    public class GuestbookDaoSqls {
        public static final String SELECT_PAGING = "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";
    }

    💾 LogDao.java

    더보기
    package kr.or.connect.guestbook.dao;
    
    import javax.sql.DataSource;
    
    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 kr.or.connect.guestbook.dto.Log;
    
    @Repository //dao에 붙이는 애노테이션
    public class LogDao {
        private NamedParameterJdbcTemplate jdbc;
        private SimpleJdbcInsert insertAction;
    
        public LogDao(DataSource dataSource) {
            this.jdbc = new NamedParameterJdbcTemplate(dataSource);
            this.insertAction = new SimpleJdbcInsert(dataSource)
                    .withTableName("log")
                    .usingGeneratedKeyColumns("id"); //primary key인 id 자동 입력!
        }
    
        public Long insert(Log log) {
            SqlParameterSource params = new BeanPropertySqlParameterSource(log);
            return insertAction.executeAndReturnKey(params).longValue(); // insert문을 내부적으로 생성해서 실행후 생성된 id값 반환
        }
    
    }

    3️⃣ Service Layer 구현

    💾 GuestbookService.java : 서비스 인터페이스

    더보기
    package kr.or.connect.guestbook.service;
    
    import java.util.List;
    
    import kr.or.connect.guestbook.dto.Guestbook;
    
    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 kr.or.connect.guestbook.service.impl;
    
    import java.util.Date;
    import java.util.List;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    import kr.or.connect.guestbook.dao.GuestbookDao;
    import kr.or.connect.guestbook.dao.LogDao;
    import kr.or.connect.guestbook.dto.Guestbook;
    import kr.or.connect.guestbook.dto.Log;
    import kr.or.connect.guestbook.service.GuestbookService;
    
    // Service Layer은 @Service 어노테이션을 붙인다.
    @Service
    public class GuestbookServiceImpl implements GuestbookService{
        
        //스프링 컨테이너가 알아서 guestbookDao 주입 시켜줌 
        @Autowired
        GuestbookDao guestbookDao; 
        
        // 스프링 컨테이너가 알아서 logDao 주입 시켜줌
        @Autowired
        LogDao logDao;  
    
        //게스트북 목록 가져오기
        @Override
        @Transactional //읽기만 하는 메소드는 @Transactional 어노테이션을 붙여주면 Read only 커넥션을 사용한다.
        public List<Guestbook> getGuestbooks(Integer start) {
            List<Guestbook> list = guestbookDao.selectAll(start, GuestbookService.LIMIT); //start부터 LIMIT(5개)만큼 가져온다.
            return list;
        }
    
        //게스트북 삭제
        @Override
        @Transactional(readOnly=false) //이 메소드는 ReadOnly가 아니므로 false 처리
        public int deleteGuestbook(Long id, String ip) {
            int deleteCount = guestbookDao.deleteById(id);
            Log log = new Log(); // 삭제 로그 남기기
            log.setIp(ip);
            log.setMethod("delete");
            log.setRegdate(new Date());
            logDao.insert(log);
            return deleteCount;
        }
    
        //게스트북 삽입
        @Override
        @Transactional(readOnly=false) //이 메소드는 ReadOnly가 아니므로 false 처리
        public Guestbook addGuestbook(Guestbook guestbook, String ip) {
            guestbook.setRegdate(new Date());
            Long id = guestbookDao.insert(guestbook);
            guestbook.setId(id);
            
            // 이 메서드는 @Transactional이 붙어있다. 즉! 이 메서드는 트랜잭션이다. 즉! 이 메서드 전체 로직은 나눌 수 없는 하나의 단위이다. 
            // 이 메서드의 모든 부분이 성공해야 DB에 반영이된다. 
            // 아래는 트랜잭션을 배우기 위한 강제 예외처리 코드 ( insert 이후 강제로 예외가 발생하게 한다. 윗 라인에서 insert가 정상적으로 될지라도 이 메서드는 트랜잭션이므로 DB에 반영이 안됨 즉, rollbakc 된다.)
            
            /*
            if(1 == 1)        
                throw new RuntimeException("test exception");
              */
            
            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();
        }
        
        
    }

    4️⃣ Persentation Layer 구현 ( Controller 구현)

    💾 GuestbookController.java

    더보기
    package kr.or.connect.guestbook.controller;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.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 kr.or.connect.guestbook.dto.Guestbook;
    import kr.or.connect.guestbook.service.GuestbookService;
    
    //컨트롤러 클래스
    @Controller
    public class GuestbookController {
      
        //서비스 객체 주입
        @Autowired
        GuestbookService guestbookService;
        
        //@PathVariable은 URL경로에 변수를 넣는것 이다. RESTful API에서 사용한다. ex)127.0.0.1:8080/abcd/abcd
        //@RequestParam은 URL 파라미터로 값을 넘기는 방식이다. ex)127.0.0.1:8080?a=b&c=d
         
        @GetMapping(path="/list")         // 페이지네이션을 위한 strat 파라미터. 디폴트 값은 0.
        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?start=0 , list?start=5, list?start=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";
        }
        
        @PostMapping(path="/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";
        }
    }

    💾 list.jsp : View 

    더보기
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    	pageEncoding="UTF-8"%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>방명록 목록</title>
    </head>
    <body>
    
    	<h1>방명록</h1>
    	<br> 방명록 전체 수 : ${count }
    	<br>
    	<br>
    
    	<c:forEach items="${list}" var="guestbook">
    
    	${guestbook.id }<br>
    	${guestbook.name }<br>
    	${guestbook.content }<br>
    	${guestbook.regdate }<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>

    👀 결과

     

    ✔️ Rest Controller

    ◾ Spring 4에서 Rest API 또는 Web API를 개발하기 위해 등장한 어노테이션

    ◾ MessageConvertor는 외부에서 전달받은 JSON을 자바 내부에서 사용할 수 있는 객체로 변환해주거나, 컨트롤러에서 리턴한 객체를 JSON으로 변환해서 클라이언트에 전달하는 역할을 한다. ( JSON으로 변환하기 위해서는 jackson 라이브러리가 필요하다. )

     

    💾 GuestbookApiController.java

    더보기
    package kr.or.connect.guestbook.controller;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.DeleteMapping;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    import kr.or.connect.guestbook.dto.Guestbook;
    import kr.or.connect.guestbook.service.GuestbookService;
    
    //Rest 컨트롤러이다.
    @RestController
    @RequestMapping(path="/guestbooks") // 이렇게 해주면 잎에 "/guestbooks"을 붙여야함.
    public class GuestbookApiController {
      
        // 서비스 객체 주입
        @Autowired
        GuestbookService guestbookService;
        
        // guest book 리스트 반환
        // 반환형이 Map 객체이다. 디스패처 서블릿은 json 메세지 컨버터를 내부적으로 사용해서 List Map객체를 json으로 변환해서 전송한다. 
        @GetMapping
        public Map<String, Object> list(@RequestParam(name="start", required=false, defaultValue="0") int start) {
            
            List<Guestbook> list = guestbookService.getGuestbooks(start);
            
            int count = guestbookService.getCount();
            int pageCount = count / GuestbookService.LIMIT;
            if(count % GuestbookService.LIMIT > 0)
                pageCount++;
            
            List<Integer> pageStartList = new ArrayList<>();
            for(int i = 0; i < pageCount; i++) {
                pageStartList.add(i * GuestbookService.LIMIT);
            }
            
            Map<String, Object> map = new HashMap<>(); // json으로 만들 데이터를 map 만들어준다.
            map.put("list", list);
            map.put("count", count);
            map.put("pageStartList", pageStartList);
            
            return map;
        }
        
        // 여기도 Guestbook 객체로 반환되는데, 이를 디스패처 서블릿이 json 메세지 컨버터를 내부적으로 사용해서 json으로 변환해서 전송한다. 
        // geuest book 작성
        @PostMapping
        public Guestbook write(@RequestBody Guestbook guestbook,
                            HttpServletRequest request) {
            String clientIp = request.getRemoteAddr();
            // id가 입력된 guestbook이 반환된다.
            Guestbook resultGuestbook = guestbookService.addGuestbook(guestbook, clientIp);
            return resultGuestbook;
        }
        
        
        //guest book 삭제
        @DeleteMapping("/{id}")
      //결과 정보를 담은  map 객체로 반환되는데,이를 디스패처 서블릿이 json 메세지 컨버터를 내부적으로 사용해서 json으로 변환해서 전송한다. 
        public Map<String, String> delete(@PathVariable(name="id") Long id,
                HttpServletRequest request) {
            String clientIp = request.getRemoteAddr();
            
            int deleteCount = guestbookService.deleteGuestbook(id, clientIp);
            return Collections.singletonMap("success", deleteCount > 0 ? "true" : "false"); 
        }
    }

    👀 GET 결과

    get

    👀 POST 결과

    post

    👀 DELETE 결과

    delete

     

    'Web Development > 부스트코스 - Back-End(Java)' 카테고리의 다른 글

    [Back End] 상태유지기술 - Session  (1) 2020.05.18
    [Back End] 상태유지기술 - Cookie  (0) 2020.05.11
    [Back End] Spring MVC  (0) 2020.04.03
    [Back End] Spring JDBC  (0) 2020.03.30
    [Back End] Spring Core  (0) 2020.03.25

    댓글

Designed by Tistory.