728x90
320x100
이 시스템의 핵심 기술인 대여와 반납기능을 구현해보자!
[개발목표]
- 책 대여 / 반납기능 구현
1. 책 대여 / 반납 구현
book.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{common/layout}"
layout:fragment="content">
<head>
<title>책 정보</title>
<script th:src="@{/js/book/book.js}"></script>
</head>
<body>
<div class="container" style="max-width: 800px">
<div class="py-5 text-center">
<h2>책 정보</h2>
</div>
<h2 th:if="${param.status}" th:text="'등록완료!'" style="text-align: center;"></h2>
<div>
<label for="bookId">책 ID</label>
<input type="text" id="bookId" name="bookId" class="form-control"
value="Testid1" th:value="${book.bookId}" readonly>
</div>
<div>
<label for="bookName">책이름</label>
<input type="text" id="bookName" name="bookName" class="form-control"
value="1111" th:value="${book.bookName}" readonly>
</div>
<div>
<label for="bookWriter">저자</label>
<input type="text" id="bookWriter" name="bookWriter" class="form-control"
value="1111" th:value="${book.bookWriter}" readonly>
</div>
<div>
<label for="authorName">소유자</label>
<input type="text" id="authorName" name="authorName" class="form-control"
value="회원1" th:value="${book.authorName}" readonly>
</div>
<div>
<label for="bookStateCode">상태</label>
<input type="text" id="bookStateCode" name="bookStateCode" class="form-control"
value="회원1" th:value="${book.bookStateCodeName}" readonly>
</div>
<div>
<label for="createDate">등록일</label>
<input type="text" id="createDate" name="createDate" class="form-control"
value="회원1" th:value="${book.createDate}" readonly>
</div>
<hr class="my-4">
<div style="float: right;">
<th:block th:if="${loginId} == ${book.authorId}">
<button class="btn btn-primary btn-lg"
onclick="location.href='editMemberForm.html'"
th:onclick="|location.href='@{/book/{bookId}/edit(bookId=${book.bookId})}'|" type="button">책 정보 수정</button>
<button class="btn btn-primary btn-lg" id="deleteBtn" type="button">삭제</button>
</th:block>
<button th:if="${loginId} != ${book.authorId}
and ${book.bookStateCode} == 'ABLE'" class="btn btn-primary btn-lg" type="button" id="rentalBtn">대여하기</button>
<button th:if="${loginId} != ${book.authorId}
and ${book.bookStateCode} == 'UNABLE'
and ${book.bookRentalId} == ${loginId}" class="btn btn-primary btn-lg" type="button" id="returnBtn">반납하기</button>
<button class="btn btn-secondary btn-lg"
onclick="location.href='members.html'"
th:onclick="|location.href='@{/book}'|" type="button">목록으로</button>
</div>
</div> <!-- /container -->
</body>
</html>
버튼 부분을 많이 수정했다. 버튼은 아래의 조건을 충족해야 했다.
- 일단 본인이 등록한 책인 경우 [대여하기] 버튼이 보이지 않아야 하고
- 본인이 등록한 책인 경우 [책 정보수정] [책 삭제] 버튼이 보여야 한다.
- 본인이 등록한 책이 아닌데 '대여가능' 상태라면 [대여하기] 버튼이 보여야 하고, '대여불가능'상태라면 [대여하기] 버튼이 숨겨져야 한다.
- 본인이 등록한 책이 아닌데 본인이 빌렸다면 [반납하기] 버튼이 보여야 한다.
이 부분을 타임리프 If문을 사용해서 했는데 머리 터질뻔; 거기다가 디자인이 자꾸 깨져서 그부분 때문에 고생을 많이했다.
bookMapper.xml
<insert id="insertRental" parameterType="seulgi.bookRentalSystem.domain.book.BookRental">
INSERT INTO BOOK_RENTAL
( BOOK_ID
, BOOK_RENTAL_ID
, BOOK_STATE_CODE
, RENTAL_DATE
) VALUE (
#{bookRental.bookId}
, #{bookRental.bookRentalId}
, #{bookRental.bookStateCode}
, now()
)
</insert>
<update id="returnBook" parameterType="seulgi.bookRentalSystem.domain.book.BookRental">
UPDATE BOOK_RENTAL BR1
JOIN (
SELECT MAX(BR2.BOOK_RENTAL_ID) AS MAX_BOOK_RENTAL_ID
FROM BOOK_RENTAL BR2
WHERE BR2.BOOK_ID = #{bookRental.bookId}
) MAX_BR ON BR1.BOOK_RENTAL_ID = MAX_BR.MAX_BOOK_RENTAL_ID
SET BR1.RETURN_DATE = NOW()
,BR1.BOOK_STATE_CODE = #{bookRental.bookStateCode}
WHERE BR1.BOOK_ID = #{bookRental.bookId}
</update>
<update id="updateBookState" parameterType="seulgi.bookRentalSystem.domain.book.Book">
UPDATE BOOK
SET BOOK_STATE_CODE = #{book.bookStateCode}
WHERE BOOK_ID = #{book.bookId}
AND USE_AT = 'Y'
</update>
- insertRental (대여기능): 대여할 때는 무조건 새로운 row를 추가해야 하기 때문에 INSERT문 사용.
- returnBook (반납기능) : 반납할 때는 대여할 때 생성된 row에서 RETURN_DATE 컬럼에만 현재 시간을 수정해주고, 대여상태를 반납으로 바꿔준다.
- (+) 근데 지금 생각해보니, RENTAL_DATE만 있으면 대여중인거고, RETURN_DATE 둘다 있으면 반납중인걸로 구분해도 됐을텐데 하는 생각이 들긴 함. 그러면 테이블 하나 없어질수 있고....
- updateBookState(책 상태변경) : 대여 / 반납 할 때 상태에 따라 BOOK 테이블의 책 상태를 '대여가능', '불가능'으로 바꾸어주는 용도로 만들었다.
bookMapper.java
package seulgi.bookRentalSystem.domain.book;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface BookMapper {
//// 생략 ////
void insertRental(@Param("bookRental") BookRental bookRental);
void updateBookState(@Param("book") Book book);
void returnBook(@Param("bookRental") BookRental bookRental);
//// 생략 ////
}
bookService, bookserviceImpl.java
package seulgi.bookRentalSystem.domain.book;
import java.util.List;
public interface Bookservice {
//// 생략 ////
void insertRental (BookRental bookRental);
void updateBookState (Book book);
void returnBook (BookRental bookRental);
//// 생략 ////
}
bookController.java
/**
* 책 대여
* @param bookId
* @param request
* @return
*/
@PostMapping("/{bookId}/rental")
public ResponseEntity<String> rentalBook (@PathVariable String bookId, HttpServletRequest request){
String loginId = (String) request.getSession().getAttribute("loginId") ;
BookRental bookRental = new BookRental();
bookRental.setBookId(bookId);
bookRental.setBookRentalId(loginId);
bookRental.setBookStateCode("RENTAL");
bookService.insertRental(bookRental);
Book book = new Book();
book.setBookId(bookId);
book.setBookStateCode("UNABLE");
bookService.updateBookState(book);
return ResponseEntity.ok("대여완료");
}
/**
* 책 반납
* @param bookId
* @param request
* @return
*/
@PostMapping("/{bookId}/return")
public ResponseEntity<String> returnBook(@PathVariable String bookId, HttpServletRequest request){
String loginId = (String) request.getSession().getAttribute("loginId");
BookRental bookRental = new BookRental();
bookRental.setBookId(bookId);
bookRental.setBookRentalId(loginId);
bookRental.setBookStateCode("RETURN");
bookService.returnBook(bookRental);
Book book = new Book();
book.setBookId(bookId);
book.setBookStateCode("ABLE");
bookService.updateBookState(book);
return ResponseEntity.ok("반납 완료");
}
- bookRental, book 객체를 각자 생성하여 bookService 2개를 돌렸다. 그대신 컨트롤러에 @Transactional 어노테이션을 추가하여 둘중에 하나라도 오류나면 롤백되도록 했다.
- 대여와 반납 컨트롤러는 거의 흡사한데 넘겨주는 책 상태코드와 대여코드만 다르다.
Book.js
function rentalBtn(){
let bookId = document.getElementById("bookId").value;
let loginId = document.getElementById("headerLoginId").textContent;
if (loginId === ''){
alert("로그인이 필요한 서비스입니다.");
window.location.href = "/login";
} else {
if (confirm("책을 대여 하시겠습니까?")){
fetch("/book/" + bookId + "/rental", {
method : "POST"
})
.then(response => {
if (response.ok) {
window.location.href = "/book/" + bookId;
alert("대여가 완료되었습니다.");
} else {
console.error("대여 실패");
}
})
.catch(error => {
console.error("Error : ", error);
});
}
}
}
function returnBtn(){
let bookId = document.getElementById("bookId").value;
if (confirm("책을 반납 하시겠습니까?")){
fetch("/book/" + bookId + "/return", {
method : "POST"
})
.then(response => {
if (response.ok){
window.location.href = "/book/" + bookId;
alert("반납이 완료되었습니다.");
} else {
console.log("반납 실패");
}
})
.catch(error => {console.error("Error : ", error)})
}
}
document.addEventListener('DOMContentLoaded', function (){
let rentalButton = document.getElementById("rentalBtn");
let returnButton = document.getElementById("returnBtn");
if (rentalButton){
document.getElementById("rentalBtn").addEventListener('click', rentalBtn);
}
if (returnButton) {
document.getElementById("returnBtn").addEventListener('click', returnBtn);
}
})
- 대여 / 반납 / 삭제 / 수정 기능이 혼재하다보니 복잡해보여서 이번 개발일지에서는 반납 / 대여 기능만 톧아보기로 한다.
1) 대여기능
function rentalBtn(){
let bookId = document.getElementById("bookId").value;
let loginId = document.getElementById("headerLoginId").textContent;
if (loginId === ''){
alert("로그인이 필요한 서비스입니다.");
window.location.href = "/login";
} else {
if (confirm("책을 대여 하시겠습니까?")){
fetch("/book/" + bookId + "/rental", {
method : "POST"
})
.then(response => {
if (response.ok) {
window.location.href = "/book/" + bookId;
alert("대여가 완료되었습니다.");
} else {
console.error("대여 실패");
}
})
.catch(error => {
console.error("Error : ", error);
});
}
}
}
- 역시나 alert() 를 사용하고 싶어서 자바스크립트로 해당기능을 뺐다.
- 비동기로 컨트롤러에 접근하였고 먼저 로그인 여부를 확인(+ 회원가입을 유도) 하며, 로그인을 한 사람일 경우 대여가 완료되면 대여가 완료되었다는 alert을 한번 더 띄워준다.
2) 반납기능
function returnBtn(){
let bookId = document.getElementById("bookId").value;
if (confirm("책을 반납 하시겠습니까?")){
fetch("/book/" + bookId + "/return", {
method : "POST"
})
.then(response => {
if (response.ok){
window.location.href = "/book/" + bookId;
alert("반납이 완료되었습니다.");
} else {
console.log("반납 실패");
}
})
.catch(error => {console.error("Error : ", error)})
}
}
- 반납기능은 로그인여부를 체크하지 않고 대여 기능과 거의 비슷하게 구현했다.
(+) 처음에 아래 처럼 구현을 하니까 해당함수가 2번이 실행이 되는거임.
document.addEventListener('DOMContentLoaded', function (){
document.getElementById("rentalBtn").addEventListener('click', function (){ rentalBtn() });
})
근데 아래 처럼 하면 두번이 실행 안되더라.
document.getElementById("rentalBtn").addEventListener('click', rentalBtn);
아마 dom생성할 때 한번 그리고 클릭할 때 한번 이렇게 2번 실행되는게 아닌가 싶음.
(+) 대여하기 / 반납하기 / 삭제하기 버튼이 타임리프 자체에서 값에 따라 보이고 안보이기 때문에 어떤 경우에는 해당 요소 자체가 없을 때도 있다. 그래서 처음에 If문 없이 리스너를 넣으니 계속 에러가 남. GPT의 조언을 얻어 저렇게 바꾸긴 했는데 그리 깔끔하진 않아서 다른 방법이 없을까 고민중에 있다.
document.addEventListener('DOMContentLoaded', function (){
let rentalButton = document.getElementById("rentalBtn");
let returnButton = document.getElementById("returnBtn");
let deleteButton = document.getElementById("deleteBtn");
if (rentalButton){
document.getElementById("rentalBtn").addEventListener('click', rentalBtn);
}
if (returnButton) {
document.getElementById("returnBtn").addEventListener('click', returnBtn);
}
if (deleteButton) {
document.getElementById("deleteBtn").addEventListener('click', deleteBtn);
}
})
728x90
320x100
'💻 뚝딱뚝딱 > 팀내도서대여시스템(OBRS)' 카테고리의 다른 글
[개발일지#008] 나의책 / 빌린책 기능 구현 (0) | 2024.04.09 |
---|---|
[개발일지#007] 책 수정 / 삭제 기능 수정 및 구현 (2) | 2024.04.08 |
[개발일지#005] 데이터베이스 수정작업 그리고 그에 따른 XML 수정 (0) | 2024.04.06 |
[개발일지#004] 타임리프 레이아웃 적용 및 네비게이션 바 생성/디자인 및 구현 (0) | 2024.04.05 |
[개발일지#003] 책 등록 / 책 정보수정 / 책 목록조회 구현 (0) | 2024.04.02 |