<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>SH's 개발일기</title>
    <link>https://shprogramming.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 12 Jun 2026 04:03:07 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>shb</managingEditor>
    <item>
      <title>[SpringBoot] Security - 회원가입 DB 인증</title>
      <link>https://shprogramming.tistory.com/147</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회원&amp;nbsp;가입하기 &lt;br /&gt;회원가입&amp;nbsp;&amp;rarr;&amp;nbsp;DB저장&amp;nbsp;&amp;rarr;&amp;nbsp;인증 &lt;br /&gt;스프링&amp;nbsp;시큐리티에선&amp;nbsp; &lt;br /&gt;1.&amp;nbsp;하나의&amp;nbsp;계정에&amp;nbsp;복수개의&amp;nbsp;권한&amp;nbsp;부여&amp;nbsp;가능.&amp;nbsp;즉!&amp;nbsp;계정:권한&amp;nbsp;=&amp;gt;&amp;nbsp;1:N&amp;nbsp;관계 &lt;br /&gt;2.&amp;nbsp;DB&amp;nbsp;저장시&amp;nbsp;password&amp;nbsp;는&amp;nbsp;반드시&amp;nbsp;&amp;lsquo;암호화&amp;rsquo;&amp;nbsp;해서&amp;nbsp;저장되어야&amp;nbsp;한다 &lt;br /&gt;a.&amp;nbsp;암호화는&amp;nbsp;스프링&amp;nbsp;시큐리티의&amp;nbsp;PasswordEncoder&amp;nbsp;사용 &lt;br /&gt;3.&amp;nbsp;인증을&amp;nbsp;위해&amp;nbsp;DB&amp;nbsp;조회시&amp;nbsp;필요한&amp;nbsp;&amp;lsquo;동작&amp;rsquo;들이&amp;nbsp;제공되어야&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;*&amp;nbsp;DB&amp;nbsp;테이블&amp;nbsp;작성&amp;nbsp;&lt;/b&gt; &lt;br /&gt;[회원계정]&amp;nbsp;1:n&amp;nbsp;[권한] &lt;br /&gt;ERD&amp;nbsp;폴더와&amp;nbsp;sql&amp;nbsp;생성 &lt;br /&gt;&lt;br /&gt;&lt;b&gt;*&amp;nbsp;UserDTO&amp;nbsp;생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* DummyData 만들기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 로그인 폼에 회원가입 폼 링크추가 &lt;br /&gt;loginForm.jsp&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* /join 핸들러 추가 &lt;br /&gt;IndexController.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;    @GetMapping(&quot;/join&quot;)
    public String join() {
        return &quot;joinForm&quot;;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;회원가입&amp;nbsp;폼&amp;nbsp;작성 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;{view_root}/joinForm.jsp&amp;nbsp;&amp;nbsp;생성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot;
    pageEncoding=&quot;UTF-8&quot;%&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
&amp;lt;title&amp;gt;회원가입 페이지&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;회원가입 페이지&amp;lt;/h1&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;form action=&quot;/joinOk&quot; method=&quot;POST&quot;&amp;gt;
    &amp;lt;input type=&quot;text&quot; name=&quot;id&quot; placeholder=&quot;아이디 입력&quot;/&amp;gt;&amp;lt;br&amp;gt;
    &amp;lt;input type=&quot;password&quot; name=&quot;pw&quot; placeholder=&quot;패스워드 입력&quot;/&amp;gt;&amp;lt;br&amp;gt;
    &amp;lt;input type=&quot;email&quot; name=&quot;email&quot; placeholder=&quot;이메일 입력&quot;/&amp;gt;&amp;lt;br&amp;gt;
    &amp;lt;input type=&quot;submit&quot; value=&quot;회원가입&quot;&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* /joinOk 핸들러 추가&lt;/b&gt; &lt;br /&gt;IndexController.java&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;    @PostMapping(&quot;/joinOk&quot;)
    public String joinOk(UserDTO user) {
        System.out.println(&quot;/joinOk: &quot; + user);

        // password 는 암호화 하여 저장
        String rawPassword = user.getPw();
        String encPassword = passwordEncoder.encode(rawPassword);
        user.setPw(encPassword);
        
        userService.addMember(user);
        
        return &quot;redirect:/login&quot;;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* UserDAO.java 작성&lt;/b&gt; &lt;br /&gt;{base-pacakge}/domain &lt;br /&gt;인터페이스&amp;nbsp;작성&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package com.lec.spring.domain;

import java.util.List;

public interface UserDAO {
    // 사용자(user) 추가
	int addUser(UserDTO user);
	  
    // 특정 id(username)의 사용자 에 권한(auth) 추가
	int addAuth(String id, String auth);
   
    // 사용자(user) 삭제
	int deleteUser(UserDTO user);
   
    // 특정 id(username)의 사용자의 특정 권한(auth) 삭제
	int deleteAuth(String id, String auth);
   
    // 특정 id(username)의 사용자의 권한(들) 전부 삭제
	int deleteAuths(String id);
   
    // 특정 id(username)의 사용자 조회
	UserDTO findById(String id);
   
    // 특정 id(username)의 사용자의 권한(들) 뽑기
	List&amp;lt;String&amp;gt; selectAuthoritiesById(String id);
	
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* UserDAO.xml&amp;nbsp;&lt;/b&gt; &lt;br /&gt;MyBatis&amp;nbsp;매퍼&amp;nbsp;파일 &lt;br /&gt;{base-pacakge}/domain&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&amp;gt;
&amp;lt;mapper namespace=&quot;com.lec.spring.domain.UserDAO&quot;&amp;gt;

	&amp;lt;insert id=&quot;addUser&quot; parameterType=&quot;com.lec.spring.domain.UserDTO&quot;
		flushCache=&quot;true&quot;&amp;gt;
		INSERT INTO test_member2(mb_id, mb_pw, mb_email)
		VALUES(#{id}, #{pw}, #{email})
	&amp;lt;/insert&amp;gt;
	
	&amp;lt;insert id=&quot;addAuth&quot; flushCache=&quot;true&quot;&amp;gt;
		INSERT INTO test_authority2
		VALUES(#{param1}, #{param2})
	&amp;lt;/insert&amp;gt;
	
	&amp;lt;delete id=&quot;deleteUser&quot; parameterType=&quot;com.lec.spring.domain.UserDTO&quot;
		flushCache=&quot;true&quot;&amp;gt;
		DELETE FROM test_member2
		WHERE mb_id = #{id}
	&amp;lt;/delete&amp;gt;
	
	&amp;lt;delete id=&quot;deleteAuth&quot; flushCache=&quot;true&quot;&amp;gt;
		DELETE FROM test_authority2
		WHERE mb_id = #{param1} AND mb_auth = #{param2}
	&amp;lt;/delete&amp;gt;
	
	&amp;lt;delete id=&quot;deleteAuths&quot; flushCache=&quot;true&quot;&amp;gt;
		DELETE FROM test_authority2
		WHERE md_id = #{param1}
	&amp;lt;/delete&amp;gt;
	
	&amp;lt;select id=&quot;findById&quot; resultType=&quot;com.lec.spring.domain.UserDTO&quot;&amp;gt;
	   SELECT
            mb_uid uid,
            mb_id id,
            mb_pw pw,
            mb_email email,
            mb_enabled enabled,
            mb_regdate regdate
        FROM test_member2
        WHERE mb_id = #{id} 
	&amp;lt;/select&amp;gt;
	
	&amp;lt;select id=&quot;selectAuthoritiesById&quot; resultType=&quot;String&quot;&amp;gt;
		SELECT mb_auth
		FROM test_authority2
		WHERE mb_id = #{id}		
	&amp;lt;/select&amp;gt;
	
&amp;lt;/mapper&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* UserDAOImpl.java 작성&lt;/b&gt; &lt;br /&gt;{base-pacakge}/domain&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;package com.lec.spring.domain;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class UserDAOImpl implements UserDAO {

	private UserDAO mapper;
	
	@Autowired
	public UserDAOImpl(SqlSession sqlSession) {
		mapper = sqlSession.getMapper(UserDAO.class);
	}
	
	
	@Override
	public int addUser(UserDTO user) {
		return mapper.addUser(user);
	}

	@Override
	public int addAuth(String id, String auth) {
		return mapper.addAuth(id, auth);
	}

	@Override
	public int deleteUser(UserDTO user) {
		return mapper.deleteUser(user);
	}

	@Override
	public int deleteAuth(String id, String auth) {
		return mapper.deleteAuth(id, auth);
	}

	@Override
	public int deleteAuths(String id) {
		return mapper.deleteAuths(id);
	}

	@Override
	public UserDTO findById(String id) {
		return mapper.findById(id);
	}

	@Override
	public List&amp;lt;String&amp;gt; selectAuthoritiesById(String id) {
		return mapper.selectAuthoritiesById(id);
	}

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* UserService.java 작성&lt;/b&gt; &lt;br /&gt;{base-pacakge}/service&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;package com.lec.spring.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.lec.spring.domain.UserDAO;
import com.lec.spring.domain.UserDTO;

@Service
public class UserService {

	@Autowired
	UserDAO dao;
	
    // 회원가입
    // ROLE_MEMBER 권한 부여
	@Transactional
	public int addMember(UserDTO user) {
		int cnt = dao.addUser(user);
		dao.addAuth(user.getId(), &quot;ROLE_MEMBER&quot;);
		return cnt;
	}

    // 회원삭제
	@Transactional
	public int deleteMember(UserDTO user) {
		dao.deleteAuths(user.getId());   // 권한(들) 먼저 삭제  (차라리 DDL 에 ON DELETE CASCADE 하자..)
		int cnt = dao.deleteUser(user);
		return cnt;
	}

    // 특정 id(username) 의 정보 가져오기
	public UserDTO findById(String id) {
		return dao.findById(id);
	}

    // 특정 id 의 권한(들) 정보 가져오기
	public List&amp;lt;String&amp;gt; selectAuthoritiesById(String id){
		return dao.selectAuthoritiesById(id);
	}
	
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* PasswordEncoder&amp;nbsp;&lt;/b&gt; &lt;br /&gt;스프링&amp;nbsp;시큐리티는&amp;nbsp;기본적으로&amp;nbsp;PasswordEncoder&amp;nbsp;객체를&amp;nbsp;통해&amp;nbsp;&amp;lsquo;password&amp;rsquo;&amp;nbsp;는&amp;nbsp;반.드.시&amp;nbsp;&amp;nbsp;암호화&amp;nbsp;하여&amp;nbsp;다루도록&amp;nbsp;되어&amp;nbsp;있다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public class SecurityConfig extends WebSecurityConfigurerAdapter{
	
    // PasswordEncoder 를 bean 으로 IoC 에 등록
    // IoC 에 등록된다, IoC 내에선 어디서든 가져다가 사용할수 있다.
	@Bean
	public PasswordEncoder encoder() {
		return new BCryptPasswordEncoder();
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* IndexController 수정&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;@Controller
public class IndexController {
	
	@Autowired
	UserService userService;
	
	@Autowired
	private PasswordEncoder passwordEncoder;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 로그인 진행, DB를 통한 인증&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UserDetails&amp;nbsp;작성&amp;nbsp; &lt;br /&gt;&lt;/b&gt;{bast-package}/config/&lt;b&gt;PrincipalDetails.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;package com.lec.spring.config;

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

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.lec.spring.domain.UserDTO;
import com.lec.spring.service.UserService;

//시큐리티가 /loginOk 주소요청이 오면 낚아채서 로그인을 진행시킨다.
//로그인 진행이 완료되면 '시큐리티 session' 에 넣어주게 된다. 
//우리가 익히 알고 있는 같은 session 공간이긴 한데..
//시큐리티가 자신이 사용하기 위한 공간을 가집니다. 
//=&amp;gt; Security ContextHolder 라는 키값에다가 session 정보를 저장합니다.
//여기에 들어갈수 있는 객체는 Authentication 객체이어야 한다.
//Authentication 안에 User 정보가 있어야 됨. 
//User 정보 객체는 ==&amp;gt; UserDetails 타입 객체이어야 한다.

//따라서 로그인한 User 정보를 꺼내려면
//Security Session 에서 
// =&amp;gt; Authentication 객체를 꺼내고, 그 안에서
//      =&amp;gt; UserDetails 정보를 꺼내면 된다.
public class PrincipalDetails implements UserDetails {

	private UserService userService;
	
	public void setUserService(UserService userService) {
		this.userService = userService;
	}
	
	private UserDTO user;
	public UserDTO getUser() {
		return user;
	}
	
	
	public PrincipalDetails(UserDTO user) {
		System.out.println(&quot;PrincipalDetails(user) 생성: &quot; + user);
		this.user = user;
	}
	

    // 해당 User 의 '권한(들)'을 리턴
    // 현재 로그인한 사용자의 권한정보가 필요할때마다 호출된다. 혹은 필요할때마다 직접 호출해 사용할수도 있다
	@Override
	public Collection&amp;lt;? extends GrantedAuthority&amp;gt; getAuthorities() {
		System.out.println(&quot;getAuthorities() 호출&quot;);
		Collection&amp;lt;GrantedAuthority&amp;gt; collect = new ArrayList&amp;lt;&amp;gt;();
		
		List&amp;lt;String&amp;gt; list = userService.selectAuthoritiesById(user.getId());  // DB 에서 권한들 읽어옴.
		
		for(String auth : list) {
			collect.add(new GrantedAuthority() {
				
				@Override
				public String getAuthority() {					
					return auth;
				}
				
				@Override
				public String toString() {				
					return auth;
				}
			});
		}
	
		
		return collect;
	}

	// 로그인 한 사용자의 password 는?
	@Override
	public String getPassword() {		
		return user.getPw();
	}

	// 로그인 한 사용자의 username 은?
	@Override
	public String getUsername() {
		return user.getId();
	}

	// 계정이 만료된건 아닌지?
	@Override
	public boolean isAccountNonExpired() {
		return true;
	}

	// 계정이 잠긴건 아니지?
	@Override
	public boolean isAccountNonLocked() {
		return true;
	}

	// 계정 credential 이 만료된건 아니지?
	@Override
	public boolean isCredentialsNonExpired() {
		return true;
	}

	// 활성화 되었니?
	@Override
	public boolean isEnabled() {
		if(&quot;1&quot;.equals(user.getEnabled())) return true;
		return false;
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;* UserDetailsService 생성&lt;/b&gt; &lt;br /&gt;{base-package}/config/&lt;b&gt;PrincipalDetailsService.java&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package com.lec.spring.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import com.lec.spring.domain.UserDTO;
import com.lec.spring.service.UserService;

//UserDetailsService
//컨테이너에 등록한다.
//시큐리티 설정에서 loginProcessingUrl(url) 로 걸어 놓았기 때문에
//로그인시 위 url 로 요청이 오면 자동으로 UserDetailsService 타입으로 IoC 되어 있는
//loadUserByUsername() 가 실행되고
//인증성공하면 결과를 UserDetails 로 리턴
@Service
public class PrincipalDetailsService implements UserDetailsService {

	@Autowired
	private UserService userService;
	
	
    // UserDetails 를 리턴한다,  --&amp;gt; 누구한테 리턴하나?
    // 시큐리티 session ( &amp;lt;= Authentication( &amp;lt;= 리턴된 UserDetails ) )a
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		System.out.println(&quot;loadUserByUsername(&quot; + username + &quot;)&quot;);
		
		UserDTO user = userService.findById(username);  // DB 조회 
		
		// 해당 username 의 user 가 DB 에 있었다면
		// UserDetails 을 생성하여 리턴
		if(user != null) {
			PrincipalDetails details = new PrincipalDetails(user);
			details.setUserService(userService);
			return details;
		}
		
        // 해당 username 의 user 가 없다면!
        // UsernameNotFoundException을 throw 해주도록 한다
		throw new UsernameNotFoundException(username);
	}

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sesstion&amp;nbsp;attribute&amp;nbsp;에&amp;nbsp;SPRING_SECURITY_CONTEXT&amp;nbsp;라는&amp;nbsp;이름으로&amp;nbsp;현재&amp;nbsp;로그인&amp;nbsp;정보가&amp;nbsp;담겨&amp;nbsp;있다. &lt;br /&gt;그리고&amp;nbsp;그&amp;nbsp;정보는&amp;nbsp;Authentication&amp;nbsp;객체다. &lt;/p&gt;</description>
      <category>Spring</category>
      <author>shb</author>
      <guid isPermaLink="true">https://shprogramming.tistory.com/147</guid>
      <comments>https://shprogramming.tistory.com/147#entry147comment</comments>
      <pubDate>Mon, 16 May 2022 16:37:08 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] Security</title>
      <link>https://shprogramming.tistory.com/146</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Authentication&amp;nbsp;(인증)&amp;nbsp;&amp;nbsp;&amp;nbsp;/&amp;nbsp;&amp;nbsp;Authorization&amp;nbsp;(인가)&lt;/b&gt; &lt;br /&gt;&lt;b&gt;인증(Authentication)&amp;nbsp;:&amp;nbsp;자신을&amp;nbsp;증명&lt;/b&gt; &lt;br /&gt;-&amp;nbsp;내가&amp;nbsp;(스스로)&amp;nbsp;자신을&amp;nbsp;증명할&amp;nbsp;만한&amp;nbsp;자료를&amp;nbsp;제시&amp;nbsp;하는&amp;nbsp;것. &lt;br /&gt;-&amp;nbsp;시스템&amp;nbsp;접근&amp;nbsp;시,&amp;nbsp;등록된&amp;nbsp;사용자인지&amp;nbsp;여부를&amp;nbsp;확인하는&amp;nbsp;것 &lt;br /&gt;-&amp;nbsp;로그인 &lt;br /&gt;&lt;br /&gt;&lt;b&gt;인가(Authorization)&amp;nbsp;:&amp;nbsp;권한&amp;nbsp;부여&lt;/b&gt; &lt;br /&gt;-&amp;nbsp;접근&amp;nbsp;후,&amp;nbsp;인증된&amp;nbsp;사용자에게&amp;nbsp;권한을&amp;nbsp;부여하는&amp;nbsp;것 &lt;br /&gt;-&amp;nbsp;남에&amp;nbsp;의해서&amp;nbsp;&amp;lsquo;자격&amp;rsquo;&amp;nbsp;이&amp;nbsp;부여된것. &lt;br /&gt;-&amp;nbsp;권한에따라&amp;nbsp;사용&amp;nbsp;가능한&amp;nbsp;기능이&amp;nbsp;제한됨 &lt;br /&gt;-&amp;nbsp;사용자&amp;nbsp;등급(ex:&amp;nbsp;일반/VIP/관리자)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버로 들어오는 request를 낚아챌 수단&lt;/b&gt;이 필요하다. &lt;br /&gt;&amp;lsquo;request&amp;rsquo;&amp;nbsp;를&amp;nbsp;처리하기&amp;nbsp;전에&amp;nbsp;&lt;b&gt;보안체크&lt;/b&gt;부터&amp;nbsp;해야&amp;nbsp;한다.&amp;nbsp;&amp;nbsp;검문소&amp;nbsp;역할을&amp;nbsp;해야&amp;nbsp;하는데 &lt;br /&gt;스프링&amp;nbsp;시큐리티는&amp;nbsp;서블릿&amp;nbsp;프로그래밍의&amp;nbsp;&lt;b&gt;filter&amp;nbsp;객체를&amp;nbsp;사용&lt;/b&gt;하여&amp;nbsp;이를&amp;nbsp;구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;filter&amp;nbsp;&lt;/b&gt; &lt;br /&gt;서블릿&amp;nbsp;2.3&amp;nbsp;부터&amp;nbsp;도입 &lt;br /&gt;'HTTP&amp;nbsp;요청과&amp;nbsp;응답을&amp;nbsp;변경할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;재사용가능한&amp;nbsp;코드'이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필터는 &amp;lsquo;객체의 형태&amp;rsquo;로 존재하며 클라이언트로부터 오는 요청(request)과 최종 자원(서블릿/JSP/기타 문서) 사이에 위치하여 클라이언트의 요청 정보를 알맞게 변경할 수 있으며, 또한 필터는 최종 자원과 클라이언트로 가는 응답(response) 사이에 위치하여 최종 자원의 요청 결과를 알맞게 변경할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Security&amp;nbsp;Filter&amp;nbsp;Chain&lt;/b&gt; &lt;br /&gt;스프링&amp;nbsp;시큐리티도&amp;nbsp;서블릿&amp;nbsp;필터를&amp;nbsp;사용한다. &lt;br /&gt;Security와 관련한 서블릿 필터도 실제로는 연결된 여러 필터들로 구성 되어 있다. 이러한 모습때문에 Chain(체인)이라 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* IndexController 작성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;package com.lec.spring.controller;

import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.lec.spring.domain.UserDTO;
import com.lec.spring.service.UserService;

@Controller
public class IndexController {
	
	@Autowired
	UserService userService;
	
	@Autowired
	private PasswordEncoder passwordEncoder;
	

	@RequestMapping({&quot;&quot;, &quot;/&quot;})
	@ResponseBody
	public String sayHello() {
		return &quot;&amp;lt;h2&amp;gt;/ : Hello&amp;lt;/h2&amp;gt;&quot;;
	}
	
	// Spring Security(이하 '시큐리티') 가 적용되면
	// /login 등의 url 로의 request 를  시큐리티가 모두 낚아 챕니다.
	// ※ 나중에 SecurityConfig 가 설정되면 낚아 채지 않게 된다.
	@GetMapping(&quot;/login&quot;)
//	@ResponseBody
	public String login() {
	    System.out.println(&quot;GET: /login&quot;);
//	    return &quot;&amp;lt;h2&amp;gt; /login : login 페이지&amp;lt;/h2&amp;gt;&quot;;
	    return &quot;loginForm&quot;;
	}
	
	@PostMapping(&quot;/login&quot;)
	public String loginFail() {
		System.out.println(&quot;POST: /login&quot;);
		return &quot;loginForm&quot;;
	}
	

	//  현재 로그인한 정보 Authentication 보기
	@RequestMapping(&quot;/auth&quot;)
	@ResponseBody
	public Authentication auth(HttpSession session) {
		return SecurityContextHolder.getContext().getAuthentication();
		//시큐리티가 /loginOk 주소요청이 오면 낚아채서 로그인을 진행시킨다.
		//로그인 진행이 완료되면 '시큐리티 session' 에 넣어주게 된다. 
		//우리가 익히 알고 있는 같은 session 공간이긴 한데..
		//시큐리티가 자신이 사용하기 위한 공간을 가집니다. 
		//=&amp;gt; Security ContextHolder 라는 키값에다가 session 정보를 저장합니다.
		//여기에 들어갈수 있는 객체는 Authentication 객체이어야 한다.
		//Authentication 안에 User 정보가 있어야 됨. 
		//User 정보 객체는 ==&amp;gt; UserDetails 타입 객체이어야 한다.

	}
	
    @GetMapping(&quot;/join&quot;)
    public String join() {
        return &quot;joinForm&quot;;
    }
	
    @PostMapping(&quot;/joinOk&quot;)
    public String joinOk(UserDTO user) {
        System.out.println(&quot;/joinOk: &quot; + user);

        // password 는 암호화 하여 저장
        String rawPassword = user.getPw();
        String encPassword = passwordEncoder.encode(rawPassword);
        user.setPw(encPassword);
        
        userService.addMember(user);
        
        return &quot;redirect:/login&quot;;
    }
    
    @RequestMapping(&quot;/accessError&quot;)
    public void accessError() {}

	
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* SampleController.java 작성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;package com.lec.spring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping(&quot;/sample/*&quot;)
public class SampleController {
	
	@GetMapping(&quot;/all/**&quot;)
	@ResponseBody
	public String doAll() {
		return &quot;&amp;lt;h1&amp;gt;/all/** : 누구나 접근 가능&amp;lt;/h1&amp;gt;&quot;;
	}
	
    @GetMapping(&quot;/user/**&quot;)
    @ResponseBody
    public String doUser() {
        return &quot;&amp;lt;h1&amp;gt;/user/** : 로그인한 사람이면 접근 가능&amp;lt;/h1&amp;gt;&quot;;
    }

    @GetMapping(&quot;/member/**&quot;)
    @ResponseBody
    public String doManager() {
        return &quot;&amp;lt;h1&amp;gt;/member/** : 로그인한 사람중 'ROLE_MEMBER' 나 'ROLE_ADMIN' 권한만 접근 가능&amp;lt;/h1&amp;gt;&quot;;
    }

    @GetMapping(&quot;/admin/**&quot;)
    @ResponseBody
    public String doAdmin() {
        return &quot;&amp;lt;h1&amp;gt;/admin/** : 로그인 한 사람중 'ROLE_ADMIN' 권한가진 사람만 접근 가능&amp;lt;/h1&amp;gt;&quot;;
    }

	
} // end controller&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* Spring Security Starter 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Spring Security (이하 &amp;lsquo;시큐리티&amp;rsquo;)가 적용되면,&amp;nbsp;&amp;nbsp;Security Filter Chain 이 가동된다. &lt;br /&gt;(기본적으로)&amp;nbsp;모~&amp;nbsp;든&amp;nbsp;페이지가&amp;nbsp;&amp;lsquo;인증&amp;rsquo;&amp;nbsp;이&amp;nbsp;필요한&amp;nbsp;페이지로&amp;nbsp;동작(redirect)하게&amp;nbsp;됩니다.&amp;nbsp; &lt;br /&gt;즉,&amp;nbsp;어떠한&amp;nbsp;url&amp;nbsp;로의&amp;nbsp;request&amp;nbsp;접근도&amp;nbsp;&amp;lsquo;시큐리티&amp;rsquo;&amp;nbsp;가&amp;nbsp;낚아챕니다.&amp;nbsp;(intercept)&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* SecurityConfig 생성&lt;/b&gt; &lt;br /&gt;{base-package}/config/ &lt;br /&gt;SecurityConfig.java&amp;nbsp;생성 &lt;br /&gt;스프링&amp;nbsp;시큐리티에&amp;nbsp;대한&amp;nbsp;설정들이&amp;nbsp;이&amp;nbsp;안에서&amp;nbsp;메소드&amp;nbsp;체이닝으로&amp;nbsp;설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;// 스프링 시큐리티 설정
@Configuration
@EnableWebSecurity  // Web Security 를 활성화 해준다.
					// 아래 스프링 시큐리티 필터가 스프링 필터 체인에 등록이 된다.
			// &amp;darr; 등록될 필터 객체
public class SecurityConfig extends WebSecurityConfigurerAdapter{
	
    // PasswordEncoder 를 bean 으로 IoC 에 등록
    // IoC 에 등록된다, IoC 내에선 어디서든 가져다가 사용할수 있다.
	@Bean
	public PasswordEncoder encoder() {
		return new BCryptPasswordEncoder();
	}
	
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.csrf().disable();    // CSRF 비활성화  (Cross Site Request Forgery)&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;권한&amp;rsquo;&amp;nbsp;기능을&amp;nbsp;부여해서 &lt;br /&gt;&lt;b&gt;/sample/user/**&amp;nbsp;&lt;/b&gt;는&amp;nbsp;&amp;larr;&amp;nbsp;&lt;b&gt;로그인&lt;/b&gt;한&amp;nbsp;사람만 &lt;br /&gt;&lt;b&gt;/sample/member/**&lt;/b&gt;&amp;nbsp;는&amp;nbsp;&amp;larr;&amp;nbsp;&lt;b&gt;회원&lt;/b&gt;만 &lt;br /&gt;&lt;b&gt;/sample/admin/**&lt;/b&gt;&amp;nbsp;은&amp;nbsp;&amp;larr;&amp;nbsp;&lt;b&gt;관리자&lt;/b&gt;만&amp;nbsp; &lt;br /&gt;접근할수&amp;nbsp;있게&amp;nbsp;설정 &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 접근 권한 세팅 진행&lt;/b&gt;&lt;br /&gt;스프링&amp;nbsp;시큐리티에서&amp;nbsp;사용하는&amp;nbsp;권한명은&amp;nbsp;&amp;lsquo;ROLE_&amp;rsquo;&amp;nbsp;로&amp;nbsp;시작하는&amp;nbsp;접두어를&amp;nbsp;갖고&amp;nbsp;동작하도록&amp;nbsp;기본&amp;nbsp;설정되어&amp;nbsp;있습니다.&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.csrf().disable();    // CSRF 비활성화  (Cross Site Request Forgery)
		
		http  // HttpSecurity 객체
			
			/**********************************************
			 * ① request URL 에 대한 접근 권한 세팅
		     * ExpressionInterceptUrlRegistry 객체 리턴.  이하 이 객체의 메소드체이닝
		     **********************************************/
			.authorizeRequests()
		
			// URL 과 접근권한 세팅(들)
	        // &amp;darr; /sample/user/** 주소로 들어오는 요청은 '인증'만 필요.
			.antMatchers(&quot;/sample/user/**&quot;).authenticated()
	        // &amp;darr; /sample/member/** 주소로 들어오는 요청은 '인증' 뿐 아니라 ROLE_MEMBER 나 ROLE_ADMIN 권한을 갖고 있어야 한다 ('인가')
	        .antMatchers(&quot;/sample/member/**&quot;).access(&quot;hasRole('ROLE_MEMBER') or hasRole('ROLE_ADMIN')&quot;)	                
	        // &amp;darr; /sample/admin/**  주소로 들어오는 요청은 '인증' 뿐 아니라 ROLE_ADMIN 권한을 갖고 있어야 한다 ('인가')
			.antMatchers(&quot;/sample/admin/**&quot;).access(&quot;hasRole('ROLE_ADMIN')&quot;)
			// &amp;darr; 그 밖의 다른 요청은 모두 permit! (위 주소들만 아니면 누구나 접근 가능!)
			.anyRequest().permitAll()&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;로그인&amp;nbsp;설정,&amp;nbsp;커스텀&amp;nbsp;로그인&amp;nbsp;페이지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;.formLogin() &lt;br /&gt;SecurityConfig.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;			.and()   // 다시, HttpSecurity 리턴,  다른 세팅 전환시 호출
			
			/******************************************** 
			 * ② 폼 로그인 설정
			 * FormLoginConfigurer&amp;lt;HttpSecurity&amp;gt; 리턴.  이하 이 객체의 메소드체이닝
			 ********************************************/
			.formLogin()  // form 기반 인증 페이지 활성화.  만약 .loginPage(url) 가 세팅되어 있지 않으면 '디폴트 로그인' form 페이지가 활성화 된다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;.loginPage(url)&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;로그인&amp;nbsp;form&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;			.loginPage(&quot;/login&quot;)  // 로그인 필요한 상황 발생시 매개변수의 url (로그인 폼) 로 redirect 발생&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 동작확인 &lt;br /&gt;&lt;b&gt;접근권한 오류시 /login으로 이동&lt;/b&gt;하게 되고 아까와&amp;nbsp;달리&amp;nbsp;달리&amp;nbsp;우리가&amp;nbsp;제공한&amp;nbsp;&lt;b&gt;로그인&amp;nbsp;페이지&lt;/b&gt;가&amp;nbsp;보인다. &lt;br /&gt;이제&amp;nbsp;view&amp;nbsp;로&amp;nbsp;form을&amp;nbsp;보여주기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 로그인시 입력한 username, password 처리하기&lt;/b&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SecurityConfig.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;			// 로그인 처리
			.loginProcessingUrl(&quot;/loginOk&quot;)  // &quot;/loginOk&quot; url 로 POST request 가 들어오면 시큐리티가 낚아채서 처리, 대신 로그인을 진행해준다.
										// 이와 같이 하면 Controller 에서 /longinOk 를 만들지 않아도 된다!
			.defaultSuccessUrl(&quot;/&quot;)   // '직접 /login' &amp;rarr; /loginOk 에서 성공하면 &quot;/&quot; 로 이동시키기
		      			// 만약 다른 특정페이지에 진입하려다 로그인 하여 성공하면 해당 페이지로 이동 (너무 편리!)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IndexController.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;	@GetMapping(&quot;/login&quot;)
//	@ResponseBody
	public String login() {
	    System.out.println(&quot;GET: /login&quot;);
//	    return &quot;&amp;lt;h2&amp;gt; /login : login 페이지&amp;lt;/h2&amp;gt;&quot;;
	    return &quot;loginForm&quot;;
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;loginForm.jsp&amp;nbsp;작성 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;{view_root}/loginForm.jsp&lt;/b&gt; &lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot;
    pageEncoding=&quot;UTF-8&quot;%&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
&amp;lt;title&amp;gt;로그인 페이지&amp;lt;/title&amp;gt;
&amp;lt;style&amp;gt;
.error {color: red}
&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;로그인 페이지&amp;lt;/h1&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;!-- loginProcessingUrl() 로 세팅한 url, 반드시 POST! --&amp;gt;
&amp;lt;form action=&quot;/loginOk&quot; method=&quot;POST&quot;&amp;gt;  &amp;lt;!-- 시큐리티로 /loginOk 을 낚아챈다.  무조건 POST --&amp;gt;
    &amp;lt;!-- &amp;darr; 아이디/패스워드 의 name 은 'username' 과 'password' 로 하자 (시큐리티의 디폴트) --&amp;gt;
    &amp;lt;input type=&quot;text&quot; name=&quot;username&quot; placeholder=&quot;아이디 입력&quot; value=&quot;${username }&quot;/&amp;gt;&amp;lt;br&amp;gt;
    &amp;lt;input type=&quot;password&quot; name=&quot;password&quot; placeholder=&quot;패스워드 입력&quot;/&amp;gt;&amp;lt;br&amp;gt;
    &amp;lt;br&amp;gt;
    &amp;lt;span class=&quot;error&quot;&amp;gt;${errorMessage }&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
    &amp;lt;input type=&quot;submit&quot; value=&quot;로그인&quot;/&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;a href=&quot;/join&quot;&amp;gt;회원가입을 아직도 안하셨나요?&amp;lt;/a&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동작확인&lt;/b&gt; &lt;br /&gt;/login (혹은 다른 인증이 필요한 url 로 접근시) &lt;br /&gt;우리가&amp;nbsp;만든&amp;nbsp;커스텀&amp;nbsp;로그인&amp;nbsp;페이지가&amp;nbsp;뜬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;username,&amp;nbsp;password&amp;nbsp;&amp;nbsp;입력하면&amp;nbsp;로그인&amp;nbsp;동작&amp;nbsp;수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;디폴트&amp;nbsp;username,&amp;nbsp;password&amp;nbsp;변경하기&lt;/b&gt; &lt;br /&gt;매번&amp;nbsp;서버가동할때&amp;nbsp;뜨는&amp;nbsp;디폴트&amp;nbsp;패스워드&amp;nbsp;복붙하기&amp;nbsp;싫다면 &lt;br /&gt;application.properties에서 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;권한&amp;nbsp;맛보기&lt;/b&gt; &lt;br /&gt;현재의&amp;nbsp;디폴트&amp;nbsp;계정으로&amp;nbsp;로그인&amp;nbsp;하면&amp;nbsp;/sample/user/**&amp;nbsp;&amp;nbsp;&amp;nbsp;만&amp;nbsp;가능하고 &lt;br /&gt;/sample/member/**&amp;nbsp;&amp;nbsp;,&amp;nbsp;&amp;nbsp;&amp;nbsp;/sample/admin/**&amp;nbsp;는&amp;nbsp;안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;application.properties&lt;/b&gt; &lt;br /&gt;#&amp;nbsp;디폴트&amp;nbsp;권한&amp;nbsp;설정,&amp;nbsp;앞의&amp;nbsp;ROLE_&amp;nbsp;&amp;nbsp;은&amp;nbsp;빼고&amp;nbsp;지정 &lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;spring.security.user.roles=MEMBER&lt;/b&gt;&lt;/span&gt; &lt;br /&gt;&lt;br /&gt;서버 재시작 하면 일단 /logout 한뒤 다시 로그인&amp;nbsp;&lt;br /&gt;그리고&amp;nbsp;/sample/member/**&amp;nbsp;페이지로&amp;nbsp;들어가보면&amp;nbsp;-&amp;gt;&amp;nbsp;접근됨 &lt;br /&gt;반면&amp;nbsp;/sample/admin/**&amp;nbsp;페이지로&amp;nbsp;들어가보면&amp;nbsp;-&amp;gt;&amp;nbsp;접근&amp;nbsp;불가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#&amp;nbsp;디폴트&amp;nbsp;권한&amp;nbsp;설정,&amp;nbsp;앞의&amp;nbsp;ROLE_&amp;nbsp;&amp;nbsp;은&amp;nbsp;빼고&amp;nbsp;지정 &lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;spring.security.user.roles=ADMIN&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; ROLE_ADMIN 권한 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 재시작 하면 일단 /logout 한뒤 다시 로그인&amp;nbsp;&lt;br /&gt;그리고&amp;nbsp;/sample/member/**&amp;nbsp;페이지로&amp;nbsp;들어가보면&amp;nbsp;-&amp;gt;&amp;nbsp;접근됨&lt;br /&gt;반면 /sample/admin/** 페이지로 들어가보면 -&amp;gt; 접근됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#&amp;nbsp;디폴트&amp;nbsp;권한&amp;nbsp;설정,&amp;nbsp;앞의&amp;nbsp;ROLE_&amp;nbsp;&amp;nbsp;은&amp;nbsp;빼고&amp;nbsp;지정 &lt;br /&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;spring.security.user.roles=ADMIN,MEMBER&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; ROLE_MEMBER 와 ROLE_ADMIN 권한 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; 하나의 계정에 복수개 권한 지정 가능&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/sample/member/**&amp;nbsp;페이지로&amp;nbsp;들어가보면&amp;nbsp;-&amp;gt;&amp;nbsp;접근됨&lt;br /&gt;반면 /sample/admin/** 페이지로 들어가보면 -&amp;gt; 접근됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재&amp;nbsp;로그인&amp;nbsp;한&amp;nbsp;정보는? &lt;br /&gt;현재 인증(로그인) 한 정보는 &lt;b&gt;Authentication 객체&lt;/b&gt;로 받아올수 있다.&lt;br /&gt;IndexController.java&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;	//  현재 로그인한 정보 Authentication 보기
	@RequestMapping(&quot;/auth&quot;)
	@ResponseBody
	public Authentication auth(HttpSession session) {
		return SecurityContextHolder.getContext().getAuthentication();
		//시큐리티가 /loginOk 주소요청이 오면 낚아채서 로그인을 진행시킨다.
		//로그인 진행이 완료되면 '시큐리티 session' 에 넣어주게 된다. 
		//우리가 익히 알고 있는 같은 session 공간이긴 한데..
		//시큐리티가 자신이 사용하기 위한 공간을 가집니다. 
		//=&amp;gt; Security ContextHolder 라는 키값에다가 session 정보를 저장합니다.
		//여기에 들어갈수 있는 객체는 Authentication 객체이어야 한다.
		//Authentication 안에 User 정보가 있어야 됨. 
		//User 정보 객체는 ==&amp;gt; UserDetails 타입 객체이어야 한다.

	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sesstion attribute에 SPRING_SECURITY_CONTEXT&amp;nbsp;라는&amp;nbsp;이름으로&amp;nbsp;현재&amp;nbsp;로그인&amp;nbsp;정보가&amp;nbsp;담겨&amp;nbsp;있다 &lt;br /&gt;그리고&amp;nbsp;그&amp;nbsp;정보는&amp;nbsp;Authentication&amp;nbsp;객체다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>shb</author>
      <guid isPermaLink="true">https://shprogramming.tistory.com/146</guid>
      <comments>https://shprogramming.tistory.com/146#entry146comment</comments>
      <pubDate>Mon, 16 May 2022 14:54:14 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] MyBatis</title>
      <link>https://shprogramming.tistory.com/145</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;XML과&amp;nbsp;애너테이션(annotation)을&amp;nbsp;사용하여&amp;nbsp;&amp;lsquo;저장&amp;nbsp;프로시저&amp;rsquo;나&amp;nbsp;&amp;lsquo;SQL&amp;nbsp;문&amp;rsquo;으로&amp;nbsp;&amp;lsquo;객체&amp;rsquo;들을&amp;nbsp;연결(매핑)시킨다. &lt;br /&gt;(SQL&amp;nbsp;Mapper&amp;nbsp;라고도&amp;nbsp;한다) &lt;br /&gt;&lt;br /&gt;** 과거 아파치재단 에서 iBATIS로 개발했으나, 그 개발진들이 &amp;lsquo;구글코드&amp;rsquo;로 옮기면서 MyBatis로 이름을 바꾸어 계속 업데이트중.&amp;nbsp;&amp;nbsp;(iBATIS 는 중단됨)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 많이 코드를 줄일수 있고, 자바 코드가 아닌&lt;u&gt;&lt;b&gt; &amp;lsquo;XML매퍼파일&amp;rsquo; 을 통해서 DB에 접근 가능&lt;/b&gt;&lt;/u&gt;. 매우 간단&lt;br /&gt;XML매퍼파일에&amp;nbsp;쿼리(SQL)&amp;nbsp;만&amp;nbsp;설정해두어도&amp;nbsp;알아서&amp;nbsp;동작&amp;nbsp; &lt;br /&gt;(기존의 Connection 만들던 방식, JdbcTemplate 을 사용하던 방식 다 필요 없어짐)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Mybatis 프레임워크&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바코드(** DAO) &amp;lt;-&amp;gt; &lt;u&gt;&lt;b&gt;XML 매퍼파일, SQL 세팅&lt;/b&gt;&lt;/u&gt; -&amp;gt; DB&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;DAO는 인터페이스&lt;/b&gt;&lt;/span&gt;로만&lt;/u&gt; 주어지고, &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;u&gt;&lt;b&gt;MyBatis가 DAO를 구현&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;mappper&amp;gt;는&amp;nbsp;&lt;u&gt;&lt;b&gt;어느&amp;nbsp;DAO에&amp;nbsp;매핑될지&amp;nbsp;지정&lt;/b&gt;&lt;/u&gt;&lt;br /&gt;&amp;lt;mapper&amp;gt;는 여러 &amp;lt;SQL세팅&amp;gt;으로 구성. 각 &amp;lt;SQL세팅&amp;gt;은 DAO의 어느 메소드에 매핑될지 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MyBatis의 Mapper : JAVA 객체와 SQL 연동&lt;/b&gt;&lt;br /&gt;MyBatis에서 Mapper 역할을 하는 매핑파일(XML)은 &amp;lsquo;데이터&amp;rsquo; 조작을 하는 &lt;u&gt;&lt;b&gt;객체와 쿼리(SQL)를 연결(mapping)&lt;/b&gt;&lt;/u&gt;한다.&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;데이터&amp;rsquo; 조작을 하는 객체는 일반적으로 DAO객체가 그 역할을 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MyBatis : 한글 페이지&lt;/b&gt;&lt;br /&gt;마이바티스&amp;nbsp;한글&amp;nbsp;:&amp;nbsp;&lt;a href=&quot;http://www.mybatis.org/mybatis-3/ko/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://www.mybatis.org/mybatis-3/ko/&lt;/a&gt; &lt;br /&gt;마이바티스&amp;nbsp;스프링&amp;nbsp;설정&amp;nbsp;:&amp;nbsp;&lt;a href=&quot;http://www.mybatis.org/spring/ko/sample.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://www.mybatis.org/spring/ko/sample.html&lt;/a&gt; &lt;br /&gt;마이바티스&amp;nbsp;매퍼XML&amp;nbsp;설정&amp;nbsp;:&amp;nbsp;&lt;a href=&quot;http://www.mybatis.org/mybatis-3/ko/sqlmap-xml.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://www.mybatis.org/mybatis-3/ko/sqlmap-xml.html&lt;/a&gt; &lt;br /&gt;마이바티스&amp;nbsp;매퍼설정:&amp;nbsp;&lt;a href=&quot;http://www.mybatis.org/mybatis-3/ko/configuration.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://www.mybatis.org/mybatis-3/ko/configuration.html&lt;/a&gt;&lt;br /&gt;그 밖에도 다양한 예제, 레퍼런스들 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MVC&amp;nbsp;기본&amp;nbsp;구조&amp;nbsp;작성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 view (jsp) 파일들 가져오기, 기존&amp;nbsp;EL,&amp;nbsp;JSTL&amp;nbsp;사용&amp;nbsp;버젼&amp;nbsp;가져오기 &lt;br /&gt;*.do&amp;nbsp;로&amp;nbsp;되어&amp;nbsp;있는&amp;nbsp;url&amp;nbsp;들&amp;nbsp;변경하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* domain : &lt;span style=&quot;color: #006dd7;&quot;&gt;DTO 작성&lt;/span&gt;&lt;/b&gt; &lt;br /&gt;기존의&amp;nbsp;DTO&amp;nbsp;가져오기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;WriteDTO.java&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package com.lec.spring.domain;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

//DTO : Data Transfer Object
//DAO 등과 연동하여 데이터를 실어 나르는 객체
//필요한 객체(entity) 만큼 작성
public class WriteDTO {
	private int uid;    //  wr_uid
	private String subject;  // wr_subject
	private String content;  // wr_content
	private String name;     // wr_name
	private int viewCnt;  // wr_viewcnt
	private LocalDateTime regDate;  // wr_regdate
	
	// 웹개발시...
	// 가능한, 다음 3가지는 이름을 일치시켜주는게 좋습니다.
	// 클래스 필드명 = DB 필드명 = form의 name명

	
	public int getUid() {
		return uid;
	}
	public void setUid(int uid) {
		this.uid = uid;
	}
	public String getSubject() {
		return subject;
	}
	public void setSubject(String subject) {
		this.subject = subject;
	}
	public String getContent() {
		return content;
	}
	public void setContent(String content) {
		this.content = content;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getViewCnt() {
		return viewCnt;
	}
	public void setViewCnt(int viewCnt) {
		this.viewCnt = viewCnt;
	}
	public LocalDateTime getRegDate() {
		return regDate;
	}
	// LocalDateTime 을 String 으로 리턴하는 getter
	public String getRegDateTime() {
		if(this.regDate == null) return &quot;&quot;;
		return this.regDate.format(DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd hh:mm:ss&quot;));
	}
	public void setRegDate(LocalDateTime regDate) {
		this.regDate = regDate;
	}

	// 개발할때 class 의 toString() 을 오버라이딩 해주면
	// 웹에 표현하거나, 디버깅이나 테스팅 하기 좋다.
	@Override
	public String toString() {
		return String.format(&quot;WriteDTO] %d: %s : %s : %s : %d : %s&quot;, 
				uid, subject, content, name, viewCnt, regDate);
	}
	
	
	
	
} // end DTO
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* domain : &lt;span style=&quot;color: #006dd7;&quot;&gt;DAO 작성&lt;/span&gt;&lt;/b&gt; &lt;br /&gt;DAO는 &lt;b&gt;interface&lt;/b&gt;로 작성 &lt;br /&gt;동작하는&amp;nbsp;&amp;lsquo;&lt;b&gt;추상메소드&lt;/b&gt;&amp;rsquo;들 작성 &amp;larr; 후에 &lt;b&gt;MyBatis가 SQL 매핑하여 생성&lt;/b&gt;해주게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;WriteDAO.java&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package com.lec.spring.domain;
import java.util.List;

// MyBatis가 매핑할 인터페이스
public interface WriteDAO {
	
	// 새글 작성 &amp;lt;-- DTO
	public abstract int insert(WriteDTO dto);
	
	// 전체 SELECT
	public abstract List&amp;lt;WriteDTO&amp;gt; select();
		
	// 특정 uid 의 글 하나 SELECT --&amp;gt; List&amp;lt;DTO&amp;gt; 로 리턴
	public abstract List&amp;lt;WriteDTO&amp;gt; selectByUid(int uid);
	
	// 특정 uid 의 글 수정 &amp;lt;- DTO
	public abstract int update(WriteDTO dto);
	
	// 특정 uid 글 삭제하기
	public abstract int deleteByUid(int uid);
	
	// 특정 uid 글 조회수 증가
	public abstract int incViewCnt(int uid);
	
} // end DAO&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;*&lt;span style=&quot;color: #006dd7;&quot;&gt; Controller 작성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;BoardController.java&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;package com.lec.spring.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.lec.spring.domain.WriteDTO;
import com.lec.spring.service.BoardService;

@Controller
@RequestMapping(&quot;/board&quot;)
public class BoardController {

	private BoardService boardService;

	@Autowired
	public void setBoardService(BoardService boardService) {
		this.boardService = boardService;
	}


	public BoardController() {
		System.out.println(&quot;BoardController() 생성&quot;);
	}
	
	
	@RequestMapping(&quot;/list&quot;)
	public void list(Model model) {
		model.addAttribute(&quot;list&quot;, boardService.list());
	}
	
	@GetMapping(&quot;/write&quot;)
	public void write(Model model) {}
	
	@PostMapping(&quot;/writeOk&quot;)
	public void writeOk(WriteDTO dto, Model model) {
		model.addAttribute(&quot;result&quot;, boardService.write(dto));
		model.addAttribute(&quot;dto&quot;, dto);  // auto-generated key 
	}

	@GetMapping(&quot;/view&quot;)
	public void view(int uid, Model model) {
		model.addAttribute(&quot;list&quot;, boardService.viewByUid(uid));
	}
	
	@GetMapping(&quot;/update&quot;)
	public void update(int uid, Model model) {
		model.addAttribute(&quot;list&quot;, boardService.selectByUid(uid));
	}
	
	@PostMapping(&quot;/updateOk&quot;)
	public void updateOk(WriteDTO dto, Model model) {
		model.addAttribute(&quot;result&quot;, boardService.update(dto));
		model.addAttribute(&quot;dto&quot;, dto);
	}
	
	@RequestMapping(&quot;/deleteOk&quot;)
	public void deleteOk(int uid, Model model) {
		model.addAttribute(&quot;result&quot;, boardService.deleteByUid(uid));
	}
	
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;Service&lt;/span&gt; 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;BoardService.java&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;package com.lec.spring.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.lec.spring.domain.WriteDAO;
import com.lec.spring.domain.WriteDTO;

//Service 단.
//JSP MVC model2 의 Command 역할 비슷
//  Controller -&amp;gt; Commmand -&amp;gt; DAO

//- Transaction 담당
//Spring
//@Controller -&amp;gt; @Service -&amp;gt; DAO -&amp;gt; JdbcTemplate
@Service
public class BoardService {

	WriteDAO dao;
	
	@Autowired
	public void setDao(WriteDAO dao) {
		this.dao = dao;
	}
	
	public BoardService() {
		System.out.println(&quot;BoardServcie() 생성&quot;);		
	}

	public List&amp;lt;WriteDTO&amp;gt; list() {
		
		return dao.select();
	}

	@Transactional
	public List&amp;lt;WriteDTO&amp;gt; viewByUid(int uid) {
		// 트랜잭션 처리!
		// 1. 조회수 증가
		// 2. 글 하나 읽어오기
		dao.incViewCnt(uid);
		return dao.selectByUid(uid);
	}

	public int write(WriteDTO dto) {
		return dao.insert(dto);
	}

	public List&amp;lt;WriteDTO&amp;gt; selectByUid(int uid) {
		return dao.selectByUid(uid);
	}

	public int update(WriteDTO dto) {
		return dao.update(dto);
	}

	public int deleteByUid(int uid) {
		return dao.deleteByUid(uid);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* &lt;span style=&quot;color: #006dd7;&quot;&gt;DAO 구현체 작성&lt;/span&gt; &lt;br /&gt;DAOImpl / &lt;/b&gt;&lt;b&gt;반복되는&lt;/b&gt; &lt;b&gt;&lt;u&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;MySQL Mapper를 주입받는 구현체&lt;/span&gt;&lt;/u&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;WriteDAOImple.java&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;package com.lec.spring.domain;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class WriteDAOImpl implements WriteDAO {

	private WriteDAO mapper;
	
	@Autowired
	public WriteDAOImpl(SqlSession sqlSession) {
		System.out.println(&quot;WriteDAOImpl() 생성&quot;);
		// MyBatis 가 생성한 WriteDAO 의 mapper (구현체)를 받아온다.
		mapper = sqlSession.getMapper(WriteDAO.class);   
	}
	
	
	@Override
	public int insert(WriteDTO dto) {
		return mapper.insert(dto);
	}

	@Override
	public List&amp;lt;WriteDTO&amp;gt; select() {
		return mapper.select();
	}

	@Override
	public List&amp;lt;WriteDTO&amp;gt; selectByUid(int uid) {
		return mapper.selectByUid(uid);
	}

	@Override
	public int update(WriteDTO dto) {
		return mapper.update(dto);
	}

	@Override
	public int deleteByUid(int uid) {
		return mapper.deleteByUid(uid);
	}

	@Override
	public int incViewCnt(int uid) {
		return mapper.incViewCnt(uid);
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;WriteDAO.xml&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MyBatis의 Mapper : JAVA 객체와 SQL 연동&lt;/b&gt;&lt;br /&gt;&lt;span&gt;MyBatis에서 Mapper 역할을 하는 매핑파일(XML)은 &amp;lsquo;데이터&amp;rsquo; 조작을 하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;u&gt;&lt;b&gt;객체와 쿼리(SQL)를 연결(mapping)&lt;/b&gt;&lt;/u&gt;한다.&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&amp;gt;
&amp;lt;mapper namespace=&quot;com.lec.spring.domain.WriteDAO&quot;&amp;gt;

	&amp;lt;select id=&quot;select&quot; resultType=&quot;com.lec.spring.domain.WriteDTO&quot;&amp;gt;
		SELECT 
			wr_uid &quot;uid&quot;, 
			wr_subject subject, 
			wr_content content, 
			wr_name name, 
			wr_viewcnt viewcnt, 
			wr_regdate regDate 
		FROM 
			test_write 
		ORDER BY 
			wr_uid DESC	
	&amp;lt;/select&amp;gt;

	&amp;lt;select id=&quot;selectByUid&quot; resultType=&quot;com.lec.spring.domain.WriteDTO&quot;&amp;gt;
		SELECT 
			wr_uid &quot;uid&quot;, 
			wr_subject subject, 
			wr_content content, 
			wr_name name, 
			wr_viewcnt viewcnt, 
			wr_regdate regDate 
		FROM 
			test_write
		WHERE wr_uid = #{uid} 
	&amp;lt;/select&amp;gt;

	&amp;lt;update id=&quot;incViewCnt&quot; flushCache=&quot;true&quot;&amp;gt;
		UPDATE test_write
		SET wr_viewcnt = wr_viewcnt + 1
		WHERE wr_uid = #{param1}
	&amp;lt;/update&amp;gt;
	
	&amp;lt;insert id=&quot;insert&quot; flushCache=&quot;true&quot;
		parameterType=&quot;com.lec.spring.domain.WriteDTO&quot;
		useGeneratedKeys=&quot;true&quot;
		keyColumn=&quot;wr_uid&quot; keyProperty=&quot;uid&quot;&amp;gt;
		INSERT INTO test_write
			(wr_subject, wr_content, wr_name)
		VALUES
			(#{subject}, #{content}, #{name})	
	&amp;lt;/insert&amp;gt;
	
	&amp;lt;update id=&quot;update&quot; flushCache=&quot;true&quot;
		parameterType=&quot;com.lec.spring.domain.WriteDTO&quot;&amp;gt;
		UPDATE test_write
		SET wr_subject = #{subject}, wr_content = #{content}
		WHERE wr_uid = #{uid}
	&amp;lt;/update&amp;gt;
	
	&amp;lt;delete id=&quot;deleteByUid&quot; flushCache=&quot;true&quot;&amp;gt;
		DELETE FROM test_write WHERE wr_uid = #{uid}
	&amp;lt;/delete&amp;gt;
	
	
	
&amp;lt;/mapper&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* Controller 의 handler 및 view 파일 준비&lt;/b&gt;&lt;br /&gt;각&amp;nbsp;url&amp;nbsp;별로&amp;nbsp;일단&amp;nbsp;매개변수&amp;nbsp;없이&amp;nbsp;view&amp;nbsp;만&amp;nbsp;보이기 &lt;br /&gt;동작&amp;nbsp;확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <author>shb</author>
      <guid isPermaLink="true">https://shprogramming.tistory.com/145</guid>
      <comments>https://shprogramming.tistory.com/145#entry145comment</comments>
      <pubDate>Fri, 13 May 2022 16:53:42 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] Validation</title>
      <link>https://shprogramming.tistory.com/144</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;폼&amp;nbsp;데이터&amp;nbsp;검증&amp;nbsp;(Parameter&amp;nbsp;Validation)&lt;/b&gt; &lt;br /&gt;신뢰성&amp;nbsp;있는&amp;nbsp;웹&amp;nbsp;어플리케이션&amp;nbsp;작성을&amp;nbsp;위해&amp;nbsp;폼데이터&amp;nbsp;검증&amp;nbsp;필수 &lt;br /&gt;기본적으로&amp;nbsp;폼데이터는&amp;nbsp;2단계&amp;nbsp;걸쳐&amp;nbsp;검증해주어야&amp;nbsp;한다. &lt;br /&gt;1단계:&amp;nbsp;&amp;lsquo;클라이언트&amp;rsquo;&amp;nbsp;에서&amp;nbsp;submit&amp;nbsp;하기&amp;nbsp;전에&amp;nbsp;검증&amp;nbsp;:&amp;nbsp;(JavaScript)&amp;nbsp;사용 &lt;br /&gt;2단계:&amp;nbsp;&amp;lsquo;서버&amp;rsquo;&amp;nbsp;에서도&amp;nbsp;검증&amp;nbsp;&amp;nbsp;:&amp;nbsp;Spring,&amp;nbsp;JSP&amp;nbsp;등&amp;nbsp;사용...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;WriteDTO 클래스 가져오기&lt;/b&gt;&lt;br /&gt;직전 단원에서 작성한 DTO 클래스와 패키지를&amp;nbsp;&amp;nbsp;복사해옵시다.&lt;br /&gt;생성자, getter, setter 의 출력코드 살려둡니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일단&amp;nbsp;기본적인&amp;nbsp;form&amp;nbsp;동작&amp;nbsp;작성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨트롤러 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;package com.lec.spring.controller;

import java.util.List;

import javax.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import com.lec.spring.domain.BoardValidator;
import com.lec.spring.domain.WriteDTO;

@Controller
@RequestMapping(&quot;/board&quot;)
public class BoardController {
	
	@RequestMapping(&quot;write&quot;)
	public void write() {}
	
	
	// BindingResult : spring validator 가 유효성 검사를 한 결과가 담긴 객체
	@RequestMapping(&quot;writeOk&quot;)
	public String writeOk(@ModelAttribute(&quot;w&quot;) @Valid WriteDTO dto,
			BindingResult result,	
			Model model,  // Model 이 BindingResult 보다 앞에 있으면 에러..
			RedirectAttributes redirectAttrs   // redirect: 시 넘겨줄 값들
			) {
		System.out.println(&quot;writeOk : &quot; + dto.getUid() + &quot;:&quot; + dto.getName());	
		String page = &quot;board/writeOk&quot;;
		
//		System.out.println(&quot;validate전 &quot;); showErrors(result);
		
		// validator 객체 생성
//		BoardValidator validator = new BoardValidator();
//		validator.validate(dto, result);
		
		System.out.println(&quot;validate후 &quot;); showErrors(result);
		if(result.hasErrors()) {   // 에러가 있었으면
			redirectAttrs.addFlashAttribute(&quot;w&quot;, dto);  // 원래 입력한 값 돌려주기.
			if(result.getFieldError(&quot;uid&quot;) != null) {
				redirectAttrs.addFlashAttribute(&quot;errUid&quot;, &quot;uid 값은 0보다 큰 정수이어야 합니다&quot;);
			}
			if(result.getFieldError(&quot;name&quot;) != null) {
				redirectAttrs.addFlashAttribute(&quot;errName&quot;, &quot;name 은 필수 입니다&quot;);
			}
			if(result.getFieldError(&quot;subject&quot;) != null) {
				redirectAttrs.addFlashAttribute(&quot;errSubject&quot;, &quot;subject 는 필수 입니다&quot;);
			}
			page = &quot;redirect:/board/write&quot;;   // 리턴값(String)  에 redirect:url &amp;lt;- 해당 url 로 redirect 한다
		}
		
		
		return page;
	}
	
	// binding 에러 출력 도우미 메소드
	public void showErrors(Errors errors) {
		if(errors.hasErrors()) {
			System.out.println(&quot;에러개수: &quot; + errors.getErrorCount());
			System.out.println(&quot;\t[field]\t|[code] &quot;);
			List&amp;lt;FieldError&amp;gt; errList = errors.getFieldErrors();
			for(FieldError err : errList) {
				System.out.println(&quot;\t&quot; + err.getField() + &quot;\t|&quot; + err.getCode());
			}
		} else {
			System.out.println(&quot;에러 없슴&quot;);
		}
	}
	
	// 이 컨트롤러의 클래스의 handler 에서 폼데이터를 바인딩할때 검증하는 객체 지정
	@InitBinder
	public void initBinder(WebDataBinder binder) {
		binder.setValidator(new BoardValidator());
	}
	
	
	
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 간단한 작성 폼 작성 : write.jsp&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot;
    pageEncoding=&quot;UTF-8&quot;%&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
&amp;lt;title&amp;gt;글 작성&amp;lt;/title&amp;gt;
&amp;lt;style&amp;gt;
span { color: red;}
&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;form action=&quot;writeOk&quot;&amp;gt;
uid(&amp;lt;span&amp;gt;숫자&amp;lt;/span&amp;gt;): 
	&amp;lt;input type=&quot;text&quot; name=&quot;uid&quot; value=&quot;${w.uid }&quot;&amp;gt;&amp;lt;span&amp;gt;${errUid }&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
작성자(&amp;lt;span&amp;gt;*&amp;lt;/span&amp;gt;): 
	&amp;lt;input type=&quot;text&quot; name=&quot;name&quot; value=&quot;${w.name }&quot;&amp;gt;&amp;lt;span&amp;gt;${errName }&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
제목(&amp;lt;span&amp;gt;*&amp;lt;/span&amp;gt;):
	&amp;lt;input type=&quot;text&quot; name=&quot;subject&quot; value=&quot;${w.subject }&quot;&amp;gt;&amp;lt;span&amp;gt;${errSubject }&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;input type=&quot;submit&quot; value=&quot;등록&quot;&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* writeOk.jsp 작성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot;
    pageEncoding=&quot;UTF-8&quot;%&amp;gt;
uid: ${w.uid}&amp;lt;br&amp;gt;
작성자: ${w.name}&amp;lt;br&amp;gt;
제목: ${w.subject }&amp;lt;br&amp;gt;
&amp;lt;button onclick=&quot;history.back()&quot;&amp;gt;돌아가기&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작 확인하기 - uid&amp;nbsp;(숫자,&amp;nbsp;int)&amp;nbsp;가&amp;nbsp;비어&amp;nbsp;있으면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폼&amp;nbsp;데이터를&amp;nbsp;검증하려면&amp;nbsp;어디에? &lt;br /&gt;커맨드&amp;nbsp;객체를&amp;nbsp;사용하는&amp;nbsp;상황에서&amp;nbsp;폼&amp;nbsp;검증은&amp;nbsp;어디서&amp;nbsp;해야&amp;nbsp;하나? &lt;br /&gt;이미&amp;nbsp;스프링(Dispatcher&amp;nbsp;Servlet)이&amp;nbsp;사전&amp;nbsp;처리&amp;nbsp;하는&amp;nbsp;단계에서&amp;nbsp;&amp;nbsp;발생하는데? &lt;br /&gt;스프링&amp;nbsp;MVC&amp;nbsp;에서&amp;nbsp;폼&amp;nbsp;데이터&amp;nbsp;검증&amp;nbsp;코드를&amp;nbsp;집어&amp;nbsp;넣으려면&amp;nbsp;어떻게?&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스프링에서&amp;nbsp;폼&amp;nbsp;데이터&amp;nbsp;검증&amp;nbsp;validator&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제의&amp;nbsp;시나리오 &lt;br /&gt;아래와&amp;nbsp;같이&amp;nbsp;폼&amp;nbsp;데이터&amp;nbsp;에&amp;nbsp;대한&amp;nbsp;규칙이&amp;nbsp;있을때,&amp;nbsp;서버로&amp;nbsp;넘어온&amp;nbsp;폼데이터를&amp;nbsp;스프링에서&amp;nbsp;다루어&amp;nbsp;보도록&amp;nbsp;합니다 &lt;br /&gt;&lt;br /&gt;&lt;b&gt;uid&amp;nbsp;값은&amp;nbsp;반드시&amp;nbsp;숫자&amp;nbsp;입력&lt;/b&gt;&amp;nbsp;:&amp;nbsp;&amp;nbsp;비어&amp;nbsp;있으면&amp;nbsp;안됨. &lt;br /&gt;&lt;b&gt;name&amp;nbsp;은&amp;nbsp;반드시&amp;nbsp;입력&lt;/b&gt;&amp;nbsp;:&amp;nbsp;비어&amp;nbsp;있으면&amp;nbsp;안됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;handler에 BindingResult 매개변수&lt;/b&gt;&lt;br /&gt;마치 handler의 Model 매개변수와 마찬가지로 DispatcherServlet&amp;nbsp;에서&amp;nbsp;제공(자동주입)&amp;nbsp;해주는&amp;nbsp;객체 &lt;br /&gt;validator가 유효성 검사를 한 결과가 담긴 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BindingResult result&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;	// BindingResult : spring validator 가 유효성 검사를 한 결과가 담긴 객체
	@RequestMapping(&quot;writeOk&quot;)
	public String writeOk(@ModelAttribute(&quot;w&quot;) @Valid WriteDTO dto,
			BindingResult result,	
			Model model,  // Model 이 BindingResult 보다 앞에 있으면 에러..
			RedirectAttributes redirectAttrs   // redirect: 시 넘겨줄 값들
			) {
		System.out.println(&quot;writeOk : &quot; + dto.getUid() + &quot;:&quot; + dto.getName());	
		String page = &quot;board/writeOk&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서 결과 확인&lt;br /&gt;일단 400 에러 발생 안함 uid 값이 0&lt;br /&gt;콘솔을&amp;nbsp;확인했을때&amp;nbsp;setUid(&amp;nbsp;0&amp;nbsp;)이&amp;nbsp;아예&amp;nbsp;호출되지&amp;nbsp;않았음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 폼 데이터 오류 개수 확인&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;	// BindingResult : spring validator 가 유효성 검사를 한 결과가 담긴 객체
	@RequestMapping(&quot;writeOk&quot;)
	public String writeOk(@ModelAttribute(&quot;w&quot;) @Valid WriteDTO dto,
			BindingResult result,	
			Model model,  // Model 이 BindingResult 보다 앞에 있으면 에러..
			RedirectAttributes redirectAttrs   // redirect: 시 넘겨줄 값들
			) {
		System.out.println(&quot;writeOk : &quot; + dto.getUid() + &quot;:&quot; + dto.getName());	
		String page = &quot;board/writeOk&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과&amp;nbsp;확인&lt;/b&gt; &lt;br /&gt;정상적인&amp;nbsp;submit&amp;nbsp;인&amp;nbsp;경우&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;에러&amp;nbsp;개수&amp;nbsp;:&amp;nbsp;0 &lt;br /&gt;uid&amp;nbsp;가&amp;nbsp;없거나&amp;nbsp;숫자가&amp;nbsp;아니면&amp;nbsp;&amp;rarr;&amp;nbsp;에러&amp;nbsp;개수&amp;nbsp;:&amp;nbsp;1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Data&amp;nbsp;Binding&lt;/b&gt; &lt;br /&gt;데이터&amp;nbsp;바인딩이란&amp;nbsp;프로퍼티&amp;nbsp;값을&amp;nbsp;타겟&amp;nbsp;객체에&amp;nbsp;설정해주는&amp;nbsp;것을&amp;nbsp;의미한다.&amp;nbsp; &lt;br /&gt;ex)&amp;nbsp;사용자의&amp;nbsp;문자열&amp;nbsp;입력값을&amp;nbsp;어플리케이션&amp;nbsp;도메인&amp;nbsp;객체의&amp;nbsp;프로퍼티값으로&amp;nbsp;동적으로&amp;nbsp;할당해주는&amp;nbsp;것과&amp;nbsp;같은&amp;nbsp;일을&amp;nbsp;데이터&amp;nbsp;바인딩이라고&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;error&amp;nbsp;출력&amp;nbsp;도우미&amp;nbsp;메소드&amp;nbsp;추가&lt;/b&gt; &lt;br /&gt;Errors&amp;nbsp;에&amp;nbsp;담겨있는&amp;nbsp;에러&amp;nbsp;들을&amp;nbsp;다&amp;nbsp;출력해보기&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;	// binding 에러 출력 도우미 메소드
	public void showErrors(Errors errors) {
		if(errors.hasErrors()) {
			System.out.println(&quot;에러개수: &quot; + errors.getErrorCount());
			System.out.println(&quot;\t[field]\t|[code] &quot;);
			List&amp;lt;FieldError&amp;gt; errList = errors.getFieldErrors();
			for(FieldError err : errList) {
				System.out.println(&quot;\t&quot; + err.getField() + &quot;\t|&quot; + err.getCode());
			}
		} else {
			System.out.println(&quot;에러 없슴&quot;);
		}
	}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* Validator 클래스 작성&lt;/b&gt; &lt;br /&gt;패키지&amp;nbsp;:&amp;nbsp;co&lt;a href=&quot;http://m.lec.spring&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;m.lec.spring&lt;/a&gt; &lt;br /&gt;클래스&amp;nbsp;:&amp;nbsp;BoardValidator&amp;nbsp; &lt;br /&gt;인터페이스&amp;nbsp;:&amp;nbsp;&amp;nbsp;implements&amp;nbsp;Validator&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Validator의 메소드 supports, validate&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;support() 작성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;ublic class BoardValidator implements Validator {

	// 이 Validator 가 제공된 Class 의 인스턴스(clazz) 를 유효성 검사 할수 있나.
	@Override
	public boolean supports(Class&amp;lt;?&amp;gt; clazz) {
		System.out.println(&quot;supports(&quot; + clazz.getName() + &quot;)&quot;);
		// &amp;darr; 검증할 객체의 클래스 타입인지 확인. 즉, WriteDTO &amp;lt;= clazz 가능 여부
		return WriteDTO.class.isAssignableFrom(clazz);
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;validate() 작성&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;	// 주어진 객체(target) 에 유효성 검사를 수행하고
	// 유효성 검사에 오류가 있는 경우 주어진 객에체 이 오류들을 error 에 등록한다.
	@Override
	public void validate(Object target, Errors errors) {
		System.out.println(&quot;validate()&quot;);
		WriteDTO dto = (WriteDTO)target;
		
		Integer uid = dto.getUid();
		if(uid == null) {  // binding 실패하면 null 로 남아있을 것이다.
			System.out.println(&quot;uid 오류&quot;);
			// 에러등록 rejectValue(field, errorCode);
			errors.rejectValue(&quot;uid&quot;, &quot;invalidUid&quot;);
		}
		
		String name = dto.getName();
		if(name == null || name.trim().isEmpty()) {
			System.out.println(&quot;name 오류: 반드시 한글자라도 입력해야 합니다&quot;);
			errors.rejectValue(&quot;name&quot;, &quot;emptyName&quot;);
		}
		
		// ValidationUtils 사용
		// 단순히 빈 폼 데이터를 처리할때는 아래 와 같이 사용 가능
		// 두번째 매개변수 &quot;subject&quot; 은 반드시 target 클래스의 필드명 이어야 함
		// 게다가 Errors 에 등록될때도 동일한 field 명으로 등록된다. 
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, &quot;subject&quot;, &quot;emptySubject&quot;);
	}

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;writeOk.do에 validator 객체 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;		// validator 객체 생성
//		BoardValidator validator = new BoardValidator();
//		validator.validate(dto, result);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;System.out.println(&quot;validate후&amp;nbsp;&quot;);&amp;nbsp;showErrors(result)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; Binding&amp;nbsp;후&amp;nbsp;사용자가&amp;nbsp;validate()&amp;nbsp;를&amp;nbsp;호출하여&amp;nbsp;확인된&amp;nbsp;에러&amp;nbsp;추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;primitive&amp;nbsp;사용에&amp;nbsp;대한&amp;nbsp;고찰&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;binding&amp;nbsp;에러인데?&amp;nbsp;&amp;nbsp;uid&amp;nbsp;값이&amp;nbsp;0&amp;nbsp;? &lt;br /&gt;binding&amp;nbsp;실패하면...&amp;nbsp;초기값으로&amp;nbsp;남아있기&amp;nbsp;때문에&amp;nbsp; &lt;br /&gt;이러한&amp;nbsp;이유로..&amp;nbsp;&amp;nbsp;primitive&amp;nbsp;type&amp;nbsp;보단&amp;nbsp;wrapper&amp;nbsp;타입으로&amp;nbsp;설정한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ValidationUtils&amp;nbsp;사용 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;ValidationUtils&amp;nbsp;클래스&lt;/b&gt; &lt;br /&gt;직전의 예제에서 데이터 검증을 위해서 Validator 인터페이스의 validate() 메소드를 사용하였습니다. &lt;br /&gt;ValidationUtils&amp;nbsp;&amp;nbsp;클래스는&amp;nbsp;validate()메소드를&amp;nbsp;좀더&amp;nbsp;편리하게&amp;nbsp;사용&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;고안된&amp;nbsp;클래스&amp;nbsp;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;write.jsp,&amp;nbsp;writeOk.jsp&amp;nbsp;수정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;validate()에 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Valid 와 @InitBinder&amp;nbsp;&amp;nbsp;사용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;validate()&amp;nbsp;를&amp;nbsp;스프링에서&amp;nbsp;호출하도록&amp;nbsp;설정 &lt;br /&gt;이전까지는&amp;nbsp;Validator&amp;nbsp;인터페이스를&amp;nbsp;구현한&amp;nbsp;클래스를&amp;nbsp;만들고,&amp;nbsp; &lt;br /&gt;validate()메소드를&amp;nbsp;직접&amp;nbsp;호출하여&amp;nbsp;사용&amp;nbsp;하였습니다. &lt;br /&gt;&lt;br /&gt;이번에는 직접 호출하지 않고,&amp;nbsp;스프링&amp;nbsp;프레임워크에서&amp;nbsp;호출하는&amp;nbsp;방법에&amp;nbsp;대해서&amp;nbsp;살펴&amp;nbsp;봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring-boot-starter-validation 를 설정하면&lt;br /&gt;@Valid&amp;nbsp;와&amp;nbsp;@InitBinder&amp;nbsp;를&amp;nbsp;사용하기&amp;nbsp;위한&amp;nbsp;hibernate-validator&amp;nbsp;가&amp;nbsp;이미&amp;nbsp;들어와있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨트롤러에 @InitBinder 추가&amp;nbsp;&amp;nbsp;&lt;/b&gt;&lt;br /&gt;BoardController.java&amp;nbsp;에&amp;nbsp;추가&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;	// 이 컨트롤러의 클래스의 handler 에서 폼데이터를 바인딩할때 검증하는 객체 지정
	@InitBinder
	public void initBinder(WebDataBinder binder) {
		binder.setValidator(new BoardValidator());
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;writeOk()&amp;nbsp;수정&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;	// BindingResult : spring validator 가 유효성 검사를 한 결과가 담긴 객체
	@RequestMapping(&quot;writeOk&quot;)
	public String writeOk(@ModelAttribute(&quot;w&quot;) @Valid WriteDTO dto,
			BindingResult result,	
			Model model,  // Model 이 BindingResult 보다 앞에 있으면 에러..
			RedirectAttributes redirectAttrs   // redirect: 시 넘겨줄 값들
			) {
		System.out.println(&quot;writeOk : &quot; + dto.getUid() + &quot;:&quot; + dto.getName());	
		String page = &quot;board/writeOk&quot;;
		
//		System.out.println(&quot;validate전 &quot;); showErrors(result);
		
		// validator 객체 생성
//		BoardValidator validator = new BoardValidator();
//		validator.validate(dto, result);
		
		System.out.println(&quot;validate후 &quot;); showErrors(result);
		if(result.hasErrors()) {   // 에러가 있었으면
			redirectAttrs.addFlashAttribute(&quot;w&quot;, dto);  // 원래 입력한 값 돌려주기.
			if(result.getFieldError(&quot;uid&quot;) != null) {
				redirectAttrs.addFlashAttribute(&quot;errUid&quot;, &quot;uid 값은 0보다 큰 정수이어야 합니다&quot;);
			}
			if(result.getFieldError(&quot;name&quot;) != null) {
				redirectAttrs.addFlashAttribute(&quot;errName&quot;, &quot;name 은 필수 입니다&quot;);
			}
			if(result.getFieldError(&quot;subject&quot;) != null) {
				redirectAttrs.addFlashAttribute(&quot;errSubject&quot;, &quot;subject 는 필수 입니다&quot;);
			}
			page = &quot;redirect:/board/write&quot;;   // 리턴값(String)  에 redirect:url &amp;lt;- 해당 url 로 redirect 한다
		}
		
		
		return page;
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과&amp;nbsp;확인&lt;/b&gt; &lt;br /&gt;supports()&amp;nbsp;가&amp;nbsp;호출됨을&amp;nbsp;알수&amp;nbsp;있다. &lt;br /&gt;주어진&amp;nbsp;WriteDTO&amp;nbsp;객체가&amp;nbsp;BoardValidator&amp;nbsp;로&amp;nbsp;검증할수&amp;nbsp;있는지&amp;nbsp;여부를&amp;nbsp;먼저&amp;nbsp;supports()&amp;nbsp;로&amp;nbsp;체크하고&amp;nbsp;나서&amp;nbsp;validate()&amp;nbsp;를&amp;nbsp;수행하고&amp;nbsp;있는&amp;nbsp;것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요&amp;nbsp;:&amp;nbsp;BindingResult&amp;nbsp;매개변수&amp;nbsp;순서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[스프링:에러] java.lang.IllegalStateException: Errors/BindingResult argument declared without preceding model attribute. Check your handler method signature! &lt;br /&gt;&lt;br /&gt;Validate를 하는 메소드 내에서 BindingResult의 순서와 관련된 에러. BindingResult가 HttpServletRequest, HttpServletResponse, ModelMap보다 먼저 선언되어야 에러가 나지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러시&amp;nbsp;원래&amp;nbsp;url&amp;nbsp;로&amp;nbsp;돌아가기.&amp;nbsp;redirect:&amp;nbsp;사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #695d46;&quot;&gt;write.jsp&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot;
    pageEncoding=&quot;UTF-8&quot;%&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
&amp;lt;title&amp;gt;글 작성&amp;lt;/title&amp;gt;
&amp;lt;style&amp;gt;
span { color: red;}
&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;form action=&quot;writeOk&quot;&amp;gt;
uid(&amp;lt;span&amp;gt;숫자&amp;lt;/span&amp;gt;): 
	&amp;lt;input type=&quot;text&quot; name=&quot;uid&quot; value=&quot;${w.uid }&quot;&amp;gt;&amp;lt;span&amp;gt;${errUid }&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
작성자(&amp;lt;span&amp;gt;*&amp;lt;/span&amp;gt;): 
	&amp;lt;input type=&quot;text&quot; name=&quot;name&quot; value=&quot;${w.name }&quot;&amp;gt;&amp;lt;span&amp;gt;${errName }&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
제목(&amp;lt;span&amp;gt;*&amp;lt;/span&amp;gt;):
	&amp;lt;input type=&quot;text&quot; name=&quot;subject&quot; value=&quot;${w.subject }&quot;&amp;gt;&amp;lt;span&amp;gt;${errSubject }&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;input type=&quot;submit&quot; value=&quot;등록&quot;&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;${errUid}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;${errName}&lt;/p&gt;</description>
      <category>Spring</category>
      <author>shb</author>
      <guid isPermaLink="true">https://shprogramming.tistory.com/144</guid>
      <comments>https://shprogramming.tistory.com/144#entry144comment</comments>
      <pubDate>Thu, 12 May 2022 18:18:45 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] Request Parameter</title>
      <link>https://shprogramming.tistory.com/143</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;request parameter를 받는 다양한 방법 &lt;br /&gt;&lt;br /&gt;스프링에서&amp;nbsp;제공하는&amp;nbsp;다양한&amp;nbsp;방법들에&amp;nbsp;대해&amp;nbsp;배웁니다. &lt;br /&gt;1.&amp;nbsp;request&amp;nbsp;parameter&amp;nbsp;처리 &lt;br /&gt;2.&amp;nbsp;GET&amp;nbsp;방식&amp;nbsp;/&amp;nbsp;POST&amp;nbsp;방식&amp;nbsp;처리 &lt;br /&gt;3.&amp;nbsp;@RequestParam &lt;br /&gt;4.&amp;nbsp;커맨드&amp;nbsp;객체 &lt;br /&gt;5.&amp;nbsp;@ModelAttribute &lt;br /&gt;6.&amp;nbsp;@PathVariable &lt;br /&gt;7.&amp;nbsp;redirect:&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;forward:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로젝트&amp;nbsp;생성&lt;/b&gt; &lt;br /&gt;Bt011_RequestParam &lt;br /&gt;Starter&amp;nbsp;:&amp;nbsp;&amp;nbsp;SpringWeb,&amp;nbsp;DevTools,&amp;nbsp;Lombok&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* HomeController 작성 &lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;package com.lec.spring;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.lec.spring.domain.WriteDTO;

@Controller
public class HomeController {

	@RequestMapping(value=&quot;/member/delete&quot;)  // @RequestMapping: GET, POST, PUT, PATCH, DELETE,.. 어떠한 방식의 request 이든 관계없이 동작
//	public void delMember(HttpServletRequest request, Model model) {
	public void delMember(Model model, HttpServletRequest request) {  // ★ 핸들어의 매개변수 순서 관계 없다!
		String id = request.getParameter(&quot;id&quot;);
		model.addAttribute(&quot;mbId&quot;, id);
	}
	
	//@RequestMapping(value=&quot;/member/regOk&quot;, method= RequestMethod.POST)  // POST 방식 request 일때만 처리
	@PostMapping(&quot;/member/regOk&quot;)
	public void registPost() {
		System.out.println(&quot;/member/regOk : POST&quot;);
	}
	
//	@RequestMapping(value=&quot;/member/regOk&quot;, method= RequestMethod.GET)
	@GetMapping(&quot;/member/regOk&quot;)
	public void registGet() {
		System.out.println(&quot;/member/regOk : GET&quot;);
	}
	
	// GET, POST 두가지 방식만 매핑하려면?
	@RequestMapping(value=&quot;/member/regOk2&quot;, method= {RequestMethod.GET, RequestMethod.POST})
	public String regOkMember2(HttpServletRequest request) {
		System.out.println(&quot;/membre/regOk2 : &quot; + request.getMethod());
		return &quot;member/regOk&quot;;
	}
	
	@RequestMapping(&quot;/member/regist&quot;)
	public void registMember() {}
	
	
	@RequestMapping(&quot;/member/find&quot;)
//	public void findMember(String id, String name, Model model) {
//	public void findMember(Model model, String id, String name) {  // 매개변수 순서에 영향 받지 않는다.
//	public void findMember(double id, String name, Model model) {  // 숫자타입이면, 해당타입으로 변환하여 binding
	
//	public void findMember(Double id, String name, Model model) { //  Wrapper 타입을 사용하면 id parameter 가 없는 경우에도 에러 없이 동작		
//		System.out.println(&quot;member/find: id=&quot; + id + &quot;, name=&quot; + name);
//		
//		model.addAttribute(&quot;id&quot;, id);
//		model.addAttribute(&quot;name&quot;, name);
//	}
	
	// 동일한 name 의 request parameter 복수개
//	public void findMember(Integer[] id, String [] name, Model model) {
//		model.addAttribute(&quot;id&quot;, Arrays.toString(id));
//		model.addAttribute(&quot;name&quot;, Arrays.toString(name));
//	}
	
	// 만약 request parameter 의 name 과 매개변수가 같을수 없는 상황이면 
	// @RequestParam 애노테이션 사용
//	public void findMember(Model model,
//			@RequestParam(&quot;id&quot;) String userid, // &quot;id&quot; 란 name 의 parameter 값을 userid 매개변수에 binding 해준다
//			@RequestParam(&quot;name&quot;) String username 
//			) {
//		model.addAttribute(&quot;id&quot;, userid);
//		model.addAttribute(&quot;name&quot;, username);
//	}
	
	// 위의 경우 id 값이 없거나 변환 불가능하면 에러 발생한다.
	// @RequestParam(value=&quot;test&quot;, required=false, defaultValue=&quot;0&quot;) 을 이용하면 가능하긴 하다.		
	// 또한, @RequestParam 과 Map&amp;lt;name, value&amp;gt; 을 사용하면 된다. 	
	
	public void findMember(Model model,
			@RequestParam Map&amp;lt;String, String&amp;gt; map
			) {
		
		model.addAttribute(&quot;id&quot;, map.get(&quot;id&quot;));
		model.addAttribute(&quot;name&quot;, map.get(&quot;name&quot;));
	}
	
	// ---------------------------------------------------
	// 커맨드 객체 (Command object) 사용
	
	// 게시글 등록 form
	@RequestMapping(&quot;/board/write&quot;)
	public void writeBoard() {}
	
	
	@PostMapping(&quot;/board/writeOk&quot;)
//	public void writeOkBoard(Model model,
//			String name,
//			String subject,
//			String content
//			) {
//		
//		WriteDTO dto = WriteDTO.builder()
//				.name(name)
//				.subject(subject)
//				.content(content)
//				.build();
//		
//		model.addAttribute(&quot;dto&quot;, dto);
//		
//	}
	// 커맨드 객체 사용방식
	// 코드 작업량이 매우 줄어든다. (성능도 더 좋다)
//	public void writeOkBoard(WriteDTO dto) {
	
//  @ModelAttribute : 커맨드 객체의 model attribute 이름을 변경할수 있다.
	public void writeOkBoard(@ModelAttribute(&quot;DTO&quot;) WriteDTO dto) {
		System.out.println(dto);
	}
	
	//-------------------------------------------------
	// @PathVariable
//	@RequestMapping(&quot;/board/writePath/{name}/{subject}/{content}&quot;)
//	public String writePathBoard(Model model,
//			@PathVariable String name,
//			@PathVariable String subject,
//			@PathVariable String content
//			) {
	@RequestMapping(&quot;/board/writePath/{k1}/{k2}/{k3}&quot;)
	public String writePathBoard(Model model,
			@PathVariable(name=&quot;k1&quot;) String name,
			@PathVariable(name=&quot;k2&quot;) String subject,
			@PathVariable(name=&quot;k3&quot;) String content
			) {
		model.addAttribute(&quot;name&quot;, name);
		model.addAttribute(&quot;subject&quot;, subject);
		model.addAttribute(&quot;content&quot;, content);
		return &quot;board/writepath&quot;;
	}
	
	
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;* parameter :&amp;nbsp;&amp;nbsp;HttpServletRequest 매개변수&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String id = request.getParameter(&quot;id&quot;);&lt;br /&gt;-&amp;gt; request 안의 parameter 값을 뽑아서 Model에 담아 봅니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@RequestMapping(value=&quot;/member/delete&quot;)  // @RequestMapping: GET, POST, PUT, PATCH, DELETE,.. 어떠한 방식의 request 이든 관계없이 동작
//	public void delMember(HttpServletRequest request, Model model) {
	public void delMember(Model model, HttpServletRequest request) {  // ★ 핸들어의 매개변수 순서 관계 없다!
		String id = request.getParameter(&quot;id&quot;);
		model.addAttribute(&quot;mbId&quot;, id);
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핸들러 메소드에&amp;nbsp;&amp;nbsp;HttpServletRequest 매개변수 받을 수 있다.&lt;br /&gt;이 안에는 parameter 라든지 여러가지 request 관련 정보들이 있다. &lt;br /&gt;JSP/Servlet&amp;nbsp;에서&amp;nbsp;배웠던&amp;nbsp;바로&amp;nbsp;그&amp;nbsp;객체이기&amp;nbsp;때문에&amp;nbsp;관련&amp;nbsp;메소드&amp;nbsp;그대로&amp;nbsp;사용&amp;nbsp;가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #695d46;&quot;&gt;* member/delete.jsp&lt;/span&gt;&lt;span style=&quot;color: #695d46;&quot;&gt; 파일 작성&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #695d46;&quot;&gt;당연히&amp;nbsp;forwarding&amp;nbsp;된&amp;nbsp;JSP니까&amp;nbsp;request&amp;nbsp;객체&amp;nbsp;그대로도&amp;nbsp;parameter사용&amp;nbsp;가능&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot;
    pageEncoding=&quot;UTF-8&quot;%&amp;gt;
${mbId}&amp;lt;br&amp;gt;
&amp;lt;%= request.getParameter(&quot;id&quot;) %&amp;gt;&amp;lt;br&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 결과 확인&lt;/b&gt; &lt;br /&gt;/member/delete?id=55&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;POST,&amp;nbsp;PUT,&amp;nbsp;DELETE&amp;nbsp;..&amp;nbsp;방식&amp;nbsp;request&amp;nbsp;받기&lt;/b&gt; &lt;br /&gt;기본적으로&amp;nbsp;@RequestMapping&amp;nbsp;은&amp;nbsp;GET&amp;nbsp;방식에&amp;nbsp;동작함. &lt;br /&gt;만약&amp;nbsp;POST&amp;nbsp;나&amp;nbsp;PUT,&amp;nbsp;DELETE&amp;nbsp;같은&amp;nbsp;다른&amp;nbsp;방식으로&amp;nbsp;동작하게&amp;nbsp;하려면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;POST&amp;nbsp;방식&amp;nbsp;request&amp;nbsp;받기 &lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;//@RequestMapping(value=&quot;/member/regOk&quot;, method= RequestMethod.POST)  // POST 방식 request 일때만 처리
	@PostMapping(&quot;/member/regOk&quot;)
	public void registPost() {
		System.out.println(&quot;/member/regOk : POST&quot;);
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;member/regOk.jsp&amp;nbsp;생성,&amp;nbsp;작성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot;
    pageEncoding=&quot;UTF-8&quot;%&amp;gt;
&amp;lt;%= request.getParameter(&quot;name&quot;) %&amp;gt;&amp;lt;br&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;우선&amp;nbsp;resist.jsp와&amp;nbsp;핸들러&amp;nbsp;작성&lt;/b&gt; &lt;br /&gt;다양한&amp;nbsp;방식의&amp;nbsp;method&amp;nbsp;로&amp;nbsp;request&amp;nbsp;하기&amp;nbsp;위한&amp;nbsp;폼을&amp;nbsp;만들어&amp;nbsp;보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;컨트롤러&amp;nbsp;작성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@RequestMapping(&quot;/member/regist&quot;)
	public void registMember() {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;member/regist.jsp&amp;nbsp;작성&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;accesslog&quot;&gt;&lt;code&gt;&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot;
    pageEncoding=&quot;UTF-8&quot;%&amp;gt;
&amp;lt;%-- action 의 url 과 method 주목 ! --%&amp;gt;
&amp;lt;form action=&quot;regOk&quot; method=&quot;GET&quot;&amp;gt;
	&amp;lt;input type=&quot;text&quot; name=&quot;name&quot; value=&quot;GET&quot;/&amp;gt;
	&amp;lt;input type=&quot;submit&quot; value=&quot;GET요청&quot;/&amp;gt;
&amp;lt;/form&amp;gt;
&amp;lt;hr&amp;gt;
&amp;lt;form action=&quot;regOk&quot; method=&quot;POST&quot;&amp;gt;
	&amp;lt;input type=&quot;text&quot; name=&quot;name&quot; value=&quot;POST&quot;/&amp;gt;
	&amp;lt;input type=&quot;submit&quot; value=&quot;POST요청&quot;/&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@RequestMapping(value=&quot;/member/regOk&quot;,&amp;nbsp;method=&amp;nbsp;RequestMethod.POST) &lt;br /&gt;@RequestMapping(value=&quot;/member/regOk&quot;,&amp;nbsp;method=&amp;nbsp;RequestMethod.GET)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &amp;gt;&amp;nbsp; 동일 request url&amp;nbsp;&amp;nbsp;&amp;amp;&lt;b&gt; 다른 request method를 다른 handler로 처리&lt;/b&gt;&amp;nbsp;하기 &lt;br /&gt;동일한 request url 이지만 request method가 다르면 , 다른 메소드로 request mapping 가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@GetMapping,&amp;nbsp;@PostMapping&lt;/b&gt;&amp;nbsp;...&amp;nbsp;-&amp;nbsp;Spring&amp;nbsp;4.3부터&amp;nbsp;등장 &lt;br /&gt;다음은&amp;nbsp;동일하게&amp;nbsp;동작한다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;	//@RequestMapping(value=&quot;/member/regOk&quot;, method= RequestMethod.POST)  // POST 방식 request 일때만 처리
	@PostMapping(&quot;/member/regOk&quot;)
	public void registPost() {
		System.out.println(&quot;/member/regOk : POST&quot;);
	}
	
//	@RequestMapping(value=&quot;/member/regOk&quot;, method= RequestMethod.GET)
	@GetMapping(&quot;/member/regOk&quot;)
	public void registGet() {
		System.out.println(&quot;/member/regOk : GET&quot;);
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* post 와 get 둘다 받으려면?&lt;/b&gt; &lt;br /&gt;GET&amp;nbsp;/&amp;nbsp;POST&amp;nbsp;둘다&amp;nbsp;받는&amp;nbsp;request&amp;nbsp;&amp;nbsp;/member/regOk2&amp;nbsp;설정&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@RequestMapping(value=&quot;/member/regOk2&quot;, method= {RequestMethod.GET, RequestMethod.POST})
	public String regOkMember2(HttpServletRequest request) {
		System.out.println(&quot;/membre/regOk2 : &quot; + request.getMethod());
		return &quot;member/regOk&quot;;
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;register.jsp&amp;nbsp;파일에도&amp;nbsp;추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* Put, Delete 방식 request.&lt;/b&gt; &lt;br /&gt;기본적으로&amp;nbsp;&amp;lt;form&amp;gt;&amp;nbsp;의&amp;nbsp;method&amp;nbsp;는&amp;nbsp;get,&amp;nbsp;post&amp;nbsp;방식만&amp;nbsp;지원. &lt;br /&gt;서버&amp;nbsp;내부의&amp;nbsp;JSP&amp;nbsp;&amp;nbsp;요청도&amp;nbsp;get,&amp;nbsp;post,&amp;nbsp;head&amp;nbsp;만&amp;nbsp;지원.&amp;nbsp;&amp;rarr;&amp;nbsp;405&amp;nbsp;&amp;nbsp;에러&amp;nbsp;발생 &lt;br /&gt;Postman&amp;nbsp;등의&amp;nbsp;툴을&amp;nbsp;활용하여&amp;nbsp;request&amp;nbsp;해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URL 매핑은 정상적으로 이루어진다 JSP 요청(forwarding)이 안될 뿐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;매개변수로&amp;nbsp;request&amp;nbsp;parameter&amp;nbsp;받기&lt;/b&gt; &lt;br /&gt;handler에&amp;nbsp;request&amp;nbsp;parameter&amp;nbsp;의&amp;nbsp;name&amp;nbsp;값과&amp;nbsp;&amp;lsquo;같은&amp;nbsp;이름(name)의&amp;nbsp;매개변수&amp;rsquo;&amp;nbsp;있으면&amp;nbsp; &lt;br /&gt;바로&amp;nbsp;그&amp;nbsp;매개변수가&amp;nbsp;&amp;nbsp;request&amp;nbsp;parameter&amp;nbsp;값을&amp;nbsp;받아온다 &lt;br /&gt;(내부적으로&amp;nbsp;스프링이&amp;nbsp;알아서&amp;nbsp;주입하는&amp;nbsp;셈이다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt; @RequestMapping(&quot;/member/find&quot;) &lt;/i&gt;&lt;br /&gt;&lt;i&gt;public&amp;nbsp;void&amp;nbsp;findMember(String&amp;nbsp;id,&amp;nbsp;String&amp;nbsp;name,&amp;nbsp;Model&amp;nbsp;model)&amp;nbsp;{ &lt;/i&gt;&lt;br /&gt;&lt;i&gt;public&amp;nbsp;void&amp;nbsp;findMember(Model&amp;nbsp;model,&amp;nbsp;String&amp;nbsp;id,&amp;nbsp;String&amp;nbsp;name)&amp;nbsp;{&amp;nbsp;&amp;nbsp;//&amp;nbsp;&lt;b&gt;매개변수&amp;nbsp;순서에&amp;nbsp;영향&amp;nbsp;받지&amp;nbsp;않는다. &lt;/b&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;public&amp;nbsp;void&amp;nbsp;findMember(double&amp;nbsp;id,&amp;nbsp;String&amp;nbsp;name,&amp;nbsp;Model&amp;nbsp;model)&amp;nbsp;{&amp;nbsp;&amp;nbsp;//&amp;nbsp;숫자타입이면,&amp;nbsp;해당타입으로&amp;nbsp;변환하여&amp;nbsp;binding &lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;public&amp;nbsp;void&amp;nbsp;findMember(Double&amp;nbsp;id,&amp;nbsp;String&amp;nbsp;name,&amp;nbsp;Model&amp;nbsp;model)&amp;nbsp;{&amp;nbsp;//&amp;nbsp;&amp;nbsp;Wrapper&amp;nbsp;타입을&amp;nbsp;사용하면&amp;nbsp;id&amp;nbsp;parameter&amp;nbsp;가&amp;nbsp;없는&amp;nbsp;경우에도&amp;nbsp;에러&amp;nbsp;없이&amp;nbsp;동작 &lt;/i&gt;&lt;br /&gt;&lt;i&gt;System.out.println(&quot;member/find:&amp;nbsp;id=&quot;&amp;nbsp;+&amp;nbsp;id&amp;nbsp;+&amp;nbsp;&quot;,&amp;nbsp;name=&quot;&amp;nbsp;+&amp;nbsp;name); &lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;model.addAttribute(&quot;id&quot;,&amp;nbsp;id); &lt;/i&gt;&lt;br /&gt;&lt;i&gt;model.addAttribute(&quot;name&quot;,&amp;nbsp;name); &lt;/i&gt;&lt;br /&gt;&lt;i&gt;}&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 데이터 바인딩 (Data Binding)&lt;/b&gt; &lt;br /&gt;&quot;프로퍼티&amp;nbsp;값을&amp;nbsp;타겟&amp;nbsp;객체에&amp;nbsp;설정해주는&amp;nbsp;것&quot; &lt;br /&gt;사용자가 입력한 값을 애플리케이션 도메인 객체에 동적으로 할당하는 기능다. Spring에서는 사용자가 입력한 값은 '문자열'이고 도메인 객체에 맞는 자료형으로 변경 필요하기 때문에 추상화되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id=&quot;aaa&quot; name= &quot;bbb&quot; -&amp;gt; &lt;b&gt;Request&lt;/b&gt; -&amp;gt; Dispatcher Servlet -&amp;gt; &lt;b&gt;Binding&lt;/b&gt; -&amp;gt; String id String name&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Controller&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;숫자타입으로&amp;nbsp;request&amp;nbsp;parameter&amp;nbsp;받기&lt;/b&gt; &lt;br /&gt;스프링은&amp;nbsp;request&amp;nbsp;parameter&amp;nbsp;를&amp;nbsp;받아올때,&amp;nbsp;&amp;nbsp;handler&amp;nbsp;의&amp;nbsp;매개변수&amp;nbsp;타입으로&amp;nbsp;형변환,&amp;nbsp;&amp;nbsp;parsing&amp;nbsp;가능하면&amp;nbsp;변환해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가급적&amp;nbsp;Wrapper&amp;nbsp;타입으로&amp;nbsp;parameter&amp;nbsp;받자 &lt;br /&gt;parameter&amp;nbsp;가&amp;nbsp;없는&amp;nbsp;경우에도&amp;nbsp;에러없이&amp;nbsp;동작하니까.&amp;nbsp;&amp;nbsp;(null&amp;nbsp;값으로&amp;nbsp;동작)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 동일한 name의 request parameter 복수개&lt;/b&gt; &lt;br /&gt;&lt;b&gt;&amp;lsquo;배열&amp;rsquo;&lt;/b&gt;로&amp;nbsp;받으면&amp;nbsp;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;동일한 name의 request parameter 복수개&lt;/i&gt; &lt;br /&gt;public&amp;nbsp;void&amp;nbsp;findMember(Integer[]&amp;nbsp;id,&amp;nbsp;String&amp;nbsp;[]&amp;nbsp;name,&amp;nbsp;Model&amp;nbsp;model)&amp;nbsp;{ &lt;br /&gt;model.addAttribute(&quot;id&quot;,&amp;nbsp;Arrays.toString(id)); &lt;br /&gt;model.addAttribute(&quot;name&quot;,&amp;nbsp;Arrays.toString(name)); &lt;br /&gt;}&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@RequestParam &lt;/b&gt;&lt;br /&gt;상황에 따라서는 handler 메소드의 &amp;lsquo;매개변수 이름&amp;rsquo; 을&amp;nbsp;&amp;nbsp;request parameter 와 일치시켜주기 어려울 때도 있다. &lt;br /&gt;그런경우에는&amp;nbsp;&lt;b&gt;@RequestParam을 사용&lt;/b&gt;하여,&amp;nbsp;&amp;nbsp;request parameter와 다른 이름의 매개변수로&amp;nbsp;&amp;nbsp;받을 수 있다.&lt;i&gt;&lt;br /&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;@RequestParam&amp;nbsp;애노테이션&amp;nbsp;사용&lt;/i&gt; &lt;br /&gt;public&amp;nbsp;void&amp;nbsp;findMember(Model&amp;nbsp;model, &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;@RequestParam(&quot;id&quot;) String userid, // &quot;id&quot; 란 name 의 parameter 값을 userid 매개변수에 binding 해준다 &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;@RequestParam(&quot;name&quot;) String username&amp;nbsp; &lt;br /&gt;)&amp;nbsp;{ &lt;br /&gt;&amp;nbsp; &amp;nbsp; model.addAttribute(&quot;id&quot;, userid); &lt;br /&gt;&amp;nbsp; &amp;nbsp; model.addAttribute(&quot;name&quot;, username); &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@RequestParam&amp;nbsp;+&amp;nbsp;Map&amp;lt;name,&amp;nbsp;value&amp;gt;&lt;/b&gt; &lt;br /&gt;위의&amp;nbsp;경우&amp;nbsp;id&amp;nbsp;값이&amp;nbsp;없거나&amp;nbsp;변환&amp;nbsp;불가능하면&amp;nbsp;에러&amp;nbsp;발생한다. &lt;br /&gt;@RequestParam(value=&quot;text&quot;,&amp;nbsp;required=false,&amp;nbsp;defaultValue=&quot;0&quot;)&amp;nbsp;을&amp;nbsp;이용하면&amp;nbsp;없어도&amp;nbsp;동작&amp;nbsp;가능하긴&amp;nbsp;하다. &lt;br /&gt;또한,&amp;nbsp;@RequestParam&amp;nbsp;과&amp;nbsp;Map&amp;lt;name,&amp;nbsp;value&amp;gt;&amp;nbsp;을&amp;nbsp;사용하면&amp;nbsp;된다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;	public void findMember(Model model,
			@RequestParam Map&amp;lt;String, String&amp;gt; map
			) {
		
		model.addAttribute(&quot;id&quot;, map.get(&quot;id&quot;));
		model.addAttribute(&quot;name&quot;, map.get(&quot;name&quot;));
	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나&amp;nbsp;&amp;lsquo;다량의&amp;nbsp;&amp;nbsp;parameter&amp;rsquo;&amp;nbsp;를&amp;nbsp;받기에&amp;nbsp;Map&amp;lt;&amp;gt;&amp;nbsp;도&amp;nbsp;가능하긴&amp;nbsp;하나&amp;nbsp;개발한&amp;nbsp;사람&amp;nbsp;외에는&amp;nbsp;유지보수&amp;nbsp;어렵다. &lt;br /&gt;그래서&amp;nbsp;다음의&amp;nbsp;&amp;lsquo;커맨더&amp;rsquo;&amp;nbsp;를&amp;nbsp;사용한&amp;nbsp;방식을&amp;nbsp;많이&amp;nbsp;사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* &amp;lsquo;커맨드 객체&amp;rsquo;를 사용한 parameter 전달&lt;/b&gt; &lt;br /&gt;HttpServletRequest&amp;nbsp;를&amp;nbsp;받든지..&amp;nbsp;&amp;nbsp;@RequestParam&amp;nbsp;을&amp;nbsp;사용하든지&amp;hellip; &lt;br /&gt;request&amp;nbsp;parameter&amp;nbsp;자체가&amp;nbsp;많아지면,&amp;nbsp;&amp;nbsp;잔코딩이&amp;nbsp;많아지고,&amp;nbsp;유지보수에&amp;nbsp;어려움이&amp;nbsp;발생한다. &lt;br /&gt;스프링에서는 &amp;lsquo;커맨드 객체( Command Object )&amp;rsquo;를 통해, 여러&amp;nbsp;request&amp;nbsp;parameter&amp;nbsp;들을&amp;nbsp;&amp;lsquo;한번에&amp;rsquo;&amp;nbsp;받아올수&amp;nbsp;있다!! &lt;br /&gt;커맨드 객체란?&amp;nbsp;&amp;nbsp;: form 으로 부터 넘어오는 parameter들을 담는 &amp;lsquo;빈(bean)&amp;rsquo; 객체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨트롤러&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;	// 커맨드 객체 (Command object) 사용
	
	// 게시글 등록 form
	@RequestMapping(&quot;/board/write&quot;)
	public void writeBoard() {}
	
	
	@PostMapping(&quot;/board/writeOk&quot;)
//	public void writeOkBoard(Model model,
//			String name,
//			String subject,
//			String content
//			) {
//		
//		WriteDTO dto = WriteDTO.builder()
//				.name(name)
//				.subject(subject)
//				.content(content)
//				.build();
//		
//		model.addAttribute(&quot;dto&quot;, dto);
//		
//	}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;writeOk.jsp&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot;
    pageEncoding=&quot;UTF-8&quot;%&amp;gt;
&amp;lt;%--
${dto}
&amp;lt;hr&amp;gt;
작성자: ${dto.name }&amp;lt;br&amp;gt;
글제목: ${dto.subject}&amp;lt;br&amp;gt;
내용: ${dto.content}&amp;lt;br&amp;gt; 
 --%&amp;gt;
&amp;lt;%-- 
작성자: ${writeDTO.name}&amp;lt;br&amp;gt;
글제목: ${writeDTO.subject}&amp;lt;br&amp;gt;
내용: ${writeDTO.content}&amp;lt;br&amp;gt; 
uid: ${writeDTO.uid }&amp;lt;br&amp;gt;
조회수: ${writeDTO.viewCnt }&amp;lt;br&amp;gt;
등록일: ${writeDTO.regDate }&amp;lt;br&amp;gt;
 --%&amp;gt;
 
작성자: ${DTO.name}&amp;lt;br&amp;gt;
글제목: ${DTO.subject}&amp;lt;br&amp;gt;
내용: ${DTO.content}&amp;lt;br&amp;gt; 
uid: ${DTO.uid }&amp;lt;br&amp;gt;
조회수: ${DTO.viewCnt }&amp;lt;br&amp;gt;
등록일: ${DTO.regDate }&amp;lt;br&amp;gt;

&amp;lt;button onclick=&quot;history.back()&quot;&amp;gt;이전으로&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커맨드 객체 사용&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;커맨드&amp;nbsp;객체(Command&amp;nbsp;Object)&amp;nbsp;handler&amp;nbsp;메소드&amp;nbsp;매개변수로&amp;nbsp;명시하면 &lt;br /&gt;스프링&amp;nbsp;내부에서&amp;nbsp;빈(bean)&amp;nbsp;객체가&amp;nbsp;생성되고&amp;nbsp;request&amp;nbsp;parameter&amp;nbsp;들을&amp;nbsp;담습니다. &lt;br /&gt;&lt;br /&gt;스프링&amp;nbsp;내부에서&amp;nbsp;WriteDTO&amp;nbsp;타입의&amp;nbsp;빈(bean)&amp;nbsp;객체가&amp;nbsp;생성되어,&amp;nbsp;&amp;nbsp;request&amp;nbsp;parameter에&amp;nbsp;대해&amp;nbsp;적절한&amp;nbsp;setter&amp;nbsp;가&amp;nbsp;호출되어&amp;nbsp;세팅됨. &lt;br /&gt;그리고&amp;nbsp;Model&amp;nbsp;에&amp;nbsp;addAttribute()&amp;nbsp;되어&amp;nbsp;더해짐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;writeOk.jsp&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EL 에 사용된 객체의&amp;nbsp;이름은 handler 매개변수 이름(dto) 가 아니라, 커맨드객체 &amp;lsquo;타입&amp;rsquo; 입니다.&lt;br /&gt;WriteDTO&amp;nbsp;타입의&amp;nbsp;bean&amp;nbsp;객체가&amp;nbsp;스프링&amp;nbsp;내부에&amp;nbsp;writeDTO&amp;nbsp;라는&amp;nbsp;id&amp;nbsp;로&amp;nbsp;생성된겁니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;용어&amp;nbsp;문제&lt;/b&gt; &lt;br /&gt;&amp;lsquo;JSP MVC 모델2&amp;rsquo; 의 경우 컨트롤러에서&amp;nbsp;수행하는&amp;nbsp;것을&amp;nbsp;Command&amp;nbsp;객체라&amp;nbsp;불렀습니다. &lt;br /&gt;(스프링에서는 (나중에 언급되겠지만) 그와 같은 역할을 하는 객체를 &lt;b&gt;Service&lt;/b&gt; 라고 도 합니다. 그러나 혼용되는게 문제) &lt;br /&gt;한편,&amp;nbsp;&amp;lsquo;스프링&amp;rsquo;에서&amp;nbsp;&amp;lsquo;커맨드&amp;nbsp;객체&amp;rsquo;라&amp;nbsp;함은&amp;nbsp;지금&amp;nbsp;배우고&amp;nbsp;있는&amp;nbsp;parameter&amp;nbsp;받기&amp;nbsp;위한&amp;nbsp;&amp;nbsp;bean&amp;nbsp;객체&amp;nbsp;입니다 &lt;br /&gt;동일한&amp;nbsp;용어로&amp;nbsp;사용되는&amp;nbsp;것들이&amp;nbsp;있으니,&amp;nbsp;&amp;nbsp;&amp;nbsp;혼선일으키지&amp;nbsp;않도록,&amp;nbsp;문맥을&amp;nbsp;잘&amp;nbsp;살핍시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관찰:&amp;nbsp;&amp;nbsp;커맨드&amp;nbsp;객체에&amp;nbsp;넘어오지&amp;nbsp;않은&amp;nbsp;필드값은&amp;nbsp;?&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@ModelAttribute&lt;/b&gt; &lt;br /&gt;기본적으로&amp;nbsp;커맨드&amp;nbsp;객체는&amp;nbsp;객체의&amp;nbsp;타입명이&amp;nbsp;&amp;nbsp;model의&amp;nbsp;&amp;lsquo;attribute&amp;rsquo;가&amp;nbsp;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커맨드 객체의 model 의 &amp;lsquo;attribute&amp;rsquo; 이름을 바꾸어 주기 위해선 @ModelAttribute를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제&amp;nbsp;Model&amp;nbsp;에는&amp;nbsp;&amp;ldquo;DTO&amp;rdquo;&amp;nbsp;라는&amp;nbsp;id&amp;nbsp;로&amp;nbsp;등록되어,&amp;nbsp;EL&amp;nbsp;에서&amp;nbsp;사용&amp;nbsp;가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BindException&lt;/b&gt; &lt;br /&gt;binding&amp;nbsp;:&amp;nbsp;parameter&amp;nbsp;들이&amp;nbsp;java&amp;nbsp;객체(command&amp;nbsp;객체&amp;nbsp;)에&amp;nbsp;담는&amp;nbsp;동작.&amp;nbsp;&amp;nbsp;이&amp;nbsp;와중에&amp;nbsp;담길수&amp;nbsp;없는&amp;nbsp;(변환할수&amp;nbsp;없는)&amp;nbsp;상황이&amp;nbsp;발생하면&amp;nbsp;에러&amp;nbsp;발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@PathVariable&lt;/b&gt; &lt;br /&gt;request parameter를 GET 방식의 query string 이 아닌 &amp;lsquo;URL&amp;nbsp;경로&amp;rsquo;&amp;nbsp;에&amp;nbsp;담아서&amp;nbsp;전달할수도&amp;nbsp;있다!&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RequestMapping(&quot;/board/writePath/{k1}/{k2}/{k3}&quot;)
	public String writePathBoard(Model model,
			@PathVariable(name=&quot;k1&quot;) String name,
			@PathVariable(name=&quot;k2&quot;) String subject,
			@PathVariable(name=&quot;k3&quot;) String content
			) {
		model.addAttribute(&quot;name&quot;, name);
		model.addAttribute(&quot;subject&quot;, subject);
		model.addAttribute(&quot;content&quot;, content);
		return &quot;board/writepath&quot;;
	}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;리다이렉트&amp;nbsp;redirect:&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/b&gt; &lt;br /&gt;handler&amp;nbsp;메소드의&amp;nbsp;뷰&amp;nbsp;리턴값&amp;nbsp;(문자열)&amp;nbsp;에&amp;nbsp;redirect:&amp;nbsp;를&amp;nbsp;사용하면&amp;nbsp;&amp;nbsp;해당&amp;nbsp;URL&amp;nbsp;로&amp;nbsp;redirect&amp;nbsp;된다. &lt;br /&gt;&lt;b&gt;RedirectAttributes&amp;nbsp;&lt;/b&gt; &lt;br /&gt;리다이렉트로&amp;nbsp;parameter&amp;nbsp;넘기기 &lt;br /&gt;스프링에서&amp;nbsp;제공하는&amp;nbsp;RedirectAttributes&amp;nbsp;객체를&amp;nbsp;사용해서&amp;nbsp;리다이텍트&amp;nbsp;되는&amp;nbsp;URL&amp;nbsp;로&amp;nbsp;parameter&amp;nbsp;를&amp;nbsp;넘겨줄수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* redirect, forward 방법 총정리&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;redirect&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;forward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Servlet&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;response.sendRedirect(URL)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;RequestDispatcher dispatcher = request.getRequestDispatcher(URL);&lt;br /&gt;dispatcher.forward(request, response);&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;JSP&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;response.sendRedirect(URL)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;&amp;lt;jsp:forward page=&quot;URL&quot;&amp;gt;&lt;br /&gt;pageContext.forward(URL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Javascript&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;location.href=URL&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;클라이언트에선 forward란게 있을 수 없다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Spring&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;handler 메소드에서&lt;br /&gt;return &quot;redirect:URL&quot;&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;handler 메소드에서&lt;br /&gt;return &quot;forward:URL&quot;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>Spring</category>
      <author>shb</author>
      <guid isPermaLink="true">https://shprogramming.tistory.com/143</guid>
      <comments>https://shprogramming.tistory.com/143#entry143comment</comments>
      <pubDate>Thu, 12 May 2022 18:03:38 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] RequestMapping</title>
      <link>https://shprogramming.tistory.com/142</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;@Controller&amp;nbsp;~&amp;nbsp;@RequestMapping &lt;br /&gt;RequestMapping&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 어플리케이션은 원하는 형태의&lt;b&gt; request URL&lt;/b&gt;을 받아서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 &lt;b&gt;URL을 처리하는 &lt;u&gt;동작(handler)를 찾아서 (mapping) 수행&lt;/u&gt;&lt;/b&gt;&lt;u&gt;하고 &lt;/u&gt;&lt;br /&gt;&lt;u&gt;&lt;b&gt;원하는&amp;nbsp;&amp;lsquo;뷰&amp;rsquo;&amp;nbsp;로&amp;nbsp;response&amp;nbsp;&lt;/b&gt;해주기&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용어: 메소드? 핸들러? 컨트롤러?&lt;br /&gt;@Controller 클래스 안의 &lt;b&gt;@RequestMapping 등으로 지정된 메소드&lt;/b&gt;를&amp;nbsp;&lt;b&gt;핸들러(Handler)&lt;/b&gt;&amp;nbsp;라고도&amp;nbsp;함. &lt;br /&gt;그러나, 보통 혼용되어 이야기 됨.&lt;br /&gt;컨트롤러 이야기 할때 메소드, 핸들러, 컨트롤러&amp;hellip;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;larr;&amp;nbsp;&amp;nbsp;대체로 같은것을 지칭하는 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 기본적인 Controller 클래스 제작 흐름&lt;/b&gt; &lt;br /&gt;&lt;b&gt;@Controller를&amp;nbsp;이용한&amp;nbsp;클래스&amp;nbsp;생성&lt;/b&gt; &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;darr;&lt;br /&gt;&amp;nbsp;&lt;b&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;요청 경로 지정 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;@RequestMapping,&amp;nbsp;@GetMapping&lt;/b&gt;&amp;nbsp;&amp;hellip;.&amp;nbsp;-&amp;nbsp;다양한&amp;nbsp;방법의&amp;nbsp;&lt;b&gt;'&lt;u&gt;경로지정'&lt;/u&gt;&lt;/b&gt; &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;darr;&lt;br /&gt;&lt;b&gt;요청&amp;nbsp;처리&amp;nbsp;메소드&amp;nbsp;(handler)&amp;nbsp;구현&lt;/b&gt;&amp;nbsp;-&amp;nbsp;다양한&amp;nbsp;방법의&amp;nbsp;매개변수&amp;nbsp;지정 &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;darr;&lt;br /&gt;&lt;b&gt;&amp;ldquo;뷰&amp;nbsp;이름&amp;rdquo;&amp;nbsp;&amp;nbsp;혹은&amp;nbsp;ModelAndView&amp;nbsp;리턴&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;URL&amp;nbsp;mapping&amp;nbsp;&lt;/b&gt;&amp;harr;&amp;nbsp;&amp;nbsp;메소드&amp;nbsp;이름&amp;nbsp;&amp;harr;&amp;nbsp;뷰이름 &lt;br /&gt;HomeController.java에&amp;nbsp;&amp;nbsp;핸들러 메소드 작성 &lt;br /&gt;URL mapping , handler(메소드) 이름, 뷰 이름 리턴값&amp;nbsp;&amp;nbsp;&amp;larr; 굳이 같을 필요는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BoardController.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;package com.lec.spring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping(&quot;/board&quot;)
public class BoardController {
	
		
	@RequestMapping(&quot;/list&quot;)   // request --&amp;gt;  /board/list
	public String listBoard() {
		return &quot;board/list_board&quot;;
	}
	
	@RequestMapping(&quot;/write&quot;)
	public String writeBoard() {
		return &quot;board/write_board&quot;;
	}

	
	@RequestMapping(&quot;/view&quot;)  // request --&amp;gt; /board/view
	public String viewBoard() {
		return &quot;board/view_board&quot;;
	}
	
	@RequestMapping(&quot;/update&quot;)
	public String updateBoard() {
		return &quot;board/update_board&quot;;
	}
	
	@RequestMapping(&quot;/delete&quot;)
	public String deleteBoard() {
		return &quot;board/delete_board&quot;;
	}
	
} // end Controller&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MemberController.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;package com.lec.spring.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping(value = &quot;/member&quot;)    // -&amp;gt; /member 로 시작하는 request 를 처리
public class MemberController {

	
	@RequestMapping(value = &quot;/save&quot;)  //  URL -&amp;gt;  /member + /save
	public String saveMember() {      //         =&amp;gt;  /member/save
		return &quot;member/save&quot;;
	}
	
	@RequestMapping(value = &quot;/load&quot;)
	public String loadMember() {
		return &quot;member/load&quot;;        //         =&amp;gt; /member/load
	}
	
	// 서버 가동중 에러 발생!
	// mapping url 중복.
//	@RequestMapping(value = &quot;/search&quot;)
//	public String searchMember() {
//		return &quot;member/search&quot;;      //        =&amp;gt; /member/search          
//	}
	
	
	// 핸들러 리턴값이 void 이면
	// mapping 되는 url문자열에 해당하는 .jsp 파일로 forwarding 된다
	// (일반적으로 많이 사용)
	@RequestMapping(&quot;/remove&quot;)
	public void removeMember() {     //  URL =&amp;gt;  /member/remove
		                             //  JSP =&amp;gt;  /member/remove.jsp
	}
	
	
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lsquo;뷰&amp;rsquo;&amp;nbsp;에&amp;nbsp;데이터&amp;nbsp;전달&amp;nbsp;:&amp;nbsp;Model&amp;nbsp;매개변수&lt;/b&gt; &lt;br /&gt;&lt;b&gt;Model은 매개변수&lt;/b&gt;로 넘겨진다.&amp;nbsp; &amp;lsquo;스프링 쪽&amp;rsquo;에서 넘겨주는 것이다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핸들러 메소드는 Model 에 담기만 하면, 스프링이 DispatcherServlet에 넘겨준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;향후&amp;nbsp;트랜잭션들을&amp;nbsp;수행한&amp;nbsp;결과를&amp;nbsp;&lt;b&gt;model&amp;nbsp;에&amp;nbsp;담아서&amp;nbsp;뷰에&amp;nbsp;넘길수&amp;nbsp;있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lsquo;뷰&amp;rsquo;&amp;nbsp;+&amp;nbsp;&amp;lsquo;Model&amp;rsquo;&amp;nbsp;&amp;nbsp;:&amp;nbsp;ModelAndView&amp;nbsp;리턴&lt;/b&gt; &lt;br /&gt;ModelAndView&amp;nbsp;에는&amp;nbsp;데이터들도&amp;nbsp;담고&amp;nbsp;&amp;lsquo;뷰&amp;rsquo;&amp;nbsp;도&amp;nbsp;지정하고&amp;nbsp;그리고&amp;nbsp;리턴되면&amp;nbsp;OK&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;확장자 패턴 가능&amp;nbsp;&amp;nbsp;*&lt;/b&gt;&lt;br /&gt;@RequestMapping(value&amp;nbsp;=&amp;nbsp;&quot;/member/*.do&quot;) &lt;br /&gt;public&amp;nbsp;String&amp;nbsp;doMember()&amp;nbsp;{ &lt;br /&gt;return&amp;nbsp;&quot;member/domember&quot;; &lt;br /&gt;}&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;b&gt;핸들러&amp;nbsp;메소드&amp;nbsp;동작&lt;/b&gt;은 &lt;br /&gt;1.&amp;nbsp;&lt;b&gt;@RequestMapping&amp;nbsp;으로&amp;nbsp;매핑된&amp;nbsp;request&lt;/b&gt;가&amp;nbsp;들어오면 &lt;br /&gt;2. &lt;b&gt;필요한 동작들(ex: 트랜잭션)&lt;/b&gt; 수행,&amp;nbsp;&amp;nbsp;&lt;b&gt;DAO 혹은 @Service 등의 객체&lt;/b&gt; 사용 &lt;br /&gt;3.&amp;nbsp;결과는&lt;b&gt;&amp;nbsp;Model에&amp;nbsp;담고,&amp;nbsp;뷰(View)&amp;nbsp;지정&amp;nbsp;&lt;/b&gt;&amp;nbsp;&amp;rArr;&amp;nbsp;합해서&amp;nbsp;ModelAndView&amp;nbsp;로&amp;nbsp;담아&amp;nbsp;리턴&amp;nbsp;가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨트롤러 클래스에 @RequestMapping !&lt;/b&gt;&lt;br /&gt;일반적으로 request URL 체계도,&amp;nbsp;&amp;nbsp;다루는 아이템별로 다루게 되는데&amp;hellip; &lt;br /&gt;가령&amp;nbsp; &lt;br /&gt;회원관리&amp;nbsp;URL&amp;nbsp;형태는&amp;nbsp;/member/○○○ &lt;br /&gt;상품관리&amp;nbsp;URL&amp;nbsp;형태는&amp;nbsp;/goods/○○○ &lt;br /&gt;&lt;br /&gt;이런식으로&amp;nbsp;request&amp;nbsp;URL&amp;nbsp;이&amp;nbsp;기획되어&amp;nbsp;관리될&amp;nbsp;것이다. &lt;br /&gt;그런데, 과연 이 모든 handler 가&amp;nbsp;하나의 @Controller 클래스에 있으면 체계적인&amp;nbsp;관리가&amp;nbsp;힘들다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;컨트롤러 클래스 작성&lt;/b&gt;&lt;br /&gt;패키지&amp;nbsp;:&amp;nbsp;{base-package}.controller &lt;br /&gt;클래스명&amp;nbsp;:&amp;nbsp;MemberController&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Controller&amp;nbsp;클래스&amp;nbsp;추가하면,&amp;nbsp;반드시&amp;nbsp;서버&amp;nbsp;재시작&amp;nbsp; &lt;br /&gt;DevTool&amp;nbsp;사용시는&amp;nbsp;굳이&amp;nbsp;할필요&amp;nbsp;없음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약&amp;nbsp;&amp;nbsp;@RequestMapping 중복되면?&lt;br /&gt;애시당초&amp;nbsp;서버&amp;nbsp;가동&amp;nbsp;중에&amp;nbsp;&amp;nbsp;&lt;b&gt;Exception&amp;nbsp;발생&lt;/b&gt;한다 &lt;br /&gt;/member/search로&amp;nbsp;request&amp;nbsp;해보자&lt;/p&gt;</description>
      <category>Spring</category>
      <author>shb</author>
      <guid isPermaLink="true">https://shprogramming.tistory.com/142</guid>
      <comments>https://shprogramming.tistory.com/142#entry142comment</comments>
      <pubDate>Thu, 12 May 2022 15:59:46 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] MVC</title>
      <link>https://shprogramming.tistory.com/141</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트는 웹루트가 (마치) 3개인 것처럼 동작한다.&lt;br /&gt;/static &lt;br /&gt;/templates &lt;br /&gt;/webapp&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static&amp;nbsp;:&amp;nbsp;정적&amp;nbsp;웹소스&amp;nbsp;,&amp;nbsp;JS,&amp;nbsp;css,&amp;nbsp;html,&amp;nbsp;이미지&amp;nbsp;... &lt;br /&gt;템플릿&amp;nbsp;:&amp;nbsp;Thymeleaf,&amp;nbsp;Mustache&amp;nbsp;... &lt;br /&gt;applicaiotn.properties&amp;nbsp;:&amp;nbsp;스프링부트&amp;nbsp;설정파일 &lt;br /&gt;webapp(웹루트)&amp;nbsp;:&amp;nbsp;JSP들... &lt;br /&gt;pom.xml&amp;nbsp;:&amp;nbsp;메이븐&amp;nbsp;설정파일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;src/main/resources&amp;nbsp;의&amp;nbsp;static&amp;nbsp;폴더&lt;/b&gt; &lt;br /&gt;이미지,&amp;nbsp;JS,&amp;nbsp;css,&amp;nbsp;html&amp;nbsp;&amp;nbsp;&amp;larr;&amp;nbsp;이런&amp;nbsp;파일들을&amp;nbsp;위한&amp;nbsp;홈&amp;nbsp;디렉토리 &lt;br /&gt;static&amp;nbsp;(정적)&amp;nbsp;이다&amp;nbsp;함은&amp;nbsp;서버입장에서&amp;nbsp;별도의&amp;nbsp;프로세스&amp;nbsp;없이&amp;nbsp;그대로&amp;nbsp;response만&amp;nbsp;하는&amp;nbsp;파일들 &lt;br /&gt;&lt;br /&gt;스프링 부트에선 홈 디렉토리가 3개라 보시면 됨&lt;br /&gt;&lt;b&gt;src/main/resource/static&amp;nbsp;&amp;nbsp;&lt;/b&gt;&amp;nbsp;&amp;larr;&amp;nbsp;정적인&amp;nbsp;파일들 &lt;br /&gt;&lt;b&gt;src/main/resource/templates&amp;nbsp;&lt;/b&gt;&amp;nbsp;&amp;nbsp;&amp;larr;&amp;nbsp;템플릿&amp;nbsp;파일들 &lt;br /&gt;&lt;b&gt;src/main/webapp&lt;/b&gt;&amp;nbsp;&amp;nbsp;&amp;larr;&amp;nbsp;동적인&amp;nbsp;파일들&amp;nbsp;(jsp&amp;nbsp;..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Jasper 라이브러리 추가하기 : 방법1&lt;/b&gt;&lt;br /&gt;Mavern&amp;nbsp;Reporitories&amp;nbsp;view&amp;nbsp;에서 &lt;br /&gt;&amp;lsquo;index&amp;nbsp;build&amp;nbsp;&amp;rsquo;&amp;nbsp;&amp;nbsp;가&amp;nbsp;되어&amp;nbsp;있다면&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;가능..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pom.xml&amp;nbsp;을&amp;nbsp;수정하면&amp;nbsp;꼭&amp;nbsp;Maven&amp;nbsp;update&amp;nbsp;하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* ViewResolver 설정&lt;/b&gt; &lt;br /&gt;&lt;b&gt;application.properties&lt;/b&gt;에서 설정 &lt;br /&gt;※단순 텍스트 파일이지만, 스프링에선 자동완성도 기능 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;handler의&amp;nbsp;리턴값&amp;nbsp;&lt;b&gt;&amp;ldquo;home&amp;rdquo;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/b&gt; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;prefix&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;ldquo;home&amp;rdquo;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;suffix&amp;nbsp;&amp;nbsp; &lt;br /&gt;&lt;br /&gt;뷰&amp;nbsp;(view)&amp;nbsp;완성&lt;b&gt;&amp;nbsp;/WEB-INF/views/home.jsp&amp;nbsp;&amp;nbsp;&lt;/b&gt;&amp;nbsp;&amp;nbsp;&amp;lt;-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/WEB-INF/views/&amp;nbsp;&amp;nbsp;+&amp;nbsp;home&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;+&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.jsp&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* spring-webmvc 라이브러리의 &lt;b&gt;DispatcherServlet&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MVC&amp;nbsp;모델에서&amp;nbsp;Controller&amp;nbsp;역할.&lt;/b&gt; &lt;br /&gt;일단&amp;nbsp;모든&amp;nbsp;request&amp;nbsp;를&amp;nbsp;받아서&amp;nbsp;Java&amp;nbsp;POJO&amp;nbsp;Controller&amp;nbsp;에&amp;nbsp;매핑시키고 &lt;br /&gt;결과(model,&amp;nbsp;view)&amp;nbsp;를&amp;nbsp;받아서&amp;nbsp;&amp;nbsp;View&amp;nbsp;에&amp;nbsp;전달 &lt;br /&gt;Controller&amp;nbsp;의&amp;nbsp;앞에&amp;nbsp;있는&amp;nbsp;Controller&amp;nbsp;라&amp;nbsp;하여&amp;nbsp;이를&amp;nbsp;FrontController&amp;nbsp;라&amp;nbsp;한다. &lt;br /&gt;View&amp;nbsp;에&amp;nbsp;전달할때,&amp;nbsp;&amp;nbsp;다양한&amp;nbsp;View&amp;nbsp;에&amp;nbsp;전달할수&amp;nbsp;있도록&amp;nbsp;ViewResolver&amp;nbsp;개념을&amp;nbsp;둔다.&amp;nbsp; &lt;br /&gt;&lt;br /&gt;resource&amp;nbsp;view&amp;nbsp;resolver&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HomeController.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;package com.lec.spring;

import java.time.LocalDateTime;
import java.util.Random;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
// Spring MVC 에서 'C' Controller 역할을 하는 클래스로 컨테이너에 생성
public class HomeController {
	
	public HomeController() {
		System.out.println(&quot;HomeController() 생성&quot;);
	}
	
	// Controller 안에는 request를 처리(handle) 하는 메소드들을 정의한다
	// 이러한 메소드들을 'handler' 라 한다.
	// handler 는 
	//  - '어떠한 request' 를 받아서, 
	//  - '어떠한 처리 (business logic)' 를 하고
	//  - '어떠한 response' 를 할지를 정의
	
	@RequestMapping(&quot;/&quot;)   //  &quot;/&quot; 라는 url 로 request 라 오면 아래 메소드가 처리(handle) 한다
	public String now(Model model) {  // Model : 데이터를 실어나르는 객체, View 까지 전달된다 
		
		System.out.println(&quot;/ : now() 호출 &quot;);
		
		LocalDateTime t = LocalDateTime.now();
		model.addAttribute(&quot;serverTime&quot;, t.toString());  // Model 에 데이터 담기 (name-value)
		
		
		
		return &quot;home&quot;;    // 리턴되는 &quot;문자열&quot; 은 -&amp;gt; View Resolver 로 전달
		
		//prefix = /WEB-INF/views/
		//suffix=.jsp
		
		// &quot;/WEB-INF/views/&quot; + &quot;home&quot; + &quot;.jsp&quot;  =&amp;gt; /WEB-INF/views/home.jsp  로 포워딩
	}
	
	@RequestMapping(&quot;/aaa&quot;)
	public String home(Model model) {
		
		int a = new Random().nextInt(10);
		int b = new Random().nextInt(5);
		model.addAttribute(&quot;first&quot;, a);
		model.addAttribute(&quot;second&quot;, b);
		
		return &quot;aaa/my&quot;;
		
		// &quot;/WEB-INF/views/&quot; + &quot;aaa/my&quot; + &quot;.jsp&quot;  =&amp;gt; /WEB-INF/views/aaa/my.jsp  로 포워딩
	}
	
	
	@RequestMapping(&quot;/aaa/bbb&quot;)
	public String aaabbb(Model model) {   // 핸들러 '이름' 은 별로 중요하지 않다.
		int a = new Random().nextInt(10);
		int b = new Random().nextInt(5);
		model.addAttribute(&quot;first&quot;, a);
		model.addAttribute(&quot;second&quot;, b);
		
		return &quot;aaa/bbb/title&quot;;  // =&amp;gt; /WEB-INF/views/aaa/bbb/title.jsp
	}
	
	
	

} // end controller&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;my.jsp&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot;
    pageEncoding=&quot;UTF-8&quot;%&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;ko&quot;&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
&amp;lt;title&amp;gt;my.jsp&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h2&amp;gt;my.jsp - /aaa&amp;lt;/h2&amp;gt;
${first } - ${second }&amp;lt;br&amp;gt;
&amp;lt;img src=&quot;cat.png&quot;/&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Spring</category>
      <author>shb</author>
      <guid isPermaLink="true">https://shprogramming.tistory.com/141</guid>
      <comments>https://shprogramming.tistory.com/141#entry141comment</comments>
      <pubDate>Thu, 12 May 2022 15:08:06 +0900</pubDate>
    </item>
    <item>
      <title>[SpringBoot] Dependency Injection 의존주입</title>
      <link>https://shprogramming.tistory.com/140</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스프링&amp;nbsp;빈&amp;nbsp;(bean)&amp;nbsp;&lt;/b&gt; &lt;br /&gt;스프링은&amp;nbsp;경량&amp;nbsp;컨테이너로서&amp;nbsp;&lt;b&gt;객체&amp;nbsp;생성,&amp;nbsp;소멸과&amp;nbsp;같은&amp;nbsp;Life&amp;nbsp;Cycle을&amp;nbsp;관리하며&amp;nbsp;스프링&amp;nbsp;컨테이너로부터&amp;nbsp;필요한&amp;nbsp;객체를&amp;nbsp;얻을&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/b&gt; &lt;br /&gt;스프링&amp;nbsp;컨테이너에&amp;nbsp;의해서&amp;nbsp;자바&amp;nbsp;객체가&amp;nbsp;만들어지게&amp;nbsp;되면&amp;nbsp;이&amp;nbsp;객체를&amp;nbsp;스프링은&amp;nbsp;&lt;b&gt;스프링&amp;nbsp;빈(Bean)&lt;/b&gt;이라고&amp;nbsp;부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스프링부트에서 bean 생성 &amp;amp; 사용&lt;/b&gt;&lt;br /&gt;스프링부트의 경우&amp;nbsp; &lt;br /&gt;&lt;b&gt;@Component,&amp;nbsp;@Service,&amp;nbsp;@Controller,&amp;nbsp;@Repository,&amp;nbsp;@Bean,&amp;nbsp;@Configuration&amp;nbsp;&lt;/b&gt;등으로&amp;nbsp;필요한&amp;nbsp;빈들을&amp;nbsp;등록하고&amp;nbsp;필요한&amp;nbsp;곳에서&amp;nbsp;&lt;b&gt;@Autowired를&amp;nbsp;통해&amp;nbsp;주입받아&amp;nbsp;사용하는&amp;nbsp;것이&amp;nbsp;일반적&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dependency Injection (의존 주입)&lt;/b&gt;&lt;br /&gt;필요한&amp;nbsp;객체는&amp;nbsp;누가&amp;nbsp;만드나? &lt;br /&gt;방법1]&amp;nbsp;&lt;b&gt;직접&amp;nbsp;new&amp;nbsp;생성&lt;/b&gt; &lt;br /&gt;방법2]&amp;nbsp;&lt;b&gt;외부에서&amp;nbsp;만들어서&amp;nbsp;주입&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;설계&amp;nbsp;변경이&amp;nbsp;발생하면?&lt;/b&gt; &lt;br /&gt;msg 변수에 담을 객체가 변경되어야 한다면?&lt;br /&gt;즉,&amp;nbsp;Main&amp;nbsp;이&amp;nbsp;&amp;lsquo;의존&amp;rsquo;하는&amp;nbsp;MessageBean&amp;nbsp;객체가&amp;nbsp;변경되어야&amp;nbsp;한다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기존&amp;nbsp;방식&amp;nbsp;&lt;/b&gt; &lt;br /&gt;만약&amp;nbsp;msg&amp;nbsp;를&amp;nbsp;수정하게&amp;nbsp;될&amp;nbsp;일이&amp;nbsp;있다면.. &lt;br /&gt;DI1Main&amp;nbsp;을&amp;nbsp;손&amp;nbsp;안대도&amp;nbsp;가능한가? &lt;br /&gt;돌아가게&amp;nbsp;하려면&amp;nbsp;DI1Main&amp;nbsp;&amp;nbsp;을&amp;nbsp;다시&amp;nbsp;컴파일&amp;nbsp;해야&amp;nbsp;한다.&amp;nbsp;(즉&amp;nbsp;손을&amp;nbsp;대야&amp;nbsp;한다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 필요한 객체를 직접 만드는 경우&lt;/b&gt;&lt;br /&gt;SW 설계에 있어 다음과 같이 2개의 구성요소가 있고 Main&amp;nbsp;은&amp;nbsp;MessageBean&amp;nbsp;이&amp;nbsp;필요하다&amp;nbsp;(즉&amp;nbsp;의존한다) &lt;br /&gt;그래서, MessageBean 쪽 설계에 변화가 생긴다면,&amp;nbsp;&amp;nbsp;Main도&amp;nbsp;수정/&amp;nbsp;재컴파일&amp;nbsp;해야&amp;nbsp;한다?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 수백, 수천개의 구성요소 (그것도 제작사가 한군데가 아닌 여러 업체가 협업을 통해 만들어진)로 구성되어&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 의존하는 구성의 SW 라면 어떻게 유지 보수 할 것인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 필요한 객체를 &amp;lsquo;외부&amp;rsquo; 에서 생성 &amp;lsquo;주입&amp;rsquo;&lt;/b&gt;&lt;br /&gt;그래서 SW 공학적으로 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;필요한 객체를 &amp;lsquo;외부&amp;rsquo;에서 생성해서 주입하는 방식&lt;/b&gt;&lt;/span&gt;이 유지 보수 측면에서 이로운 점이 많다.&lt;br /&gt;스프링&amp;nbsp;프레임&amp;nbsp;워크에서&amp;nbsp;제공하는&lt;b&gt;&amp;nbsp;&amp;lsquo;의존주입&amp;nbsp;(Dependency&amp;nbsp;Injection)&amp;rsquo;&amp;nbsp;기능&lt;/b&gt;을&amp;nbsp;통해&amp;nbsp;이를&amp;nbsp;배워보도록&amp;nbsp;하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비록&amp;nbsp;Main&amp;nbsp;이&amp;nbsp;MessageBean&amp;nbsp;객체를&amp;nbsp;필요(의존)&amp;nbsp;하지만,&amp;nbsp;외부에서&amp;nbsp;주입되는&amp;nbsp;MessageBean&amp;nbsp;객체가&amp;nbsp;변경되었다고&amp;nbsp;해서&amp;nbsp;&amp;nbsp;Main&amp;nbsp;을&amp;nbsp;수정하거나&amp;nbsp;재컴파일&amp;nbsp;할&amp;nbsp;필요는&amp;nbsp;없다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;연습&amp;nbsp;2-1&amp;nbsp;:&amp;nbsp;&amp;nbsp;빈&amp;nbsp;객체&amp;nbsp;생성&amp;nbsp;연습&lt;/b&gt; &lt;br /&gt;컨테이너에서&amp;nbsp;객체를&amp;nbsp;생성한뒤&amp;nbsp;이를&amp;nbsp;의존주입받아&amp;nbsp;동작하는&amp;nbsp;코드&amp;nbsp;만들기 &lt;br /&gt;&lt;br /&gt;패키지&amp;nbsp;:&amp;nbsp;&amp;nbsp;base-package.ex2_1 &lt;br /&gt;인터페이스&amp;nbsp;:&amp;nbsp;OperatorBean &lt;br /&gt;클래스&amp;nbsp;:&amp;nbsp;&amp;nbsp;DIApp&amp;nbsp;&amp;nbsp;main()&amp;nbsp;포함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음&amp;nbsp;요구사항을&amp;nbsp;만족하는&amp;nbsp;프로그램을&amp;nbsp;작성 &lt;br /&gt;- 클래스 구조도가 오른쪽과 같이 구성되도록 작성하기 &lt;br /&gt;&lt;b&gt;- 클래스&amp;nbsp;&lt;/b&gt; &lt;br /&gt;- DIApp ( main() ) &lt;br /&gt;- OperatorBean( interface ) &lt;br /&gt;- PlusOp, MinusOp (OperatorBean 구현체) &lt;br /&gt;- Config.java (@Configuration)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main() 에서의 operator 와 각 operand1, operand2 값 지정은 Config.java&amp;nbsp;&amp;nbsp;을 통해 외부&amp;nbsp;주입&amp;nbsp;될수&amp;nbsp;있도록&amp;nbsp;설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MessageBean.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;package com.lec.spring.beans;

public interface MessageBean {
	public abstract void sayHello();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Score.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package com.lec.spring.beans;

import lombok.Data;

@Data
public class Score {
	int kor;
	int eng;
	int math;
	String comment;
	
	public Score() {
		System.out.println(&quot;Score() 생성&quot;);
	}
	
	public Score(int kor, int eng, int math, String comment) {
		super();
		System.out.printf(&quot;Score(%d, %d, %d, %s) 생성\n&quot;, kor, eng, math, comment);
		this.kor = kor;
		this.eng = eng;
		this.math = math;
		this.comment = comment;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Student.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;package com.lec.spring.beans;

import lombok.Data;

@Data
public class Student {
	String name;
	int age;
	Score score;
	
	public Student() {
		super();
		System.out.println(&quot;Student() 생성&quot;);
	}
	
	public Student(String name, int age, Score score) {
		super();
		System.out.printf(&quot;Student(%s, %d, %s) 생성\n&quot;, name, age, score.toString());
		this.name = name;
		this.age = age;
		this.score = score;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MessageEng.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package com.lec.spring.di01;

import com.lec.spring.beans.MessageBean;

public class MessageEng implements MessageBean {
	
	String msgEng = &quot;Good Morning&quot;;
	
	// 생성자 : 언제 생성되는지 예의주시
	public MessageEng() {
		System.out.println(&quot;MessageEng() 생성&quot;);
	}	

	@Override
	public void sayHello() {
		System.out.println(msgEng);

	}

} // end MessageBean&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MessgaeKor.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package com.lec.spring.di01;

import com.lec.spring.beans.MessageBean;

public class MessageKor implements MessageBean {

	String msgKor = &quot;안녕하세요&quot;;
	
	// 생성자 : 언제 생성되는지 주목
	public MessageKor() {
		System.out.println(&quot;MessageKor() 생성&quot;);
	}
	
	@Override
	public void sayHello() {
		System.out.println(msgKor);
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DiMain01.java&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package com.lec.spring.di01;

import com.lec.spring.beans.MessageBean;

/*
	Dependency Injection (DI, 의존주입)
	필요한 객체는 누가 만들어 사용하나?
	
	방법1 : 직접 생성하여 사용
	
	문제점 : MessageBean 객체의 설계가 바뀐다면????
	
	결국 --&amp;gt; 사용하는 쪽에서의 코드수정 발생 -&amp;gt; 재컴파일
*/
public class DiMain01 {

	public MessageBean msg;
	
	public void setMsg(MessageBean msg) {
		this.msg = msg;
	}
	
	public static void main(String[] args) {
		System.out.println(&quot;Main 시작&quot;);

		DiMain01 app = new DiMain01();
		app.test();
		
		System.out.println(&quot;Main 종료&quot;);
	}

	private void test() {
		// 필요한 MessageBean 객체를
//		msg = new MessageKor();   // 직접 만들어(new) 사용
//		msg.sayHello();		
		
		// 필요한 MessageBean 객체의 설계 변경이 발생되면
		msg = new MessageEng();   // 직접 만들어(new) 사용
		msg.sayHello();
		
		// 결국 --&amp;gt; 사용하는 쪽에서의 코드수정 발생 -&amp;gt; 재컴파일
		
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;용어&lt;/b&gt; &lt;br /&gt;스프링 프레임워크에서 작업하면서 &amp;ldquo;컨테이너(Container)&amp;rdquo;,&amp;nbsp;&amp;nbsp;&amp;ldquo;스프링 컨테이너&amp;rdquo;, &amp;ldquo;,IoC 컨테이너&amp;rdquo;,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;ldquo;Application Context 객체&amp;rdquo;&amp;nbsp;&amp;nbsp;&amp;ldquo;빈(bean) 컨테이너&amp;rdquo; .모두 (거의) 같은 말. &lt;br /&gt;IOC&amp;nbsp;:&amp;nbsp;Inversion&amp;nbsp;Of&amp;nbsp;Control&amp;nbsp;&amp;nbsp;의&amp;nbsp;약자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국&amp;nbsp;스프링이란? &lt;br /&gt;&lt;b&gt;부품을 생성하고 조립하는 라이브러리 집합체&lt;/b&gt; 라고도 할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 스프링 빈 (bean)&amp;nbsp;&lt;/b&gt; &lt;br /&gt;스프링은&amp;nbsp;경량&amp;nbsp;컨테이너로서&amp;nbsp;객체&amp;nbsp;생성,&amp;nbsp;소멸과&amp;nbsp;같은&amp;nbsp;Life&amp;nbsp;Cycle을&amp;nbsp;관리하며&amp;nbsp;스프링&amp;nbsp;컨테이너로부터&amp;nbsp;필요한&amp;nbsp;객체를&amp;nbsp;얻을&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;br /&gt;스프링&amp;nbsp;컨테이너에&amp;nbsp;의해서&amp;nbsp;자바&amp;nbsp;객체가&amp;nbsp;만들어지게&amp;nbsp;되면&amp;nbsp;이&amp;nbsp;객체를&amp;nbsp;스프링은&amp;nbsp;스프링&amp;nbsp;빈(Bean)이라고&amp;nbsp;부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 스프링부트에서 bean 생성 &amp;amp; 사용&lt;/b&gt;&lt;br /&gt;스프링부트의&amp;nbsp;경우&amp;nbsp; &lt;br /&gt;@Component,&amp;nbsp;@Service,&amp;nbsp;@Controller,&amp;nbsp;@Repository,&amp;nbsp;@Bean,&amp;nbsp;@Configuration&amp;nbsp;등으로&amp;nbsp;필요한&amp;nbsp;빈들을&amp;nbsp;등록하고&amp;nbsp;필요한&amp;nbsp;곳에서&amp;nbsp;@Autowired를&amp;nbsp;통해&amp;nbsp;주입받아&amp;nbsp;사용하는&amp;nbsp;것이&amp;nbsp;일반적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 의존 자동 주입 : @Autowired 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;컨테이너 안&amp;rsquo;에 이미 생성된 빈(bean)객체, 그리고 이를 의존하는 다른 빈 객체들로 하여금&amp;nbsp; &lt;br /&gt;자동적으로 참조 하여 주입될수 있게 하는 것이 Autowired(자동주입)를 구현하기 위해서는 아래와 같이 한다. &lt;br /&gt;&lt;b&gt;1.&amp;nbsp;&amp;lsquo;자동주입&amp;nbsp;대상&amp;rsquo;에&amp;nbsp;@Autowired&amp;nbsp;애노테이션&amp;nbsp;사용&lt;/b&gt; &lt;br /&gt;-&amp;nbsp;&lt;b&gt;자동주입&amp;nbsp;대상&amp;nbsp;:&amp;nbsp;&amp;nbsp;생성자,&amp;nbsp;멤버변수,&amp;nbsp;setter메소드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;★&amp;nbsp;Autowired(자동주입)는&amp;nbsp;세군데&amp;nbsp;발생&lt;/b&gt;&lt;/span&gt; &lt;br /&gt;-&amp;nbsp;&lt;b&gt;생성자&lt;/b&gt;&amp;nbsp;(Constructor&amp;nbsp;injection) &lt;br /&gt;-&amp;nbsp;&lt;b&gt;멤버변수&lt;/b&gt;&amp;nbsp;(field&amp;nbsp;injection) &lt;br /&gt;-&amp;nbsp;&lt;b&gt;setter&lt;/b&gt;&amp;nbsp;(setter&amp;nbsp;injection)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@Autowired&amp;nbsp;검색순위&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;타입(다형성&amp;nbsp;적용)&amp;nbsp;&amp;rarr;&amp;nbsp;&amp;nbsp;이름&amp;nbsp;&amp;rarr;&amp;nbsp;&amp;nbsp;@Qualifier&amp;nbsp;&amp;rarr;&amp;nbsp;&amp;nbsp;@Primary&lt;/b&gt; &lt;br /&gt;검색 실패시 예외 처리 또는 null 처리&lt;br /&gt;&amp;nbsp;@Autowired(required=false)&amp;nbsp;:&amp;nbsp;빈이&amp;nbsp;존재하지&amp;nbsp;않으면&amp;nbsp;&amp;nbsp;null&amp;nbsp;&amp;nbsp;주입하여&amp;nbsp;처리&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package com.lec.spring.di05;

import com.lec.spring.beans.MessageBean;

public class MessageEng implements MessageBean {
	
	String msgEng = &quot;Good Morning&quot;;
	
	// 생성자 : 언제 생성되는지 예의주시
	public MessageEng() {
		System.out.println(&quot;MessageEng() 생성&quot;);
	}	

	@Override
	public void sayHello() {
		System.out.println(msgEng);

	}

} // end MessageBean&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package com.lec.spring.di05;

import com.lec.spring.beans.MessageBean;

public class MessageKor implements MessageBean {

	String msgKor = &quot;안녕하세요&quot;;
	
	// 생성자 : 언제 생성되는지 주목
	public MessageKor() {
		System.out.println(&quot;MessageKor() 생성&quot;);
	}
	
	@Override
	public void sayHello() {
		System.out.println(msgKor);
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;package com.lec.spring.di05;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import com.lec.spring.beans.MessageBean;
import com.lec.spring.beans.Score;
import com.lec.spring.beans.Student;

@Configuration
public class Config05 {

	public Config05() {
		System.out.println(&quot;Config05() 생성&quot;);
	}
	
	@Bean
	public Score scoreA() {
		return new Score(77, 55, 89, &quot;괜찮아요&quot;);
	}
	
	@Bean(name = &quot;Park&quot;)
	public Student stu1() {
		return new Student(&quot;박주찬&quot;, 19, scoreA());
	}
	
	@Bean(name = &quot;Hong&quot;)
	public Student stu2() {
		return new Student(&quot;홍길동&quot;, 25, scoreA());
	}
	
	@Bean(name=&quot;eng&quot;)
	public MessageBean msg1() {
		return new MessageEng();
	}

	@Bean
	@Primary  // 동일한 타입의 빈이 여러개 있을때 autowired 되는 우선순위
	public MessageBean msg2() {
		return new MessageKor();
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;package com.lec.spring.di05;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import com.lec.spring.beans.MessageBean;
import com.lec.spring.beans.Student;

/*
 * Autowired (자동주입) 는 세군데 발생
 * - 생성자  (constructor injection)
 * - 멤버변수 (field injection)
 * - setter (setter injection)
 */

@SpringBootApplication
public class DIMain05 implements CommandLineRunner {

	Student stu1;
	Student stu2;
	
	@Autowired  // construcgtor injection
	// Sprig 4.3 &amp;lt;= 부터는 
	// 생성자가 '하나만' 있는 경우 생성자 @Autowired 생략가능
	
	// 기본적으로 자동주입(autowired) 는
	// 1. 같은 타입의  bean 이 주입
	// 2. 같은 타입이 여러개 이면, name 이 같거나 (혹은 유사) 한것이 주입
	// 3. @Qualifier, @Primary 등으로 특정 이름의 bean 주입
	public DIMain05(@Qualifier(&quot;Park&quot;) Student stu1, @Qualifier(&quot;Hong&quot;) Student stu2) {   // 동일 타입의 Student bean 이 복수개 있는데도 동작한다?!!
								
		System.out.println(&quot;DIMain05(Student, Student) 생성&quot;);
		this.stu1 = stu1;
		this.stu2 = stu2;
	}
	
	@Autowired   // filed injection
	MessageBean msg1;  // 동일한 이름의 bean 자동주입
	
	MessageBean msg2;
	
	@Autowired   // setter injection
	public void setMsg2(MessageBean msg) {
		this.msg2 = msg;
	}
	
	
	MessageBean msg3;
	@Autowired   // setter injection
	@Qualifier(&quot;eng&quot;)
	public void setMsg3(MessageBean msg) {
		this.msg3 = msg;
	}
	
	
	public static void main(String[] args) {
		System.out.println(&quot;Main 시작&quot;);
		SpringApplication.run(DIMain05.class, args);
		System.out.println(&quot;Main 종료&quot;);		

	}


	@Override
	public void run(String... args) throws Exception {
		System.out.println(&quot;- run() ----------------------------&quot;);
		
		System.out.println(stu1);
		System.out.println(stu2);
		
		msg1.sayHello();
		msg2.sayHello();
		msg3.sayHello();
		
		System.out.println(&quot;-----------------------------&quot;);		
	}

}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Spring</category>
      <author>shb</author>
      <guid isPermaLink="true">https://shprogramming.tistory.com/140</guid>
      <comments>https://shprogramming.tistory.com/140#entry140comment</comments>
      <pubDate>Thu, 12 May 2022 13:37:28 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Spring이란</title>
      <link>https://shprogramming.tistory.com/139</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 스프링의 핵심&lt;/b&gt; &lt;br /&gt;- 스프링은 자바 언어 기반의 프레임워크 &lt;br /&gt;- 자바 언어의 가장 큰 특징 - &lt;b&gt;객체 지향 언어&lt;/b&gt; &lt;br /&gt;- 스프링은 객체 지향 언어가 가진 강력한 특징을 살려내는 프레임워크 &lt;br /&gt;- 스프링은 &lt;b&gt;좋은 객체 지향 애플리케이션을 개발할 수 있도록 도와주는 프레임워크&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 스프링 프레임워크 특징&lt;/b&gt;&lt;br /&gt;&lt;b&gt;1.&amp;nbsp;경량&amp;nbsp;컨테이너&lt;/b&gt; &lt;br /&gt;&amp;nbsp; &amp;nbsp;a. 자바 객체를 담고 있는 컨테이너 &lt;br /&gt;&lt;b&gt;2.&amp;nbsp;DI&amp;nbsp;(Dependency&amp;nbsp;Injection:&amp;nbsp;의존주입)&amp;nbsp;&amp;nbsp;지원&lt;/b&gt; &lt;br /&gt;&amp;nbsp; &amp;nbsp;a. 설정파일로 객체 의존 관계 설정 및 생성. &lt;br /&gt;&lt;b&gt;3.&amp;nbsp;AOP&amp;nbsp;(Aspect&amp;nbsp;Orientec&amp;nbsp;Programming)&amp;nbsp;지원&lt;/b&gt; &lt;br /&gt;&amp;nbsp; &amp;nbsp;a. 트랜잭션, 로깅, 보안 등&amp;nbsp;&amp;nbsp;공통기능을 핵심기능에서 분리하여 적용 가능 &lt;br /&gt;&lt;b&gt;4.&amp;nbsp;IoC&amp;nbsp;(Inversion&amp;nbsp;Of&amp;nbsp;Control)&amp;nbsp;제어반전&amp;nbsp;지원 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;5.&amp;nbsp;POJO&amp;nbsp;(Plain&amp;nbsp;Old&amp;nbsp;Java&amp;nbsp;Object)&amp;nbsp;지원&lt;/b&gt; &lt;br /&gt;&amp;nbsp; &amp;nbsp;a. 컨테이너 안의 객체는 특별한 상속이 필요없는 평범한 자바 객체 &lt;br /&gt;&lt;b&gt;6.&amp;nbsp;트랜잭션&amp;nbsp;처리를&amp;nbsp;위한&amp;nbsp;일관된&amp;nbsp;방법&amp;nbsp;지원&amp;nbsp;(영속성)&lt;/b&gt; &lt;br /&gt;&amp;nbsp; &amp;nbsp;a. 설정파일을 통한 일관된 트랜잭션 처리 &lt;br /&gt;&lt;b&gt;7.&amp;nbsp;다양한&amp;nbsp;API&amp;nbsp;지원&amp;nbsp;&lt;/b&gt; &lt;br /&gt;&amp;nbsp; &amp;nbsp;a. 엔터프라이즈 어플리케이션 개발에 필요한 다양한 API 지원&amp;nbsp; &lt;br /&gt;&amp;nbsp; &amp;nbsp;(스케쥴링, 메일..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든&amp;nbsp;Spring&amp;nbsp;project&amp;nbsp;의&amp;nbsp;핵심&amp;nbsp;기반은&amp;nbsp;&lt;b&gt;&amp;lsquo;Spring&amp;nbsp;Framework&amp;rsquo;&amp;nbsp;&lt;/b&gt;다 &lt;br /&gt;- 핵심기술 : 스프링 DI 컨테이너, AOP, 이벤트, 기타 &lt;br /&gt;- 웹기술 : 스프링 MVC, 스프링 WebFlux &lt;br /&gt;- 데이터접근기술 : 트랜잭션, JDBC, ORM 지원,&amp;nbsp;&amp;nbsp;XML지원 &lt;br /&gt;- 기술통합: 캐시, 이메일, 원격접근, 스케쥴링 &lt;br /&gt;- 테스트: 스프링 기반 테스트 지원 &lt;br /&gt;- 언어: 코틀린, 그루비 &lt;br /&gt;- 최근에는 Spring Boot 를 통해 Spring Framework 기술들을 편리하게 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* Spring이란 단어&lt;/b&gt;&lt;br /&gt;-&amp;nbsp;애매하고,&amp;nbsp;너무&amp;nbsp;범위가&amp;nbsp;넓다.&amp;nbsp;문맥에&amp;nbsp;따라&amp;nbsp;다르게&amp;nbsp;사용 &lt;br /&gt;-&amp;nbsp;스프링&amp;nbsp;DI&amp;nbsp;컨테이너&amp;nbsp;기술 &lt;br /&gt;-&amp;nbsp;스프링&amp;nbsp;프레임워크 &lt;br /&gt;-&amp;nbsp;스프링&amp;nbsp;프레임워크&amp;nbsp;+&amp;nbsp;스프링&amp;nbsp;부트&amp;nbsp;+&amp;nbsp;스프링xxx&amp;nbsp;&amp;nbsp;등을&amp;nbsp;모두&amp;nbsp;포함하는&amp;nbsp;생태계&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 프레임워크 (FrameWork)?&lt;/b&gt;&lt;br /&gt;어플리케이션&amp;nbsp;프레임워크&amp;nbsp;는&amp;nbsp;프로그래밍&amp;nbsp;개발환경에서&amp;nbsp;기본적인&amp;nbsp;구조&amp;nbsp;(골조?)&amp;nbsp;를&amp;nbsp;제공하는&amp;nbsp;개발환경이다.&amp;nbsp;&amp;nbsp;&amp;nbsp;이를&amp;nbsp;통해,&amp;nbsp;어플리케이션&amp;nbsp;의&amp;nbsp;개발및&amp;nbsp;유지&amp;nbsp;보수의&amp;nbsp;생산성을&amp;nbsp;향상&amp;nbsp;시키는데&amp;nbsp;목적이&amp;nbsp;있다. &lt;br /&gt;기본구조&amp;nbsp;라&amp;nbsp;함은&amp;nbsp;프레임워크&amp;nbsp;안에는&amp;nbsp;수많은&amp;nbsp;클래스,&amp;nbsp;함수,&amp;nbsp;개발툴&amp;nbsp;등의&amp;nbsp;집합체이고,&amp;nbsp;이들을&amp;nbsp;구동하는&amp;nbsp;다양한&amp;nbsp;구성요소들로&amp;nbsp;복잡하게&amp;nbsp;만들어져&amp;nbsp;있다.&amp;nbsp; &lt;br /&gt;&amp;lsquo;인터페이스&amp;rsquo;&amp;nbsp;등의&amp;nbsp;제공을&amp;nbsp;통해&amp;nbsp;개발자가&amp;nbsp;이를&amp;nbsp;기반으로&amp;nbsp;개발함.&amp;nbsp; &lt;br /&gt;결국&amp;nbsp;이미&amp;nbsp;제공된는&amp;nbsp;&amp;lsquo;틀&amp;rsquo;과&amp;nbsp;&amp;lsquo;클래스,&amp;nbsp;함수규칙&amp;rsquo;&amp;nbsp;들의&amp;nbsp;동작을&amp;nbsp;이해해야&amp;nbsp;하고,&amp;nbsp;이를&amp;nbsp;사용하는&amp;nbsp;것인데..&amp;nbsp;그러다&amp;nbsp;보니,&amp;nbsp;처음에&amp;nbsp;학습하고&amp;nbsp;숙달되는데&amp;nbsp;어려움이&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 프레임워크 vs 라이브러리&lt;/b&gt;&lt;br /&gt;프레임워크는&amp;nbsp;단지&amp;nbsp;미리&amp;nbsp;만들어&amp;nbsp;둔&amp;nbsp;반제품이나,&amp;nbsp;확장해서&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;준비된&amp;nbsp;추상&amp;nbsp;라이브러리의&amp;nbsp;집합이&amp;nbsp;아니다. &lt;br /&gt;프레임워크가 어떤 것인지 이해하려면 라이브러리와 프레임워크가 어떻게 다른지 알아야 한다.&lt;br /&gt;&lt;b&gt;라이브러리를&amp;nbsp;사용하는&amp;nbsp;애플리케이션&amp;nbsp;코드는&amp;nbsp;애플리케이션&amp;nbsp;흐름을&amp;nbsp;직접&amp;nbsp;제어&lt;/b&gt;한다.&amp;nbsp; &lt;br /&gt;단지 동작하는 중에 &lt;b&gt;필요한 기능이 있을 때 능동적으로 라이브러리를 사용할 뿐&lt;/b&gt;이다.&lt;br /&gt;반면에&amp;nbsp;&lt;b&gt;프레임워크는&amp;nbsp;거꾸로&amp;nbsp;애플리케이션&amp;nbsp;코드가&amp;nbsp;프레임워크에&amp;nbsp;의해&amp;nbsp;사용&lt;/b&gt;된다. &lt;br /&gt;프레임워크에는 분명한 제어의 역전(IOC) 개념이 적용되어 있어야 합니다.&lt;br /&gt;&lt;b&gt;애플리케이션&amp;nbsp;코드는&amp;nbsp;프레임워크가&amp;nbsp;짜놓은&amp;nbsp;틀에서&amp;nbsp;수동적으로&amp;nbsp;동작해야&amp;nbsp;&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* Maven 이란?&lt;/b&gt; &lt;br /&gt;-&amp;nbsp;&lt;a href=&quot;http://maven.apache.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://maven.apache.org/&lt;/a&gt; &lt;br /&gt;-&amp;nbsp;자바용&amp;nbsp;프로젝트&amp;nbsp;빌드&amp;nbsp;관리&amp;nbsp;도구임&amp;nbsp;. &lt;br /&gt;-&amp;nbsp;아파치&amp;nbsp;라이선스로&amp;nbsp;배포되는&amp;nbsp;오픈&amp;nbsp;소스&amp;nbsp;소프트웨어 &lt;br /&gt;-&amp;nbsp;Project&amp;nbsp;object&amp;nbsp;model&amp;nbsp;(POM)&amp;nbsp;개념에&amp;nbsp;기반을&amp;nbsp;두고&amp;nbsp;있음&amp;nbsp;. &lt;br /&gt;-&amp;nbsp;Maven&amp;nbsp;can&amp;nbsp;manage&amp;nbsp;a&amp;nbsp;project's&amp;nbsp;build,&amp;nbsp;reporting&amp;nbsp;and&amp;nbsp;&amp;nbsp;&amp;nbsp;documentation&amp;nbsp;from&amp;nbsp;a&amp;nbsp;central&amp;nbsp;piece&amp;nbsp;of&amp;nbsp;information &lt;br /&gt;-&amp;nbsp;History &lt;br /&gt;2004&amp;nbsp;년&amp;nbsp;07&amp;nbsp;월&amp;nbsp;13&amp;nbsp;일&amp;nbsp;,&amp;nbsp;Apache&amp;nbsp;Maven&amp;nbsp;발표 &lt;br /&gt;2018&amp;nbsp;년&amp;nbsp;10&amp;nbsp;월&amp;nbsp;&amp;nbsp;24&amp;nbsp;현재&amp;nbsp;version&amp;nbsp;3.6.x&lt;/p&gt;</description>
      <category>Spring</category>
      <author>shb</author>
      <guid isPermaLink="true">https://shprogramming.tistory.com/139</guid>
      <comments>https://shprogramming.tistory.com/139#entry139comment</comments>
      <pubDate>Wed, 11 May 2022 15:11:16 +0900</pubDate>
    </item>
    <item>
      <title>[JSP] Comment</title>
      <link>https://shprogramming.tistory.com/138</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 게시판 댓글 기능 (JSON + AJAX 사용)&lt;/b&gt;&lt;br /&gt;JSON&amp;nbsp;resopnse&amp;nbsp;+&amp;nbsp;AJAX&amp;nbsp;를&amp;nbsp;사용한&amp;nbsp;댓글&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 구현할 댓글 기능&lt;/b&gt; &lt;br /&gt;- 댓글 목록&amp;nbsp; &lt;br /&gt;- 댓글 작성 &lt;br /&gt;- 댓글 삭제 : 삭제는 한번 confirm 하고 진행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;*&amp;nbsp;ERD,&amp;nbsp;DDL&amp;nbsp;작성 &lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;*&amp;nbsp;라이브러리&amp;nbsp;준비 &lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;*&amp;nbsp;select&amp;nbsp;쿼리문&amp;nbsp;준비 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;#&amp;nbsp;특정글&amp;nbsp;의&amp;nbsp;(댓글&amp;nbsp;+&amp;nbsp;사용자)&amp;nbsp;정보 &lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;필요한&amp;nbsp;쿼리문&amp;nbsp;작성 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;D.java &lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;	// ★ 댓글
	// 댓글과 작성자 SELECT
	public static final String SQL_COMMENT_SELECT = 
			&quot;SELECT c.uid \&quot;uid\&quot;, c.content \&quot;content\&quot;, c.regdate \&quot;regdate\&quot;,\r\n&quot;
			+ &quot;	u.uid \&quot;user_uid\&quot;, u.username \&quot;user_username\&quot;, u.password \&quot;user_password\&quot;, u.name \&quot;user_name\&quot;, u.authorities \&quot;user_authorities\&quot;, u.regdate \&quot;user_regdate\&quot;\r\n&quot;
			+ &quot;FROM t2_comment c, t2_user u\r\n&quot;
			+ &quot;WHERE c.user_uid = u.uid &quot;
			;
	
	// '특정 글'의 댓글과 작성자 정보 SELECT
	public static final String SQL_COMMENTS_BY_WRITE =
			SQL_COMMENT_SELECT
			+ &quot;AND c.write_uid = ?\r\n&quot;
			+ &quot;ORDER BY c.uid DESC&quot;
			;
	
	// 특정글에 댓글 추가
	public static final String SQL_COMMENT_INSERT = 
			&quot;INSERT INTO t2_comment(user_uid, write_uid, content) VALUES&quot;
			+ &quot;(?, ?, ?)&quot;;	
	
	// 특정 댓글 삭제
	public static final String SQL_COMMENT_DELETE_BY_UID = 
			&quot;DELETE FROM t2_comment WHERE uid = ?&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;*&amp;nbsp;CommentDTO&amp;nbsp;생성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;package domain;

import java.time.LocalDateTime;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CommentDTO {
	private int uid;   // uid
	private String content;
	// java.time.* 객체 변환을 위한 annotation
	@JsonDeserialize(using = LocalDateTimeDeserializer.class)
	@JsonSerialize(using = LocalDateTimeSerializer.class)
	@JsonFormat(pattern = &quot;yyyy-MM-dd HH:mm:ss&quot;, timezone = &quot;Asia/Seoul&quot;)
	@JsonProperty(&quot;regdate&quot;)
	private LocalDateTime regDate;
	private UserDTO user;    // user_uid (FK) 작성자
	@JsonIgnore
	private WriteDTO write;  // write_uid (FK) 글
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;*&amp;nbsp;CommentDAO&amp;nbsp;생성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;package domain;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import common.D;

public class CommentDAO {
	Connection conn;
	PreparedStatement pstmt;
	Statement stmt;
	ResultSet rs;
	
	// DAO 객체가 생성될때 Connection 도 생성된다
	public CommentDAO(){
		try {
			Class.forName(D.DRIVER);
			conn = DriverManager.getConnection(D.URL, D.USERID, D.USERPW);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} // end try
	}// 생성자
	
	// DB 자원 반납 메소드, 만들어놓으면 편함..
	public void close() throws SQLException{
		if(rs != null) rs.close();
		if(pstmt != null) pstmt.close();
		if(stmt != null) stmt.close();
		if(conn != null) conn.close();
	} // end close();
	
	// 댓글목록읽기
	// ResultSet --&amp;gt; List&amp;lt;DTO&amp;gt; 로 리턴
	public List&amp;lt;CommentDTO&amp;gt; buildList(ResultSet rs) throws SQLException{
		List&amp;lt;CommentDTO&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
		
		while(rs.next()) {
			int uid = rs.getInt(&quot;uid&quot;);
			String content = rs.getString(&quot;content&quot;);
			if(content == null) content = &quot;&quot;;
			LocalDateTime regDate = rs.getObject(&quot;regdate&quot;, LocalDateTime.class); 			
			
			// user_id 의 User 정보 가져오기
			UserDTO user = UserDTO.builder()
					.uid(rs.getInt(&quot;user_uid&quot;))
					.username(rs.getString(&quot;user_username&quot;))
					.password(rs.getString(&quot;user_password&quot;))
					.name(rs.getString(&quot;user_name&quot;))
					.authorities(rs.getString(&quot;user_authorities&quot;))
					.regdate(rs.getObject(&quot;user_regdate&quot;, LocalDateTime.class))
					.build()
					;
			
			CommentDTO dto = CommentDTO.builder()
					.uid(uid)
					.content(content)
					.regDate(regDate)
					.user(user)
					.build()
					;
			
			list.add(dto);
			
		} // end while
		
		return list;
	}
	
	
	// 특정글의 댓글들 SELECT
	public List&amp;lt;CommentDTO&amp;gt; selectByWrite(int write_uid) throws SQLException {
		List&amp;lt;CommentDTO&amp;gt; list = null;
		try{
			pstmt = conn.prepareStatement(D.SQL_COMMENTS_BY_WRITE);
			pstmt.setInt(1, write_uid);
			rs = pstmt.executeQuery();
			list = buildList(rs);
		}finally{
			close();
		} // end try
		return list;		
	} // end selectByWrite()
	
	
	// 댓글 작성 &amp;lt;-- DTO
	public int insert(CommentDTO dto) throws SQLException {
		int cnt = 0;
		
		String content = dto.getContent();
		UserDTO user = dto.getUser();  // 댓글 작성자 (로그인 한 사용자)
		WriteDTO write = dto.getWrite();  // 댓글이 달린 글 
		
		
		int uid;  // auto-generated keys 값
		// auto-generated 컬럼
		String [] generatedCols = {&quot;uid&quot;};  

		try{	
			pstmt = conn.prepareStatement(D.SQL_COMMENT_INSERT, generatedCols);
			pstmt.setInt(1, user.getUid());
			pstmt.setInt(2, write.getUid());
			pstmt.setString(3, content);
			cnt = pstmt.executeUpdate();
			
			if(cnt &amp;gt; 0){
				// auto-generated keys 값 뽑아오기
				rs = pstmt.getGeneratedKeys();
				if(rs.next()) {
					uid = rs.getInt(1);
					dto.setUid(uid);  // DTO 에 set
				}
			}
			
		}finally{
			close();
		} // end try
		
		
		return cnt;
	} // end insert()
	
	
	// 특정 댓글 삭제하기 &amp;lt;-- uid
	public int deleteByUid(int uid) throws SQLException {
		int cnt = 0;
		
		try{
			pstmt=conn.prepareStatement(D.SQL_COMMENT_DELETE_BY_UID);
			pstmt.setInt(1, uid);
			cnt = pstmt.executeUpdate();
		}finally{
			close();
		} // end try
		
		return cnt;
	} // end deleteByUid()
	
	
	
} // end DAO&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;*&amp;nbsp;command.comment&lt;/b&gt; &lt;br /&gt;CmdListCommand.java &lt;br /&gt;CmdWriteCommand.java &lt;br /&gt;CmdDeleteCommand.java&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;*&amp;nbsp;CommentController&amp;nbsp;생성&lt;/b&gt; &lt;br /&gt;이번에는&amp;nbsp;페이지를&amp;nbsp;response&amp;nbsp;하는게&amp;nbsp;아니다.&amp;nbsp;(viewPage&amp;nbsp;가&amp;nbsp;필요없다) &lt;br /&gt;걍&amp;nbsp;&amp;nbsp;각&amp;nbsp;Command&amp;nbsp;를&amp;nbsp;execute()&amp;nbsp;만&amp;nbsp;해주면&amp;nbsp;된다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package controller;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import command.Command;
import command.comment.CmtDeleteCommand;
import command.comment.CmtListCommand;
import command.comment.CmtWriteCommand;

@WebServlet(&quot;/comment/*&quot;)
public class CommentController extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    public CommentController() {
        super();
    }

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doAction(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		request.setCharacterEncoding(&quot;UTF-8&quot;);
		doAction(request, response);
	}

	protected void doAction(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		System.out.println(&quot;CommentController.actionDo() 호출&quot;);
		
		String method = request.getMethod();
				
		// URL로부터 URI, ContextPath, Command 분리
		String uri = request.getRequestURI();
		String conPath = request.getContextPath() + &quot;/comment&quot;;
		String com = uri.substring(conPath.length());
		
		// 테스트 출력
		System.out.println(&quot;uri: &quot; + uri);  
		System.out.println(&quot;conPath: &quot; + conPath);  
		System.out.println(&quot;com: &quot; + com);

		Command command = null;   // 어떠한 로직을 수행할지

		switch(com) {
		case &quot;/list&quot;:
			new CmtListCommand().execute(request, response);
			break;
		case &quot;/write&quot;:
			if(method.equals(&quot;POST&quot;)) {
				new CmtWriteCommand().execute(request, response);
			}
			break;
		case &quot;/delete&quot;:
			if(method.equals(&quot;POST&quot;)) {
				new CmtDeleteCommand().execute(request, response);
			}
			break;
		} // end switch
	
	
	} // end doAction()
	
	

} // end Controller&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;*&amp;nbsp;Response&amp;nbsp;할&amp;nbsp;객체&amp;nbsp;준비&amp;nbsp;(공통)&lt;/b&gt; &lt;br /&gt;일단&amp;nbsp;&lt;b&gt;QryResult.java&amp;nbsp;객체&lt;/b&gt; &lt;br /&gt;'공통적'으로&amp;nbsp;&amp;nbsp;response 할 내용들을 필드로 담아서 정의&lt;br /&gt;(@Data, @NoArgsConstructor&amp;nbsp;&amp;nbsp;두개만)&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;package domain;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class QryResult {
	int count;  // 결괏값 
	String status;  // 결과 메세지
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;*&amp;nbsp;Response&amp;nbsp;할&amp;nbsp;객체&amp;nbsp;준비&lt;/b&gt;&amp;nbsp;(&amp;nbsp;댓글&amp;nbsp;List&amp;nbsp;&amp;nbsp;Response)&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;package domain;

import java.util.List;

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class QryCommentList extends QryResult {
	List&amp;lt;CommentDTO&amp;gt; list;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;댓글&amp;nbsp;목록&lt;/b&gt; &lt;br /&gt;특정글의&amp;nbsp;댓글&amp;nbsp;목록&amp;nbsp;가져와서&amp;nbsp;response&amp;nbsp;하기 &lt;br /&gt;&lt;b&gt;CmtListCommand.java&amp;nbsp;작성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package command.comment;

import java.io.IOException;
import java.sql.SQLException;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;

import command.Command;
import domain.CommentDAO;
import domain.CommentDTO;
import domain.QryCommentList;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

public class CmtListCommand implements Command {

	@Override
	public void execute(HttpServletRequest request, HttpServletResponse response) throws IOException {
		
//		AjaxWriteResult obj = AjaxWriteResult.builder()
//				.num(177)
//				.name(&quot;홍길동&quot;)
//				.arr(new int[] {10, 20, 30})
//				.cities(Arrays.asList(&quot;서울&quot;, &quot;인천&quot;, &quot;부산&quot;))
//				.item2(new AjaxItem(10, &quot;사과&quot;))
//				.build()				
//				;
		
		int uid = Integer.parseInt(request.getParameter(&quot;uid&quot;));
		
		QryCommentList obj = new QryCommentList();
		ObjectMapper mapper = new ObjectMapper();   // JSON 매핑할 Mapper 객체
		
		
		try {
			List&amp;lt;CommentDTO&amp;gt; list = new CommentDAO().selectByWrite(uid);
			obj.setCount(list.size());
			obj.setStatus(&quot;OK&quot;);
			obj.setList(list);
		} catch (SQLException e) {
			e.printStackTrace();
			obj.setStatus(&quot;댓글목록 ERROR: &quot; + e.getMessage());
		}
		
		String output = mapper.writeValueAsString(obj);  // JSON String &amp;lt;- Java Object
		System.out.println(&quot;output : &quot; + output);
		response.setContentType(&quot;application/json; charset=utf-8&quot;);  // MIME 설정
		response.getWriter().write(output);

	}

}

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
class AjaxWriteResult {
	private int num;   // -&amp;gt; number 로
	
	@JsonProperty(&quot;Name&quot;)  // JSON 으로 출력되는 이름지정
	private String name; // -&amp;gt; string
	private int[] arr;  // -&amp;gt;  array 로
	@JsonIgnore   // JSON 변환에서 제외
	private List&amp;lt;String&amp;gt; cities; // -&amp;gt; array 로 변환
	
	public int getAge() {
		return 43;
	}
	
	// -&amp;gt; object 로 변환
	AjaxItem item1;
	AjaxItem item2;
	
}

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
class AjaxItem {
	private int stock;
	private String product;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;java.time.*&amp;nbsp;처리해주기 &lt;br /&gt;여기서&amp;nbsp;CommentDTO&amp;nbsp;List&amp;nbsp;를&amp;nbsp;넣으면&amp;nbsp;response&amp;nbsp;할때&amp;nbsp;에러&amp;nbsp;난다. &lt;br /&gt;1.&amp;nbsp;Java8&amp;nbsp;의&amp;nbsp;Date/Time&amp;nbsp;객체를&amp;nbsp;위한&amp;nbsp;Jackson&amp;nbsp;의&amp;nbsp;추가&amp;nbsp;모듈&amp;nbsp;설치&amp;nbsp;+ &lt;br /&gt;2.&amp;nbsp;java.time.*&amp;nbsp;에&amp;nbsp;대한&amp;nbsp;annotation&amp;nbsp;&amp;nbsp;설정이&amp;nbsp;&amp;nbsp;필요.하다 &lt;br /&gt;일단&amp;nbsp;server&amp;nbsp;stop&amp;nbsp;하고&amp;nbsp;라이브러리&amp;nbsp;설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Jackson + java.time.*&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;annotaion 설정하기 &lt;br /&gt;&lt;br /&gt;* 불필요한 필드에 @JsonIgnore 처리하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 일단 하단에 댓글 html 기본양식 붙이기 &lt;br /&gt;board/comment.jsp&amp;nbsp;생성,&amp;nbsp;&amp;nbsp;댓글&amp;nbsp;기본&amp;nbsp;양식 &lt;br /&gt;그리고 board/view.jsp&amp;nbsp;&amp;nbsp;에 include 시키기 그리고&amp;nbsp;결과&amp;nbsp;확인&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* ROLE_MEMBER 가진 사용자만 댓글 작성 가능케 하기 &lt;br /&gt;&lt;b&gt;comment.jsp&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;%@ page language=&quot;java&quot; contentType=&quot;text/html; charset=UTF-8&quot;
    pageEncoding=&quot;UTF-8&quot;%&amp;gt;
&amp;lt;%@ taglib prefix=&quot;c&quot; uri=&quot;http://java.sun.com/jsp/jstl/core&quot;%&amp;gt;   
&amp;lt;%@ taglib prefix=&quot;fn&quot; uri=&quot;http://java.sun.com/jsp/jstl/functions&quot; %&amp;gt; 

&amp;lt;script src=&quot;https://kit.fontawesome.com/51772bd9bd.js&quot; crossorigin=&quot;anonymous&quot;&amp;gt;&amp;lt;/script&amp;gt;
    
&amp;lt;div class=&quot;container my-3 border rounded&quot;&amp;gt;
    &amp;lt;div class=&quot;mb-3 mt-3&quot;&amp;gt;
        &amp;lt;label&amp;gt;댓글: &amp;lt;span id=&quot;cmt_cnt&quot;&amp;gt;&amp;lt;/span&amp;gt; 개&amp;lt;/label&amp;gt;
        
        &amp;lt;%-- ROLE_MEMBER 권한 가진 사용자만 댓글 작성 가능 --%&amp;gt;
        &amp;lt;c:if test=&quot;${fn:contains(sessionScope.PRINCIPAL.authorities, 'ROLE_MEMBER') }&quot;&amp;gt;
        &amp;lt;div class=&quot;input-group my-2&quot;&amp;gt;
            &amp;lt;input type=&quot;text&quot; class=&quot;form-control&quot; id=&quot;input_comment&quot;&amp;gt;
            &amp;lt;button type=&quot;button&quot; class=&quot;btn btn-outline-primary&quot; id=&quot;btn_comment&quot;&amp;gt;작성&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;/c:if&amp;gt;

        &amp;lt;table class=&quot;table table-hover mt-3&quot; id=&quot;cmt_table&quot;&amp;gt;
            &amp;lt;thead&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;th style=&quot;width: 16.66%&quot;&amp;gt;작성자&amp;lt;/th&amp;gt;
                &amp;lt;th&amp;gt;내용&amp;lt;/th&amp;gt;
                &amp;lt;th style=&quot;width: 16.66%&quot;&amp;gt;작성일&amp;lt;/th&amp;gt;
              &amp;lt;/tr&amp;gt;
            &amp;lt;/thead&amp;gt;
            &amp;lt;tbody id=&quot;cmt_list&quot;&amp;gt;

            &amp;lt;/tbody&amp;gt;
        &amp;lt;/table&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;    &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 확인 후에는 일부 내용 삭제하기 &lt;br /&gt;댓글개수,&amp;nbsp;&amp;lt;tbody&amp;gt;&amp;nbsp;안의&amp;nbsp;내용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* js/view.js 파일 생성&lt;/b&gt; &lt;br /&gt;* board/view.jsp 에는 &lt;br /&gt;&amp;nbsp;jQuery&amp;nbsp;와&amp;nbsp;함께&amp;nbsp;js&amp;nbsp;파일&amp;nbsp;포함 &lt;br /&gt;contextPath&amp;nbsp;와&amp;nbsp;현재&amp;nbsp;로그인한&amp;nbsp;user&amp;nbsp;의&amp;nbsp;uid&amp;nbsp;정보가&amp;nbsp;js&amp;nbsp;에&amp;nbsp;넘겨져야&amp;nbsp;하므로&amp;nbsp;jsp&amp;nbsp;파링레&amp;nbsp;설정 &lt;br /&gt;view.js&amp;nbsp;을&amp;nbsp;include&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* view.js&amp;nbsp;&amp;nbsp;작성&lt;/b&gt; &lt;br /&gt;ajax()&amp;nbsp;request&amp;nbsp;-&amp;nbsp;response&amp;nbsp;하기 &lt;br /&gt;댓글&amp;nbsp;목록&amp;nbsp;렌더링&amp;nbsp;해주기 &lt;br /&gt;작성자&amp;nbsp;본인인&amp;nbsp;경우에만&amp;nbsp;삭제&amp;nbsp;버튼&amp;nbsp;보여주기 &lt;br /&gt;댓글&amp;nbsp;작성&amp;nbsp;input&amp;nbsp;은&amp;nbsp;ROLE_MEMBER&amp;nbsp;권한을&amp;nbsp;가진이에게만&amp;nbsp;보여주기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 댓글 작성&lt;/b&gt; &lt;br /&gt;다음의&amp;nbsp;항목들이&amp;nbsp;서버로&amp;nbsp;전달되어야&amp;nbsp;한다 &lt;br /&gt;1.&amp;nbsp;어느글에&amp;nbsp;대한&amp;nbsp;댓글인지? &lt;br /&gt;2.&amp;nbsp;어느&amp;nbsp;사용자가&amp;nbsp;작성한&amp;nbsp;댓글인지? &lt;br /&gt;3.&amp;nbsp;댓글&amp;nbsp;내용은&amp;nbsp;무엇인지? &lt;br /&gt;서버에서 INSERT 트랜잭션후 다시 댓글 list 를 불러와서 update 를 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* 댓글 작성 버튼 누를시 동작&lt;/b&gt;&lt;br /&gt;view.js&amp;nbsp;의&amp;nbsp;document.ready()&amp;nbsp;에&amp;nbsp;등록 &lt;br /&gt;1.&amp;nbsp;입력한&amp;nbsp;값&amp;nbsp;검증 &lt;br /&gt;2.&amp;nbsp;전달할&amp;nbsp;parameter&amp;nbsp;준비 &lt;br /&gt;3.&amp;nbsp;ajax&amp;nbsp;request&amp;nbsp;-&amp;nbsp;response &lt;br /&gt;4.&amp;nbsp;response&amp;nbsp;후&amp;nbsp;댓글&amp;nbsp;목록&amp;nbsp;업데이트&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* CmtWriteCommand 작성&lt;/b&gt;&lt;br /&gt;CmtWriteCommand.java&amp;nbsp;작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;package command.comment;

import java.io.IOException;
import java.sql.SQLException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.fasterxml.jackson.databind.ObjectMapper;

import command.Command;
import domain.CommentDAO;
import domain.CommentDTO;
import domain.QryResult;
import domain.UserDTO;
import domain.WriteDTO;

public class CmtWriteCommand implements Command {

	@Override
	public void execute(HttpServletRequest request, HttpServletResponse response) throws IOException {
		int writeUid = Integer.parseInt(request.getParameter(&quot;write_uid&quot;));  // 어느글에 대한 댓글?
		int userUid = Integer.parseInt(request.getParameter(&quot;user_uid&quot;));  // 누가 쓰는 댓글?
		String content = request.getParameter(&quot;content&quot;);
		
		WriteDTO write = new WriteDTO();
		write.setUid(writeUid);
		UserDTO user = new UserDTO();
		user.setUid(userUid);

		CommentDTO dto = CommentDTO.builder()
				.write(write)
				.user(user)
				.content(content)
				.build();
		
		QryResult obj = new QryResult();  // response 할 Java 객체
		ObjectMapper mapper = new ObjectMapper();  // JSON 으로 매핑할 Mapper 객체
		
		try {
			int cnt = new CommentDAO().insert(dto);
			obj.setCount(cnt);
			obj.setStatus(&quot;OK&quot;);
		} catch (SQLException e) {
			e.printStackTrace();
			obj.setStatus(&quot;댓글 작성 ERROR: &quot; + e.getMessage());
		}
		
		String output = mapper.writeValueAsString(obj);  // JSON 문자열 변환
		response.setContentType(&quot;application/json; charset=utf-8&quot;); // MIME 설정
		response.getWriter().write(output);  // 전송
		
	}

}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;*&amp;nbsp;댓글&amp;nbsp;삭제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;몇번(uid)&amp;nbsp;댓글이&amp;nbsp;삭제되는&amp;nbsp;것인가? &lt;br /&gt;2.&amp;nbsp;삭제하기&amp;nbsp;전에&amp;nbsp;한번&amp;nbsp;confirm&amp;nbsp;시키기 &lt;br /&gt;3.&amp;nbsp;댓글&amp;nbsp;삭제&amp;nbsp;버튼 &lt;br /&gt;명심!&amp;nbsp;:&amp;nbsp;댓글목록을&amp;nbsp;불러온&amp;nbsp;후에,&amp;nbsp;삭제&amp;nbsp;버튼에&amp;nbsp;대한&amp;nbsp;이벤트&amp;nbsp;리스터를&amp;nbsp;동작시켜야&amp;nbsp;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #695d46;&quot;&gt;view.js&lt;/span&gt;&lt;span style=&quot;color: #695d46;&quot;&gt; 에 addDelete() 함수&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;* CmtDeleteCommand 작성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;package command.comment;

import java.io.IOException;
import java.sql.SQLException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.fasterxml.jackson.databind.ObjectMapper;

import command.Command;
import domain.CommentDAO;
import domain.QryResult;

public class CmtDeleteCommand implements Command {

	@Override
	public void execute(HttpServletRequest request, HttpServletResponse response) throws IOException {
		int uid = Integer.parseInt(request.getParameter(&quot;uid&quot;));
		
		QryResult obj = new QryResult();
		ObjectMapper mapper = new ObjectMapper();
		
		try {
			int cnt = new CommentDAO().deleteByUid(uid);
			obj.setCount(cnt);
			obj.setStatus(&quot;OK&quot;);
		} catch (SQLException e) {
			e.printStackTrace();
			obj.setStatus(&quot;댓글 삭제 ERROR: &quot; + e.getMessage());
		}
		
		String output = mapper.writeValueAsString(obj);  // JSON 문자열 변환
		response.setContentType(&quot;application/json; charset=utf-8&quot;); // MIME 설정
		response.getWriter().write(output);  // 전송

	}

}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>JSP</category>
      <author>shb</author>
      <guid isPermaLink="true">https://shprogramming.tistory.com/138</guid>
      <comments>https://shprogramming.tistory.com/138#entry138comment</comments>
      <pubDate>Wed, 13 Apr 2022 18:29:18 +0900</pubDate>
    </item>
  </channel>
</rss>