0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Kohyoh

0
Posted at

スクリーンショット 2026-05-26 5.58.42.png

スクリーンショット 2026-05-26 5.58.57.png

package com.example.library.service;

import com.example.library.model.BookItem;
import com.example.library.model.BorrowRecord;
import com.example.library.model.User;
import com.example.library.repository.BookItemRepository;
import com.example.library.repository.BorrowRecordRepository;
import com.example.library.repository.UserRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

/**
 * 図書管理の主要なビジネスロジック(貸出・返却など)を担当するサービスクラス。
 */
@Service
public class LibraryService {

    private final BorrowRecordRepository borrowRecordRepository;
    private final BookItemRepository bookItemRepository;
    private final UserRepository userRepository;

    public LibraryService(BorrowRecordRepository borrowRecordRepository, 
                          BookItemRepository bookItemRepository, 
                          UserRepository userRepository) {
        this.borrowRecordRepository = borrowRecordRepository;
        this.bookItemRepository = bookItemRepository;
        this.userRepository = userRepository;
    }

    /**
     * 蔵書を借りるロジック
     * @param userHrid ユーザーID
     * @param serialNumber 蔵書のシリアルナンバー
     */
    @Transactional
    public BorrowRecord borrowBook(String userHrid, String serialNumber) {
        // 1. ユーザーと対象の書籍が存在するか確認する
        User user = userRepository.findById(userHrid)
            .orElseThrow(() -> new IllegalArgumentException("指定されたユーザーが存在しません。"));
        BookItem bookItem = bookItemRepository.findById(serialNumber)
            .orElseThrow(() -> new IllegalArgumentException("指定された書籍が存在しません。"));

        // 2. すでに他の利用者が借りていないか(貸出中ではないか)の確認
        // returnDateが未設定(null)のレコードが存在する場合、その本はまだ返却されていません。
        boolean isAlreadyBorrowed = borrowRecordRepository.findBySerialNumberAndReturnDateIsNull(serialNumber).isPresent();
        if (isAlreadyBorrowed) {
            throw new IllegalStateException("この書籍はすでに貸し出し中です。");
        }

        // 3. ユーザーの現在の貸出状況を取得
        List<BorrowRecord> currentBorrows = borrowRecordRepository.findByUserHridAndReturnDateIsNull(userHrid);

        // ビジネスルール: 1人の利用者が同時に借りられる冊数は5冊まで
        if (currentBorrows.size() >= 5) {
            throw new IllegalStateException("同時に借りられる書籍は最大5冊までです。");
        }

        // ビジネスルール: 1人の利用者に同書籍タイトルを同時に貸し出すことはできない
        String targetTitleId = bookItem.getBookTitle().getId();
        for (BorrowRecord record : currentBorrows) {
            // 現在貸出中のシリアルナンバーから書籍情報を取得し、タイトルIDを比較する
            BookItem borrowedItem = bookItemRepository.findById(record.getSerialNumber()).orElseThrow();
            if (borrowedItem.getBookTitle().getId().equals(targetTitleId)) {
                throw new IllegalStateException("すでに同じタイトルの書籍を借りています。");
            }
        }

        // 4. 貸出記録の作成と保存
        BorrowRecord newRecord = new BorrowRecord();
        newRecord.setUserHrid(user.getHrid());
        newRecord.setSerialNumber(bookItem.getSerialNumber());
        newRecord.setBorrowDate(LocalDateTime.now());
        // ビジネスルール: 貸し出された書籍は2週間後が返却期限となる
        newRecord.setDueDate(LocalDateTime.now().plusWeeks(2));
        // returnDateはセットしない(nullのまま)ことで「貸出中」を表現します

        return borrowRecordRepository.save(newRecord);
    }

    /**
     * 蔵書を返却するロジック(※要件に従い詳細な解説コメントを記載)
     * 
     * @param userHrid 返却操作を行うユーザーID
     * @param serialNumber 返却する書籍のシリアルナンバー
     */
    @Transactional
    public void returnBook(String userHrid, String serialNumber) {
        /*
         * 【返却ロジックの解説】
         * 
         * 1. 貸出中レコードの特定
         *    まず、指定されたユーザーが指定された書籍(シリアルナンバー)を「現在借りているか」を確認します。
         *    これを判定するためには、DBから「userHrid」と「serialNumber」が一致し、
         *    かつ「returnDate(返却日)がnull」であるBorrowRecord(貸出履歴レコード)を検索します。
         *    ※ returnDateがnullである = まだ返却されていない(現在貸出中である)ことを意味します。
         */
        BorrowRecord currentRecord = borrowRecordRepository.findBySerialNumberAndReturnDateIsNull(serialNumber)
            .orElseThrow(() -> new IllegalStateException("この書籍は現在貸し出しされていません。"));

        /*
         * 2. 操作権限(借りている本人か)の検証
         *    返却手続きは、その本を借りている本人(または管理者)しか行えないべきです。
         *    上記で取得した貸出中レコードのuserHridと、操作しているユーザーのuserHridが一致するかチェックします。
         */
        if (!currentRecord.getUserHrid().equals(userHrid)) {
            throw new IllegalStateException("この書籍は別の利用者が借りているため、返却できません。");
        }

        /*
         * 3. 返却日時の記録とステータス更新
         *    貸出中(未返却)であることを表していた `returnDate` に、現在日時をセットします。
         *    これにより、このレコードは「返却済み」扱いとなり、
         *    次回の `findBySerialNumberAndReturnDateIsNull` 検索にはヒットしなくなります。
         *    結果として、この書籍(シリアルナンバー)は他のユーザーが新たに借りられる状態(貸出可能)に戻ります。
         */
        currentRecord.setReturnDate(LocalDateTime.now());

        /*
         * 4. データベースへの保存
         *    更新したレコードをDBに保存(UPDATE)して返却処理を完了させます。
         *    ※ @Transactional アノテーションがついているため、途中でエラーが起きた場合は全ての変更がロールバックされ、
         *       データの不整合(返却日だけ入ってしまった等)を防ぐ安全な設計になっています。
         */
        borrowRecordRepository.save(currentRecord);
    }
}
1![スクリーンショット 2026-05-26 5.58.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4283200/9fd4e892-9074-4d76-b543-8a9f33ea6585.png)
![スクリーンショット 2026-05-26 5.58.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4283200/66f7babd-75ec-450a-9f21-88afe15a47b1.png)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?