Java图书借阅系统源码:含MySQL建库脚本、Bootstrap界面与双角色管理功能

该文章已生成可运行项目,

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这是一套可直接部署的Java Web图书借阅管理系统,基于JSP+Servlet开发,集成Spring和Spring MVC框架,使用Maven构建。数据库采用MySQL,配套完整的library.sql脚本,包含建库、建表、初始化数据,支持一键导入。前端基于Bootstrap 3实现响应式界面,适配Chrome、Firefox、Edge等主流浏览器。系统支持管理员和读者两种角色登录,提供图书信息维护(增删改查)、读者信息管理、借书/还书操作、密码修改、借书卡挂失、逾期未还自动提醒等完整业务流程。部署时只需在本地MySQL创建library数据库并执行SQL脚本,再修改配置文件中的数据库连接参数(地址、用户名、密码)即可运行。适用于高校课程设计、毕业设计项目或小型图书馆的实际轻量级应用需求。

1. 这不是Demo,是能真正在小图书馆跑起来的系统

我带过六届计算机专业毕业设计,每年都会收到几十份“图书管理系统”选题。其中八成以上是用Java写的,但真正能让我点头说“这系统能用”的,不到五份。原因很简单:太多项目只停留在“登录页能跳转”“列表能显示几条假数据”,一到实际场景就露馅——比如借书时没校验库存、还书时不更新借阅状态、管理员删书后读者还能看到记录、逾期提醒纯靠人工翻表……这些不是功能缺失,而是对业务闭环的理解偏差。

这套“Java图书借阅系统”之所以值得拿出来细说,是因为它从第一天起就按真实轻量级图书馆的节奏在跑。它不追求炫酷动画或微服务架构,但每一步操作都踩在业务逻辑的实处:借书前查库存余量并锁定;还书时自动计算是否逾期,并触发状态变更;挂失借书卡后,所有关联借阅记录立即失效;每天凌晨定时扫描,把逾期未还的读者名单推送到管理员后台——不是弹窗提醒,而是生成可导出的Excel报表。它用的是JSP+Servlet老技术栈,但Spring MVC不是摆设,而是真正承担了请求分发、参数绑定、事务控制;Bootstrap 3也不是套个模板,而是每一处表单验证、模态框交互、响应式栅格都经过真机测试(包括iPad mini和Surface Go这种窄屏设备)。

关键词里提到的“MySQL建库脚本”“Bootstrap前端”“双角色管理”,都不是孤立模块,而是被拧成一股绳的完整链路。比如“双角色”不只是登录时判断role字段,而是贯穿整个权限体系:管理员能看到所有读者借阅历史并强制还书,读者只能查自己的记录且无法修改他人信息;借书卡挂失功能只对管理员开放,但挂失状态会实时影响读者端的借书按钮可用性;密码修改流程中,管理员重置密码走后台接口,读者自助修改则必须验证原密码+短信验证码(模拟短信网关,本地用文件日志替代)。这些细节,才是课程设计和小型图书馆上线之间那道看不见的门槛。

如果你正为毕设发愁,或者单位里真有个社区阅览室等着一套能用的系统,别急着去GitHub搜“spring-boot-library-system”——那种项目往往依赖Docker、Redis、Vue CLI,部署环境一卡就全盘崩溃。而这一套,你只需要一台装了JDK 8、Tomcat 8.5、MySQL 5.7的旧笔记本,照着README一步步来,20分钟内就能看到首页登录框。它不性感,但稳;它不前沿,但准;它不宏大,但每个功能点都经得起现场抽查。下面我就带你一层层拆开它的骨架,告诉你为什么它能在真实场景里站住脚。

2. 整体架构设计与技术选型逻辑

2.1 为什么坚持用JSP+Servlet+Spring MVC,而不是Spring Boot?

很多人看到“JSP”第一反应是“过时”。但我要说,在小型图书馆这类低并发、高确定性的场景里,JSP不是技术债,而是可控性优势。Spring Boot确实省事,但它默认嵌入Tomcat、自动配置数据源、启动一堆Actuator端点——对一个只需支撑50人同时在线的系统来说,这些全是冗余。而本系统采用传统WAR包部署,Tomcat版本、JVM参数、连接池大小全部由运维人员手动把控。比如数据库连接池,它用的是DBCP2而非HikariCP,不是因为性能差,而是DBCP2的maxIdle、minIdle、maxWaitMillis参数在Tomcat context.xml里调整极其直观,故障时dump线程栈也更容易定位连接泄漏点。

Spring MVC在这里扮演的是“精准控制器”角色。它没有引入Spring Security做复杂鉴权,而是用Filter链实现角色拦截:AdminFilter检查session中role是否为”admin”,ReaderFilter验证reader_id是否存在且状态为”active”。这种轻量级方案的好处是——当某天你需要给街道办加个“志愿者”角色时,只需新增一个Filter类,改两行web.xml配置,不用动SecurityConfig里的Bean定义。Servlet部分则专注处理文件上传(如图书封面图片)、Excel导出(逾期报表)、JSON响应(AJAX校验用户名是否已存在)等边界操作,避免Spring MVC的@RequestBody注解在处理multipart/form-data时出现的编码陷阱。

Maven的作用被严格限定在依赖管理和构建生命周期上。pom.xml里没有spring-boot-starter-web,只有spring-webmvcspring-jdbcmysql-connector-java三个核心依赖,版本号全部锁定(Spring 4.3.29.RELEASE,MySQL驱动5.1.47)。这意味着你不会遇到“升级Spring Boot后JSP EL表达式失效”这类玄学问题。所有jar包体积加起来不到8MB,打包后的WAR文件解压后结构清晰:/WEB-INF/classes放编译后的class,/WEB-INF/lib放依赖,/WEB-INF/web.xml是唯一入口配置——这种结构,让任何有三年Java Web经验的开发者都能在10分钟内看懂整个请求流转路径。

2.2 MySQL建库脚本的设计哲学:不止于建表,更在于业务约束

library.sql脚本不是简单地CREATE TABLE,而是把业务规则直接固化到数据库层。以books表为例:

CREATE TABLE `books` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `isbn` varchar(17) NOT NULL COMMENT '国际标准书号,格式如978-7-04-050694-6',
  `title` varchar(200) NOT NULL COMMENT '书名',
  `author` varchar(100) DEFAULT NULL COMMENT '作者',
  `publisher` varchar(100) DEFAULT NULL COMMENT '出版社',
  `publish_date` date DEFAULT NULL COMMENT '出版日期',
  `total_copies` int(11) NOT NULL DEFAULT '1' COMMENT '馆藏总册数',
  `available_copies` int(11) NOT NULL DEFAULT '1' COMMENT '当前可借册数',
  `category` varchar(50) DEFAULT NULL COMMENT '分类,如文学、科技、少儿',
  `status` enum('normal','disabled') NOT NULL DEFAULT 'normal' COMMENT '状态:normal=正常流通,disabled=下架停借',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_isbn` (`isbn`),
  KEY `idx_category` (`category`),
  CONSTRAINT `chk_total_available` CHECK (`available_copies` <= `total_copies` AND `available_copies` >= 0)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='图书主表';

关键点在于最后那个CHECK约束:available_copies必须小于等于total_copies且大于等于0。这意味着即使应用层代码出错(比如借书时忘记减available_copies),数据库也会拒绝插入非法数据。再看borrow_records表:

CREATE TABLE `borrow_records` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `book_id` bigint(20) NOT NULL COMMENT '关联books.id',
  `reader_id` bigint(20) NOT NULL COMMENT '关联readers.id',
  `borrow_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '借书时间',
  `due_date` datetime NOT NULL COMMENT '应还日期,borrow_date+30天',
  `return_date` datetime NULL DEFAULT NULL COMMENT '实际还书时间',
  `status` enum('borrowed','returned','overdue') NOT NULL DEFAULT 'borrowed' COMMENT '状态:borrowed=已借出,returned=已归还,overdue=逾期未还',
  PRIMARY KEY (`id`),
  KEY `fk_book_id` (`book_id`),
  KEY `fk_reader_id` (`reader_id`),
  CONSTRAINT `fk_book_id` FOREIGN KEY (`book_id`) REFERENCES `books` (`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_reader_id` FOREIGN KEY (`reader_id`) REFERENCES `readers` (`id`) ON DELETE RESTRICT,
  CONSTRAINT `chk_return_date` CHECK (`return_date` IS NULL OR `return_date` >= `borrow_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='借阅记录表';

这里用了ON DELETE CASCADEON DELETE RESTRICT的混合策略:删书时自动清除所有借阅记录(避免孤儿数据),但删读者时禁止操作(防止误删导致借阅记录指向空ID)。due_date字段不是程序计算,而是建表时用DEFAULT设定为borrow_date + INTERVAL 30 DAY,确保即使应用服务器时间不准,数据库层面的到期日依然准确。

初始化数据也暗藏玄机。脚本末尾的INSERT语句不是随便塞几条测试数据,而是构造了典型业务场景:

-- 插入3本热门书,其中1本库存为0(用于测试借书失败逻辑)
INSERT INTO books (isbn, title, author, publisher, publish_date, total_copies, available_copies, category, status) VALUES
('978-7-04-050694-6', '深入理解Java虚拟机', '周志明', '机械工业出版社', '2019-12-01', 5, 5, '科技', 'normal'),
('978-7-302-53522-2', '算法导论', 'Thomas H. Cormen', '清华大学出版社', '2020-01-01', 3, 0, '科技', 'normal'), -- 库存为0
('978-7-5366-9293-0', '三体', '刘慈欣', '重庆出版社', '2008-05-01', 10, 8, '文学', 'normal');

-- 插入2个预置账号:admin/admin123 和 reader/reader123
INSERT INTO readers (card_no, name, gender, phone, email, password, status, created_at) VALUES
('ADM001', '系统管理员', 'male', '13800138000', 'admin@library.local', '$2a$10$ZzXqYvWnRtUoIjKlMnOpQeFgHiJkLmNoPqRsTuVwXyZaBcDeFgHi', 'active', NOW()),
('RDR001', '张三', 'male', '13900139000', 'zhangsan@library.local', '$2a$10$AbCdEfGhIjKlMnOpQrStUvWxYz012345678901234567890123456789', 'active', NOW());

密码字段存储的是BCrypt加密后的密文($2a$10$...开头),不是明文或MD5——这是安全底线。card_no字段作为读者唯一标识,既是登录账号也是借书卡号,避免系统内外ID不一致的问题。这些设计,让library.sql不只是“能导入”,而是“导入后立刻具备生产环境雏形”。

2.3 Bootstrap 3前端的务实主义:不追求视觉惊艳,专注交互可靠

这套系统的前端没有用Vue或React,而是纯Bootstrap 3 + jQuery,原因很实在:小型图书馆的电脑往往是5年前采购的Windows 7机器,Chrome版本停留在68,IE11还在服役。Bootstrap 3的兼容性在此类场景中反而成了优势——它不依赖ES6语法,jQuery 1.12.4能完美运行在IE9+环境。

界面布局采用经典的“三栏式”:左侧固定导航菜单(含角色标识)、顶部动态面包屑、主体内容区。导航菜单的HTML结构如下:

<div class="sidebar-nav">
  <ul>
    <li><a href="index.jsp"><i class="glyphicon glyphicon-home"></i> 首页</a></li>
    <li class="dropdown">
      <a href="#" class="dropdown-toggle" data-toggle="dropdown">
        <i class="glyphicon glyphicon-book"></i> 图书管理 <span class="caret"></span>
      </a>
      <ul class="dropdown-menu">
        <li><a href="book/list.jsp">图书列表</a></li>
        <li><a href="book/add.jsp">新增图书</a></li>
      </ul>
    </li>
    <!-- 其他菜单项 -->
  </ul>
</div>

关键在于data-toggle="dropdown"的实现不是靠Bootstrap JS插件,而是手写了一段轻量级JS:

// 解决IE下dropdown不展开问题
$('.dropdown-toggle').on('click', function(e) {
  e.preventDefault();
  var $dropdown = $(this).next('.dropdown-menu');
  if ($dropdown.is(':visible')) {
    $dropdown.hide();
  } else {
    $('.dropdown-menu').hide(); // 关闭其他下拉菜单
    $dropdown.show();
  }
});

这样既规避了Bootstrap JS在老旧浏览器中的兼容性问题,又保证了交互一致性。表单验证也放弃HTML5原生属性(required在IE9下无效),全部用jQuery Validation Plugin实现,且规则与后端校验完全同步:

$("#bookForm").validate({
  rules: {
    isbn: {
      required: true,
      minlength: 13,
      maxlength: 17,
      digits: false // 允许横杠
    },
    title: "required",
    author: "required",
    total_copies: {
      required: true,
      digits: true,
      min: 1,
      max: 999
    }
  },
  messages: {
    isbn: "请输入正确的ISBN号(如978-7-04-050694-6)",
    title: "书名不能为空",
    author: "作者不能为空",
    total_copies: "馆藏册数必须是1-999之间的整数"
  }
});

消息提示采用Bootstrap Alert组件,但做了降级处理:当用户禁用JavaScript时,后端校验失败会跳转到错误页面并显示红色Alert;启用JS时,则在表单顶部动态插入Alert。这种“渐进增强”策略,确保了无论用户环境如何,核心功能都不失效。

3. 核心业务模块实现详解

3.1 双角色登录与权限控制:从Session到URL拦截的完整链路

登录功能看似简单,实则串联了整个系统的安全基座。系统采用“一次登录,全程凭证”的Session机制,但Session中存储的不是明文密码,而是经过SHA-256哈希+盐值加密的token:

// LoginServlet.java
String salt = UUID.randomUUID().toString().replace("-", "");
String hashedPassword = DigestUtils.sha256Hex(password + salt);
// 查询数据库匹配hashedPassword
if (user != null && user.getPassword().equals(hashedPassword)) {
  HttpSession session = request.getSession(true);
  session.setAttribute("user", user); // user对象含id, card_no, name, role, status
  session.setAttribute("salt", salt); // 后续密码修改需验证原密码
  session.setMaxInactiveInterval(30 * 60); // 30分钟无操作自动失效
}

角色权限控制分为三层:

第一层:Filter拦截(URL级别)
web.xml中配置:

<filter>
  <filter-name>AdminFilter</filter-name>
  <filter-class>com.library.filter.AdminFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>AdminFilter</filter-name>
  <url-pattern>/admin/*</url-pattern>
</filter-mapping>

AdminFilter代码精简到15行:

public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) 
    throws IOException, ServletException {
  HttpServletRequest request = (HttpServletRequest) req;
  HttpServletResponse response = (HttpServletResponse) resp;
  HttpSession session = request.getSession(false);

  if (session == null || !"admin".equals(session.getAttribute("role"))) {
    response.sendRedirect(request.getContextPath() + "/error/403.jsp");
    return;
  }
  chain.doFilter(req, resp);
}

第二层:JSP页面元素级控制(UI级别)
header.jsp中动态渲染导航菜单:

<c:choose>
  <c:when test="${sessionScope.user.role == 'admin'}">
    <li><a href="admin/reader/list.jsp"><i class="glyphicon glyphicon-user"></i> 读者管理</a></li>
    <li><a href="admin/book/list.jsp"><i class="glyphicon glyphicon-book"></i> 图书管理</a></li>
  </c:when>
  <c:otherwise>
    <li><a href="reader/my-borrow.jsp"><i class="glyphicon glyphicon-list"></i> 我的借阅</a></li>
  </c:otherwise>
</c:choose>

第三层:Service层业务逻辑校验(数据级别)
例如借书操作,BorrowService.borrowBook()方法开头就有双重校验:

public Result borrowBook(Long bookId, Long readerId) {
  // 1. 检查读者状态(是否挂失、是否禁用)
  Reader reader = readerDao.findById(readerId);
  if (!"active".equals(reader.getStatus())) {
    return Result.fail("借书卡状态异常,请联系管理员");
  }

  // 2. 检查图书状态(是否下架、是否有库存)
  Book book = bookDao.findById(bookId);
  if (!"normal".equals(book.getStatus()) || book.getAvailableCopies() <= 0) {
    return Result.fail("该图书暂不可借");
  }

  // 3. 检查读者当前借阅数量(最多5本)
  int currentBorrowed = borrowRecordDao.countByReaderIdAndStatus(readerId, "borrowed");
  if (currentBorrowed >= 5) {
    return Result.fail("您已借阅5本书,达到上限");
  }

  // 执行借书逻辑...
}

这种三层防护,确保了即使有人绕过前端直接调用/admin/book/delete.jsp?id=123,Filter会拦截;就算他伪造Session,Service层的校验也会兜底。权限不是靠“信任前端”,而是靠“处处设防”。

3.2 借书/还书的核心事务:ACID保障下的状态流转

借书和还书是系统最核心的业务,必须保证数据强一致性。以借书为例,整个流程涉及4张表的联动更新,且必须在一个数据库事务中完成:

@Transactional
public Result borrowBook(Long bookId, Long readerId) {
  // 步骤1:查询图书和读者(SELECT FOR UPDATE锁定行)
  Book book = bookDao.findByIdForUpdate(bookId); // 加行锁
  Reader reader = readerDao.findByIdForUpdate(readerId);

  // 步骤2:业务校验(同上,略)

  // 步骤3:更新图书可用册数
  book.setAvailableCopies(book.getAvailableCopies() - 1);
  bookDao.update(book);

  // 步骤4:插入借阅记录
  BorrowRecord record = new BorrowRecord();
  record.setBookId(bookId);
  record.setReaderId(readerId);
  record.setBorrowDate(new Date());
  record.setDueDate(DateUtils.addDays(new Date(), 30)); // 30天后到期
  record.setStatus("borrowed");
  borrowRecordDao.insert(record);

  // 步骤5:记录操作日志
  Log log = new Log();
  log.setOperator(reader.getCardNo());
  log.setOperation("borrow");
  log.setTarget("book_" + bookId);
  logDao.insert(log);

  return Result.success("借书成功,应还日期:" + DateUtils.format(record.getDueDate()));
}

关键点在于bookDao.findByIdForUpdate(bookId)——这是MySQL的SELECT ... FOR UPDATE语句,它会在查询到的图书记录上加排他锁,直到事务提交。这意味着当两个读者同时点击借同一本书时,第一个事务会获得锁并完成借书,第二个事务会被阻塞,待第一个提交后才执行校验。如果此时图书已被借完,第二个事务会因available_copies <= 0而失败,返回友好提示。

还书逻辑同样严谨:

@Transactional
public Result returnBook(Long recordId) {
  // 1. 查询借阅记录(FOR UPDATE)
  BorrowRecord record = borrowRecordDao.findByIdForUpdate(recordId);

  // 2. 校验状态(必须是borrowed状态)
  if (!"borrowed".equals(record.getStatus())) {
    return Result.fail("该记录状态异常,无法归还");
  }

  // 3. 更新借阅记录状态和还书时间
  record.setStatus("returned");
  record.setReturnDate(new Date());
  borrowRecordDao.update(record);

  // 4. 更新图书可用册数
  Book book = bookDao.findById(record.getBookId());
  book.setAvailableCopies(book.getAvailableCopies() + 1);
  bookDao.update(book);

  // 5. 计算是否逾期(精确到小时)
  long diffHours = DateUtils.diffHours(record.getReturnDate(), record.getDueDate());
  if (diffHours > 0) {
    // 逾期处理:记录逾期天数,更新读者信用分(模拟)
    readerDao.deductCredit(record.getReaderId(), (int) diffHours / 24);
  }

  return Result.success("还书成功");
}

这里有个易忽略的细节:diffHours计算使用的是DateUtils.diffHours(),它不是简单减毫秒数除3600,而是考虑了夏令时和闰秒,确保逾期判定绝对准确。很多类似系统用System.currentTimeMillis()相减,结果在跨时区或系统时间被手动调整时出现误判。

3.3 借书卡挂失与逾期提醒:从手动操作到自动任务的演进

挂失功能表面是更新readers.status字段,实则触发一系列连锁反应:

@Transactional
public Result suspendReader(Long readerId) {
  Reader reader = readerDao.findById(readerId);
  if ("suspended".equals(reader.getStatus())) {
    return Result.fail("该读者已处于挂失状态");
  }

  // 1. 更新读者状态
  reader.setStatus("suspended");
  readerDao.update(reader);

  // 2. 自动归还其所有未归还图书(避免挂失后仍计逾期)
  List<BorrowRecord> borrowedRecords = borrowRecordDao.findByReaderIdAndStatus(readerId, "borrowed");
  for (BorrowRecord record : borrowedRecords) {
    record.setStatus("returned");
    record.setReturnDate(new Date());
    borrowRecordDao.update(record);

    // 3. 增加图书可用册数
    Book book = bookDao.findById(record.getBookId());
    book.setAvailableCopies(book.getAvailableCopies() + 1);
    bookDao.update(book);
  }

  // 4. 发送挂失通知(邮件/短信模拟)
  notificationService.sendSuspendNotice(reader);

  return Result.success("挂失成功,已自动归还所有未还图书");
}

逾期提醒则采用Quartz定时任务,而非简单的TimerTask(后者在Tomcat重启后会丢失):

// OverdueJob.java
public class OverdueJob implements Job {
  @Override
  public void execute(JobExecutionContext context) throws JobExecutionException {
    // 查询所有borrowed状态且due_date < now的记录
    List<BorrowRecord> overdueRecords = borrowRecordDao.findOverdueRecords();

    if (overdueRecords.isEmpty()) return;

    // 按读者聚合逾期记录
    Map<Long, List<BorrowRecord>> readerOverdueMap = overdueRecords.stream()
        .collect(Collectors.groupingBy(BorrowRecord::getReaderId));

    // 生成Excel报表(使用Apache POI)
    Workbook workbook = new XSSFWorkbook();
    Sheet sheet = workbook.createSheet("逾期未还清单");
    // ... 写入表头和数据

    // 保存到指定目录(如/webapps/library/reports/overdue_20240520.xlsx)
    FileOutputStream fos = new FileOutputStream(
        getReportPath() + "overdue_" + DateUtils.format(new Date(), "yyyyMMdd") + ".xlsx");
    workbook.write(fos);
    fos.close();

    // 发送邮件通知管理员(调用JavaMail API)
    mailService.sendOverdueReport(workbook);
  }
}

定时任务配置在quartz.properties中:

org.quartz.scheduler.instanceName = OverdueScheduler
org.quartz.threadPool.threadCount = 3
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
# 每天凌晨2点执行
org.quartz.trigger.cron = 0 0 0 2 * ? *

这个设计的好处是:报表生成和邮件发送分离,即使邮件服务器暂时不可用,报表依然会生成;任务失败时Quartz会自动重试;所有配置外置化,无需重启应用即可调整执行时间。

4. 部署与实操避坑指南

4.1 本地部署全流程:从零开始的20分钟实战

部署不是“复制粘贴”,而是理解每个环节的意图。以下是我在实验室带学生部署时总结的标准流程:

第一步:环境准备(5分钟)
- JDK:必须JDK 8u202或更高(低版本不支持Spring 4.3的某些特性)
- Tomcat:推荐8.5.99(避免9.x的HTTP/2兼容性问题)
- MySQL:5.7.32是经过充分测试的版本,8.0+需额外配置allowPublicKeyRetrieval=true

提示:不要用XAMPP/MAMP这类集成包!它们的MySQL配置常包含skip-grant-tables,导致权限校验失效。务必用官方安装包,root密码设为library123(脚本中已预设)。

第二步:数据库导入(3分钟)
- 启动MySQL服务
- 执行命令创建数据库:CREATE DATABASE library CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
- 执行SQL脚本:mysql -u root -p library < library.sql
- 验证:SELECT COUNT(*) FROM books; 应返回3条;SELECT * FROM readers WHERE card_no='ADM001'; 应看到管理员账号

注意:library.sqlSET NAMES utf8mb4;必须存在,否则中文字符会乱码。若导入后出现问号,说明客户端编码未设置,需在MySQL命令行执行SET NAMES utf8mb4;后再导入。

第三步:配置文件修改(2分钟)
- 打开src/main/resources/jdbc.properties
properties jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/library?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=library123
- 若MySQL不在本地,改localhost为IP;若端口非3306,同步修改;serverTimezone=Asia/Shanghai必须添加,否则时间字段存入错误。

第四步:项目构建与部署(5分钟)
- 在项目根目录执行:mvn clean package -Dmaven.test.skip=true
- 生成的WAR包位于target/books-management-system.war
- 将WAR包复制到tomcat/webapps/目录下
- 启动Tomcat:bin/startup.bat(Windows)或bin/startup.sh(Linux)
- 观察logs/catalina.out,确认无ClassNotFoundExceptionSQLException

第五步:首次访问验证(5分钟)
- 浏览器打开http://localhost:8080/books-management-system
- 用预置账号登录:ADM001 / admin123RDR001 / reader123
- 管理员登录后,点击“图书管理”→“新增图书”,填入ISBN978-7-302-53522-2(即已存在的《算法导论》),观察提示“ISBN已存在”
- 读者登录后,尝试借阅978-7-302-53522-2(库存为0),应提示“该图书暂不可借”

实测心得:Tomcat启动后首次访问可能较慢(JSP编译耗时),耐心等待30秒。若页面空白,检查logs/localhost.<date>.log中是否有Servlet.init()异常。

4.2 常见问题排查速查表

问题现象可能原因排查步骤解决方案
登录页报500错误,日志显示java.lang.ClassNotFoundException: com.mysql.jdbc.DriverMySQL驱动未放入Tomcat lib目录1. 检查tomcat/lib/下是否有mysql-connector-java-5.1.47.jar
2. 查看pom.xml中scope是否为compile
将jar包复制到tomcat/lib/,或在pom.xml中移除<scope>provided</scope>
登录成功后跳转到空白页,地址栏显示/books-management-system/admin/index.jspJSP文件编码不一致1. 用Notepad++打开admin/index.jsp,查看编码是否为UTF-8无BOM
2. 检查IDEA中File Encoding是否设为UTF-8
统一设为UTF-8无BOM,重新保存所有JSP文件
借书时提示“数据库连接超时”,但Navicat能连上连接池配置不合理1. 查看src/main/resources/dbcp-config.propertiesmaxActive=20是否过小
2. 检查MySQL wait_timeout是否太短(默认28800秒)
maxActive调至50,MySQL执行SET GLOBAL wait_timeout=86400;
Bootstrap样式失效,页面变成纯文字CSS文件路径错误或4041. 浏览器F12,Network标签页过滤CSS,看bootstrap.min.css是否返回404
2. 检查webapp/css/目录是否存在
确认webapp/css/bootstrap.min.css路径正确,或在<link>标签中用绝对路径<link href="${pageContext.request.contextPath}/css/bootstrap.min.css" rel="stylesheet">
新增图书时中文乱码,数据库存为问号JDBC URL缺少编码参数1. 检查jdbc.properties中URL是否含characterEncoding=utf8mb4
2. 查看MySQL变量:SHOW VARIABLES LIKE 'character_set%';
确保URL含useUnicode=true&characterEncoding=utf8mb4,MySQL执行SET NAMES utf8mb4;

4.3 生产环境加固建议(来自真实项目教训)

这套系统虽为轻量级,但在社区图书馆实际运行一年后,我们总结出几条血泪经验:

数据库层面
- 必须开启MySQL慢查询日志:slow_query_log=ONlong_query_time=2,定期分析/var/log/mysql/mysql-slow.log,发现SELECT * FROM borrow_records WHERE status='borrowed'这类全表扫描,及时在status字段加索引。
- 定期清理历史日志:DELETE FROM logs WHERE create_time < DATE_SUB(NOW(), INTERVAL 90 DAY);,避免日志表膨胀拖慢查询。

应用层面
- 禁用Tomcat默认管理页面:删除tomcat/webapps/managertomcat/webapps/host-manager目录,防止未授权访问。
- 密码重置链接需加时效性:当前系统密码重置邮件含reset.jsp?token=xxx,token有效期应设为1小时,且使用SecureRandom生成,避免被暴力破解。

运维层面
- 建立每日备份脚本:mysqldump -u root -plibrary123 library > /backup/library_$(date +%Y%m%d).sql,配合crontab每天凌晨1点执行。
- 监控可用内存:在Tomcat启动脚本bin/setenv.sh中添加JAVA_OPTS="-Xms512m -Xmx1024m -XX:MaxMetaspaceSize=256m",防止内存溢出。

最后分享一个小技巧:当需要快速演示给领导看时,不必每次都重启Tomcat。用JRebel插件(免费版支持热替换class),修改Java代码后Ctrl+S,几秒内生效,连页面都不用刷新——这才是工程师该有的效率。

5. 扩展可能性与教学价值延伸

这套系统真正的价值,不在于它“已经实现了什么”,而在于它“天然适合扩展什么”。我在指导毕设时,常把它作为“能力跃迁”的跳板:

技术栈升级路径
- 若想练Spring Boot:保留原有业务逻辑,将DAO层改为MyBatis-Plus,Controller层用@RestController,配置application.yml替代properties,2小时就能跑通。
- 若想学前后端分离:前端用Vue CLI重写,后端提供RESTful API(/api/books, /api/borrows),JWT替代Session,权限控制改用Spring Security Method Security。
- 若想接触云服务:把MySQL迁移到阿里云RDS,Tomcat部署到ECS,用OSS存储图书封面图片,Nginx做反向代理和静态资源缓存。

业务功能深化方向
- 增加预约功能:读者可预约热门图书,当有副本归还时自动通知,需设计预约队列和超时释放逻辑。
- 引入RFID借还书:对接USB RFID读卡器,用Java Native Access(JNA)调用底层驱动,实现刷卡秒借还。
- 构建数据分析看板:用ECharts绘制借阅热度图、读者活跃度曲线、图书借阅TOP10,数据源来自现有表。

但最值得强调的,是它对教学场景的适配性。我曾用它做过一次“逆向工程教学”:给学生一份去掉注释、混淆包名的WAR包,让他们通过JD-GUI反编译,结合library.sql分析出完整的MVC分层结构。结果90%的学生能准确画出Controller→Service→DAO的调用链,并指出BorrowService中事务边界的划定位置——这比单纯讲“什么是MVC”有效十倍。

所以,如果你正面临毕设选题焦虑,或者需要一套能真正落地的小型图书馆系统,别被那些标榜“Spring Cloud”“Vue3 Composition API”的项目迷惑。真正的工程能力,体现在对业务细节的敬畏、对技术选型的克制、对部署环境的尊重。这套系统没有黑科技,但它每一步都踩得扎实。就像一位老师傅做的木工活,榫卯严丝合缝,不用胶水也能承重百年。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这是一套可直接部署的Java Web图书借阅管理系统,基于JSP+Servlet开发,集成Spring和Spring MVC框架,使用Maven构建。数据库采用MySQL,配套完整的library.sql脚本,包含建库、建表、初始化数据,支持一键导入。前端基于Bootstrap 3实现响应式界面,适配Chrome、Firefox、Edge等主流浏览器。系统支持管理员和读者两种角色登录,提供图书信息维护(增删改查)、读者信息管理、借书/还书操作、密码修改、借书卡挂失、逾期未还自动提醒等完整业务流程。部署时只需在本地MySQL创建library数据库并执行SQL脚本,再修改配置文件中的数据库连接参数(地址、用户名、密码)即可运行。适用于高校课程设计、毕业设计项目或小型图书馆的实际轻量级应用需求。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值