본문으로 바로가기

2019/01/09 과제 정리 및 TIP

category 수업내용 정리/정리안된 메모 2019. 6. 16. 21:03

2019/01/09 과제 정리 및 TIP

[인터셉터 : '구성요소에 접근 할 수 있다'라는 뜻은?]

Dao 혹은 Service등 미리 구현해 놓은 스프링의 구성요소 (Bean)을 사용 할 수 있다는 것 이다. 

[수정 할 점 : 주소창과 화면 맞춰주기]

로그인에 성공했을 때 main 화면으로 가도록 했는데, 위의 코드 처럼 작성하면 단순하게 포워딩 이기 때문에 주소창은 login이고, 화면은 main이 보이게 된다. 그렇기 때문에 'redirect:' 코드를 추가로 작성해주어서 주소창과 화면창을 맞춰주자.


[알아 둘 점 : 요청방식으로 요청 나누기]

loginForm과 login의 가장 큰 차이점으로 요청방식을 볼 수 있다. LoginForm 요청의 경우 단순한 화면 요청이기 때문에 get 방식을 사용하지만, login은 사용자의 정보가 담겨있는 요청이기 때문에 Post 방식을 사용하게 된다.

이미지의 코드는 사용자가 직접 주소를 바꾸는 것과 동일한 방법. 즉, GET 방식이다. 

value = "/요청값",method=RequestMethod.요청방식​

이런식으로 코드를 작성하게 되면, 요청의 방식에 따라서 구분이 가능하다. 

[Ambiguous handler metods mapped...]

앞으로 자주 보게 될 오류 중 하나. 예외가 중복이 이루어졌을 떄 발생하는 예외이다.

[서비스 작성하기]

(1) Login Service 작성하기

서비스의 login()을 작성하기 위해서 ID값으로 DB에서 특정 데이터를 선택 할 수 있는 select를 하나 추가로 생성해 주었다.

login()의 조건문에서 else는 조건식으로 선언한 member 객체가 null 이거나 입력받은 비밀번호가 현재 데이터와 맞지 않는 경우.

두 조건 중 하나라도 거짓이면 else로 들어가게 되며, 로그인 실패 결과로 false 값을 반환하게 된다. 반대로 조건식을 모두 충족하면 true를 반환하는 간단한 service이다.

(2)join, modify Service 작성하기

insert와 update의 경우 DAO에서 수행된 row(처리한 데이터의 개수)를 반환하기 때문에 dao 각 기능의 반환값이 0보다 크다면 성공했다는 뜻이다. 

[컨트롤러의 핸들러(메소드) 파라미터로 들어 갈 수 있는 클래스]

https://docs.spring.io/spring/docs/4.3.21.RELEASE/spring-framework-reference/htmlsingle/#mvc-ann-methods


[컨트롤러 작성하기] *인터셉터 설정 안했음.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

packagecontroller;

 

importjavax.servlet.http.HttpServletRequest;

importjavax.servlet.http.HttpSession;

 

importorg.springframework.beans.factory.annotation.Autowired;

importorg.springframework.stereotype.Controller;

importorg.springframework.ui.Model;

importorg.springframework.web.bind.annotation.RequestMapping;

importorg.springframework.web.bind.annotation.RequestMethod;

 

importmodel.Member;

importservice.MemberService;

 

@Controller

@RequestMapping("/member")

public classMemberController {

@Autowired

    privateMemberService memberService;

@RequestMapping("/memberList")

    public StringmemberList(Model model) {

model.addAttribute("memberList", memberService.getAllMemberList());

        return "memberList";

}

@RequestMapping("/main")

    public Stringmain() {

        return "main";

}

//    /loginForm : 로그인 페이지 요청

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

    public StringloginForm() {

        return "login";

}

//    /login  : 로그인요청, 성공 : 메인으로 이동, 실패 : 로그인페이지

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

    public Stringlogin(Stringid,Stringpw,HttpServletRequest request) {

        //로그인 처리 후, 메인 요청 생성

        //Controller method(handler)에서 기본객체 접근하기

        //파라미터에 기본객체를 적어주면 받아올 수 있다.

HttpSession session=request.getSession();

        booleanresult=memberService.login(id, pw);

        Stringurl= "redirect:login";

        if(result) {//로그인 성공시 메인으로 이동

            //session 속성에 'userid'를 넣어주기

session.setAttribute("userid", id);

url= "redirect:main";

}

        returnurl;

}

//    /joinForm : 회원가입 페이지 요청

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

    public StringjoinForm() {

        return "join";

}

//    /join : 회원가입 요청, 성공 : 로그인페이지, 실패 : 로그인 페이지

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

    public Stringjoin(Member member) {

        booleanresult;

        Stringurl= "redirect:login";

result=memberService.join(member);

        returnurl;

}

//    /modifyForm : 로그인 되어있는 회원 정보 수정 페이지 요청

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

    public StringmodifyForm() {

        return "memberUpdate";

}

//    /modify : 회원 수정 요청, 성공 : 메인 , 실패 : 메인

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

    public Stringmodify(Member member) {

        booleanresult=memberService.modify(member);

        Stringurl= "redirect:main";

        returnurl;

}

//    /logout : 로그아웃 요청, 로그인 페이지

@RequestMapping("/logout")

    public Stringlogout(HttpSession session) {

        //로그아웃 처리 후 로그인이동

session.removeAttribute("userid");

        return "redirect:login";

}

}

Colored by Color Scripter

cs​

[인터셉터 작성하기]


[redirect을 이용하면서 데이터 넘겨주기] 검색어 : 컨트롤러에서 메시지 보내기

기존 redirect는 브라우저에게 새로운 요청을 생성하도록 하는 것으로, 요청객체에 속성을 담아두더라도 새로운 요청객체가 만들어지기 때문에 속성이 삭제되어 전달 할 수 없었다.

하지만, 스프링에서는 redirect 할 때 속성을 전달 할 수 있게 지원해준다. (단,1회성)

RedirectAttributes 객체 이용

현재 코드에서는 로그인에 실패했을 떄 단순하게 로그인 페이지를 다시 요청하기 때문에, 사용자의 입장에서는 무엇이 잘못되었는지 알 수 없다. = 아무 동작을 하지 않는다고 생각 할 수 있음.

RedirectAttributes는 스프링에서만 사용되는 객체이기 때문에, 요청을 정의했던 app-context.xml에 이미지처럼 mvc:annotation-driven을 설정을 추가해주어야 한다.

이미지에 보이는 것 처럼, 파라미터에 RedirectAttributes을 작성해주고 해당 객체를 이용해서 속성에 값을 넣어서 데이터를 보낼 수 있다.

로그인에 실패하게 되면, 이미지에 보이는 것 처럼 컨트롤러에서 보냈던 msg라는 key값에 담긴 문자열 (로그인실패)가 출력되는 것을 확인 할 수 있다.

[외부파일 가져오기]

 

위 이미지에서 보이는 것 처럼 외부에서 특정 파일을 가져오려고 하면, 제대로 동작하지 않는 것을 알 수 있다.

앞서 언급된 문제는 디스패쳐서블릿의 요청 처리 부분을 살펴보면 알 수 있다. 현재 설정에는 모든 요청 ( / )을 디스패쳐서블릿이 한다고 되어 있기 때문이다.

잠깐

​이러한 상황에서 경로가 꼬인 상태이다.

contextPath/img/imge.jps : 우리가 찾아야할 경로 

contextPath/member/img/imge.jps : 만들어내는 경로 

contextPath/member/login : 현재 우리의 경로 ​

스크립트릿 <%%> 을 활용, request 객체로 contextPath를 받아온 뒤 경로를 작성해주는 부분 앞에 EL 태그를 활용해서 contextPath를 넣어준다.

**** 오류나서 안되었던 부분

서버 클린이 안되었었음.

[AOP적용해보기]

진짜 완전 좋은 블로그 : http://addio3305.tistory.com/43 로그와 인터셉터 설명이 기가막힘

root-context.xml 파일을 이미지에 나와있는 코드 추가하여 수정하기

빨간색으로 체크된 파일 프로젝트에 추가하기​

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

packageaop;

 

importorg.apache.log4j.Logger;

importorg.aspectj.lang.annotation.After;

importorg.aspectj.lang.annotation.Aspect;

importorg.aspectj.lang.annotation.Before;

importorg.aspectj.lang.annotation.Pointcut;

importorg.springframework.stereotype.Component;

 

@Component

@Aspect

public classMemberAspect {

    

    private static finalLogger logger

    =Logger.getLogger(MemberAspect.class);

    

    //Service 패키지에 있는 XXXService로 끝나는 클래스의

    //선언된 모든 메서드가 실행될 때 aop 적용

@Pointcut("execution(public * service.*Service.*(..))")

    public voidservicept() {}

    

@Before("servicept()")

    public voidstart() {//befor

        System.out.println("=====서비스 시작합니다.=====");

logger.info("=====서비스 시작합니다.=====");

}

    

@After("servicept()")

    public voidfinish()throwsException{//after

        System.out.println("=====서비스 종료합니다.=====");

logger.info("=====서비스 종료합니다.=====");

        try{

            throw newException("예외 발생!!");

}catch(Exception e) {

logger.error(e.getMessage());

}

}

}

 

Colored by Color Scripter

cs​

MemberAspect.java 코드 (AOP 패키지에 있음)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

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

<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">

<log4j:configuration

    xmlns:log4j='http://jakarta.apache.org/log4j/'>

    <appender name="STDOUT"

        class="org.apache.log4j.ConsoleAppender">

        <layout class="org.apache.log4j.PatternLayout">

            <param name="ConversionPattern"

                value="%d %-5p [%t] %C{2} (%F:%L) - %m%n" />

        </layout>

    </appender>

    <!-- 파일출력을 위한 설정 -->

    <appender name="filelog"

        class="org.apache.log4j.DailyRollingFileAppender">

        <param name="File" value="c:/log/service.log" />

        <param name="Append" value="true" />

        <layout class="org.apache.log4j.PatternLayout">

            <param name="ConversionPattern"

                value="%d %-5p [%t] %C{2} (%F:%L) - %m%n" />

        </layout>

    </appender>

 

    <appender name="errorlog"

        class="org.apache.log4j.DailyRollingFileAppender">

        <param name="File" value="c:/log/error.log" />

        <param name="Append" value="true" />

        <layout class="org.apache.log4j.PatternLayout">

            <param name="ConversionPattern"

                value="%d %-5p [%t] %C{2} (%F:%L) - %m%n" />

        </layout>

    </appender>

    <!-- aop라는 패키지에 appender를 이용해서 log를 남기겠다 -->

    <!-- level : 설정한 레벨보다 같거나 높은 레벨의 메시지만 출력 -->

    <logger name="aop">

        <level value="ERROR"></level>

        <appender-ref ref="errorlog" />

    </logger>

 

    <root>

        <priority value="info" />

        <appender-ref ref="STDOUT" />

    </root>

</log4j:configuration>

Colored by Color Scripter

cs​

log4j.xml

결과 : 서비스가 동작 할 때 마다 콘솔창에 메시지와 로그가 남게됨

*예외는 고의로 발생시킨 것. 

AOP는 예전에 수업정리 한 거 참고해서 다시 공부하기 !

.

.
[AOP 적용하기 : 스프링에서 제공하는 AOP(트랜젝션) 사용하기] 

꼭 읽어보고 연습해야 될 블로그 : http://egloos.zum.com/springmvc/v/495798

검색어 : 트랜젝션, transaction, 트랜잭션

Transaction ? : 논리적 작업 단위(LUW, Logical Units of Work)​

특정한 작업을 하기 위해서 여러 행동을 수행하게 되는데, 여러 행동들이 모두 수행됐을 때만 작업이 수행된 것으로 간주하는 것.

- 특정 행동이 수행되지 않으면 나머지 모든 행동들도 수행되지 않은 상태로 되돌림

- 모든 행동이 정상적으로 수행됐을 때만 모든 행동을 수행한 것으로 간주.

A라는 작업을 하기 위해서 1 ~ 4 행동을 하여야 한다고 했을 때. 

1번 행동 : 0을 1로 바꾸는 것 

2번 행동 : 0을 2로 바꾸는 것

3번 행동 : 0을 3으로 바꾸는 것

4번 행동 : 0을 4로 바꾸는 것 

A작업을 수행한다고 했을 때, 위에 있는 모든 행동들이 완료되어야 A작업이 수행된 것으로 간주 하는 것으로 풀어서 설명하면

만약 1,2,4 행동이 수행되고, 3번만 수행되지 않았을 때 3번 행동 뿐만 아니라, 1,2,4 행동도 수행되지 않은 것으로 간주하는 것이다.

(예제로 살표보기)

회원 가입하면, 자동으로 게시판에 인사말을 남기는 작업

1. 회원테이블에 회원정보를 insert

2. 게시판 테이블에 인사말 insert

*둘 중 하나라도 작업이 실패하면, 두 작업 모두 취소(roll back)

준비사항 : boardDao

[시작]

transaction​은 공용으로 사용되는 것이기 때문에 root-context.xml에 정의해주게 된다.

첨부된 이미지는 트랜젝션을 관리하는 객체인 'transactionManager'를 스프링컨테이너에 bean으로 등록하는 코드.

다음으로 transaction의 advice를 설정하는데, 이때 Namespaces 탭에서 tx를 체크해준다.

transaction의 advice를 준비해준다. 빨간색으로 체크된 부분은 앞서 만든 transaction bean의 ID를 적어주면 된다.

*advice ? 각 조인포인트에 삽입되어져 동작할 수 있는 코드 (예전에 배웠던 거임 까먹지 말자)

마지막으로, transaction의 포인트 컷을 설정해주면 된다. 이때 사용된 표현식 ​​

execution(public * service.*ServiceImp.*(..)) 은 아래 설명을 보고 어떻게 읽는지 연습하자.

[표현식 읽기]execution(수식어패턴? 리턴타입패턴 클래스이름패턴?이름패턴(파라미터패턴) 

▶ 수식어패턴 : public, private 등등의 수식어를 명시, 생략 가능

▶ 리턴타입 : 리턴 타입을 명시

▶ 클래스이름, 이름패턴 : 클래스 이름 및 메서드이름을 패턴으로 명시

▶ 파라미터패턴 : 매칭될 파라미터에 대해 명시

▶ '*' : 모든 값을 표현

▶ '..' : 0개 이상을 의미

execution

(public

*

service.

*

ServiceImp.

*

(..))

명시자

public메서드

모든 리턴 타입

서비스 패키지 하위에 있는

모든 메서드

ServiceImp에 등록된

모든

파라미터가 0개 이상인 메서드​

****위에 표는 직접 작성한거라 틀렸을 가능성 높음

회원가입에 성공하면 (회원테이블에 회원정보를 insert), (게시판 테이블에 인사말 insert) 로직을 동작시키기 위해서 Service의 join()를 수정하였다.

회원가입 창으로 부터 전달받은 데이터를 이용해서, 회원테이블과 게시판 테이블에 데이터를 insert 하는 로직.

(transaction이 동작에 어떤 영향을 주는가?)

앞서 작업(root-context.xml에 transaction등록 및 설정) 을 통해 service에 있는 모든 메서드들은 transaction의 영향을 받는 상태이다.

방금 작업을 통해서 join()는 회원테이블과 게시판테이블에 데이터를 각각 인서트 하는 작업을 수행하게 되는데

두 작업 중 어느 한 작업이라도 제대로 수행되지 않으면, 두 작업모두 수행되지 않은 것 처럼 동작을 하게 된다. 

EX) 회원가입을 진행하는데, 중복된 아이디여서 회원테이블에는 인서트가 불가능한 상황.

transaction이 적용되지 않으면 ​회원테이블에는 데이터가 올라가지 않지만, 게시판 테이블에는 데이터가 올라가게 된다.

하지만 transaction을 적용하게 되면, 회원가입 테이블에 인서트 하는 작업이 중복된 아이디(유니크 값)로 인해 수행되지 않으면, 게시판 테이블에 데이터가 올라가는 작업역시 이루어지지 않는다.

정확히 말하면 보드테이블에 데이터가 업로드 되었다가 transaction 객체가 롤백을 시키는 것.

(try-catch 를 사용한 이유)

트라이-캐치를 잡아주지 않고, 회원가입시 중복된 아이디를 입력 할 경우

(1) 회원테이블에 중복된 아이디 값이 입력되기 때문에 (유니크코드) 데이터가 들어가지 않는다.

(2) 회원테이블에 인서트 하는 작업이 수행되지 않았기 때문에 transaction로 인해서 게시판 테이블에도 데이터가 들어가지 않는다.

즉, transaction은 제대로 작동하여 DB에 데이터가 입력되는 것은 막을 수 있지만. 화면은 회원테이블에 인서트를 하는 단계에서 오류(ORA-00001: unique constraint)가 발생하여, 500 에러가 발생하게 된다 

= 회원가입 실패하면 본래 로그인폼으로 돌아가야 하는데 그 작업을 수행 하지 않고 오류를 띄움.

그렇기에 예외를 잡아주어 화면이 제대로 넘어가게끔 하는 것.

join()에서 반환하는 return값(int형)은 컨트롤러에서 회원가입 성공/실패 유무를 따지는데 사용된다.

하지만 이렇게 트라이-캐치를 잡아 줄 경우 transaction은 본래 예외가 발생했을 떄 동작을 하기 때문에, 제대로 동작하지 않는다.

회원테이블에는 데이터가 인서트되지 않지만, 게시판테이블에는 데이터가 들어가는 문제점이 발생한다.

= 트라이-캐치로 회원테이블 인서트(중복된 아이디로 가입시도)를 잡아주기 때문에...

(앞서 언급된 문제해결하기)

앞서 언급된 문제를 해결하기 위해서 catch에 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();을 작성하게 된다. 

중복된 아이디로 회원가입을 시도하여, 오류가 발생하고. 이것이 트라이-캐치 에서 캐치에 걸리게 된다면, 해당 코드가 작동되어 transaction의 롤백작업을 하게끔 해준다.

즉, 본래는 transaction이 예외가 발생하면 동작을 하지만 임의로 롤백 메서드를 발동시켜 우리가 원하는 로직대로 움직이게 하는 것 

우리가 원하는 로직 ?

사용자가 회원가입을 할 때 회원테이블에 해당 데이터가 입력되는 동시 게시판테이블에도 데이터가 올라가게 하는데. 두 작업 중 어느 한 작업이라도 제대로 수행되지 않으면, 두 작업 모두 안하는 것.

'수업내용 정리 > 정리안된 메모' 카테고리의 다른 글

2019-01-10  (0) 2019.06.16
[Annotation(@) 이용해서 트랜잭션 등록하기]  (0) 2019.06.16
2019-01-08  (0) 2019.06.16
2019/01/07  (0) 2019.06.16
Dispatcher Servlet(Front-controller) 등록하기  (0) 2019.06.16