본문 바로가기

컴퓨터학원(복습)(수료)

자바(JAVA)기반 안드로이드 웹&앱 개발 76일차 스프링 (AOP, 트랜잭션, 파일업로드, 확장자 제한, 중복처리UUID )

https://blog.naver.com/tnwnsrla/222399457507

Spring AOP (Aspect-Oriented Programming)(관점지향 프로그래밍)

계속해서 수 많은 개념들이 나오고 있습니다.. 그럴 때마다 그냥 넘어가고 싶지만, 지금 잘 알아둬야 나중...

blog.naver.com

https://jojoldu.tistory.com/71?category=635883

AOP 정리 (3)

AOP란? Spring의 핵심 개념중 하나인 DI가 애플리케이션 모듈들 간의 결합도를 낮춰준다면, AOP는 애플리케이션 전체에 걸쳐 사용되는 기능을 재사용하도록 지원하는 것입니다. AOP (Aspect-Oriented Programming)..

jojoldu.tistory.com

오늘부터는 스프링 AOP 에 관해서 시작합니다.

개념은 해당 링크로 대체합니다.

(새로운 프로젝트를 생성하여 진행합니다. exam03 복사 후, exam04로 name(pom.xml도 병행 수정) 수정)

1. AOP 설정

1) pom.xml 에 aspectj-version, slf4j-version 을 수정하고 aspectj 의존성을 추가합니다.

aspectj, slf4j version 수정

pom.xml 에 aspectj 의존성(dependency) 추가

2. 서비스 계층을 설계합니다.

1) SampleService.java 인터페이스를 추가합니다.

2) SampleServiceImpl.java 를 추가합니다. (인터페이스 메소드 구현)

SampleService.java / SampleServiceImpl.java

3. Advice 를 작성합니다. Advice 는 실질적으로 부가기능을 담은 구현체입니다.

순수하게 부가기능에만 집중할 수 있으며 Advice 는 Aspect 가 '무엇'을 '언제'(@Before, @After)할지

정의할 수 있습니다.

1) org.zerock.aop.LogAdvice.java 를 생성합니다. 로그를 기록해주는 LogAdvice 입니다.

(1) * org.zerock.service.SampleService*.*(..) 은 다음과 같은 의미입니다.

AspectJ의 표현식

org.zerock.aop.LogAdvice.java

2) AOP를 설정해주기 위해서 Proxy 객체를 만들어주는 설정을 추가 root-context.xml 에

Namespaces에 aop를 추가합니다.

(1) 이 과정을 마치면 위 LogAdvice.java 에서 볼 수 있듯이 메소드 옆에 화살표가 생기는 것이

AOP가 적용되었음을 의미합니다.

4. SampleServiceTests.java 를 생성하여 AOP가 잘 적용되었는지 확인합니다.

JDK 의 Dynamic Proxy 기법이 적용되어 Proxy 객체가 생성됨을 확인할 수 있습니다.

[args 를 이용한 파라미터 추적]

1. args 라는 특별한 변수를 이용해서 파라미터를 설정하고 기록 가능합니다.

LogAdvice.java 에 logBeforeWithParam()을 설정합니다.

(파라미터가 오지 않았거나, println을 찍는 등 디버깅 하는 것을 대신한다?라고 생각하시면 될 듯 합니다.)

LogAdvice.java, SampleServiceTests.java

[@AfterThrowing - 예외발생을 감지해서 AOP 로 처리 ]

1. 예외가 발생할 때를 LogAdvice(부가기능 클래스)에 넣어 log를 찍는 메소드 logException()을 추가합니다.

1) SampleServiceTests.java 에 테스트 메소드 testAddError()를 추가합니다.

(1) NumberFormatException 을 주기 위해 Integer를 반환하는 doAdd() 메소드에 ABC String을 넣습니다.

LogAdvice.java, SampleServiceTests.java 예외발생 확인

[ @Around 와 ProceedingJoinPoint ]

- @Around 의 경우는 직접 해당 메소드를 실행할 것을 결정할 수 있습니다.

- 파라미터로 ProceedingJoinPoint 를 지정하고 사용해야 합니다.

- ProceedingJoin Point 의 메서드

- getTarget() : 실제로 실행해야 하는 객체

- proceed() : 실제 메서드의 실행

1. LogAdvice.java 에 logTime() 메소드를 작성합니다. 메소드의 실행시간(작동시간)을 구하는 것입니다.

LogAdvice.java logTime(), SampleServiceTests.java testAdd()

[스프링에서 트랜잭션 관리]

1. 트랜잭션

1) 비즈니스 용어에서 '거래'의 의미

2) 하나의 '거래'는 여러 번의 DB 관련 작업이 이루어지므로 이런 작업들을 '하나의 트랜잭셔으로 처리'한다고 표현

3) 트랜잭션의 원칙 ACID

트랜잭션의 원자성(Atomcity), 일관성(Consistency), 격리(Isolation), 영속성(Durablity) 줄여서 ACID

[댓글과 게시물의 반정규화]

1. 정규화를 하면 여러 번의 조인이 필요하고, 성능의 저하가 오는 경우 '반 정규화'를 통해서 해결

2. 반정규화는 자주 사용하는 값을 칼럼으로 작성해야 유지하는 방식

1) 예 : 게시물과 댓글의 숫자의 경우

(1) 기존에 작성되었던 board/list 화면에 Title 옆에 댓글의 숫자를 띄우고 싶습니다.

이때 TBL_Board에 replyCnt 필드를 추가해서 댓글 갯수를 세어 표시해주면 됩니다.

ㄱ) 장점 : tbl_board 만 검색하면 된다.

ㄴ) 단점 : 댓글변경 시 tbl_board도 변경, 필드(replyCnt)도 추가됨.(정규화 위배)

(2) 단점에도 불구하고 반정규화를 사용하는 이유

ㄱ) 반정규화를 사용하지 않으면 TBL_BOARD 와 TBL_REPLY 가 조인해야 되므로

시간이 오래 걸리고 서버에 부하가 걸린다.

게시물 테이블(TBL_BOARD) 와 댓글 테이블(TBL_REPLY)

[트랜잭션 설정]

1. root-context.xml 에 Namespace 에 tx 를 클릭하고 Source 에 다음과 같이 추가합니다.

root-context Namespace

[트랜잭션 예제]

1. 예제 테이블을 생성합니다.

create table tbl_sample1 (col1 varchar(500));

create table tbl_sample2 (col2 varchar(50));

2. 각각의 테이블에 대한 Mapper 인터페이스 를 생성하고,

SampleTxService.java 인터페이스를 생성한 후

이를 구현한 SampleTxServiceImpl.java를 생성합니다.

Sample1Mapper.java, Sample2Mapper.java

1) SampleTxService.java 인터페이스에 데이터를 더하는 addData() 를 생성하고

SampleTxServiceImpl.java 에 이 인터페이스 메소드를 구현합니다.

SampleTxService.java, SampleTxServiceImpl.java

2) SampleTxServiceTests.java 를 생성하여 테스트 하여봅니다.

(1) tbl_sample 2 는 50글자(varchar50)로 설정하였으므로,

긴 글을 주어, tbl_sample1에는 저장이 되고 tbl_sample에는 저장이 안되게 해보는 것입니다.

SampleTxServieTests.java 에 의도한 상황에 맞게 결과가 나온 것을 확인할 수 있습니다.

이렇게 되면, 완벽한 프로그램이 아니겠죠.

하나는 입력이 되고 하나가 입력이 안되었는데도 실행이 된다면 불완전합니다.

하나의 트랜잭션으로 2개의 SQL 문을 실행하면서 2 개의 SQL 문이 성공하면 commit 을 하고

둘 중의 하나가 에러가 나면 다시 원래로 돌리고 싶은 것입니다.

즉, 트랜잭션(Transaction) 처리가 필요한 것입니다.

3. SampleTxServiceImpl.java 의 addData()를 트랜잭션 화 하기 위해

@Transactional 어노테이션을 추가합니다.

1) 그 후 동일한 테스트 메소드를 활용하여 결과를 확인하면, rollback()이 되었음을 확인할 수 있습니다.

tbl_sample1 에 데이터가 입력되지 않음을 확인할 수 있습니다.

SampleTxServiceImpl.java

SampleServiceTests.java

※ Transactionl 적용 순서 ( Method > Class > Interface )

- 메서드의 @Transactional 설정이 가장 우선 시 됩니다.

- 클래스의 @Transactional 설정은 메소드보다 우선순위가 낮습니다.( 메소드 > 클래스 )

- 인터페이스의 @Transactional 설정이 가장 낮은 우선순위입니다.

[댓글과 댓글 수에 대한 처리]

1. tbl_board 테이블에 댓글 수를 의미하는 replyCnt 칼럼을 추가합니다.

alter table tbl_board add (replyCnt int default 0);

1) replycnt 에 댓글 게시글의 데이터가 들어갈 수 있도록 쿼리문을 작성합니다.

update tbl_board set replyCnt = (select count(rno) from tbl_reply

where tbl_reply.bno = tbl_board.bno)

※ Update 가 되지 않을 때는 Workbench 메뉴의 Edit > Preferences > SQL Editor > Safe Updates Disable

하고 사용자 재접속을 합니다.

2. 새로운 칼럼이 추가되었으으몰 BoardVO 에 변수추가를,

BoardMapper.java (인터페이스)에 updateReplyCnt() 를,

BoardMapper.xml 에 updateReplyCnt() 메소드에 해당하는 쿼리문을 작성합니다.

BoardVO.java, BoardMapper.java

BoardMapper.xml

3. ReplyServiceImpl.java (댓글서비스 클래스) 의 register(), remove() 에 해당 내용을 작성합니다.

1) @Transactional 어노테이션을 추가해서 트랜잭션 화 시킵니다.

2) register() 는 댓글이 등록되었을 때 tbl_board 의 replyCnt의 수를 업데이트 해주는 코드를 추가합니다.

(1) vo.getBno 로 해당 게시글을 가져오고 1(amount)를 추가합니다.

3) remove()는 댓글이 삭제되었을 때, tbl_board의 replyCnt의 수를 업데이트 해주는 코드를 추가합니다.

(1) vo.getBno로 해당 게시글을 가져오고 -1(amount)를 감소시킵니다.

(2) ReplyVO vo = mapper.read(rno); 로 가져와야 (1)을 할 수 있습니다. (파라미터가 Integer rno)

4. 화면(list.jsp)에 게시글 제목 옆에 댓글 수를 보여주는 것을 표시해야 하므로 수정해줍니다.

1) list.jsp 의 ${board.title} 옆에 ${board.replyCnt}를 추가하여 줍니다.

list.jsp, 댓글을 추가/삭제 해보면서 잘 연동되는지 확인해보십시오.

[파일 업로드]

1. <form> 태그를 이용하는 방식

1) 브라우저의 제한(?)이 없어야 하는 경우에 사용

2) 일반적으로 페이지 이동과 동시에 첨부파일을 업로드 하는 방식

3) <iframe>을 이용해서 화면의 이동 없이 첨부파일을 처리하는 방식

2. Ajax 를 이용하는 방식 : 첨부파일을 별도로 처리하는 방식 (교안에서는 이 방법 사용)

1) <input type='file'> 을 이용하고 Ajax 로 처리하는 방식

2) Dran And Drop 이나 jQuery 라이브러리들을 이용해서 처리하는 방식

[파일 업로드 라이브러리]

1. cos.jar

1) 2002년도 이후에 개발이 종료되었으므로, 사용권장하지 않음

2. commons-fileupload

1) 가장 일반적으로 많이 활용되고, 서블릿 스펙 3.0 이전에도 사용가능

3. 서블릿 3.0 이상

1) 3.0이상부터는 자체적인 파일 업로드 처리가 API 상에서 지원

exam04 를 복사해서 exam05 프로젝트를 새로 생성합니다.

[스프링 첨부파일을 위한 설정]

1. web.xml 에서 서블릿 버젼을 수정하고, <multipart-config>를 추가합니다.

web.xml

2. servlet-context.xml 에 다음과 같이 추가합니다.

servlet-context.xml 기존에 있던 설정 유지(exam04 복사했기 때문)

[<form> 방식의 파일 업로드]

1. UploadController.java 를 생성하여 uploadForm() 메소드를 생성합니다.

1) /view/uploadForm.jsp를 생성하여 다음과 같이 작성합니다.

UploadController.java, /view/uploadForm.jsp

결과하면

2. UploadForm.java(VO) 를 생성하고 uploadFormPost() 메소드를 작성합니다.

1) 위 화면에서 설명을 적고 파일을 선택하여 Submit 을 해봅니다.

아직 파일 저장 처리는 하지 않았습니다.

옛날 IE 브라우저는 파일경로 전체가 전송되므로 별도의 조작이 필요하다고 합니다.

UploadForm.java, uploadFormPost()

Submit 후 Console 결과

MultipartFile 의 주요 메소드

3. 이제 파일 저장을 실제로 하기 위해 UploadController 에 다음과 같이 작성합니다.

1) 파일 경로를 설정해주고 업로드되는 파일 이름(getOriginalFilename과

파일저장(transferTo)을 하는 것입니다.

UploadController.java

[Ajax 를 이용하는 파일 업로드]

1. 파일 데이터만을 전송하는 방식, 다른 input 필드는 별도로 전송

2. FormData 객체를 이용 : IE 경우 버전 10이후부터 지원

3. UploadController.java 에 uploadAjax() 메소드를 추가합니다.

UploadController.java (주석은 무시하십시오..)

4. uploadAjax.jsp 를 만들어 테스트 할 환경을 만듭니다.

1) 버튼을 위한 이벤트도 추가되야 하므로 script 도 같이 작성합니다.

(1) file 을 배열로 받아서 배열 순으로 업로드 하겠다는 것입니다.

5. UploadController.java 에 uploadAjaxPost() 메소드를 생성합니다.

UploadController.java console 에 파일이 잘가고 있음을 확인할 수 있습니다.

F12 를 눌러 개발자화면을 웹브라우저에서 보면 404 Found가 나옵니다.

uploadAjaxAction.jsp 가 없기 때문이고, 비동기식 이기 때문에 화면이 바뀌지 않은 것이였습니다.

[파일 전송 시 고려사항]

1. 동일한 잉름으로 파일이 업로드 된다면 기존 파일이 사라지는 문제

2. 이미지 파일의 경우에는 원본 파일의 용량이 큰 경우 섬네일 이미지를 생성해야 하는 문제

3. 이미지 파일과 일반 파일을 구분해서 다운로드 혹은 페이지에서 조회하도록 처리하는 문제

4. 첨부파일 공격에 대비하기 위한 업로드 파일의 확장자 제한

[파일의 확장자나 크기의 사전 처리]

1. uploadAjax.jsp 에 정규식을 이용해서 파일 확장자 체크를 하는 코드를 작성합니다.

exe,sh,zip,alz 종류의 확장자, 5MB 파일이 넘는 경우 경고창이 나오며 업로드 할 수 없습니다.

[중복된 이름의 첨부파일 처리]

1. UploadController.java에 ????/??/?? 형태의 문자열을 생성하는 getFolder() 메소드를 생성하고

uploadAjaxPost() 를 이에 맞게 수정합니다.

UploadController.java

localhost:8080/uploadAjax 에서 테스트 해본 결과 upload 폴더 밑에 날짜 형식으로

폴더들이 생성되어 파일들이 저장됨을 확인할 수 있습니다.

2. 파일이름 중복방지를 위해 uploadAjaxPost() 에 해당 내용을 추가합니다.

1) UUID(범용교유식별자, universally unique identifier)를 만드는 클래스의 메소드를 활용합니다.

2) 또한, 기존의 getOriginalFilename() 로 기존의 파일이름을 가져오던 것을

uploadFilename 에 담긴 UUID 파일이름으로 바꿔주어야 합니다.

UploadController.java

잘 동작함을 확인합니다.