ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

共享图书管理系统实战:JSP+Spring+Oracle 从建库到借阅全流程

共享图书管理系统实战:JSP+Spring+Oracle 从建库到借阅全流程 简介这份资源是《共享图书管理系统设计与实现》的完整Word文档面向计算机相关专业学生、课程设计或毕业设计开发者以及希望了解图书馆信息化改造的技术人员。文档围绕传统借阅模式效率低、资源利用率不高等问题提出一套结合智能推荐、多平台访问与数据分析的共享图书管理方案并给出从需求分析到系统实现的完整论述。资源包仅含1个docx文件大小约13.15MB内容涵盖绪论、相关理论与技术、系统需求分析等章节涉及面向对象、I/O、JSON、JDBC、JSP、Ajax、Tomcat、Eclipse与Oracle 11g等知识点目录结构完整便于按章节查阅与引用。目前已有109人学习下载适合作为课程设计选题参考、论文写作素材或系统开发前的需求梳理资料帮助读者快速理解共享图书管理系统的设计思路与关键技术选型。1. 共享图书管理系统从书荒到流转一套 JSPSpringOracle 的落地拆解手里有书的人找不到想读的人想读书的人不知道谁手里有——这是我在做社区图书角时最头疼的事。共享图书管理系统要解决的就是这个匹配问题把散落在个人手里的图书登记成可检索、可借阅、可追踪的资产让借还流程从微信群接龙变成有状态、有记录、有权限的线上操作。这套系统适合谁做高校课程设计、社区图书角、小型书店的借阅台账都算典型场景。技术选型上JSP 负责页面渲染Spring 管 Bean 和事务Ajax 做局部刷新Oracle 11g 扛数据存储——这套组合在存量项目里极其常见尤其是那些跑在 Windows Server 上的老系统。下面我按实际搭建顺序把每个环节的参数和坑讲清楚。2. 环境搭建与数据库设计Oracle 11g 建库和 JSP 工程骨架2.1 Oracle 11g 安装后的三个必调参数Oracle 11g 在 Windows 上装完之后别急着建表。我见过太多人卡在连接超时或者中文乱码上其实都是初始化参数没调。第一个要改的是字符集第二个是连接数第三个是内存分配。字符集在安装时就要选对。如果装完发现NLS_CHARACTERSET不是ZHS16GBK或AL32UTF8JSP 页面插中文必乱码。查当前字符集的命令-- 查看数据库字符集必须与 JSP 页面编码一致 SELECT * FROM nls_database_parameters WHERE parameter NLS_CHARACTERSET;如果结果是WE8MSWIN1252这类西文字符集最省事的做法是重建库别想着改字符集改完容易出半角问号。安装时选“高级安装”语言选中文字符集选ZHS16GBK和后面 JSP 的pageEncoding对齐。连接数默认是 150社区图书角并发不高但连接池如果配大了会报ORA-00020。查当前会话数-- 查看当前会话数和最大连接数 SELECT COUNT(*) FROM v$session; SHOW PARAMETER processes;processes默认 150连接池maxActive设 20 就够。内存方面sga_target和pga_aggregate_target按机器内存的 60% 分配4G 内存的机器给sga_target1500M、pga_aggregate_target500M改完重启实例生效。2.2 图书共享场景的四张核心表共享图书和图书馆管理最大的区别在于书属于个人借阅关系是人对人。所以表结构里必须有“所有者”字段借阅记录要同时记录出借人和借阅人。-- 用户表区分普通用户和管理员 CREATE TABLE t_user ( user_id NUMBER(10) PRIMARY KEY, username VARCHAR2(50) NOT NULL UNIQUE, password VARCHAR2(100) NOT NULL, role NUMBER(1) DEFAULT 0, -- 0普通用户 1管理员 phone VARCHAR2(20), create_time DATE DEFAULT SYSDATE ); -- 图书表owner_id 标记书的主人 CREATE TABLE t_book ( book_id NUMBER(10) PRIMARY KEY, title VARCHAR2(200) NOT NULL, author VARCHAR2(100), isbn VARCHAR2(20), owner_id NUMBER(10) REFERENCES t_user(user_id), status NUMBER(1) DEFAULT 0, -- 0可借 1已借出 2下架 cover_img VARCHAR2(300), create_time DATE DEFAULT SYSDATE ); -- 借阅记录表出借人和借阅人都要记 CREATE TABLE t_borrow ( borrow_id NUMBER(10) PRIMARY KEY, book_id NUMBER(10) REFERENCES t_book(book_id), lender_id NUMBER(10) REFERENCES t_user(user_id), borrower_id NUMBER(10) REFERENCES t_user(user_id), borrow_date DATE DEFAULT SYSDATE, return_date DATE, status NUMBER(1) DEFAULT 0 -- 0借出中 1已归还 2逾期 ); -- 图书分类表 CREATE TABLE t_category ( cate_id NUMBER(10) PRIMARY KEY, cate_name VARCHAR2(50) NOT NULL );主键用NUMBER(10)配合序列别用自增Oracle 11g 没有AUTO_INCREMENT。建序列CREATE SEQUENCE seq_user START WITH 1 INCREMENT BY 1 NOCACHE; CREATE SEQUENCE seq_book START WITH 1 INCREMENT BY 1 NOCACHE; CREATE SEQUENCE seq_borrow START WITH 1 INCREMENT BY 1 NOCACHE;NOCACHE是为了避免序列跳号课程设计里老师会查这个。插入时用seq_user.NEXTVAL。2.3 JSP 工程目录与 Spring 配置骨架在 Eclipse 或 IDEA 里建 Dynamic Web Project目录结构按 Maven 习惯来src/main/java放 Java 代码src/main/resources放 Spring 配置WebContent或webapp放 JSP。Spring 的applicationContext.xml里配数据源和事务!-- 数据源Oracle 11g 驱动类名固定 -- bean iddataSource classorg.apache.commons.dbcp.BasicDataSource property namedriverClassName valueoracle.jdbc.driver.OracleDriver/ property nameurl valuejdbc:oracle:thin:localhost:1521:orcl/ property nameusername valuebookshare/ property namepassword valuebookshare/ property namemaxActive value20/ property namemaxIdle value5/ /bean !-- 事务管理器 -- bean idtxManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /beanurl里的orcl是 SID不是服务名。如果安装时用了 Pluggable Database要换成jdbc:oracle:thin:localhost:1521/pdborcl这个坑我踩过报ORA-12505就是这里写错了。驱动包ojdbc6.jar要放进WEB-INF/lib别只放构建路径里Tomcat 跑起来会找不到。3. Ajax 局部刷新与 JSP 页面交互借还操作不跳页3.1 Ajax 请求参数赋值与编码格式共享图书系统里最频繁的操作是“借书”和“还书”如果每次点按钮都整页刷新体验很差。用 Ajax 发请求后端返回 JSON前端局部更新按钮状态和列表。这里第一个坑就是编码JSP 页面用UTF-8Ajax 请求头也要声明UTF-8否则中文书名传到后端变乱码。// 借书操作Ajax 提交参数用 JSON 格式 function borrowBook(bookId) { var xhr new XMLHttpRequest(); xhr.open(POST, borrowAction.do, true); // 关键设置请求编码与 JSP pageEncoding 一致 xhr.setRequestHeader(Content-Type, application/x-www-form-urlencoded;charsetUTF-8); xhr.onreadystatechange function() { if (xhr.readyState 4 xhr.status 200) { var res JSON.parse(xhr.responseText); if (res.code 0) { // 局部更新按钮状态不刷新整页 document.getElementById(btn_ bookId).innerText 已借出; document.getElementById(btn_ bookId).disabled true; } else { alert(res.msg); } } }; // 给 Ajax 请求参数赋值注意 encodeURIComponent 处理中文 xhr.send(bookId bookId borrowerId encodeURIComponent(currentUserId)); }Content-Type里的charsetUTF-8必须写不写的话 Tomcat 默认用ISO-8859-1解析 POST 体中文参数直接变问号。encodeURIComponent处理参数值里的特殊字符书名里带或时不会截断。后端 Spring MVC 的RequestParam接收时如果发现乱码在web.xml里加CharacterEncodingFilterfilter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filterforceEncoding设为true才会强制覆盖请求和响应的编码只设encoding有时不生效。3.2 JSP 个人信息展示页面的数据绑定个人信息页要展示当前用户的基本信息、我借出的书、我借入的书。JSP 里用 JSTL 遍历别用脚本片段% %可读性差还容易出错。后端 Controller 把数据放进ModelJSP 用${}取。% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !-- 个人信息展示用户基本资料 -- div classuser-info p用户名${user.username}/p p手机号${user.phone}/p /div !-- 我借出的书遍历 borrowList -- table trth书名/thth借阅人/thth状态/th/tr c:forEach items${lendList} varb tr td${b.title}/td td${b.borrowerName}/td td${b.status 0 ? 借出中 : 已归还}/td /tr /c:forEach /tablepageEncodingUTF-8和contentType里的charsetUTF-8两个都要写前者管 JSP 文件本身的编码后者管响应给浏览器的编码。只写一个在某些 Tomcat 版本上会出乱码。c:forEach的items属性接收后端传来的Listvar是循环变量名EL 表达式${b.title}会自动调用 getter。3.3 图书封面上传与坐标定位显示图书封面图上传后如果尺寸不统一列表页会参差不齐。常见做法是用 CSS 固定容器大小图片按坐标定位裁剪。JSP 里给图片容器设position: relative图片设position: absolute通过top和left控制显示区域。/* 封面容器固定 120x160超出部分隐藏 */ .cover-box { width: 120px; height: 160px; overflow: hidden; position: relative; } .cover-box img { position: absolute; /* 坐标定位让图片居中显示裁掉多余部分 */ top: 50%; left: 50%; transform: translate(-50%, -50%); min-width: 100%; min-height: 100%; }transform: translate(-50%, -50%)配合top:50%; left:50%实现居中裁剪比算具体像素值省事。如果要求严格按坐标定位比如只显示图片的某个区域用clip: rect(top, right, bottom, left)但clip只能用于position:absolute元素且不支持百分比得用像素值。上传时后端用MultipartFile接收存到服务器目录数据库只存相对路径别存绝对路径换服务器就失效。4. 借阅流程与权限控制Spring 事务和状态机4.1 借书还书的事务边界借书操作涉及两张表t_book的status要改成 1t_borrow要插一条记录。这两个操作必须在一个事务里否则改完图书状态但插入失败书就“消失”了。Spring 的Transactional注解加在 Service 方法上Service public class BorrowService { Autowired private BookDao bookDao; Autowired private BorrowDao borrowDao; // 借书改图书状态 插借阅记录必须原子 Transactional public int borrowBook(Long bookId, Long borrowerId, Long lenderId) { // 先查图书当前状态防止并发重复借 Book book bookDao.selectById(bookId); if (book.getStatus() ! 0) { return -1; // 已被借出 } // 更新图书状态为已借出 bookDao.updateStatus(bookId, 1); // 插入借阅记录 Borrow borrow new Borrow(); borrow.setBookId(bookId); borrow.setBorrowerId(borrowerId); borrow.setLenderId(lenderId); borrow.setStatus(0); borrowDao.insert(borrow); return 0; } }Transactional默认只对RuntimeException回滚如果 DAO 层抛的是SQLException受检异常事务不会回滚。要么在方法上写Transactional(rollbackFor Exception.class)要么在 DAO 里把异常转成RuntimeException。我一般直接写rollbackFor Exception.class省心。并发借同一本书的问题两个用户同时点借书都查到status0都去更新最后插两条借阅记录。解决办法是在updateStatus的 SQL 里加条件-- 只有当前状态为 0 时才更新返回影响行数 UPDATE t_book SET status 1 WHERE book_id #{bookId} AND status 0;Service 里判断updateStatus返回的影响行数如果是 0 说明被别人抢先了直接返回失败。这比加锁轻量Oracle 的行锁在UPDATE时自动加不用额外处理。4.2 基于角色的页面访问控制系统里管理员能看所有图书和借阅记录普通用户只能看自己的。控制粒度到 URL 级别用 Spring 拦截器实现。先定义拦截器public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); User user (User) session.getAttribute(loginUser); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return false; } // 管理员页面普通用户访问直接踢回首页 String uri request.getRequestURI(); if (uri.contains(/admin/) user.getRole() ! 1) { response.sendRedirect(request.getContextPath() /index.jsp); return false; } return true; } }在spring-mvc.xml里注册mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/register/ mvc:exclude-mapping path/static/**/ bean classcom.bookshare.interceptor.AuthInterceptor/ /mvc:interceptor /mvc:interceptorsexclude-mapping把登录、注册、静态资源排除掉否则登录页自己都被拦截死循环。session.getAttribute(loginUser)里的 key 要和登录时setAttribute的 key 完全一致大小写敏感这个坑我见过有人写成loginuser排查半天。4.3 借阅状态流转与逾期判断借阅记录的状态有借出中、已归还、逾期。逾期不是手动改的而是每次查询时动态判断return_date为空且borrow_date超过 30 天就算逾期。在 SQL 里直接算-- 查询当前用户的借阅记录动态标记逾期 SELECT b.borrow_id, bk.title, b.borrow_date, b.return_date, CASE WHEN b.return_date IS NULL AND SYSDATE - b.borrow_date 30 THEN 2 ELSE b.status END AS real_status FROM t_borrow b JOIN t_book bk ON b.book_id bk.book_id WHERE b.borrower_id #{userId} ORDER BY b.borrow_date DESCSYSDATE - b.borrow_date得到的是天数差Oracle 里日期直接相减就是天数。CASE WHEN动态算状态不用定时任务去刷。还书时更新return_date SYSDATE、status 1同时把t_book的status改回 0这两个操作同样要放在一个事务里。5. 避坑与排查JSPOracle 组合的五个血泪教训5.1 中文乱码现象是页面显示问号原因是编码链路没对齐现象JSP 页面输入中文书名保存到 Oracle 后查出来是???。原因编码链路有三段——JSP 文件编码、请求编码、数据库字符集。任何一段不是 UTF-8 或 GBK 都会断。解决JSP 头部pageEncodingUTF-8web.xml加CharacterEncodingFilter且forceEncodingtrueOracle 字符集用ZHS16GBK或AL32UTF8。三段统一后还乱码检查 Tomcat 的server.xml里Connector有没有URIEncodingUTF-8GET 请求的中文参数靠这个。5.2 ORA-12505现象是连接被拒原因是 SID 和服务名搞混现象Spring 启动时报ORA-12505, TNS:listener does not currently know of SID given in connect descriptor。原因JDBC URL 里写的是服务名但 Oracle 监听器认的是 SID。11g 默认安装用 SIDorcl如果建了 PDB 则服务名是pdborcl。解决用jdbc:oracle:thin:localhost:1521:orcl试不行就换jdbc:oracle:thin:localhost:1521/pdborcl。冒号和服务名分隔符斜杠的区别记牢。5.3 Ajax 返回 200 但前端不执行回调现象是点了没反应原因是 JSON 解析失败现象浏览器 Network 里看到请求返回 200响应体也有内容但onreadystatechange里的逻辑不执行。原因后端返回的不是合法 JSON比如多了 BOM 头或者返回了 HTML 错误页。解决后端 Controller 方法加ResponseBody返回对象让 Spring 自动转 JSON别手动拼字符串。前端JSON.parse外面套try-catch解析失败时把xhr.responseText打印出来看通常是登录过期被拦截器重定向到了登录页返回的是 HTML。5.4 序列跳号现象是主键不连续原因是序列用了 CACHE现象插入数据后主键是 1、2、21、22中间跳了。原因CREATE SEQUENCE默认CACHE 20Oracle 预分配 20 个值在内存实例重启或回滚时未用的值就丢了。解决建序列时加NOCACHE或者接受跳号——主键只要求唯一不要求连续。课程设计里如果老师检查连续性用NOCACHE生产环境用CACHE 20性能更好。5.5 JSP 页面加载后只刷新一次现象是页面反复刷新原因是刷新逻辑写在了错误位置现象想在页面加载完后自动刷新一次获取最新数据结果页面无限刷新。原因把location.reload()直接写在了window.onload里每次刷新又触发onload死循环。解决用sessionStorage标记是否已刷新过window.onload function() { // 只刷新一次用 sessionStorage 做标记 if (!sessionStorage.getItem(refreshed)) { sessionStorage.setItem(refreshed, 1); location.reload(); } };sessionStorage在页面刷新后仍然保留关闭标签页才清空。这样第一次加载设标记并刷新第二次加载看到标记就不刷了。6. 从课程设计到真实可用的三个进阶技巧第一个技巧把借阅到期提醒做成站内信而不是邮件。课程设计里常要求“逾期提醒”用 JavaMail 发邮件配置复杂还容易被判垃圾邮件。我一般直接在t_user加个unread_msg字段借阅到期前三天由定时任务Spring 的Scheduled往站内信表插一条记录用户登录时在个人信息页顶部显示红点。定时任务配置Component public class OverdueTask { Autowired private BorrowDao borrowDao; // 每天凌晨 1 点检查逾期cron 表达式六位秒 分 时 日 月 周 Scheduled(cron 0 0 1 * * ?) public void checkOverdue() { // 查出所有借出超过 25 天的记录插入提醒 ListBorrow list borrowDao.selectNearOverdue(25); for (Borrow b : list) { borrowDao.insertRemind(b.getBorrowerId(), b.getBookId()); } } }cron表达式六位?表示不指定别写成七位那是 Quartz 的格式。Scheduled需要在applicationContext.xml里加task:annotation-driven/才生效。第二个技巧图书列表分页别用rownum嵌套用 Oracle 11g 的OFFSET FETCH。很多人写SELECT * FROM (SELECT ROWNUM rn, t.* FROM t_book t WHERE ROWNUM 20) WHERE rn 10嵌套两层容易写错。11g 支持更直观的写法-- 分页查询第 2 页每页 10 条 SELECT * FROM t_book ORDER BY create_time DESC OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY;OFFSET是跳过的行数FETCH NEXT是取多少行。注意ORDER BY必须写在OFFSET前面否则报语法错误。这个语法在 11g R2 才支持11g R1 用不了装库时确认版本是 11.2.0.4。第三个技巧验证系统是否真的可用别只看页面能打开。我习惯用三个检查点第一用两个不同浏览器登录两个账号同时借同一本书看是否只有一个成功第二把系统时间调到 31 天后看借阅记录是否自动标记逾期第三直接改数据库里t_book的status为 1刷新页面看按钮是否变成“已借出”。这三个点过了基本逻辑就稳了。我自己的习惯是每次改完 DAO 层 SQL先把这三个场景跑一遍再提交代码比写单元测试快而且覆盖了并发和状态流转这两个最容易翻车的地方。希望帮到你。本文还有配套的精品资源点击获取
返回列表