티스토리 뷰

 

※ 2차 게시판 완료 ※

>> 댓글을 추가하면, 보드의 댓글 수 증가 → 2개의 작업이 유기적으로 진행되도록

 

(+) junit 버전 변경

기존의 프로젝트 4.7 버전 → 4.13 변경.

pom.xml에서 버전만 변경하면 됨.

 

1. AOP(Aspect Oriented Programming)

ex) 게시판에서 회원가입, 로그인, 글쓰기/수정/삭제, 상세보기 등등의 주업무 - 사용자 입장(관점)에서의 주요 목적.

콘솔에 로그 찍는 것 같은 개발자/운영자 관점의 보조 업무 로직

 

-주 업무[primary(Core) Concern]

-보조 업무[cross-cutting concern] : 주 업무들 중간중간에 사용. 끼웠다 뺐다. 로그 처리, 보안 처리, 트랜잭션 처리

 

물리적으로 분리되어 있지만 논리적으로는 하나로 이루어져있는 것처럼

 

(1) 주요 용어

Aspect Advice Target
횡단 관심사 - 로깅, 보안, 트랜잭션 횡단 관심사를 구현한 객체 핵심 로직을 가지고 있는 객체 - 주 업무
서비스, 등록/조회 등등의
JoinPoint Pointcut Proxy 객체
Advice의 적용 대상
Advice가 꽂혀있는 것이 JoinPoint
여러 JoinPoint 중에서
Advice가 적용되는 Select 기준
Target 객체 + Advice

└Advice : aop 패키지를 만들어 관련 작업

└Target : Advice를 적용시킬 대상.  ex) 서비스 클래스, 특정 함수(메소드) 등등

└Proxy : 어드바이스가 적용될 타켓들이 JoinPoint

 

(2) 어드바이스 종류

Before Advice After Returning Advice After Throwing Advice
Target의 JoinPoint 호출 전에
실행되는 Advice 코드
모든 실행이 정상적으로 이루어진 후
동작하는 Advice 코드
예외가 발생한 뒤
동작하는 Advice 코드
After Advice Around Advice  
코드의 정상적/비정상적 실행에
상관없이 실행되는 Advice 코드
메소드 실행 자체를 제어(가장 강력)  

└Around Advice : 함수 앞뒤로 실행해 소요 시간 등을 구할 수 있음. 메소드 직접 제어.

 

(3) Pointcut

Advice를 어떤 PointPoint에 결합할 것인지 결정

execution(@execution) within(@within) this
메소드를 기준으로 Pointcut 설정 특정한 타입(클래스) 기준으로
Pointcut 설정
주어진 인터페이스를
구현한 객체를 대상으로 Pointcut 설정
args(@args) @annotation  
특정한 파라미터를 가지는 대상 특정한 어노테이션이 적용된 대상  

 


2. 환경설정

AOP 사용을 위한 환경 설정 변경

(기존의 프로젝트는 따로 보관해둘 것.)

-버전만 변경 : aspectj, slf4j

-코드 가져오기 : aspectjweaver


3. 트랜잭션

하나의 '거래(비지니스 용어)'여러 번의 데이터베이스 관련 작업이 이루어짐. 이 작업들을 하나의 트랜잭션으로 처리.

 

(1) 트랜잭션의 원칙 - ACID

원자성(Atomicity) 일관성(Consistency) 격리(Isolation) 영속성(Durability)
하나의 트랜잭션은
하나의 단위로 처리
트랜잭션으로 처리된 데이터와
일반 데이터에 차이 없어야 함.
트랜잭션 처리 과정에
외부에서의 간섭 없어야 함.
트랜잭션 성공하면,
결과는 영속적 보관.

└트랜잭션 A는 α, β로 구성. α, β의 처리 결과는 동일해야 함. 어떤 작업이 잘못되는 경우 모든 것은 다시 원점.

└한덩어리로 처리 : 모두 처리되던가 모두 처리가 되지 않던가. ex) 계좌이체 : 내 통장 -, 수취인 통장 +

└영속성 : 영원 x 다시 바꾸기 전까지 고정.

 

(2) 댓글과 게시물의 반정규화

-반정규화 : 자주 사용하는 값을 컬럼으로 작성해 유지하는 방식

 (*정규화 : 여러 번의 조인 필요. 성능 저하 유발)

 

(3) 트랜잭션 설정

(3-1) root-context.xml 설정 추가

(3-2) @Transactional

가장 우선   가장 낮은 우선순위
메소드의 @Transactional 클래스의 @Transactional 인터페이스의 @Transactional

 

 


4. 실습 - AOP

(0) pom.xml 설정

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.wsy</groupId>
	<artifactId>webboard</artifactId>
	<name>boardSystem01Clone</name>
	<packaging>war</packaging>
	<version>1.0.0-BUILD-SNAPSHOT</version>
	<properties>
		<java-version>11</java-version><!-- ★ 변경 -->
		<org.springframework-version>5.2.19.RELEASE</org.springframework-version><!-- ★ 변경 -->
		<!-- ★ 관점지향을 위해 버전 변경 -->
		<org.aspectj-version>1.9.8</org.aspectj-version>
		<!-- ★ 관점지향을 위해 버전 변경 -->
		<org.slf4j-version>1.7.36</org.slf4j-version>
		<!-- 위의 2개는 버전만 변경하면 저절로 고쳐짐. 다만 maven repository에서 검색하기 위해 이름 참조. 밑에서. -->
	</properties>
	<dependencies>
...		
		<!-- AspectJ -->
		<dependency>
			<groupId>org.aspectj</groupId>
			<artifactId>aspectjrt</artifactId>
			<version>${org.aspectj-version}</version><!-- ★ 위에서 바꿔주면 적용됨. -->
		</dependency>	
		
		<!-- Logging -->
		<!-- ★ -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId><!-- maven repository 검색. 최신버전. 위에서 적용. -->
			<version>${org.slf4j-version}</version>
		</dependency>
...
<!-- ★★코드 추가★★ -->
...
	<!-- spring tx --><!-- 트랜잭션할 때 사용 -->
	<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-tx</artifactId>
		<version>5.2.19.RELEASE</version>
	</dependency>
...
	<!-- ★ lombok --><!--로고 찍기 위해서 필요-->
	<!-- lombok.jar 다운 후 인스톨/업데이터 과정 필요 -->
	<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.22</version>
		<scope>provided</scope>
	</dependency>
...
	<!-- ★ 관점 지향을 위해 필요한 코드 가져오기 -->
	<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
	<dependency>
		<groupId>org.aspectj</groupId>
		<artifactId>aspectjweaver</artifactId>
		<version>1.9.8</version>
	</dependency>
<!-- End -->	
	</dependencies>
...
</project>

 

(1) 인터페이스 생성

-위치 : com.wsy.webboard.service

-이름 : SampleService

package com.wsy.webboard.service;

public interface SampleService {
	
	Integer doAdd(String str1, String str2) throws Exception;
	
	int doSub(int num1, int num2);
	int doMul(int num1, int num2);
}

 

(2) 인터페이스 구현 클래스 생성

package com.wsy.webboard.service;

import org.springframework.stereotype.Service;

@Service
public class SampleServiceImpl implements SampleService {

	@Override
	public Integer doAdd(String str1, String str2) throws Exception {
		return Integer.parseInt(str1)+Integer.parseInt(str2);
	}

	@Override
	public int doSub(int num1, int num2) {
		return num1 - num2;
	}

	@Override
	public int doMul(int num1, int num2) {
		return num1 * num2;
	}
}

 

(3) root-context.xml

네임스페이스 >>AOP 체크

<!-- ★ 코드 추가 -->
<context:component-scan base-package="com.wsy.webboard.service, com.wsy.webboard.aop"/>
<aop:aspectj-autoproxy/> <!-- aspectj를 사용하도록 설정 -->

└SampleServiceImpl에 가보면 doAdd 함수만 빨간색 화살표가 되어 있는 것을 확인할 수 있음. Advice 적용된 상태.

└Advice가 적용된 것에 빨간색 화살표가 표시된다는데? → 제대로 적용됐다면.

 

(4) 클래스 생성

-위치 : com.wsy.webboard.aop ← 새로 만들기

-이름 : LogAdvice

package com.wsy.webboard.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import lombok.extern.java.Log;

@Aspect //Aspect임을 명시
@Log
@Component
public class LogAdvice {
	//SampleService~로 시작하는 모든 클래스 / SampleService로 시작하는 모든 함수
	//advice 끼우기 / execution 함수
	@Before( "execution(* com.wsy.webboard.service.SampleService*.*(..)) ") //service 패키지 안의 service로 시작하는 모든 클래스 & 모든 함수
	public void logBefore() {
		log.info("- - - - - before - - - - -");
	}
}

 

(5) 테스트

-위치 : src/test/java  >>  com.wsy.webboard.test ← 새로 패키지 생성(클래스 생성할 때 수정)

-이름 : SampleAopTest

-SampleServiceImpl에 service임을 명시하는 어노테이션

package com.wsy.webboard.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.wsy.webboard.service.SampleService;

import lombok.extern.java.Log;
import lombok.extern.log4j.Log4j;

@Log4j
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class SampleAopTest {
	
	@Autowired
	private SampleService sampleService;
	
	@Test
	public void testClass(){
		log.info(sampleService);
		log.info(sampleService.getClass().getName());
		log.info(sampleService.doAdd("11", "22")); //이것까지 써줘야 before가 찍힘.
		
	}
}

 

>> console

INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@1e178745, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@192c3f1e, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@26b3fd41, org.springframework.test.context.support.DirtiesContextTestExecutionListener@7494f96a, org.springframework.test.context.transaction.TransactionalTestExecutionListener@561b6512, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@2e377400, org.springframework.test.context.event.EventPublishingTestExecutionListener@1757cd72]
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
INFO : com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@3359c978
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
INFO : com.wsy.webboard.test.SampleAopTest - com.wsy.webboard.service.SampleServiceImpl@58399d82
INFO : com.wsy.webboard.test.SampleAopTest - jdk.proxy2.$Proxy31
INFO : com.wsy.webboard.test.SampleAopTest - 33
3월 15, 2022 9:49:51 오후 com.wsy.webboard.aop.LogAdvice logBefore
정보: -----------before------------
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.

(라이브러리가 있는데도 안 될 때)

*junit 관련 Annotation이 제대로 자동입력되지 않을 때,

STS 종료 후, c드라이브 > admin > m2 > repository > junit 폴더 삭제 후 다시 STS 실행 > maven update

> 다시 junit 사용 준비를 위해 다시 다운?

 

★ junit이 안 됐는데,

junit 버전이 안 맞았기 때문!

pom.xml에서 junit 버전 변경 : 4.13 >> 준비되는 것 기다렸다가 실행

>> 그래도 안 되면, repository 삭제 후 STS 재실행 >> maven update >> junit TEST

 

(6) 여러 어드바이스 종류 사용

(6-1) com.wsy.webboard.aop >> LogAdivice.java

package com.wsy.webboard.aop;

import java.util.Arrays;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import lombok.extern.java.Log;

@Aspect //Aspect임을 명시
@Log
@Component
public class LogAdvice {
	//SampleService~로 시작하는 모든 클래스 / SampleService로 시작하는 모든 함수
	//advice 끼우기 / execution 함수
	@Before( "execution(* com.wsy.webboard.service.SampleService*.*(..))") //service 패키지 안의 service로 시작하는 모든 클래스 & 모든 함수
	public void logBefore() {
		log.info("- - - - - before - - - - -");
	}
	
	//놓친 실습 해보기
//	@Before( "execution(* com.wsy.webboard.service.SampleService*.doAdd(String, String)) && args(str1, str2)")
	public void logBeforeWithParam(String str1, String str2) {
		log.info("str1 : " + str1);
		log.info("str2 : " + str2);
	}
	
	//예외 발생 감지
//	@AfterThrowing(pointcut = "execution(* com.wsy.webboard.service.SampleService*.*(..))", throwing="exception")
	public void longException(Exception exception) {
		log.info("Exception...!!!");
		log.info("exception : " + exception);
	}
	
//	@Around("execution(* com.wsy.webboard.service.SampleService*.*(..))")
	public Object logTime( ProceedingJoinPoint pjp) {
		long start = System.nanoTime();
		log.info("target : " + pjp.getTarget());
		log.info("param : " + Arrays.toString(pjp.getArgs()));
		
		Object result = null;
		try {
			result = pjp.proceed(); //정수 값 2개를 더해서 저장
		} catch (Throwable e) {
			e.printStackTrace();
		}
		long end = System.nanoTime(); //끝나는 시간 찍음
		log.info("Time : " + (end-start));  //전체 수행하는 시간
		return result; //결과 리턴
	}
}

└//놓친 실습 해보기에서 괄호 하나가 빠져서 프로젝트 전체가 안 열렸던 것

 

(6-2) src/test/java  >> SampleAOPTest.java

package com.wsy.webboard.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.wsy.webboard.service.SampleService;

import lombok.extern.log4j.Log4j;

@Log4j
@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
@ContextConfiguration("file:src/main/webapp/WEB-INF/spring/root-context.xml")
public class SampleAopTest {
	
	@Autowired
	private SampleService sampleSerivce;
	
	@Test
	public void testClass() throws Exception {
		log.info(sampleSerivce);
		log.info(sampleSerivce.getClass().getName());
		log.info(sampleSerivce.doAdd("11", "33")); //이것까지 찍어야 before 찍힘
//		log.info(sampleSerivce.doAdd("11", "aa")); //예외발생시킬 때 사용할 것
//		log.info(sampleSerivce.doSub(1, 2));
//		log.info(sampleSerivce.doSub(10, 2));
	}
}

 

>> 결과 출력 : console

@Before( "execution(* com.wsy.webboard.service.SampleService*.doAdd(String, String)) && args(str1, str2)")
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@c0c2f8d, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@305b7c14, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@6913c1fb, org.springframework.test.context.support.DirtiesContextTestExecutionListener@66d18979, org.springframework.test.context.transaction.TransactionalTestExecutionListener@bccb269, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@609cd4d8, org.springframework.test.context.event.EventPublishingTestExecutionListener@17f7cd29]
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
INFO : com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@6bb7cce7
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
INFO : com.wsy.webboard.test.SampleAopTest - com.wsy.webboard.service.SampleServiceImpl@63da207f
INFO : com.wsy.webboard.test.SampleAopTest - jdk.proxy2.$Proxy31
3월 15, 2022 10:07:01 오후 com.wsy.webboard.aop.LogAdvice logBeforeWithParam
정보: str1 : 11
INFO : com.wsy.webboard.test.SampleAopTest - 33
3월 15, 2022 10:07:01 오후 com.wsy.webboard.aop.LogAdvice logBeforeWithParam
정보: str2 : 22
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
@AfterThrowing
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@2e377400, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@1757cd72, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@445b295b, org.springframework.test.context.support.DirtiesContextTestExecutionListener@49e5f737, org.springframework.test.context.transaction.TransactionalTestExecutionListener@5c671d7f, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@757277dc, org.springframework.test.context.event.EventPublishingTestExecutionListener@687e99d8]
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
INFO : com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@31be6b49
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
INFO : com.wsy.webboard.test.SampleAopTest - com.wsy.webboard.service.SampleServiceImpl@70e02081
INFO : com.wsy.webboard.test.SampleAopTest - jdk.proxy2.$Proxy32
3월 15, 2022 10:02:16 오후 com.wsy.webboard.aop.LogAdvice logBefore
정보: -----------before------------
3월 15, 2022 10:02:16 오후 com.wsy.webboard.aop.LogAdvice longException
정보: Exception...!!!
3월 15, 2022 10:02:16 오후 com.wsy.webboard.aop.LogAdvice longException
정보: exception : java.lang.NumberFormatException: For input string: "aa"
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.
@Around
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
INFO : org.springframework.test.context.support.DefaultTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@5aa9e4eb, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@6989da5e, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@385c9627, org.springframework.test.context.support.DirtiesContextTestExecutionListener@139982de, org.springframework.test.context.transaction.TransactionalTestExecutionListener@682b2fa, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@217ed35e, org.springframework.test.context.event.EventPublishingTestExecutionListener@7dcf94f8]
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
INFO : com.zaxxer.hikari.pool.HikariPool - HikariPool-1 - Added connection com.mysql.cj.jdbc.ConnectionImpl@450794b4
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
INFO : com.wsy.webboard.test.SampleAopTest - com.wsy.webboard.service.SampleServiceImpl@682bd3c4
INFO : com.wsy.webboard.test.SampleAopTest - jdk.proxy2.$Proxy31
3월 15, 2022 10:04:50 오후 com.wsy.webboard.aop.LogAdvice logTime
정보: target : com.wsy.webboard.service.SampleServiceImpl@682bd3c4
3월 15, 2022 10:04:50 오후 com.wsy.webboard.aop.LogAdvice logTime
정보: param : [11, 22]
3월 15, 2022 10:04:50 오후 com.wsy.webboard.aop.LogAdvice logTime
정보: Time : 39737900
INFO : com.wsy.webboard.test.SampleAopTest - 33
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
INFO : com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.

 


5. 실습 - 댓글 트랜잭션

댓글 입력/읽기/수정/삭제까지 완료된 프로젝트를 가지고트랜잭션 처리 : 댓글 입력/삭제하면 댓글수 증감

 

*MySQL-board_tbl : replycount / INT / (default) 0 설정되어 있는지 확인. 댓글수 처리

 

(1) BoardVO.java

기존의 클래스에 댓글 수 관련 처리할 필드 추가

package com.wsy.webboard.domain;

import java.util.Date;

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

@Data  //getter, setter, toString, 생성자 자동 완성
@NoArgsConstructor // 기본 생성자
@AllArgsConstructor // 모든 필드를 매개변수로 가지는 생성자
public class BoardVO {
	private int bno;
	private String title;
	private String content;
	private String writer;
	private Date regdate;
	private Date updatedate;
	private int hitCount;
	//트랜잭션 처리 - 댓글 개수
	private int replycount;  //MySQL의 열(Column) 이름과 같아야
}

 

(2) BaordMapper.xml

<!-- 댓글 트랜잭션 처리 -->
<update id="replyCountUpdate">
	update board_tbl set replycount = replycount + #{amount} where bno = #{bno}
</update>

 

(3) root-context.xml

원래는 이거 먼저 해줘야 함.

[Namespaces] -> tx 체크 >

<!-- 댓글 트랜잭션 처리 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven /> <!-- transaction Annotaion이 붙어있는 곳에 자동으로 주입 -->

└클래스에서 자동완성 기능 사용해서 가져올 것. xml은 오타 찾기 어려우니까.

 

(4) BoardMapper.java

//댓글 트랜잭션 처리  //requestParam을 붙이는 이유 : 매퍼에 값을 2개 보낼 수 없으므로 requestParam이라는 객체에 bno, amount를 담아서 보냄.
//	public void replyCountUpdate(int bno, int amount); mapper는 값을 2개 이상 보낼 수 없음. 그래서 requestParam이나 HashMap을 이용해 보냄.
	public void replyCountUpdate(@Param("bno") int bno,
			@Param("amount") int amount);

 

└원래 requestParam을 썼는데, Param으로 변경(?)

 

(5) ReplyServiceImpl.java

package com.wsy.webboard.service;

import java.util.List;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.wsy.webboard.domain.ReplyVO;
import com.wsy.webboard.mapper.BoardMapper;
import com.wsy.webboard.mapper.ReplyMapper;

import lombok.AllArgsConstructor;

@Service
@AllArgsConstructor //AllAutowired 됨
public class ReplyServiceImpl implements ReplyService {
	
//	@Autowired
	private ReplyMapper replyMapper;
	private BoardMapper boardMapper; //댓글 트랜잭션 처리 위해
	
	@Transactional //둘 다 처리되거나 둘 다 처리되지 않거나
	@Override
	public int register(ReplyVO vo) {
		//댓글 트랜잭션 처리 위해
		boardMapper.replyCountUpdate(vo.getBno(), 1); //★
		return replyMapper.insert(vo);
	}
...
	@Override
	public int remove(int rno) { //삭제할 때, bno 필요한데 rno만 있으니까 rno를 이용해 처리
		ReplyVO vo = replyMapper.read(rno);
		boardMapper.replyCountUpdate(vo.getBno(), -1); //★
		return replyMapper.delete(rno);
	}
...
}

 

(6) 댓글 개수 표시되도록 - list.jsp

<table class="table table-hover">
	<thead>
		<tr>
			<th>번호</th>
			<th>제목</th>
			<th>작성자</th>
			<th>작성일시</th>
			<th>조회수</th>
		</tr>
	</thead>
	<tbody>
		<c:forEach items="${list}" var="board">
			<tr>
				<td>${board.bno}</td>
				<!-- ★ 글 번호, 현재 페이지 번호 주소에 들고 다니도록 -->                                                                     <!-- ★ 댓글 개수 -->
				<td><a href="/board/detail?bno=${board.bno }&pageNum=${p.currentPage}&field=${field}&word=${word}">${board.title }(${board.replycount })</a></td><!-- ★ -->
				<!-- 경로에 번호를 바로 적어줄 때, boardController 수정 -->
				<!-- <td><a href="/board/detail/${board.bno }">${board.title }</a></td> -->
				<td>${board.writer}</td>
				<td><fmt:formatDate pattern="yyyy-MM-dd" value="${board.regdate}" /></td>
				<td>${board.hitCount}</td>
			</tr>
		</c:forEach>
	</tbody>
</table>

 

(7) 결과 출력

└replycount 설정하기 전에 생성되어 있던 댓글은 개수에 포함되지 않는듯.때문에 기존의 댓글을 전부 지웠을 때, -2가 되었고 다시 생성하니 0이되었음.

 

 END

 


- 2022.03.15(화) AOP 실습까지 완료 -

boardSystem01Clone.zip
2.90MB

 

 

 

- 댓글 개수까지 완료 -

boardSystem01Clone_댓글수.zip
2.90MB

'수업 > └Spring Framework' 카테고리의 다른 글

[05]웹게시판_로그인/사용자권한/댓글  (0) 2022.03.14
[05]REST & Ajax_개념  (0) 2022.03.10
[04]웹 게시판 CRUD 클론_1차  (0) 2022.03.07
[04]웹 게시판(CRUD)_1차  (0) 2022.03.04
[03_2]Spring MVC - Legacy Project  (0) 2022.03.03
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/07   »
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
글 보관함