우선 첫번째 전제로 페이지 폼 태그에서


<form action="insert.me" method="POST" enctype="multipart/form-data">

<input type="file" name="photo">

</form>


과 같이 enctype="multipart/form-data"으로 데이터를 전송해야 한다.






두번째 전제로 라이브러리 다운로드


업로드 관련 라이브러리를 pom.xml에 추가하자( https://qdgbjsdnb.tistory.com/237 )




세번째 전제로 업로드 관련 서블릿 xml 설정들


    <!-- 파일 업로드를 하기 위해 bean 등록 -->
    <beans:bean class="org.springframework.web.multipart.commons.CommonsMultipartResolver" id="multipartResolver">
        <beans:property name="maxUploadSize" value="10000000"></beans:property>
    </beans:bean>


용량 10MB 이하만 등록가능 하기 때문에 10MB보다 조금 적게 max를 설정








구현


    @RequestMapping("insert.me")
    public String insertMember(Member m, Model model, HttpServletRequest request, @RequestParam(value = "photo", required = false) MultipartFile photo) {
        System.out.println("Member : " + m);
        System.out.println("photo : " + photo);
       
        String root = request.getSession().getServletContext().getRealPath("resources");
       
        String filePath = root + "\\uploadFiles";
       
        // 파일명 변경
        String originFileName = photo.getOriginalFilename();
        String ext = originFileName.substring(originFileName.lastIndexOf("."));
        String changeName = CommonUtils.getRandomString();
       
        try {
            photo.transferTo(new File(filePath + "\\" + changeName + ext));
           
            ms.insertMember(m);
           
            return "redirect:goMain.me";
           
        } catch (Exception e) {
            new File(filePath + "\\" + changeName + ext).delete();
           
            model.addAttribute("msg", "회원가입 실패!");
            return "common/errorPage";
        }
    }




주목할 부분은 빨간색 글씨로 입력


ext는 기존의 파일의 확장자를 따옵니다.


DB의 중복을 피하기위해 랜덤 String을 생성해 이름을 변경


photo.transferTo(new File(filePath + "\\" + changeName + ext)); 를 통해 저장


만약 DB 등록에 실패하면 new File(filePath + "\\" + changeName + ext).delete();로 저장된 파일 삭제






사용자가 View를 통해서 로그인 정보(ID, PW)를 입력하여 로그인을 요청했다는 가정하에 진행


Controller에 로그인 정보가 넘어왓을 때







위와 같은 구조로 순서는


View - Controller - try( Service - Dao ), catch(LoginException)


진행


Controller 데이터 처리는 저번 포스트와 같이 하면 된다. ( https://qdgbjsdnb.tistory.com/238 )


소개한 8가지 방법 중 아무거나 써도 된다.


이 중 7번째


@Controller
public class MemberController {

    @Autowired
    private MemberService ms;


    @RequestMapping("login.me")
    public String loginCheck(Member m, Model model, HttpSession session) {
        Member loginUser;
        try {
            loginUser = ms.loginMember(m);
           
            session.setAttribute("loginUser", loginUser);
           
            return "redirect:goMain.me";
        } catch (LoginException e) {
            model.addAttribute("msg", e.getMessage());
           
            return "common/errorPage";
        }
       
        return null;
    }

}




Member 타입 객체 loginUser는


Spring이 객체화 해 준 Service 인터페이스인 MemberService 타입 객체 ms의


loginMember 메소드를 이용하여 로그인 정보(ID, PW)를 넘긴다.




loginUser = ms.loginMember(m);의 loginMembe메소드나 그 하위 메소드에서


throw new LoginException을 하용하기 때문에 try catch문이 필요.





MemberService.java


public interface MemberService {

    Member loginMember(Member m) throws LoginException;

}

MemberServiceImpl.java


@Service

public class MemberServiceImpl implements MemberService {
    @Autowired
    private SqlSessionTemplate sqlSession;
    // 이렇게만 쓰면 Autowired로 생성되지 않기 때문에 xml에 따로 명시해야함
    // root-context.xml에서 진행
    
    @Autowired
    private MemberDao md;


    @Override
    public Member loginMember(Member m) throws LoginException {


        System.out.println(sqlSession.hashCode());


        Member loginUser = md.loginCheck(sqlSession, m);
        return loginUser;
    }

}




SqlSessionTemplate sqlSession은 DB를 연결해주는 라이브러리를 이용한 객체


저번에 설명한 대로 설정파일을 읽어 Spring에서 자동 생성한다. ( https://qdgbjsdnb.tistory.com/236 )


MemberDao md; 또한 컨트롤러에서 Service생성과 똑같이 생성된다.


이번엔 굳이 try catch를 사용하지 않고 throw를 사용






※참고 - 기존의 서블릿 방식으로 객체를 수작업으로 생성하면 .hashcode를


여러번 호출 하였을 때 값이 계속 바뀌는것을 확인 할 수 있다.


Spring에서는 Spring 컨테이너로 생성해준 객체의 .hash코드를


여러번 호출하면 매번 같은 값을 출력하는걸 확인할 수 있다.


이를 통해 같은 객체를 매번 새롭게 생성하는것이 아닌 재사용을 통해


메모리를 좀 더 효율적으로 사용하고 있다고 확인할 수 있다.






MemberDao.java


public interface MemberDao {

    Member loginCheck(SqlSessionTemplate sqlSession, Member m) throws LoginException;

}




MemberDaoImpl.java



@Repository
public class MemberDaoImpl implements MemberDao{

    @Override
    public Member loginCheck(SqlSessionTemplate sqlSession, Member m) throws LoginException {
        Member loginUser = sqlSession.selectOne("Member.loginCheck", m);
       
        System.out.println("Dao Member : " + loginUser);
       
        if (loginUser == null) {
            throw new LoginException("로그인 정보가 존재하지 않습니다.");
        }
       
        return loginUser;
    }

}


MemberDaoImple에서는 SqlSessionTemplat과 myBatis 메소드를 이용해서


DB를 조회해온다.


메소드 명, 사용법은 저번에 설명한 mybatis와 완전 같음.


( https://qdgbjsdnb.tistory.com/228?category=733892, https://qdgbjsdnb.tistory.com/229?category=733892 )


sqlSession.selectOne("Member.loginCheck", m);


조회 결과를 통해 try catch




mybatis와 DB 관련 라이브러리를 이용하기 때문에 pom.xml에 라이브러리 추가는 필수이다.( https://qdgbjsdnb.tistory.com/237 )








LoginException.java의 경우는 간단하게 다음과 같다.


public class LoginException extends Exception{
    public LoginException(String msg) {
        super(msg);
    }
}







이와 같이 Spring을 이용하면 이와같이 코드의 길이가 매우 짧아지고


자동으로 해주는것(프로그래머가 아닌 Spring Container가)이 많아져 편리하다.


특히 누구든 코드 분석하기가 훨씬 쉬워져 다른 사람과 작업에 좋다.


협업에 알맞지만 Spring 구조와 설정 사용법 등 너무 복잡하고 어렵다는 단점이 있다.





다음과 같은 Ajax 요청이 있다고 하자.


    <script type="text/javascript">
        function duplicationCheck() {
            var userId = $("#userId").val();
           
            $.ajax({
                url:"duplicationCheck.me",
                type:"post",
                data:{userId:userId},
                success:function(data){
                    /* alert(data); */
                    console.log(data);
                    console.log(data.userId);
                },
                error:function(status){
                    console.log(status);
                }
               
            });
           
            return false
        }
    </script>





duplicationCheck.me로 ID가 중복되는지 체크해달라는 요청이다.


Controller, Service, Dao 중 Service, Dao 사용방법은 단순 DB 조회로 생략


Controller에서 4가지 방법을 확인해보자.


기본적으로 pom.xml을 통해서 관련 라이브러리가 추가되었음을 전제로 진행 ( https://qdgbjsdnb.tistory.com/237?category=733876 )


또한 관련 라이브러리 추가로 인한 xml 설정


servlet.xml, 서블릿 xml에 다음 태그들을 추가




    <!-- jsonView 설정 -->
    <beans:bean id="jsonView" class="net.sf.json.spring.web.servlet.view.JsonView"></beans:bean>
    <!-- beanNameViewResolver는 없는 자원에 대해 논리적 이름을 가지고 view를 지정한다. -->
    <beans:bean id="viewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver">
        <beans:property name="order" value="1"></beans:property>
    </beans:bean>
    
    <!-- 메세지 컨버터 추가 -->
    <!-- 자바 객체를 자바스크립트 객체로 바꿔주는 역할을 한다. -->
    <beans:bean id="jacksonMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"></beans:bean>
    <beans:bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <beans:property name="messageConverters">
            <beans:list>
                <beans:ref bean="jacksonMessageConverter"></beans:ref>
            </beans:list>
        </beans:property>
    </beans:bean>


xml 설정은 이것으로 마치고 구현



1. 스트림을 이용, 다른 툴은 이용하지 않고 기본 기능을 통해서 값 전달



    // 1. 스트림을 이용한 ajax 처리
    @RequestMapping("duplicationCheck.me")
    public void duplicationCheck(@RequestParam String userId, HttpServletResponse response) {
        System.out.println(userId);
       
        try {
            response.getWriter().print(false);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }



2. ObjectMapper를 이용, 잭슨 라이브러리를 사용


ObjectMapper라는 객체를 생성하여 메소드를 이용해서 객체 자체를 전달



    // 2. ObjectMapper를 이용한 ajax
    @RequestMapping("duplicationCheck.me")
    public void duplicationCheck(@RequestParam String userId, HttpServletResponse response) {
        // 잭슨 라이브러리를 사용함
        ObjectMapper mapper = new ObjectMapper();
       
        Member m = new Member();
       
        m.setUserId(userId);
       
        try {
            response.getWriter().print(mapper.writeValueAsString(m));
        } catch (JsonGenerationException e) {
            e.printStackTrace();
        } catch (JsonMappingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }




결과 Data 값은 위와 같이 출력된다.


배열이나 객체 타입이 아닌 String 형으로 나열되어 나온다.








3. jsonView를 이용, Controller의 ModelAndView 방식과 유사


반환 타입이 ModelAndView



    // 3. jsonView를 사용한 방식
    @RequestMapping("duplicationCheck.me")
    public ModelAndView duplicationCheck(String userId, ModelAndView mv) {
       
        Member m = new Member();
        m.setUserId(userId);
       
        mv.addObject("member", m);
       
        mv.setViewName("jsonView");
       
        // 한글의 경우 인코딩 처리를 해야함
       
        return mv;
    }




결과는 이와같이 오브젝트 타입으로 안에 배열 형식으로 객체가 담겨있다.





4. @ResponseBody를 이용, HashMap을 통해 원하는값을 보냄




    // 4. @ResponseBody를 이용한 ajax
    @RequestMapping("duplicationCheck.me")
    public @ResponseBody HashMap<String, Object> duplicationCheck(@RequestParam String userId, HttpServletResponse response) {
       
        HashMap<String, Object> hmap = new HashMap<String, Object>();
       
        hmap.put("userId", userId);
       
        return hmap;
    }






결과는 위와 같다.


HashMap을 통해서 원하는 값만 간편하게 보내면 된다.








4가지 방식을 보면 사용방법은 모두 쉽다.


문제는 반환되는 형식이 어떠하냐인데 마음에 드는 방법에 익숙해지거나


상황에 맞는 방법을 골라서 하면 될거같다.





Servlet을 사용할 땐 SHA-512 방식의 암호화를 사용해 보았는데 이번엔


BCrypt 방식을 이용해보자.


예제는 회원가입 시 Controller에서 입력받은 암호를 암호화 처리를 통해 변경하는 작업



MemberController.java


@Controller
public class MemberController {
    // 의존성 주입용 필드 선언
    // 어노테이션 + Interface 타입
    @Autowired
    private MemberService ms;
    // 인터페이스를 상속받는 객체에 @Component나 @Service 어노테이션이 필요
    // 객체 생성 시 new Service와 같은 부분을 Spring에게 맡기는것.
   
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;


    @RequestMapping("insert.me")
    public String insertMember(Member m, Model model) {
       
        System.out.println("member : " + m);
       
        String encPassword = passwordEncoder.encode(m.getUserPwd());
       
        System.out.println(encPassword);
       
        m.setUserPwd(encPassword);
       
        int result = ms.insertMember(m);
       
        if (result > 0) {
            return "redirect:goMain.me";
        } else {
            model.addAttribute("msg", "회원가입 실패!");
            return "common/errorPage";
        }
       
    }

}



먼저 bcrypt란?
DB에 비밀번호를 저장할 목적으로 설계된 알고리즘

단방향 해쉬 함수는 암호화된 메세지를 수학적인 연산을 통해 암호화 된 메세지인 다이제스트를 생성한다.


원본 메세지를 가지고 암호화된 메세지를 복호화 할 수 있는 것을 양방향이라고 하고


암호화된 메세지를 복호화 할 수 없는 것을 단방향이라고 한다.


다이제스트란 결과값들이라고 생각하면 된다.


단방향 해쉬 함수도 사용하면 안되는 이유


1. 단방향 해쉬함수는 많은 다이제스트가 확보되면 평문을 찾아낼 수 있다.


2. 비밀번호를 저장하기 위한 목적으로 설계된 알고리즘이 아닌,


검색을 위해 설계된 알고리즘이다.


그만큼 다이제스트의 생성이 매우 빨라 뚫리는데 시간이 짧다.


이를 해결하기 위해 슬링(salting)기법이 추가되었다.


원본 메세지에 문자열을 추가하여 동일한 길이의 다이제스트를 생성하는 것을 슬링이라 한다.

하지만 salt 값을 알아내면 나머지는 단방향 해쉬함수를 통한 다이제스트를 복호화 하는 것과 별 차이가 없다.


bcrypt 방식은 이러한 salt값을 랜덤하게 생성하여 암호화를 하는 방식이다.


추가적으로 다이제스트를 생성하는데 걸릴 시간을 결정할 수도 있다.


생성 시간에 오래 걸린다면 대량의 다이제스트 생성 역시 오래걸리게 된다.


1999년에 발표되어 현재까지 자주 사용되는 강력한 비밀번호 저장용 매커니즘이다.





설명은 이렇고 사용 자체는 라이브러리 등록, 선언


암호화 메소드 or 암호 일치하는지?에 대한 메소드 선언밖에 없는 간단한 방법이다.






작업 순서로는


메이븐 시큐리티 라이브러리 추가 - Autowired 추가 - security xml 추가 - web.xml에 추가된 xml들 추가이다.







메이븐 시큐리티 라이브러리 pom.xml에 추가한다. - https://qdgbjsdnb.tistory.com/237


Autowired 추가는 빨간글자로 표시해 두었다.



다음은 web.xml 파일 추가부분


/WEB-INF/config/spring-security.xml 추가


    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:root-context.xml
            /WEB-INF/config/spring-security.xml
        </param-value>
    </context-param>



/WEB-INF/config/spring-security.xml의 설정을 읽어오라고 명시하였으니


해당 경로에 파일을 생성한다.


<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
   



    <beans:bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></beans:bean>
   



</beans:beans>






기존에 있는 기본 beans 선언 안에


<beans:bean id="bcryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></beans:bean>


BCryptPasswordEncoder Bean 선언만 하면 끝난다.








메소드의 경우는 BCryptPasswordEncoder 로 선언된 객체를 통해


encode(String형)을 사용하면 String형이 암호화되고


matches(입력한 암호, DB에 저장된 암호)를 입력했을 땐 암호가 일치하는치 체크해준다.(true or false)





        String encPassword = passwordEncoder.encode(m.getUserPwd());



        if (!passwordEncoder.matches(m.getUserPwd(), encPassword)) {
            throw new LoginException("로그인 실패!");
        } else {
            loginUser = md.selectMember(sqlSession, m);
        }






먼저 Servlet 프로젝트에서 사용했던 가장 익숙한 방법.


예제는 전부 login.me라는 맵핑 주소를 통해 연결되고 POST 방식으로 전달 되었다.




1. HttpServletRequest, HttpServletResponse를 이용



    // 1. 서블릿에서 했던 방식대로 HttpServletRequest, HttpServletResponse를
    // 매개변수로 선언하면 스프링 컨테이너가 이 메소드를 실행할 때 자동으로 두 객체를 인자로 주입해준다.
    @RequestMapping("/login.me")
    public String loginCheck(HttpServletRequest request, HttpServletResponse response) {
        String userId = request.getParameter("userId");
        String userPwd = request.getParameter("userPwd");
       
        System.out.println(userId + "  " + userPwd);
       
        return "main/main";
    }



2. 어노테이션 RequestParam을 이용하여 값을 받아오기



    // 2. RequestParam 어노테이션을 이용해서 파라미터 값 받아오기
    // => 스프링에서는 조금 더 간단하게 파라미터를 받아올 수 있는 방법을 제공한다(RequestParam)
    @RequestMapping(value="/login.me", method=RequestMethod.POST)
    public String loginCheck(@RequestParam("userId")String userId, @RequestParam String userPwd) {
        System.out.println(userId + "  " + userPwd);
       
        return "main/main";
    }


@RequestParam("전달받은 name") 처럼 name을 명시해도 되고


아니면 ("")을 생략해도 자동으로 잡히기 때문에 편한 방식으로 사용하면 된다.






3. 어노테이션 RequestParam은 생략이 가능하다.




// 3. RequestParam 어노테이션은 생략해도 파라미터값을 가져와서 매개변수에 저장할 수 있다.
    @RequestMapping(value="login.me", method=RequestMethod.POST)
    public String loginCheck(String userId, String userPwd) {
        System.out.println(userId + "  " + userPwd);
       
        return "main/main";
    }


생략을 통해서 구현하면 본인은 괜찮지만 나중에 봤을 때나 다른사람이 봤을 때


못알아 볼 수 있기 때문에 그냥 명시하는것을 추천





4. @ModelAttribute를 이용, 객체에 자동으로 Setter을 이용하여 값을 가져온다.




    // 4. @ModelAttribute를 이용한 값 전달받는 방법(커맨드 객체)
    @RequestMapping(value="login.me", method=RequestMethod.POST)
    public String loginCheck(@ModelAttribute Member m) {
        System.out.println(m);
       
        return "main/main";
    }

해당 객체에 Setter 메소드가 있어야 되고


전달받은 name과 해당 객체의 필드명이 같아야함.


오타에 주의하자.




5. @ModelAttribute를 생략하는 방법 + 값 리턴방법(예전에 쓰던 request, response)




    // 5. 위의 어노테이션을 생략하고 객체로 받는 방법
    @RequestMapping(value="login.me", method=RequestMethod.POST)
    public String loginCheck(Member m, HttpServletRequest request, HttpServletResponse response) {
        System.out.println(m);
       
        MemberService ms = new MemberServiceImpl(); // 결합을 느슨하게 만듦
       
        try {
            Member loginUser = ms.loginMember(m);
           
            request.getSession().setAttribute("loginUser", loginUser);
           
            return "redirect:goMain.me";
        } catch (LoginException e) {
            request.setAttribute("msg", e.getMessage());
            return "common/errorPage";
        }
       
        return "main/main";
    }


Member m만 입력해도 Spring이 자동으로 맞춰준다.






6. ModelAndView라는 객체를 사용하여 값을 리턴



    // 6. ModelAndView로 리턴 처리
    // => model은 뷰로 전달할 데이터를 앱 형식으로 담을 때 사용하는 객체로 scope는 request이다.
    // => view는 requestDispatcher처럼 forward할 뷰 페이지 정보를 담은 객체이다.
    // => ModelAndView는 이 두가지를 합쳐 놓은 객체이며
    // => Model 객체를 따로 사용하는 것도 가능하다.
    // => 하지만 view 객체는 따로 사용하지 못한다.
    @RequestMapping("login.me")
    public ModelAndView loginCheck(Member m, ModelAndView mv, HttpSession session) {
        try {
            Member loginUser = ms.loginMember(m);
           
            session.setAttribute("loginUser", loginUser);
           
            mv.setViewName("redirect:goMain.me");
           
        } catch (LoginException e) {
            mv.addObject("msg", e.getMessage());
            mv.setViewName("common/errorPage");
        }
       
       
        return mv;
    }


ModelAndView는 리턴 받는 페이지의 정보도 담아가고 리턴 받을 값도 전부 담아서


메소드 리턴타입으로 돌려주는걸 확인 할 수 있다.





7. model 객체로 값을 리턴하고 String 리턴으로 뷰 이동



    // 7. Model 객체를 따로 사용하고 String으로 뷰 이름을 리턴하는 방법
    @RequestMapping("login.me")
    public String loginCheck(Member m, Model model, HttpSession session) {
        Member loginUser;
        try {
            loginUser = ms.loginMember(m);
           
            session.setAttribute("loginUser", loginUser);
           
            return "redirect:goMain.me";
        } catch (LoginException e) {
            model.addAttribute("msg", e.getMessage());
           
            return "common/errorPage";
        }
       
        return null;
    }



8. @SessionAttributes, model 객체를 사용하여 Session 리턴


@SessionAttributes("loginUser")
@Controller
public class MemberController {


    // 8. session에 저장을 할  때 @SessionAttributes 사용하기
    // => 모델에 Attribute가 추가될 때 자동으로 키 값을 찾아 세션에 등록하는 기능을 제공하는
    //    어노테이션이다.
    @RequestMapping("login.me")
    public String loginCheck(Member m, Model model) {
       
        try {
            Member loginUser = ms.loginMember(m);
           
            model.addAttribute("loginUser", loginUser);
           
            return "redirect:goMain.me";
           
        } catch (LoginException e) {
            model.addAttribute("msg", e.getMessage());
            return "common/errorPage";
        }
       
    }

}

@SessionAttributes("loginUser") 라고 명시를 해 두면


model.addAttribute("loginUser", loginUser);라고 추가했을 때 자동으로


Session으로 추가된다.











보너스로 세션 파기하는 방법


    @RequestMapping("logout.me")
    public String LogOutputStream(HttpServletRequest request) {
        request.getSession().invalidate();
       
        return "redirect:goMain.me";
    }


    @RequestMapping("goMain.me")
    public String goMain() {
        // 포워드만 해주는 메소드
        return "main/main";
    }


리턴을 통해서 바로 페이지로 돌아가게 되면


새로고침 시 get, post 값을 한 번 더 전송할 수 있다.


이를 방지하기 위해 포워드만 해주는 메소드로 redirect 우회하면 된다.









※ 위의 메소드들에서 사용하는 ms.***** ms 객체는


@Autowired로 생성한 bean 객체


db에서 로그인 정보를 확인해오는 객체이다.






Spring에서는 객체를 직접 new Service();와 같이 생성하지 않아도 사용할 수 있게 설정할 수 있다.


new Service와 같은 객체화를 Spring에 맡기는 것이다.


- 컨트롤러 -


@Controller
public class MemberController {
    // 의존성 주입용 필드 선언
    // 어노테이션 + Interface 타입
    @Autowired
    private MemberService ms;

    // 인터페이스를 상속받는 객체에 @Component나 @Service 어노테이션이 필요
    // 객체 생성 시 new Service와 같은 부분을 Spring에게 맡기는것.


}




- Service 인터페이스 -


public interface MemberService {

}


- Service -


// Component, Service 둘다 사용 가능
/*@Component*/
@Service
public class MemberServiceImpl implements MemberService {

}


위와 같은 Member java 파일들이 있다고 할 때


컨트롤러에서 객체를 자동 생성해 주는 부분은


@Autowired
private MemberService ms;


이 부분이다.


어노테이션 @Autowired를 위에 명시하고


객체화 할(MemberServiceImpl) Java 파일 상단에


서비스의 경우는


@Component


@Service


Dao의 경우는 @Repository


해당하는 어노테이션을 명시한다.







이런식으로 어노테이션을 명시해두면


@Autowired
private MemberService ms;


Autowired만으로도 MemberService가 MemberServiceImpl 자바 파일로 객체화 되어 생성된다.


Service나 Dao와 같은 Spring framework 에서 사용하는 기본 패턴의 경우는


@Component, @Service, @Repository와 같은 어노테이션으로 충분하다.


이 외에도 작성자가 계속 간단하게 사용하고 싶어서 임의로 만들고 싶다면 XML 파일에 등록해 두면 된다.










보통 Spring에서는 Service 부분에서 SqlSession 이라는 객체를 생성하여 DB에 접근하기 위한


객체를 만들게 된다.


예전에 JDBCTemplate이라고 Properties 파일을 이용해 설정을 받아


객체를 생성하여 DB를 조회한것과 유사하다.


먼저 SqlSession을 만들기 위해서 mybatis 라이브러리를 다운로드 해야한다.


pom.xml 파일을 열어


        <!-- Test -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.7</version>
            <scope>test</scope>
        </dependency>



아래에 <dependency> 태그를 추가하자.


        <!-- jdbc 라이브러리 추가 -->
        <dependency>
            <groupId>com.jslsolucoes</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>11.2.0.1.0</version>
        </dependency>

        <!-- 마이바티스 라이브러리 추가 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>

        <!-- spring-jdbc 추가 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>

        <!-- 마이바티스 스프링 모듈 추가 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.1</version>
        </dependency>

        <!-- dbcp 라이브러리 추가 -->
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>


이 태그를 입력하고 파일을 저장하면 Maven이 자동으로 라이브러리 다운로드를 시작한다.


Progress 탭을 보고 다운로드가 완료되면


Problems에서 빨간색으로 된 오류가 있는지 확인해보고


톰캣을 실행시켜 정상적으로 켜지는지 확인해보자.


만약 문제가 있다면 라이브러리가 다운받아지는 폴더인 repository의 폴더들을 삭제하고


프로젝트 우클릭 - Maven - 업데이트를 한번 하고 다시 시도해보자.


오류가 또 난다면 반복







라이브러리가 정상적으로 다운로드 됫다면 root-context xml 파일을 열자.


sqlSessionTemplate을 Bean에 등록하는 작업





    <!-- 데이터베이스 접속에 관련된 클래스들을 bean으로 등록할 수 있다. -->
    <!-- sqlSessionTemplate 등록 -->
    <!-- SqlSessionTemplate 기본 생성자가 비어있지 않기 때문에 아무것도 안넣으면 오류가 난다. -->
    <!-- 생성자 매개변수인 sqlSession을 추가해주자. -->
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg ref="sqlSession"></constructor-arg>
    </bean>
   
    <!-- sqlSession도 bean으로 등록한다. -->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
        <property name="dataSource" ref="dataSource"></property>
    </bean>
   
    <!-- dataSource 객체 bean 등록 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
        <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"></property>
        <property name="username" value="DB 계정이름을 써주세요"></property>
        <property name="password" value="DB 계정비밀번호를 써주세요"></property>
        <property name="defaultAutoCommit" value="false"></property>
    </bean>




위의 코드를 xml 파일 중간 부분에 아무대나 추가하면 된다.


자동으로 생성될 객체들의 생성자를 설정하는 부분이다.


sqlSessionTemplate은 sqlSession를 인자로 받아 생성되고


sqlSession은 dataSource를 인자로 받아 생성된다.


dataSource의 Property(매개변수 인자들)를 보면 driver 설정, url 설정, 접속계정 설정, 접속비번 설정


설정을 하고 생성하게 된다.


그리고 Auto Commit을 False로 두었을 때 여러개의 DB 작업을 하면


성공한 곳 까지는 commit이 자동으로 되기 때문에 나중에 트랜잭션(AOP를 이용) 기능을 설정해주어야 한다.




이번에는 bean 태그의 속성들을 보자


<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"></bean>


id, class를 입력받게 된다.


id는 변수명이라고 보면 되고


class의 경우는 내가 생성할 객체의 해당하는 파일의 위치를 말한다.


org.mybatis.spring 위치에 있는 SqlSessionTemplate 파일을 이용해서 생성한다는 의미




이와같이 root-context.xml에 명시를 하고 톰캣을 실행해보면 정상적으로 자동 생성되는 것을 확인 할 수 있다.


활용으로 자기가 만든 객체도 비슷하게 생성할 수 있다.














Spring IoC(Inversion of Control)란,


프로그램을 돌릴 때 객체 생성, 변경 등 관리를 개발자가 아닌


프로그램을 돌리는 컨테이너가 직접 관리하는 것을 말한다.


Spring Framework는 IoC 구조를 통해서 직접 관리를 수행하게 된다.


우리가 XML에 설정할 부분이 많은 이유이다.


Spring에서 관리하는 객체를 Bean이라고 하고 이 객체들을 관리하는 컨테이너를 Bean Factory라고 한다.


이러한 컨테이너들에 의해 객체의 생명주기와 의존성을 관리하고


코드의 구현시간을 단축하게 해 준다.


또한 메모리 사용에도 좀 더 효율적인 것으로 알 고 있다.







Spring DI(Dependency Injection)란


Spring에서 IoC 구현의 핵심 기술로 의존성 주입을 의미한다.


사용하는 객체들을 개발자가 생성하지 않고 개발자가 xml 파일에 설정해둔


xml 기록을 토대로 컨테이너가 객체에 연결해주는 방법이다.


이러한 방법을 사용하면 코드가 단순해지고


객체간의 결합도(종속 관계)가 느슨해진다.


결합도란 한 클래스에서 필드 객체를 생성 시 발생하는 두 객체 간의 관계를 말하며


객체간의 내용이 수정 될 경우 영향을 미치는 정도라고 한다.




이러한 방식은 프로젝트 협업 시 공동 작업을 하기 때문에 이러한 방법을 사용하면 편의성이 높아지고


모든 객체를 xml에서 간편하게 확인할 수 있다.


생성할 객체에는 꼭 getter, setter가 존재해야 한다.


그리고 자동으로 bean 객체를 찾아서(어노테이션) 생성하는 기능을 Bean Scanning이라고 한다.







MVC 구조를 이용한다.


먼저 Spring Framework의 생명주기, 쉽게 말하면 돌아가는 순서를 알아야한다.



이미지 출처 : https://terasolunaorg.github.io/guideline/1.0.1.RELEASE/en/Overview/SpringMVCOverview.html



위 그림을 Spring Framework의 생명주기라고 하는데


쉽게 말해서 그냥 돌아가는 구조를 말한다.


간단하게 Client(사용자) - Dispatcher Servlet - Controller - Service - Dao 순서로 흘러간다.


그리고 중간에 Dispatcher Servlet은 Handler Mapping의 도움을 받아 해당하는 Controller(맵핑주소)를 찾고


Controller는 DB를 조회하던 무었이던 설정해둔 코딩 그대로 역할을 하고


View로 사용자에게 결과를 리턴해준다.


해당하는 View를 찾아주는 기능은 View Resolver라고 한다.





큰 틀은 Client(사용자) - Dispatcher Servlet - Controller - Service - Dao 순서로 돌아가는데


그 외의 도움을 주는 기능들 Dispatcher Servlet, Handler Mapping, View Resolver 등을 우리가


XML 설정 파일을 이용해 설정해두어야 정상적으로 작동한다.







먼저 Java 파일의 소스를 살펴보자.


맨 처음 프로젝트를 생성 했을 때 있는 HomeController.java 파일이다.


import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class HomeController {
   
    private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
   
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String home(Locale locale, Model model) {
        logger.info("Welcome home! The client locale is {}.", locale);
       
        Date date = new Date();
        DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
       
        String formattedDate = dateFormat.format(date);
       
        model.addAttribute("serverTime", formattedDate );
       
        return "home";
    }
   
}


Logger 부분은 로그 남기는 부분으로 나중에 따로 게시



먼저 @, 어노테이션을 보자.


@Controller = Spring Framework가 이 파일이 Controller인지 알기 위한 어노테이션


@RequestMapping = 핸들러 맵핑이 @RequestMapping가 써있는 메소드를 미리 찾아 value에 명시한 키워드를 기억하고 있다가


요청이 들어오면 연결시켜 준다.





보통 @RequestMapping는 뒤에 value와 method 인자를 갖고있고 (예 : (value = "/", method = RequestMethod.GET))


value 자리에는 맵핑 주소


method는 GET 방식 전달인지 POST방식 전달인지 정한다.


이 예제는 요청 주소가 '/'라면 GET 방식으로 값을 받아 동작하는 컨트롤러라는 의미이다.


함수명은 마음대로 사용해도 된다.





여기서 리턴 값은 home이라는 문자열이다.


이 리턴값을 이용해 View Resoler가 해당 페이지를 연결시켜 준다.


설정해둔 접두사 + 리턴값 + 접미사 형태로 합쳐져 url을 완성시킨다.


이 설정은 XML 파일에서 확인할 수 있다.


관련 xml 파일 : web.xml, ***-servlet.xml






web.xml 파일을 열어보면 중간에


    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/config/action-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
       
    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>



라고 Servlet 설정을 해두었다.


여기서 url-pattern이 /인 경우 /WEB-INF/config/action-servlet.xml 파일의 설정을 이용한다는 의미이고


action-servlet.xml을 열어보면




    <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>
   
    <context:component-scan base-package="com.kh.tsp" />




라는 구문으로 등록되어 있다.


beans:bean 태그에서 InternalResourceViewResolver 클래스를 이용(ViewResolver)


접두사 : /WEB-INF/views/


접미사 : .jsp


로 설정하고


Controller는 com.kh.tsp 패키지 경로 아래의 파일들을 모두 스캔한다.






아까 위의 HomeController를 이용해서 home이라는 문자열을 리턴받았다.


그렇다면 view resolver는 다음과 같은 결과로 url를 만든다.


"/WEB-INF/views/" + "home" + ".jsp"


"/WEB-INF/views/home.jsp"







이러한 원리로 Spring Framework에서는


Dispatcher Servlet과 Handler Mapping, Controller가 상호작용하여


작동하게 된다.







https://qdgbjsdnb.tistory.com/228 저번 게시글에서 myBatis에서


사용하는 xml 파일을 게시했는데 그에대한 부연 설명


먼저 mybatis config, 기본설정 xml



<?xml version="1.0" encoding="UTF-8"?>

<!-- 태그명-내용 = 키-값 처럼 쓰인다. -->

<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <settings>
        <!-- 만약에 null로 데이터가 전달 되었다면 빈칸이 아닌 null로 인식해라 라는 뜻 -->
        <setting name="jdbcTypeForNull" value="NULL"/>
    </settings>
   
    <!-- vo 객체들의 풀 네임을 사용하기 번거롭기 때문에 별칭을 등록하는 부분 -->
    <typeAliases>
        <!-- 클래스에 대한 별칭 -->
        <typeAlias type="com.kh.mb.member.model.vo.Member" alias="Member"/>
        <typeAlias type="com.kh.mb.board.model.vo.Board" alias="Board"/>
        <typeAlias type="com.kh.mb.board.model.vo.Reply" alias="Reply"/>
    </typeAliases>
   
    <!-- DB 연결할 설정에 대한 정보를 선언하는 부분 -->
    <environments default="firstDev">
        <environment id="firstDev">
            <!-- 트랙잭션 매니저는 JDBC 혹은 MANAGED 둘 중 하나를 선택할 수 있음 -->
            <!-- JDBC는 JDBC가 commit과 rollback의 기능을 직접 사용 가능하게 하는 옵션(수동 commit) -->
            <!-- MANAGED는 트랙잭션에 대해 어떤 영향도 행사하지 않는다는 뜻(자동 commit) -->
            <transactionManager type="JDBC"/>
           
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.driver.OracleDriver"></property>
                <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"/>
                <property name="username" value="mybatis"/>
                <property name="password" value="mybatis"/>
            </dataSource>
        </environment>
    </environments>
   
    <mappers>
        <mapper resource="resources/mappers/member-mapper.xml"/>
        <mapper resource="resources/mappers/board-mapper.xml"/>
    </mappers>
   
</configuration>

출처: https://qdgbjsdnb.tistory.com/ [하위^^]


<?xml version="1.0" encoding="UTF-8"?>

<!-- 태그명-내용 = 키-값 처럼 쓰인다. -->

<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <settings>
        <!-- 만약에 null로 데이터가 전달 되었다면 빈칸이 아닌 null로 인식해라 라는 뜻 -->
        <setting name="jdbcTypeForNull" value="NULL"/>
    </settings>
   
    <!-- vo 객체들의 풀 네임을 사용하기 번거롭기 때문에 별칭을 등록하는 부분 -->
    <typeAliases>
        <!-- 클래스에 대한 별칭 -->
        <typeAlias type="com.kh.mb.member.model.vo.Member" alias="Member"/>
        <typeAlias type="com.kh.mb.board.model.vo.Board" alias="Board"/>
        <typeAlias type="com.kh.mb.board.model.vo.Reply" alias="Reply"/>
    </typeAliases>
   
    <!-- DB 연결할 설정에 대한 정보를 선언하는 부분 -->
    <environments default="firstDev">
        <environment id="firstDev">
            <!-- 트랙잭션 매니저는 JDBC 혹은 MANAGED 둘 중 하나를 선택할 수 있음 -->
            <!-- JDBC는 JDBC가 commit과 rollback의 기능을 직접 사용 가능하게 하는 옵션(수동 commit) -->
            <!-- MANAGED는 트랙잭션에 대해 어떤 영향도 행사하지 않는다는 뜻(자동 commit) -->
            <transactionManager type="JDBC"/>
           
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.driver.OracleDriver"></property>
                <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"/>
                <property name="username" value="mybatis"/>
                <property name="password" value="mybatis"/>
            </dataSource>
        </environment>
    </environments>
   
    <mappers>
        <mapper resource="resources/mappers/member-mapper.xml"/>
        <mapper resource="resources/mappers/board-mapper.xml"/>
    </mappers>
   
</configuration>




위와 같이 설정 파일을 구성하였다.


이해한대로 키워드를 설명하면


- mybatis를 사용하기 위한 기본 선언


<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">




- <configuration> 설정 파일 내용( 전체를 감싸고 있음 )


- <settings> mybatis 기본 설정 중 널처리에 관련된 설정


- <typeAliases> mybatis에서 프로그램단에서 작성한 객체를 사용하기 위한 선언


- <environments>, <transactionManager>, <dataSource>, <property> 사용할 DB에 대한 정보를 선언


- <mappers> DB를 사용하기 위한 정보와 쿼리문들을 선언한 xml 파일의 경로를 선언







다음은 쿼리문이 들어있는 xml 파일



<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<!-- 파일의 별칭 -->
<mapper namespace="Member">
    <resultMap type="com.kh.mb.member.model.vo.Member" id="memberResultSet">
        <id property="mid" column="MID"/>
        <result property="userId" column="USER_ID"/>
        <result property="userPwd" column="USER_PWD"/>
        <result property="userName" column="USER_NAME"/>
        <result property="email" column="EMAIL"/>
        <result property="birthDay" column="BIRTHDAY"/>
        <result property="gender" column="GENDER"/>
        <result property="phone" column="PHONE"/>
        <result property="address" column="ADDRESS"/>
        <result property="enrollDate" column="ENROLL_DATE"/>
        <result property="modifyDate" column="MODIFY_DATE"/>
        <result property="status" column="STATUS"/>
    </resultMap>
   
    <select id="loginMember" parameterType="Member" resultMap="memberResultSet">
        SELECT *
        FROM MEMBER
        WHERE USER_ID = #{userId}
        AND USER_PWD = #{userPwd}
    </select>
   
    <insert id="insertMember" parameterType="Member">
        INSERT INTO member
        VALUES (SEQ_MID.NEXTVAL, #{userId}, #{userPwd}, #{userName}, #{email}, #{birthDay}, #{gender}, #{phone}, #{address}, SYSDATE, SYSDATE, DEFAULT)
    </insert>
   
   
</mapper>







- <mapper> mybatis config xml에서 불러올 부분, 이름을 지정해서 찾는다.


- <resultMap> Sql DB 조회 결과를 만들어둔 java 객체파일과 연결시켜서 해당하는 값 = 컬럼 관계를 선언


   id를 지정하여 사용, 여러개를 만들어 유동적으로 사용할 수 있다.( 같은 객체 또한 가능 )


- <select>, <insert>, <update> 등등 sql 쿼리문이 들어가는 영역, id를 지정하여 java에서 불러올 때 찾으며


   parameterType으로 매개변수를 설정하고 resultMap으로 반환할 대상을 정한다.






SQL 쿼리문이 들어가는 영역은 줄넘김을(Enter) 허용한다.


EL 태그와 비슷하게 메소드를 통해서 전달받은 객체를 사용할 수 있다.


또한 여기서 사용할 수 있는 태그 조건문 같은 태그가 따로 있다.


다음 예를 보자


    <!-- 게시글 수 조회용 쿼리문 -->
    <select id="selectListCount" resultType="_int">
        SELECT COUNT(*)
        FROM board
        WHERE status = 'Y'
    </select>
   
    <!-- 게시물 목록 조회용 쿼리문 -->
    <select id="selectBoardList" resultMap="boardResultSet">
        SELECT *
        FROM board b
        JOIN member m ON (b.bwriter = m.mid)
        WHERE b.status = 'Y'
        ORDER BY bid DESC
    </select>
   
    <!-- 조회수 증가용 쿼리문 -->
    <update id="updateBoardCount" parameterType="_int">
        UPDATE board
        SET bCount = (SELECT bCount FROM board WHERE bid = #{bid}) + 1
        WHERE bid = #{bid}
    </update>
   
    <!-- 상세보기용 쿼리문 -->
    <select id="selectBoardOne" parameterType="_int" resultMap="boardResultSet2">
        SELECT *
        FROM board b
        JOIN member m ON(b.bWriter = m.mid)
        LEFT JOIN reply r ON (r.ref_bid = b.bid)
        WHERE b.bid = #{bid}
        AND b.status = 'Y'
        ORDER BY rid DESC
    </select>
   
    <!-- 검색 결과 수 조회용 쿼리문 -->
    <select id="selectSearchResultCount" resultType="_int">
        SELECT COUNT(*)
        FROM board b
        JOIN member m ON (b.bWriter = m.mid)
        WHERE b.status = 'Y'
        <!-- <if test="writer != null">
            AND user_name = #{ writer }
        </if>
        <if test="title != null">
            AND bTitle LIKE '%' || #{title} || '%'
        </if>
        <if test="content != null">
            AND bContent Like '%' || #{content} || '%'
        </if> -->
        <choose>
            <when test="writer != null">
                AND user_name = #{writer}
            </when>
            <when test="title != null">
                AND bTitle LIKE '%' || #{title} || '%'
            </when>
            <otherwise>
                AND bContent LIKE '%' || #{content} || '%'
            </otherwise>
        </choose>
    </select>
   
    <!-- 만약 쿼리문 안에 < 기호가 포함된다면 연산자로 인식하지 않고 태그로 인식한다. -->
    <!-- 이걸 해결해 주기 위해서 쿼리문을 cdata 주석으로 감싸야 한다. -->
    <!--
    <![CDATA[
        <
    ]]>
     -->
     
     <!-- 게시물 검색 결과 조회용 메소드 -->
     <select id="selectSearchResultList" resultMap="boardResultSet">
         SELECT *
         FROM board b
         JOIN member m ON (b.bwriter = m.mid)
         WHERE b.status = 'Y'
         <choose>
             <when test="writer != null">
                 AND user_name = #{writer}
             </when>
             <when test="title != null">
                 AND bTitle LIKE '%' || #{title} || '%'
             </when>
             <otherwise>
                 AND bContent LIKE '%' || #{content} || '%'
             </otherwise>
         </choose>
         ORDER BY bid DESC
     </select>



#{ 객체 필드값 } 과 같이 값을 원하는 부분에 사용하고


<if> 태그, <choose>, <when>, <otherwise> 태그가 있고


    <![CDATA[

<
    ]]>


위의 CDATA를 통해서 xml에서 <> 꺽쇠 키워드를 방지 할 수 있다.




- <if> if는 java에서 사용하는 if와 같이 쓰인다. else는 따로 없는듯


- <choose>, <when>, <otherwise> java에서의 switch case 문과 비슷한 역할을 한다.




그런데 만약 choose 문에서 WHERE 라고 쓰고 조건문을 넣었는데


해당하는 조건이 없어서


SELECT * FROM 테이블 WHERE


위와같이 입력되면 안되기 때문에


아예 WHERE 키워드 뒤에 1=1 을 넣어서 방지하거나


<WHERE>태그를 사용하여 감싸면 조건이 true 일때 WHERE을 따로 입력해준다.




Mybatis의 기능으로 DB를 매우 간편하게 조회할 수 있는데


다음의 로그인 예제를 컨트롤러부터 보고 확인해보자.


순서는 controller -> Service -> dao




String userId = request.getParameter("userId");
        String userPwd = request.getParameter("userPwd");
       
        System.out.println("userId : " + userId);
        System.out.println("userPwd : " + userPwd);
       
        // myBatis는 데이터 한개만 받기 때문에 처리를 해야함
        Member m = new Member();
        m.setUserId(userId);
        m.setUserPwd(userPwd);
       
        // 객체간의 의존성을 줄여줌, Spring의 핵심기능
        // 예) Service에서 메소드를 수정하면 Controller에서도 수정을 해야함 -> 의존성이 강하다.
        MemberService ms = new MemberServiceImpl();
        try {
            Member member = ms.selectMember(m);
           
            HttpSession session = request.getSession();
           
            session.setAttribute("loginUser", member);
           
            response.sendRedirect("index.jsp");
        } catch (LoginFailException e) {
            RequestDispatcher error = request.getRequestDispatcher("WEB-INF/views/common/errorPage.jsp");
            request.setAttribute("message", e.getMessage());
           
            error.forward(request, response);
        }



컨트롤러에서 Service의 인터페이스 생성, 일반 클래스 객체화 방법을 통해서 호출


JSP 페이지로부터 받은 값 m을 전달한다.








public interface MemberService {
    // 로그인용 메소드
    // public abstract가 전부 들어가기 때문에 애초에 생략해도 된다.(Interface)
    Member selectMember(Member m) throws LoginFailException;
   
}



import org.apache.ibatis.session.SqlSession;

import com.kh.mb.member.model.dao.MemberDao;
import com.kh.mb.member.model.exception.InsertFailException;
import com.kh.mb.member.model.exception.LoginFailException;
import com.kh.mb.member.model.vo.Member;
import static com.kh.mb.common.Template.*;


public class MemberServiceImpl implements MemberService{
   
    // 자동으로 더 넓거나 같은 접근제한자가 잡힌다.
    // 이러한 인터페이스를 사용한 구조는 딱 정해져있는 메소드를 사용하므로(매개변수도 바꿀 수 없다.)
    // 행위를 강제한다고 한다.
    @Override
    public Member selectMember(Member m) throws LoginFailException {
        // SqlSession은 MyBatis에서 커넥션 대신 사용
        SqlSession session = getSqlSession();
       
        Member member = new MemberDao().selectMember(session, m);
       
        session.close();
       
        return member;
    }

}


인터페이스를 이용한 구조로 강제와된 selectMember 메소드드를 통해 Dao에 값을 전달


SqlSession session = getSqlSession();의 SqlSession 객체는 myBatis 라이브러리의 객체이다.


getSqlSession은 myBatis의 SQLSession을 생성하기 위해 공통적으로 이용하는 기능이므로


따로 common에 Template 자바 파일을 만들어 생성/관리 한다.


해당 템플릿 파일은 다음과 같다.




import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

public class Template {
    public static SqlSession getSqlSession() {
        SqlSession session = null;
       
        try {
            InputStream stream = Resources.getResourceAsStream("resources/mybatis-config.xml");
           
            System.out.println(stream);
           
            // openSession 인자에 false를 주면 Auto Commit이 차단 됨
            session = new SqlSessionFactoryBuilder().build(stream).openSession(false);
           
            System.out.println(session);
           
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
       
       
       
        return session;
    }
}



Resources.getResourceAsStream("resources/mybatis-config.xml"); 를 통해서 해당 경로에 있는


mybatis-config.xml에 작성 해 둔 설정을 불러와 SqlSession 객체를 생성한다.


mybatis-config.xml 파일은 다음과 같이 쓰여져있다.


<?xml version="1.0" encoding="UTF-8"?>

<!-- 태그명-내용 = 키-값 처럼 쓰인다. -->

<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <settings>
        <!-- 만약에 null로 데이터가 전달 되었다면 빈칸이 아닌 null로 인식해라 라는 뜻 -->
        <setting name="jdbcTypeForNull" value="NULL"/>
    </settings>
   
    <!-- vo 객체들의 풀 네임을 사용하기 번거롭기 때문에 별칭을 등록하는 부분 -->
    <typeAliases>
        <!-- 클래스에 대한 별칭 -->
        <typeAlias type="com.kh.mb.member.model.vo.Member" alias="Member"/>
        <typeAlias type="com.kh.mb.board.model.vo.Board" alias="Board"/>
        <typeAlias type="com.kh.mb.board.model.vo.Reply" alias="Reply"/>
    </typeAliases>
   
    <!-- DB 연결할 설정에 대한 정보를 선언하는 부분 -->
    <environments default="firstDev">
        <environment id="firstDev">
            <!-- 트랙잭션 매니저는 JDBC 혹은 MANAGED 둘 중 하나를 선택할 수 있음 -->
            <!-- JDBC는 JDBC가 commit과 rollback의 기능을 직접 사용 가능하게 하는 옵션(수동 commit) -->
            <!-- MANAGED는 트랙잭션에 대해 어떤 영향도 행사하지 않는다는 뜻(자동 commit) -->
            <transactionManager type="JDBC"/>
           
            <dataSource type="POOLED">
                <property name="driver" value="oracle.jdbc.driver.OracleDriver"></property>
                <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"/>
                <property name="username" value="mybatis"/>
                <property name="password" value="mybatis"/>
            </dataSource>
        </environment>
    </environments>
   
    <mappers>
        <mapper resource="resources/mappers/member-mapper.xml"/>
        <mapper resource="resources/mappers/board-mapper.xml"/>
    </mappers>
   
</configuration>






<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">


이 부분은 mybatis홈페이지에서 가져와야 한다.


http://www.mybatis.org/mybatis-3/ko/getting-started.html


위 링크에 가면 자세히 설명이 되어있다


mybatis의 경우 페이지 한글화가 정말 잘 되 있어서


위의 페이지에서 xml 태그들의 설명을 위의 예제와 함께 이해해보자.








예제로 돌아와서 서비스에서


Member member = new MemberDao().selectMember(session, m);으로


SqlSession객체와 로그인 정보를 담고있는 멤버 객체를 Dao에 전달한다.





import org.apache.ibatis.session.SqlSession;

import com.kh.mb.member.model.exception.InsertFailException;
import com.kh.mb.member.model.exception.LoginFailException;
import com.kh.mb.member.model.vo.Member;

public class MemberDao{
   
    public Member selectMember(SqlSession session, Member m) throws LoginFailException {
        Member member = null;
       
        member = session.selectOne("Member.loginMember", m);
       
        if (member == null) {
            session.close();
            throw new LoginFailException("로그인 실패");
           
        }
       
        return member;
    }
}


selectMember 메소드에서 m에 담겨있는 정보를


selectOne이라는 mybatis 메소드를 이용하게 된다.


Member.loginMember 이라는 키워드를 통해서


Member라는 해당 namespace로 지정된 xml 파일을 찾아 loginMember에 지정된 쿼리문을 실행시킨다.


그 쿼리문에는 Member 객체를 받아서 이용하게 된다.







먼저 아까 SqlSession에 설정한 mybatis-config xml 파일을 살펴보면


<mappers>
        <mapper resource="resources/mappers/member-mapper.xml"/>
        <mapper resource="resources/mappers/board-mapper.xml"/>
    </mappers>


mappers 태그에 member-mapper.xml이라는 파일을 볼 수 있다.


그리고 member-mapper.xml 파일은 다음과 같이 쓰여져있다.







<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<!-- 파일의 별칭 -->
<mapper namespace="Member">
    <resultMap type="com.kh.mb.member.model.vo.Member" id="memberResultSet">
        <id property="mid" column="MID"/>
        <result property="userId" column="USER_ID"/>
        <result property="userPwd" column="USER_PWD"/>
        <result property="userName" column="USER_NAME"/>
        <result property="email" column="EMAIL"/>
        <result property="birthDay" column="BIRTHDAY"/>
        <result property="gender" column="GENDER"/>
        <result property="phone" column="PHONE"/>
        <result property="address" column="ADDRESS"/>
        <result property="enrollDate" column="ENROLL_DATE"/>
        <result property="modifyDate" column="MODIFY_DATE"/>
        <result property="status" column="STATUS"/>
    </resultMap>
   
    <select id="loginMember" parameterType="Member" resultMap="memberResultSet">
        SELECT *
        FROM MEMBER
        WHERE USER_ID = #{userId}
        AND USER_PWD = #{userPwd}
    </select>
   
    <insert id="insertMember" parameterType="Member">
        INSERT INTO member
        VALUES (SEQ_MID.NEXTVAL, #{userId}, #{userPwd}, #{userName}, #{email}, #{birthDay}, #{gender}, #{phone}, #{address}, SYSDATE, SYSDATE, DEFAULT)
    </insert>
   
   
</mapper>




<mapper namespace="Member">


namespace가 Member로 설정되서


session.selectOne("Member.loginMember", m);라는 메소드에서 이 xml까지 찾아와서


<select id="loginMember" parameterType="Member" resultMap="memberResultSet">
        SELECT *
        FROM MEMBER
        WHERE USER_ID = #{userId}
        AND USER_PWD = #{userPwd}
    </select>


id가 loginMember 라고 지정되어 있는 이 쿼리문을 사용하게 된다.


parameterType과 resultMap, #{userId} 등 특이한 키워드들이 보이는데


이에 대한 설명은 myBatis xml 파일을 설명하는 게시글에서 추가로 한다.


참고로 mybatis 홈페이지에도 충분히 이해하기 쉽게 설명이 되어있다.






xml에 지정해놓은 여러가지 키워드들을 통해서 DB를 조회하고 값을 리턴하게 된다.


























Front Controller는 맵핑 주소를 관리하는 java 파일이 된다.


맵핑 주소로 접근 할 때 모든 접근을 Front Controller를 통하여 해당하는 컨트롤러에 이동시켜준다.


예전에 했던 필터와 유사




예를 들어 주소가 me로 끝나는 맵핑 주소는 항상 위의 컨트롤러를 지나게 된다.


다음 예를 보자.



package com.kh.mb.frontController;

import java.io.IOException;

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

@WebServlet("*.me")
public class MemberFrontController extends HttpServlet {
    private static final long serialVersionUID = 1L;
      
    public MemberFrontController() {
        super();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=utf-8");
       
        String uri = request.getRequestURI();
        System.out.println(uri);
       
        String action = uri.substring(uri.lastIndexOf("/") + 1, uri.lastIndexOf(".me"));
        System.out.println("action : " + action);
       
        RequestDispatcher rd = null;
        switch (action) {
        case "login":
            rd = request.getRequestDispatcher("login");
            break;
        case "logout":
            rd = request.getRequestDispatcher("logout");
            break;
        case "showInsertForm":
            rd = request.getRequestDispatcher("showInsertForm");
            break;
        case "minsert":
            rd = request.getRequestDispatcher("minsert");
            break;
        }
       
        rd.forward(request, response);
       
    }

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

}





필터 클래스에서 했던것처럼 문자 형식을 UTF-8로 처리해주고


맵핑 주소 문자열을 분리해서 Switch Case 문으로 해당하는 컨트롤러로 연결시킨다.






















+ Recent posts