오늘은 데이터베이스 연결 객체와
AOP설정 aop:befor aop:after aop:around를 이용한 기능코드 호출(실행),
JDBC(JDBCTemplate를 사용하는 두 가지 방법
(JdbcDaoSupport클래스를 상속받아 사용, JdbcTemplate를 필드변수로 선언해 사용))
을 공부했다.
DB연결 객체
@Repository
public class NoticeBoardDao {
private Connection conn = null;
private PreparedStatement stmt = null;
private ResultSet rs = null;
...
Connection : DB 연결을 담당하는 객체
`conn = JDBCUtil.*getConnection*();`
Statement 객체를 생성할 수 있게 JDBC연결을 가져옴
PreparedStatement : SQL문 실행 담당하는 객체
stmt = conn.prepareStatement(DELETE);
미리 정의해 둔 DELETE 쿼리를 담아서 저장하고
stmt.setInt(1, id);
stmt.executeUpdate();
? 에 넘겨줄 값을 넣어서
실행시킴
private final String DELETE = "DLELTE FROM FreeBoard WHERE ID = ?";
ResultSet : 쿼리 실행 결과를 담는? 담기 위한? 담을? 객체
public List<BoardDto> getBoardList(){
System.out.println("NoticeBoardDao - getBoardList 메서드 호출");
List<BoardDto> boardDtolist = new ArrayList<>();
try{
conn = JDBCUtil.getConnection();
stmt = conn.prepareStatement(GET_NOTICE_LIST);
rs = stmt.executeQuery();
while (rs.next()){
...
ResultSet rs에 ← stmt안에 담은 sql쿼리를 executeQuery() 쿼리 실행 결과 저장
—NoticeBoardDao.java—
package com.bit.springboard.dao;
import com.bit.springboard.common.JDBCUtil;
import com.bit.springboard.dto.BoardDto;
import org.springframework.stereotype.Repository;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
@Repository
public class NoticeBoardDao {
private Connection conn = null;
private PreparedStatement stmt = null;
private ResultSet rs = null;
// NoticeBoard 게시글 작성하기
private final String POST = "INSERT INTO Notice(TITLE, CONTENT, WRITER_ID) VALUES(?,?,?)";
// NoticeBoard 게시글 수정하기
private final String MODIFY = "UPDATE Notice SET TITLE = ?, CONTENT = ?, MODDATE = ? WHERE ID = ?";
// NoticeBoard 게시글 삭제
private final String DELETE = "DELETE FROM Notice WHERE ID = ?";
// 게시글 전체 불러오기
private final String GET_NOTICE_LIST = "SELECT Notice.ID, Notice.TITLE, Notice.CONTENT,Notice.WRITER_ID, member.NICKNAME, Notice.REGDATE, Notice.MODDATE, Notice.CNT " +
"FROM Notice " +
"join member " +
"on Notice.WRITER_ID = member.ID";
// 게시글 하나 찝어서 보기
private final String GET_ONE_NOTICE = "SELECT" +
" notice.ID," +
" notice.TITLE," +
" notice.CONTENT," +
"notice.WRITER_ID," +
" member.NICKNAME," +
" notice.REGDATE," +
" notice.MODDATE," +
" notice.CNT" +
" FROM notice" +
" join member" +
" on notice.WRITER_ID = member.ID" +
" WHERE notice.id = ?";
//************************************************************
public void noticePost(BoardDto boardDto){
System.out.println("NoticeBoardDao post 메서드 실행");
try {
conn = JDBCUtil.getConnection();
stmt = conn.prepareStatement(POST);
stmt.setString(1, boardDto.getTitle());
stmt.setString(2, boardDto.getContent());
stmt.setInt(3, boardDto.getWRITER_ID());
stmt.executeUpdate();
} catch (Exception e){
System.out.println(e.getMessage());
} finally {
JDBCUtil.close(conn, stmt);
}
System.out.println("NoticeBoardDao post 메서드 종료");
}
public void noticeModify(BoardDto boardDto){
System.out.println("NoticeBoadrDao - modify 메서드 호출");
try {
conn = JDBCUtil.getConnection();
stmt = conn.prepareStatement(MODIFY);
stmt.setString(1, boardDto.getTitle());
stmt.setString(2, boardDto.getContent());
stmt.setString(3, boardDto.getModdate().toString());
stmt.setInt(4, boardDto.getId());
stmt.executeUpdate();
} catch (Exception e){
System.out.println(e.getMessage());
} finally {
JDBCUtil.close(conn, stmt);
}
System.out.println("NoticeBoadrDao - modify 메서드 종료");
}
public void delete(int id){
System.out.println("NoticeBoardDao - delete 메서드 호출");
try {
conn = JDBCUtil.getConnection();
stmt = conn.prepareStatement(DELETE);
stmt.setInt(1,id);
stmt.executeUpdate();
} catch (Exception e){
System.out.println(e.getMessage());
} finally {
JDBCUtil.close(conn, stmt);
}
System.out.println("NoticeBoardDao - delete 메서드 종료");
}
public List<BoardDto> getBoardList(){
System.out.println("NoticeBoardDao - getBoardList 메서드 호출");
List<BoardDto> boardDtolist = new ArrayList<>();
try{
conn = JDBCUtil.getConnection();
stmt = conn.prepareStatement(GET_NOTICE_LIST);
rs = stmt.executeQuery();
while (rs.next()){
BoardDto boardDto = new BoardDto();
boardDto.setId(rs.getInt("ID"));
boardDto.setTitle(rs.getString("TITLE"));
boardDto.setContent(rs.getString("CONTENT"));
boardDto.setWRITER_ID(rs.getInt("WRITER_ID"));
boardDto.setNickname(rs.getString("NICKNAME"));
boardDto.setRegdate(rs.getTimestamp("REGDATE").toInstant().atZone(ZoneId.of("UTC")).toLocalDateTime());
boardDto.setModdate(rs.getTimestamp("MODDATE").toInstant().atZone(ZoneId.of("UTC")).toLocalDateTime());
boardDto.setCnt(rs.getInt("CNT"));
boardDtolist.add(boardDto);
}
} catch (Exception e){
System.out.println(e.getMessage());
} finally {
JDBCUtil.close(conn, stmt, rs);
}
System.out.println("NoticeBoardDao - getBoardList 메서드 종료");
return boardDtolist;
}
public BoardDto getBoard(int id){
System.out.println("NoticeBoardDao - getBoard 메서드 실행");
BoardDto boardDto = new BoardDto();
try {
conn = JDBCUtil.getConnection();
stmt = conn.prepareStatement(GET_ONE_NOTICE);
stmt.setInt(1, id);
rs = stmt.executeQuery();
if (rs.next()){
boardDto.setId(rs.getInt("ID"));
boardDto.setTitle(rs.getString("TITLE"));
boardDto.setContent(rs.getString("CONTENT"));
boardDto.setWRITER_ID(rs.getInt("WRITER_ID"));
boardDto.setNickname(rs.getString("NICKNAME"));
boardDto.setRegdate(rs.getTimestamp("REGDATE").toLocalDateTime());
boardDto.setModdate(rs.getTimestamp("MODDATE").toLocalDateTime());
boardDto.setCnt(rs.getInt("CNT"));
}
} catch (Exception e){
System.out.println(e.getMessage());
} finally {
JDBCUtil.close(conn, stmt, rs);
}
System.out.println("NoticeBoardDao - getBoard 메서드 종료");
return boardDto;
}
}
List <Board> getBoardList() 메서드의
boardDto.setRegdate(rs.getTimestamp("REGDATE").toInstant().atZone(ZoneId.of("UTC")).toLocalDateTime()); boardDto.setModdate(rs.getTimestamp("MODDATE").toInstant().atZone(ZoneId.of("UTC")).toLocalDateTime());
UTC로 지정해 주어 시간이 정확하게 나타남.
반면, BoardDto getBoard(int id) 메서드의
boardDto.setRegdate(rs.getTimestamp("REGDATE").toLocalDateTime()); boardDto.setModdate(rs.getTimestamp("MODDATE").toLocalDateTime());
시차가 존재한다.
-->

DB 연동 방식(JDBC/DBCP/JNDI)
1. JDBC(Java DataBase Connectivity):
자바에서 제공해 주는 DB 연결 표준 API.
DB 연결 요청이 올 때마다 Web Application이 커넥션을 계속해서 생성하는 방식.
많은 사용자가 동시에 DB 접속을 요청하면, 커넥션의 개수가 증가하여 DB 서버 과부하나 메모리 부족을 야기할 수 있다. 비효율적인 방식이어서 현재는 많이 사용되고 있지 않다.

2. DBCP(DataBase Connection Pool):
Web Application에서 지정한 개수만큼의 DB Connection을 미리 만들어놓고, DB 접속 요청이 올 때마다
만들어놓은 DB Connection을 대여하는 방식.
초기 Connection 개수, 최대 Connection 개수, 평소에 유지되는 Connection 개수 등을 설정할 수 있다.
메모리 낭비가 되지 않도록 적절한 개수를 설정하는 것이 중요하다.

3. JNDI(Java Naming and Directory Interface):
방식은 DBCP와 같지만 DB Connection의 관리주체가 Web Application이 아닌 WAS(Web Application Server)로 변경.
설정이 간편하고 Web Application에 과부하를 방지할 수 있어서 가장 많이 사용되는 방식.

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

로그콘솔 클래스 추가.
package com.bit.springboard.service.impl;
import com.bit.springboard.common.LogConsole;
import com.bit.springboard.dao.NoticeBoardDao;
import com.bit.springboard.dto.BoardDto;
import com.bit.springboard.service.BoardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class NoticeBoardServiceImpl implements BoardService {
private NoticeBoardDao noticeBoardDao;
private LogConsole logConsole;
@Autowired
public NoticeBoardServiceImpl(NoticeBoardDao noticeBoardDao) {
this.noticeBoardDao = noticeBoardDao;
this.logConsole = new LogConsole();
}
@Override
public void post(BoardDto boardDto) {
logConsole.consoleLog();
noticeBoardDao.noticePost(boardDto);
}
@Override
public void modify(BoardDto boardDto) {
logConsole.consoleLog();
noticeBoardDao.noticeModify(boardDto);
}
@Override
public void delete(int id) {
logConsole.consoleLog();
noticeBoardDao.delete(id);
}
@Override
public List<BoardDto> getBoardList() {
logConsole.consoleLog();
return noticeBoardDao.getBoardList();
}
@Override
public BoardDto getBoard(int id) {
logConsole.consoleLog();
return noticeBoardDao.getBoard(id);
}
}
→
로그콘솔 클래스가 → 로그콘솔 V2 클래스로 업데이트된 상황.
= > 다음과 같이 모두 교체해야 됨
—NoticeBoardServiceImpl 클래스 전체 코드—
package com.bit.springboard.service.impl;
import com.bit.springboard.common.LogConsoleV2;
import com.bit.springboard.dao.NoticeBoardDao;
import com.bit.springboard.dto.BoardDto;
import com.bit.springboard.service.BoardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class NoticeBoardServiceImpl implements BoardService {
private NoticeBoardDao noticeBoardDao;
private LogConsoleV2 logConsole;
@Autowired
public NoticeBoardServiceImpl(NoticeBoardDao noticeBoardDao) {
this.noticeBoardDao = noticeBoardDao;
this.logConsole = new LogConsoleV2();
}
@Override
public void post(BoardDto boardDto) {
logConsole.consoleLogPlus();
noticeBoardDao.noticePost(boardDto);
}
@Override
public void modify(BoardDto boardDto) {
logConsole.consoleLogPlus();
noticeBoardDao.noticeModify(boardDto);
}
@Override
public void delete(int id) {
logConsole.consoleLogPlus();
noticeBoardDao.delete(id);
}
@Override
public List<BoardDto> getBoardList() {
logConsole.consoleLogPlus();
return noticeBoardDao.getBoardList();
}
@Override
public BoardDto getBoard(int id) {
logConsole.consoleLogPlus();
return noticeBoardDao.getBoard(id);
}
}
root-context.xml 에 aop 설정 추가해 주고.
<beans
...
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" ...
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
...
...
<!--AOP설정-->
<!--공통적으로 사용될 클래스를 bean 으로 등록-->
<bean id="logConsole" class="com.bit.springboard.common.LogConsoleV2"/>
<!--aop:config AOP 의 설정의 루트 엘리먼트.-->
<aop:config>
<!-- aop:pointcut 공통적인 기능이 실행될 클래스와 메서드를 지정한다.-->
<aop:pointcut id="allpointcut" expression="execution(* com.bit.springboard.service..*Impl.*(..))"/>
<!-- 리턴타입과 관계없이 모든 메서드에서 실행할것을 명시 service 패키지의 Impl로 끝나는모든 클래스의 매개변수 관계없이.-->
<!-- aop:aspect 공통 기능의 메소드와 실행될 메서드를 매핑하는 작업-->
<aop:aspect ref="logConsole">
<!--before 로 지정하면 메서드 실행 이전에 먼저 method="" 로 지정한 메서드 실행 , 위에 지정한 pointcut 매핑-->
<aop:before method="consoleLogPlus" pointcut-ref="allpointcut"/>
</aop:aspect>
</aop:config>
</beans>
추가 정리:
포인트 컷 (pointcut) -
첫 번째 파라미터 : (* or void or !void) 리턴타입을 지정
두 번째 파라미터 : 메소드가 존재하는 클래스나 패키지를 지정
세 번째 파라미터 :
(*:모든메서드 or
get*:get으로 시작하는 메서드 or
(..):매개변수 타입, 개수 제약없음 or
(*):타입 제약 없지만, 1개의 매개변수 or
(com.bit.springboard.dto.BoardDTO):매개변수는 하나이고, 매개변수가BoardDTO인 or
(!com.bit.springboard.dto.BoardDTO):매개변수는 하나이고, 매개변수가BoardDTO가 아닌 or
(Integer, ..):매개변수 타입과 개수에 제약없지만 첫 매개변수는 Int형 ) 메소드를 지정
<aop:pointcut id="allpointcut" expression="execution(* com.bit.springboard.service..*Impl.*(..))"/>
(*:모든리턴타입 com.bit.springboard.service..*Impl:임플로끝나는모든클래스.*:모든메서드명(..:매개변수타입과 개수 제한없이 모두))
aop 동작 시점:
before: 포인트컷 메소드가 실행되기 전에 공통 기능 코드를 실행.
after-returning: 포인트컷 메소드가 정상적으로 종료되고 공통 기능 코드를 실행.
after-throwing: 포인트컷 메소드가 에러를 발생시켰을 때 공통 기능 코드를 실행.
after: 포인트컷 메소드가 정상 종료되거나 에러를 발생시키거나와 상관없이 메소드가 종료되면 무조건 공통 기능 코드 실행.
<- JoinPoint 인터페이스 사용
ProceedingJoinPoint 인터페이스 사용 ->
around: 포인트컷 메소드 실행 전 후로 한 번씩 공통 기능 코드 실행.
어드바이스:
공통 관심에 해당되는 공통 기능 코드(consoleLog, consoleLogPlus).
어드바이스를 지정할 때는 공통 기능 코드가 실행될 시점과 함께 지정.
위빙:
<aop:before method="consoleLogPlus" pointcut-ref="allpointcut"/>
자동으로 consolLogPlus 를 주입해주는 행위
애즈팩트:
어드바이스 + 포인트 컷.
포인트 컷에 위빙이 일어나서 공통 기능 코드가 포인트 컷에 주입된 상태를 애즈팩트라고 한다.
어드바이저:
애즈팩트와 동일.
공통 기능 코드의 메소드 명을 모를 때나 공통 기능이 들어있는 클래스 명을 모를 때 애즈팩트 대신 어드바이저를 사용한다.
트랜잭션을 설정할 때 commit, rollback 시점을 개발자가 알 수 없기 때문에
이 때도 애즈팩트 대신 어드바이저를 사용해서 자동적으로 commit, rollback이 일어나도록 설정한다.
—작성 후 변경한 NoticeBoardImpl 클래스 전체 코드—
(위에서 작성한 logconsole 변수와 메서드 호출 지워주기 - aop설정을 해두어 자동 호출될 것임)
package com.bit.springboard.service.impl;
import com.bit.springboard.dao.NoticeBoardDao;
import com.bit.springboard.dto.BoardDto;
import com.bit.springboard.service.BoardService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class NoticeBoardServiceImpl implements BoardService {
private NoticeBoardDao noticeBoardDao;
@Autowired
public NoticeBoardServiceImpl(NoticeBoardDao noticeBoardDao) {
this.noticeBoardDao = noticeBoardDao;
}
@Override
public void post(BoardDto boardDto) {
noticeBoardDao.noticePost(boardDto);
}
@Override
public void modify(BoardDto boardDto) {
noticeBoardDao.noticeModify(boardDto);
}
@Override
public void delete(int id) {
noticeBoardDao.delete(id);
}
@Override
public List<BoardDto> getBoardList() {
return noticeBoardDao.getBoardList();
}
@Override
public BoardDto getBoard(int id) {
return noticeBoardDao.getBoard(id);
}
}
NoticeBoardServiceRun 클래스 실행 결과 —>

<aop:before method="consoleLogPlus" pointcut-ref="allpointcut"/>
</aop:aspect>
---
aop:after-returning 실행결과

`<!--공통적으로 사용될 클래스를 bean 으로 등록-->
<bean id="logConsole" class="com.bit.springboard.common.LogConsoleV2"/>
<bean id="afterReturning" class="com.bit.springboard.common.AfterReturning"/>`
<aop:aspect ref="AfterReturning"> <!—새로 생성한 AfterReturning클래스—>
<aop:after-returning method="afterReturningLog" pointcut-ref="allpointcut"/>
</aop:aspect>
---
aop:after-throwing 실행결과

@Override
public void post(BoardDto boardDto) {
if(boardDto.getId() == 0){
throw new RuntimeException("id에 0은 입력될 수 없습니다.");
}
freeBoardDao.post(boardDto);
}
일부러 예외가 발생하도록 수정
//설정
<bean id="afterThrowing" class="com.bit.springboard.common.AfterThrowing"/>
--
<aop:aspect ref="afterThrowing">
<aop:after-throwing method="afterThrowingLog" pointcut-ref="allpointcut"/>
</aop:aspect>
---
aop:after 실행결과


// 설정
<bean id="after" class="com.bit.springboard.common.After"/>
--
<aop:aspect ref=”after">
<aop:after method="afterLog" pointcut-ref="allpointcut"/>
</aop:aspect>
aop:around 실행결과 -->

// 설정
<bean id="around" class="com.bit.springboard.common.Around"/>
--
<aop:aspect ref=”around">
<aop:around method="aroundLog" pointcut-ref="allpointcut"/>
</aop:aspect>
pom.xml 에서 AspectJ의 aspectjrt → <scope>runtime</scope> 하나 주석처리 해주면 오류 해결된다.
JdbcDaoSupport클래스를 상속받아 사용하기, JdbcTemplate를 필드변수로 선언해 사용하기
// JDBCDaoSupport 클래스 상속 방식으로 JDBC Template 사용하기
@Repository
public class NoticeBoardDaoJDBCSupport extends JdbcDaoSupport {
// JDBCDaoSupport 클래스 상속 방식으로 JDBC Template 사용하기
@Autowired
public void setSuperDataSource(DataSource dataSource){
super.setDataSource(dataSource);
}
...
//JDBC Template 을 필드변수로 선언하고 의존성 주입해 사용하기!!
@Repository
public class NoticeBoardDaoFieldVar {
private JdbcTemplate jdbcTemplate;
//JDBC Template 을 필드변수로 선언하고 의존성 주입해 사용하기!!
@Autowired // 데이터 소스 연결
public void NoticeDotJDBC(JdbcTemplate jdbcTemplate){
this.jdbcTemplate = jdbcTemplate;
}
...
memberDtoList = jdbcTemplate.query(GET_MEMBERS, new MemberRowMapper());
셀렉트 결과들 : List → query로 새 객체에 넣어서 받아왔다
jdbcTemplate.update(JOIN, memberDto.getUsername(), memberDto.getPassword(), memberDto.getEmail(), memberDto.getNickname(), memberDto.getTel());
업데이트 결과 → update로 처리했다?
단순 업데이트(쿼리실행)와 실행결과 반납(셀렉트 처리한 거 어쩌라고 느낌?) 표현의… 차이 query()와 update()
'Spring > spring' 카테고리의 다른 글
| 스프링 6일차 정리 (0) | 2024.07.17 |
|---|---|
| 스프링 5일차 정리 (0) | 2024.07.16 |
| 스프링 4일차 정리 (0) | 2024.07.14 |
| 스프링 2일차 정리 (0) | 2024.07.10 |
| 스프링 1일차 정리 (0) | 2024.07.09 |