티스토리 뷰
※ 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 실습까지 완료 -
- 댓글 개수까지 완료 -
'수업 > └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
- html layout
- improt
- caption-side
- text formatting
- html base tag
- 미디어 태그
- html
- scanner
- Java
- css
- 스크립태그
- input type 종류
- 외부구성요소
- ScriptTag
- border-spacing
- JavaScript
- html a tag
- empty-cell
- selcetor
- 변수
- BAEKJOON
- 입력양식
- typeof
- initialized
- html pre
- A%B
- CascadingStyleSheet
- 기본선택자
- html atrribute
- html input type
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |