## 69. Mybatis + Spring IoC 컨테이너 = DAO 구현체 자동 생성
- Mybatis의 Spring 연동 플러그인을 사용하여 DAO를 자동 생성하는 방법
- Mybatis의 Log4j 활성화 하기
MyBatis
ㄴ MyBatis for Java > Github Project 선택
=>
ㄴ Repositories 선택
=>
ㄴ spring 선택
=>
ㄴ Essentials > See the published docs 선택
=>
ㄴ Getting Started 선택하여 문서 확인
ㄴ central.sonatype.com 에서 mybatis-spring 검색
=>
ㄴ mybatis-spring 3.0.2 버전 Gradle(short) 버전 코드를 [Copy to clipboard] 선택하여 복사하기
=>
build.gradle (:app-server)
ㄴ 복사해둔 코드 붙여넣기
=>
ㄴ 코끼리 아이콘 선택하여 Gralde reload 해주기
=>
ㄴ Project 선택
=>
ㄴ External Libraries > 라이브러리가 추가됨을 확인
AppConfig.java
ㄴ 해당 코드 복사하여 주석 처리 해두기
=>
https://mybatis.org/spring/getting-started
=>
ㄴ 해당 코드 이용하기
=>
AppConfig.java
ㄴ SqlSessionFactory를 Spring 빈으로 구성하도록 함
=>
ㄴ DriverManagerDataSource 설정 확인
ㄴ spring-jdbc 라이브러리 검색
=>
ㄴ spring-jdbc 6.0.11 버전 Gradle (short) 타입의 코드를 [Copy to clipboard] 선택하여 복사하기
=>
build.gradle
ㄴ 복사한 코드 붙여넣기
=>
ㄴ 코끼리 아이콘 선택하여 리로드
=>
ㄴ spring-jdbc 라이브러리 추가된 것을 확인
=>
AppConfig.java
ㄴ Spring의 JavaConfig 방식을 사용하고 있으며, @Bean 어노테이션을 통해 빈을 정의
ㄴ SqlSessionFactory 빈을 설정하는 부분에서 DataSource를 매개변수로 받도록 함
ㄴ SqlSessionFactoryBean을 생성할 때 필요한 데이터 소스를 주입받기 위함
ㄴ DataSource 빈도 정의
ㄴ DriverManagerDataSource를 사용하여 데이터 소스를 설정하고 반환하도록 함
ㄴ 실제 데이터베이스 연결 정보를 설정하는 부분
=>
AppConfig.java
ㄴ @PropertySource 애노테이션을 이용하여 bitcamp/report/config/ncloud/jdbc.properties 클래스 패스 상에 위치한 jdbc.properties 파일을 로드하도록 함
=>
AppConfig.java
=>
ㄴ 이전에 찾아두었던 문서 참고
=>
AppConfig.java
ㄴ @Value 어노테이션을 사용하여 jdbc.driver, jdbc.url, jdbc.username, jdbc.password 프로퍼티 값을 주입하도록 함
=>
AppConfig.java
=>
AppConfig.java
ㄴ 주입받은 프로퍼티 값을 이용하여 DataSource 빈을 구성하고 반환하도록 함
ㄴ 프로퍼티 파일에서 읽은 값으로 데이터베이스 연결을 설정
=>
AppConfig.java
ㄴ dataSource -> ds 로 변수명 변경
=>
mybatis-config.xml
ㄴ 해당 코드 제거
https://mybatis.org/spring/transactions.html
AppConfig.java
ㄴ AppConfig 의 dataSource() 메서드가 호출될 경우 해당 문구를 출력하도록 함
=>
AppConfig.java
ㄴ 트랜잭션 관리 설정 추가
=>
mybatis-config.xml
ㄴ 해당 코드 제거
=>
mybatis-config.xml
ㄴ 해당 설정을 AppConfig 에서 해주도록 함
=>
AppConfig.java
=>
mybatis-config.xml
ㄴ 해당 코드 제거
=>
AppConfig.java
ㄴ "bitcamp.report.dao.mysql" 패키지 내에서 마이바티스 매퍼 인터페이스를 검색하고 스프링 빈으로 등록하도록 함
ㄴ @MapperScan 어노테이션은 마이바티스(MyBatis)와 관련이 있으며, 마이바티스 매퍼 인터페이스를 검색하고 이를 스프링 빈으로 등록하는 데 사용함
=>
mybatis-config.xml
ㄴ 해당 코드 제거
=>
ㄴ mybatis-config.xml 파일은 이제 필요 없으므로 삭제
=>
listener/MyServletRequestListener.java
ㄴ 해당 코드 주석 처리 해보기
=>
ㄴ App 실행하기
=>
ㄴ 로그인 선택
=>
ㄴ [로그인] 선택
=>
ㄴ 오류 발생
=>
AppConfig.java
ㄴ 일반적으로 스프링 애플리케이션에서는 PlatformTransactionManager를 사용하는 것이 더 일반적이며, 스프링의 다양한 트랜잭션 관리 전략과 더 쉽게 통합할 수 있음
ㄴ 그래서 일반적으로 PlatformTransactionManager 인터페이스를 사용하고 그에 해당하는 구현체인 DataSourceTransactionManager를 반환하는 것이 권장됨
=>
AppConfig.java
ㄴ bitcamp.report.dao.mysql -> bitcamp.report.dao
=>
AppConfig.java
ㄴ MyBatis 매퍼 XML 파일의 위치를 설정(이 위치는 매퍼 인터페이스와 SQL 매핑 파일을 찾기 위해 사용)
MySQLBoardDao.java
ㄴ @Component 애노테이션 주석 처리 해보기
=>
MySQLMemberDao.java
ㄴ @Component 애노테이션 주석 처리 해보기
=>
DispatcherServlet.java
ㄴ 해당 코드 제거
=>
App 재실행
=>
=>
AppConfig.java
ㄴ 매퍼 XML 파일이 여러 개인 경우에는 setMapperLocations 메서드를 사용하여 배열 또는 리스트로 여러 리소스를 지정할 수 있음
ㄴ Resource 객체들을 배열 또는 리스트로 생성하고 setMapperLocations를 통해 설정할 수 있음
=>
ㄴ App 재실행
=>
=>
=>
ㄴ 로그인 정상적으로 실행됨
=>
ㄴ 게시물 목록도 정상적으로 출력됨을 확인
=>
AppConfig.java
ㄴ 이렇게 배열로 지정할 수 있음
=>
AppConfig.java
ㄴ 이렇게 배열로 지정할 수 있음
=>
AppConfig.java
ㄴ getResources 를 이용해 여러 개의 XxxDao.xml 을 받을 수 있음
=>
ㄴ App 재실행
=>
ㄴ 게시글 목록 정상적으로 출력됨을 확인
=>
=>
ㄴ 로그인 정상적으로 실행됨을 확인
=>
ㄴ 로그아웃도 정상적으로 실행됨을 확인
=>
ㄴ 회원 목록도 정상적으로 출력됨을 확인
build.gradle(:app-common)
ㄴ app-common 의 build.gradle 스크립트 파일에서도 해당 라이브러리 추가해주도록 함
=>
ㄴ 리로드 해주기
BoardDao.xml
ㄴ 해당 쿼리 확인
=>
app-common/src/main/java/bitcamp/report/dao/BoardDao.java
ㄴ category와 no 두 개의 파라미터를 받으므로 @Param 애노테이션 이용하기
ㄴ SQL 쿼리에서 파라미터를 사용할 때 파라미터 이름을 사용하게 되는데, 이 이름을 지정하지 않으면 자동으로 파라미터의 이름을 사용함
ㄴ category 파라미터를 SQL에서 categoryNo라는 이름으로 사용하도록 함
ㄴ no 파라미터를 SQL에서 boardNo라는 이름으로 사용하도록 함
=>
BoardDao.xml
ㄴ 해당 코드 제거
ㄴ 이제 map 타입으로 받지 않음
=>
MemberDao.java
ㄴ 해당 쿼리 확인
=>
app-common/src/main/java/bitcamp/report/dao/MemberDao.java
ㄴ phone과 password 두 개의 파라미터를 받으므로 @Param 애노테이션 이용하기
ㄴ SQL 쿼리에서 파라미터를 사용할 때 파라미터 이름을 사용하게 되는데, 이 이름을 지정하지 않으면 자동으로 파라미터의 이름을 사용함
ㄴ phone 파라미터를 SQL에서 phone 라는 이름으로 사용하도록 함
ㄴ password 파라미터를 SQL에서 password 라는 이름으로 사용하도록 함
=>
MemberDao.xml
ㄴ 해당 코드 제거
ㄴ 이제 member 타입으로 받지 않음
ㄴ dao 패키지 자체를 삭제
LoginController.java
ㄴ 해당 코드 대신 아래와 같이 설정
=>
LoginController.java
ㄴ Member 객체를 받지 말고 phone 과 password 2개를 받도록 함
=>
LoginController.java
=>
ㄴ Member 객체가 아닌 2개의 값 phone, password 를 findByPhoneAndPassword 메서드에 넘겨주도록 함
=>
ㄴ App 재실행
=>
=>
=>
ㄴ 로그인 정상적으로 실행됨을 확인
=>
ㄴ 게시글 목록이 정상적으로 출력됨을 확인
=>
ㄴ 게시글 상세보기가 정상적으로 출력됨
ㄴ 해당 패키지에 있는 SqlSessionFactoryProxy.java 가 없어도 잘 실행되므로 해당 파일 삭제
BoardAddController.java
ㄴ SqlSessionFactor 대신 PlatformTransactionManager 이용하기
=>
ㄴ PlatformTransactionManager 이용 하기
ㄴ 해당 코드 복사
=>
BoardAddController.java
ㄴ 복사한 코드 붙여넣기
=>
BoardAddController.java
ㄴ 이름 수정하기
=>
BoardAddController.java
ㄴ sqlSessionFactory.openSession(false).commit();
return "redirect:list?category=" + request.getParameter("category");
이 코드 대신 위와 같이 TransactionManager 이용하기
=>
BoardAddController.java
ㄴ sqlSessionFactory.openSession(false).rollback(); 대신 txManager.rollback(status); 이용하기 (TransactionManager 이용하기)
=>
App 재실행
=>
=>
ㄴ 로그인 정상적으로 실행됨을 확인
=>
ㄴ 게시글 목록이 정상적으로 출력됨을 확인
=>
ㄴ 새 글 선택하여 내용 입력 후 [등록] 선택
=>
ㄴ 게시글이 정상적으로 등록됨을 확인
=>
=>
=>
ㄴ 크롬이 아닌 다른 브라우저에서 띄웠을 때(세션, 쿠키 다름)에도 등록된 게시글이 정상적으로 출력됨을 확인
다른 Controller 도 같은 방식으로 변경해주기
BoardDetailController.java
package bitcamp.report.controller;
import bitcamp.report.dao.BoardDao;
import bitcamp.report.vo.Board;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component("/board/detail")
public class BoardDetailController implements PageController {
BoardDao boardDao;
PlatformTransactionManager txManager;
public BoardDetailController(BoardDao boardDao, PlatformTransactionManager txManager) {
this.boardDao = boardDao;
this.txManager = txManager;
}
@Override
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("tx1");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
int category = Integer.parseInt(request.getParameter("category"));
int no = Integer.parseInt(request.getParameter("no"));
Board board = boardDao.findBy(category, no);
if (board != null) {
board.setViewCount(board.getViewCount() + 1);
boardDao.updateCount(board);
txManager.commit(status);
request.setAttribute("board", board);
}
return "/WEB-INF/jsp/board/detail.jsp";
} catch (Exception e) {
txManager.rollback(status);
request.setAttribute("refresh", "2;url=/board/list?category=" + request.getParameter("category"));
throw e;
}
}
}
BoardUpdateController.java
package bitcamp.report.controller;
import bitcamp.report.dao.BoardDao;
import bitcamp.report.service.NcpObjectStorageService;
import bitcamp.report.vo.AttachedFile;
import bitcamp.report.vo.Board;
import bitcamp.report.vo.Member;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.util.ArrayList;
@Component("/board/update")
public class BoardUpdateController implements PageController {
BoardDao boardDao;
PlatformTransactionManager txManager;
NcpObjectStorageService ncpObjectStorageService;
public BoardUpdateController(BoardDao boardDao, PlatformTransactionManager txManager, NcpObjectStorageService ncpObjectStorageService) {
this.boardDao = boardDao;
this.txManager = txManager;
this.ncpObjectStorageService = ncpObjectStorageService;
}
@Override
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
Member loginUser = (Member) request.getSession().getAttribute("loginUser");
if (loginUser == null) {
request.getParts(); // 일단 클라이언트가 보낸 파일을 읽는다. 그래야 응답 가능!
return "redirect:../auth/login";
}
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("tx1");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
Board board = new Board();
board.setWriter(loginUser);
board.setNo(Integer.parseInt(request.getParameter("no")));
board.setTitle(request.getParameter("title"));
board.setContent(request.getParameter("content"));
board.setCategory(Integer.parseInt(request.getParameter("category")));
ArrayList<AttachedFile> attachedFiles = new ArrayList<>();
for (Part part : request.getParts()) {
if (part.getName().equals("files") && part.getSize() > 0) {
String uploadFileUrl = ncpObjectStorageService.uploadFile(
"bitcamp-nc7-bucket-25", "board/", part);
AttachedFile attachedFile = new AttachedFile();
attachedFile.setFilePath(uploadFileUrl);
attachedFiles.add(attachedFile);
}
}
board.setAttachedFiles(attachedFiles);
if (boardDao.update(board) == 0) {
throw new Exception("게시글이 없거나 변경 권한이 없습니다.");
} else {
if (attachedFiles.size() > 0) {
boardDao.insertFiles(board);
}
txManager.commit(status);
return "redirect:list?category=" + request.getParameter("category");
}
} catch (Exception e) {
txManager.rollback(status);
request.setAttribute("refresh", "2;url=detail?category=" + request.getParameter("category") +
"&no=" + request.getParameter("no"));
throw e;
}
}
}
BoardDeleteController.java
package bitcamp.report.controller;
import bitcamp.report.dao.BoardDao;
import bitcamp.report.vo.Board;
import bitcamp.report.vo.Member;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component("/board/delete")
public class BoardDeleteController implements PageController {
BoardDao boardDao;
PlatformTransactionManager txManager;
public BoardDeleteController(BoardDao boardDao, PlatformTransactionManager txManager) {
this.boardDao = boardDao;
this.txManager = txManager;
}
@Override
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
Member loginUser = (Member) request.getSession().getAttribute("loginUser");
if (loginUser == null) {
return "redirect:../auth/login";
}
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("tx1");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
Board b = new Board();
b.setNo(Integer.parseInt(request.getParameter("no")));
b.setWriter(loginUser);
b.setCategory(Integer.parseInt(request.getParameter("category")));
boardDao.deleteFiles(b.getNo());
if (boardDao.delete(b) == 0) {
throw new Exception("해당 번호의 게시글이 없거나 삭제 권한이 없습니다.");
} else {
txManager.commit(status);
return "redirect:list?category=" + request.getParameter("category");
}
} catch (Exception e) {
txManager.rollback(status);
request.setAttribute("refresh", "2;url=list?category=" + request.getParameter("category"));
throw e;
}
}
}
BoardFileDeleteController.java
package bitcamp.report.controller;
import bitcamp.report.dao.BoardDao;
import bitcamp.report.vo.AttachedFile;
import bitcamp.report.vo.Board;
import bitcamp.report.vo.Member;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component("/board/fileDelete")
public class BoardFileDeleteController implements PageController {
BoardDao boardDao;
PlatformTransactionManager txManager;
public BoardFileDeleteController(BoardDao boardDao, PlatformTransactionManager txManager) {
this.boardDao = boardDao;
this.txManager = txManager;
}
@Override
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
Member loginUser = (Member) request.getSession().getAttribute("loginUser");
if (loginUser == null) {
return "redirect:../auth/login";
}
Board board = null;
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("tx1");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
int category = Integer.parseInt(request.getParameter("category"));
int fileNo = Integer.parseInt(request.getParameter("no"));
AttachedFile attachedFile = boardDao.findFileBy(fileNo);
board = boardDao.findBy(category, attachedFile.getBoardNo());
if (board.getWriter().getNo() != loginUser.getNo()) {
throw new Exception("게시글 변경 권한이 없습니다!");
}
if (boardDao.deleteFile(fileNo) == 0) {
throw new Exception("해당 번호의 첨부파일이 없거나 삭제 권한이 없습니다.");
} else {
txManager.commit(status);
return "redirect:detail?category=" + category + "&no=" + board.getNo();
}
} catch (Exception e) {
txManager.rollback(status);
request.setAttribute("refresh", "2;url=detail?category=" + request.getParameter("category") +
"&no=" + board.getNo());
throw e;
}
}
}
MemberAddController.java
package bitcamp.report.controller;
import bitcamp.report.dao.MemberDao;
import bitcamp.report.service.NcpObjectStorageService;
import bitcamp.report.vo.Member;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
@Component("/member/add")
public class MemberAddController implements PageController {
MemberDao memberDao;
PlatformTransactionManager txManager;
NcpObjectStorageService ncpObjectStorageService;
public MemberAddController(MemberDao memberDao, PlatformTransactionManager txManager, NcpObjectStorageService ncpObjectStorageService) {
this.memberDao = memberDao;
this.txManager = txManager;
this.ncpObjectStorageService = ncpObjectStorageService;
}
@Override
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (request.getMethod().equals("GET")) {
return "/WEB-INF/jsp/member/form.jsp";
}
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("tx1");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
Member m = new Member();
m.setName(request.getParameter("name"));
m.setPhone(request.getParameter("phone"));
m.setPassword(request.getParameter("password"));
m.setPosition(request.getParameter("position").charAt(0));
Part photoPart = request.getPart("photo");
if (photoPart.getSize() > 0) {
String uploadFileUrl = ncpObjectStorageService.uploadFile(
"bitcamp-nc7-bucket-25", "member/", photoPart);
m.setPhoto(uploadFileUrl);
}
memberDao.insert(m);
txManager.commit(status);
return "redirect:list";
} catch (Exception e) {
txManager.rollback(status);
request.setAttribute("message", "회원 등록 오류!");
request.setAttribute("refresh", "2;url=list");
throw e;
}
}
}
MemberDeleteController.java
package bitcamp.report.controller;
import bitcamp.report.dao.MemberDao;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component("/member/delete")
public class MemberDeleteController implements PageController {
MemberDao memberDao;
PlatformTransactionManager txManager;
public MemberDeleteController(MemberDao memberDao, PlatformTransactionManager txManager) {
this.memberDao = memberDao;
this.txManager = txManager;
}
@Override
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("tx1");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
if (memberDao.delete(Integer.parseInt(request.getParameter("no"))) == 0) {
throw new Exception("해당 번호의 회원이 없습니다!");
} else {
txManager.commit(status);
return "redirect:list";
}
} catch (Exception e) {
txManager.rollback(status);
request.setAttribute("refresh", "2;url=list");
throw e;
}
}
}
MemberUpdateController.java
package bitcamp.report.controller;
import bitcamp.report.dao.MemberDao;
import bitcamp.report.service.NcpObjectStorageService;
import bitcamp.report.vo.Member;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
@Component("/member/update")
public class MemberUpdateController implements PageController {
MemberDao memberDao;
PlatformTransactionManager txManager;
NcpObjectStorageService ncpObjectStorageService;
public MemberUpdateController(MemberDao memberDao, PlatformTransactionManager txManager, NcpObjectStorageService ncpObjectStorageService) {
this.memberDao = memberDao;
this.txManager = txManager;
this.ncpObjectStorageService = ncpObjectStorageService;
}
@Override
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("tx1");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
Member member = new Member();
member.setNo(Integer.parseInt(request.getParameter("no")));
member.setName(request.getParameter("name"));
member.setPhone(request.getParameter("phone"));
member.setPassword(request.getParameter("password"));
member.setPosition(request.getParameter("position").charAt(0));
Part photoPart = request.getPart("photo");
if (photoPart.getSize() > 0) {
String uploadFileUrl = ncpObjectStorageService
.uploadFile("bitcamp-nc7-bucket-25", "member/", photoPart);
member.setPhoto(uploadFileUrl);
}
if (memberDao.update(member) == 0) {
throw new Exception("회원이 없습니다.");
} else {
txManager.commit(status);
return "redirect:list";
}
} catch (Exception e) {
txManager.rollback(status);
request.setAttribute("refresh", "2;url=list");
throw e;
}
}
}
AppConfig.java
ㄴ 해당 코드 제거
=>
=>
ㄴ 로그인 성공적으로 실행됨을 확인
=>
ㄴ 회원목록이 정상적으로 출력됨을 확인
=>
=>
=>
오류 발생
=>
member/detail.jsp
ㄴ 회원 상세보기 오류 해결
=>
ㄴ 회원이 정상적으로 출력됨을 확인
ㄴ 게시글 목록이 정상적으로 출력됨을 확인
=>
=>
ㄴ 게시글이 정상적으로 등록됨을 확인
Logging 설정
https://mybatis.org/mybatis-3/logging.html
ㄴ Logging 설정 방법 확인
ㄴ 해당 코드 복사하기
=>
AppConfig.java
ㄴ 복사한 코드 붙여넣기
ㄴ 해당 코드 추가하지 않아도 됨 (log4j2.xml 파일 추가 시 해당 설정 필요 없음)
https://central.sonatype.com/artifact/org.apache.logging.log4j/log4j-core/2.20.0
=>
ㄴ 2.20.0 버전 선택
=>
ㄴ [Copy to clipboard] 선택
=>
bulid.gradle (:app-server)
ㄴ build.gradle 스크립트 파일의 dependencies 에 Log4j 2.x 버전 라이브러리 추가
=>
ㄴ 코끼리 아이콘 선택하여 gradle build 하기
=>
=>
ㄴ External Libraries 선택하여 log4j 2.x 버전 라이브러리 추가됨을 확인
=>
=>
ㄴ resources 폴더에 log4j2.xml 파일 추가
=>
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<!-- 로그 출력 형태를 정의한다. -->
<Appenders>
<!-- 표준 출력 장치인 콘솔로 출력하는 방식을 정의한다. -->
<Console name="stdout" target="SYSTEM_OUT">
<PatternLayout pattern="[%-5level] %d{yyyy-MM-dd} [%t] %c{1} - %msg%n" />
</Console>
<!--
<File name="file" fileName="./logs/file/sample.log" append="false">
<PatternLayout pattern="[%-5level] %d{yyyy-MM-dd} [%t] %c{1} - %msg%n" />
</File>
-->
</Appenders>
<!-- 로그 출력을 적용할 대상과 로그 출력 레벨을 지정한다. -->
<Loggers>
<Logger name="bitcamp.report.dao" level="debug" additivity="false">
<AppenderRef ref="stdout" />
</Logger>
<Logger name="bitcamp.report" level="debug" additivity="false">
<AppenderRef ref="stdout" />
</Logger>
<!-- Root => 모든 대상에 적용할 기본 로그 출력 형식과 레벨 -->
<Root level="warn" additivity="false">
<AppenderRef ref="stdout" /> <!-- 로그를 출력할 때 사용할 출력 방식 지정 -->
</Root>
</Loggers>
</Configuration>
=>
App 실행
=>
ㄴ [Clear All] 선택
=>
=>
=>
ㄴ 로그가 정상적으로 출력됨을 확인
=>
servlet/DispatcherServlet.java
ㄴ Spring IoC 컨테이너에서 등록된 모든 빈의 이름과 클래스 이름을 출력해보기
=>
App 실행
=>
=>
servlet/DispatcherServlet.java
ㄴ 주석 처리해주기