
简介面向需要完成Java课程设计或期末大作业的学生提供基于JavaWeb的校园二手图书交易系统完整源码与数据库。项目由助教审定本地编译可运行评审分达95分以上难度适中适合学习JavaWeb项目开发流程也适合作为课程设计蓝本。资源包共485个文件压缩后仅3.18MB其中92个Java文件为核心业务源码230个XML配置涵盖框架与项目配置另有Vue前端组件、JavaScript脚本、SQL数据库脚本及编译后的class文件内容完整目录结构清晰便于按模块查阅。目前已有192人学习下载。项目包含管理员控制、订单处理、闲置商品管理等模块覆盖用户登录、图书发布、浏览、下单与订单管理等典型校园交易流程。通过源码可掌握JavaWeb分层架构、数据库交互及前后端协作方式还可将数据库脚本直接导入使用方便在现有基础上扩展预约、收藏等功能。源码注释和类命名规范清晰便于快速定位关键逻辑是JavaWeb综合训练的实用参考。1. JavaWeb校园二手图书交易系统一门课设为什么能拉开15分差距课程设计打分最狠的差距往往不在功能多少而在一套系统有没有把“从登录到下单”的交易闭环跑完整。校园二手图书交易系统是JavaWeb课设里的常客需求清楚、表结构不复杂但真正要拿到95分以上高分大作业你需要把登录校验、图书发布、图片上传、购物车、订单库存扣减这几段全部打通还能在答辩现场把事务逻辑讲明白。这套方案覆盖Java基础、Servlet、JSP、MySQL增删改查全链路适合大三正在做Java课设或者期末大作业的同学照着落地、改着验收。2. 技术选型和模块边界动手之前先定三层结构2.1 ServletJSP为什么还是课设第一选择每次带学弟做课设十个里有八个会问“能不能直接用Spring Boot”。能用但打分老师看到你用框架重点就不会放在功能上了而是追着你问“原理你能讲清吗”。Spring Boot把Tomcat、参数绑定、JSON转换都封装掉了答辩时问一个Servlet生命周期你很容易卡壳。JavaWeb课设老老实实用ServletJSP写反而是在用最接近Java基础的方式证明你真懂Web开发。Servlet负责接收请求和跳转JSP负责渲染页面POJO装数据这套模型课堂上都讲过过滤器还能顺手做登录拦截这些天然就是加分材料。选型结论Servlet 3.0 JSP 2.3 Tomcat 9 MySQL 5.7/8.x JDBC最稳。Tomcat 10别碰包名从javax改成了jakarta照着老教程抄代码会大面积报ClassNotFound纯属给自己挖坑。数据库连接也别一上来就上连接池课设这个并发量DriverManager数据库连接完全够用。先把JDBC的Connection、PreparedStatement、ResultSet三件套写顺答辩时老师问“数据库是怎么连的”你能把手写DBUtil的每个参数讲清楚这比报一个黑匣子式的框架依赖要实在得多。2.2 六个核心模块与页面流转校园二手图书交易系统看着界面多拆开其实就是六个模块每个模块对应一个Servlet、一到两个JSP、一张主表。把模块边界划清楚后面写代码才不会乱。模块入口Servlet核心JSP页面数据库表用户注册登录UserServletlogin.jsp / register.jsptb_user图书发布与展示BookServletpublish.jsp / detail.jsptb_book分类与搜索BookServletindex.jsp / search.jsptb_book购物车管理CartServletcart.jsptb_cart订单交易OrderServletconfirm.jsp / orders.jsptb_order评论与后台管理AdminServletadmin/index.jsptb_comment模块之间唯一的流转规则就是页面跳转首页点图书进详情页详情页加购物车购物车勾选去结算结算成功后跳到订单列表。这条路径就是答辩时的DEMO主线只要主线不断功能分就差不了。后台管理我一般会放一个管理员角色负责下架违规图书这能体现你有权限分级的意识。2.3 三层结构Servlet、Service、DAO各管一段源码组织建议按三层结构拆包这是95分以上高分大作业的基本盘。包名用项目名全小写例如com.example.bookmarket下面按controller、service、dao、entity、util分层。src/main/java/com/example/bookmarket/ ├── controller/ # Servlet只做参数接收和页面跳转 ├── service/ # 业务逻辑与事务边界 ├── dao/ # JDBC数据访问只写SQL ├── entity/ # 与表对应的POJO └── util/ # DBUtil、分页工具等很多低分课设的通病是把SQL全部写在Servlet里一个方法几百行老师看代码第一眼就劝退了。分层之后各管一段Servlet从request里拿参数、调用Service、把结果塞进request域再forwardService处理业务规则比如下单时扣库存、判断图书是否已售DAO只做增删改查。这样每个类都很短答辩时老师随便点开一个文件你都能说清它的职责。我自己的习惯是Service层接口实现类分开写虽然课设多几个文件但看着专业。如果时间紧接口可以省直接用实现类不影响验收关键是DAO和Servlet绝对不能互相越界。3. 数据库设计五张表撑起“上架-下单-成交”全流程3.1 表结构设计从tb_user到tb_comment的取舍这套系统说到底是交易系统数据库设计的核心就一句话谁、在什么时候、用什么价格、把哪本书卖给了谁。围绕这个主线五张表就够用户表、图书表、购物车表、订单表、评论表。别贪多老师看重的是表间关系能不能讲圆。用户表的关键字段是role和statusrole区分普通用户和管理员status用于冻结账号。图书表是核心表seller_id绑定发布者category做分类筛选sell_price和original_price分开存因为二手书原价和成交价差距很大。订单表必须冗余一份price和receiver、phone、address这叫“订单快照”用户买的时候什么价、送到哪要原样留在订单里不能等图书改价或用户改了资料再变这是交易系统最基本的要求。购物车表我加了联合唯一键(user_id, book_id)同一本书在同一个用户的购物车里只能出现一次。评论表独立出来是为了在图书详情页展示历史评价挂到图书表上会让图书表越来越臃肿。3.2 建表SQL与初始数据一份能直接跑通的脚本数据库名建议用book_market字符集统一utf8mb4不然中文容易乱码。下面是完整建表脚本存成init.sql直接执行。-- 用户表role 0普通用户 1管理员 CREATE TABLE tb_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT MD5加密后的密码, nickname VARCHAR(50) DEFAULT COMMENT 昵称, phone VARCHAR(20) DEFAULT , email VARCHAR(100) DEFAULT , avatar VARCHAR(255) DEFAULT COMMENT 头像相对路径, role TINYINT NOT NULL DEFAULT 0 COMMENT 0普通 1管理员, status TINYINT NOT NULL DEFAULT 0 COMMENT 0正常 1冻结, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 图书表status 0在售 1已售 2下架 CREATE TABLE tb_book ( id INT PRIMARY KEY AUTO_INCREMENT, seller_id INT NOT NULL COMMENT 卖家ID关联tb_user, title VARCHAR(100) NOT NULL COMMENT 书名, author VARCHAR(50) DEFAULT , publisher VARCHAR(100) DEFAULT , category VARCHAR(30) NOT NULL DEFAULT 其他 COMMENT 分类, original_price DECIMAL(10,2) DEFAULT 0 COMMENT 原价, sell_price DECIMAL(10,2) NOT NULL COMMENT 售价, book_condition TINYINT DEFAULT 3 COMMENT 1全新 2九成 3八成 4有笔记划线, description TEXT COMMENT 书况描述, cover VARCHAR(255) DEFAULT COMMENT 封面路径, status TINYINT NOT NULL DEFAULT 0, click_count INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_seller (seller_id), KEY idx_category (category), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT图书表; -- 购物车表同一用户同一本书只有一条记录 CREATE TABLE tb_cart ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_book (user_id, book_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT购物车表; -- 订单表price/receiver/phone/address为下单时快照 CREATE TABLE tb_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT 订单号业务唯一, buyer_id INT NOT NULL COMMENT 买家ID, book_id INT NOT NULL, price DECIMAL(10,2) NOT NULL COMMENT 成交价快照, receiver VARCHAR(50) NOT NULL, phone VARCHAR(20) NOT NULL, address VARCHAR(255) NOT NULL, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待付款 1待发货 2待收货 3已完成 4已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME DEFAULT NULL, KEY idx_buyer (buyer_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; -- 评论表一个用户对一本书只能评一次 CREATE TABLE tb_comment ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, content VARCHAR(500) NOT NULL, rating TINYINT DEFAULT 5 COMMENT 1-5星, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_book (user_id, book_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT评论表; -- 初始管理员密码为 admin123 的MD5 INSERT INTO tb_user (username, password, nickname, role) VALUES (admin, 0192023a7bbd73250516f069df18b500, 管理员, 1);这段脚本里几个细节要说明order_no单独设唯一键是为了防重复下单生成规则可以用时间戳加用户ID例如String.valueOf(System.currentTimeMillis()) userId。book_condition用数字而不是字符串排序和筛选都方便。点击量click_count先留字段后面做排行榜直接count就好。3.3 外键、索引和修改表结构注意建表SQL里我没有写外键约束这是刻意的。课设阶段用外键删除图书时会受订单限制演示时容易当场翻车。表之间的关联关系在Service层用代码控制比如删除图书前先查订单状态这反而能在答辩时讲成“业务层先校验再执行删除”。数据库层面只保留索引idx_status、idx_category就是给列表页和筛选页用的。老师验收前临时加字段是课设最常用的场景。比如突然要你加“教材/非教材”标记不用重建表一条ALTER TABLE就够ALTER TABLE tb_book ADD COLUMN course_name VARCHAR(100) DEFAULT COMMENT 所属课程非教材留空 AFTER category;MySQL表结构修改是高频操作做完ALTER之后对应的entity类要同步加字段JSP里也要加输入框三层同步改才不出错。这也提醒你数据库脚本一定要用git管起来每次改动都提交别等到答辩前一天发现库和代码对不上。4. 核心功能实现登录、发书、下单三块代码一次跑通4.1 登录与会话控制Session超时和密码加密登录是系统的门面也是最容易被挑刺的模块。课设级别的正确做法是密码MD5后存库登录时把用户输入的密码MD5再比对。Session在登录成功后写入setMaxInactiveInterval控制30分钟超时让“未登录不能下单”这个规则生效。WebServlet(/user/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password DigestUtils.md5Hex(req.getParameter(password)); UserDao userDao new UserDao(); User user userDao.findByUsernameAndPassword(username, password); if (user null) { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } if (user.getStatus() 1) { req.setAttribute(msg, 账号已被冻结请联系管理员); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } HttpSession session req.getSession(); session.setAttribute(loginUser, user); session.setMaxInactiveInterval(60 * 30); resp.sendRedirect(req.getContextPath() /book/list); } }逻辑说明先查用户再查状态两个if分支的顺序不要反。密码比对发生在SQL层面findByUsernameAndPassword同时带用户名和密文两个条件查不到就是用户名或密码错误不会把“用户不存在”和“密码错误”分开暴露。最后用sendRedirect而不是forward是为了避免刷新页面时重复提交表单这是表单提交后跳转的标准姿势。参数上60*30就是30分钟课设演示时想省事可以调大但别取消Session永不过期会被老师扣分。配套的登录拦截用Filter来做最干净注册一个/*的过滤器放行/login、/register、静态资源其余路径检查session里有没有loginUser。WebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; String uri req.getRequestURI(); if (uri.contains(/login.jsp) || uri.contains(/user/login) || uri.contains(/register.jsp) || uri.contains(/user/register) || uri.endsWith(.css) || uri.endsWith(.js) || uri.endsWith(.jpg)) { chain.doFilter(request, response); return; } if (req.getSession().getAttribute(loginUser) null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }这里要强调Filter放行的静态资源列表要完整漏了.jpg会导致图书封面全部加载不出来。另一方面管理员接口记得加白名单之外的权限判断比如AdminServlet在处理请求前检查loginUser.getRole()1否则普通用户直接访问URL就能进后台这是权限控制的常见漏洞。4.2 发布图书图片上传用Servlet 3.0 Part API发布图书是交易系统的起点能出书的模块一定要支持封面图上传。Servlet 3.0起的MultipartConfig配合Part接口比commons-fileupload少引一个依赖代码更干净。WebServlet(/book/add) MultipartConfig(maxFileSize 2 * 1024 * 1024, maxRequestSize 5 * 1024 * 1024) public class AddBookServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); HttpSession session req.getSession(); User loginUser (User) session.getAttribute(loginUser); if (loginUser null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } Book book new Book(); book.setSellerId(loginUser.getId()); book.setTitle(req.getParameter(title)); book.setAuthor(req.getParameter(author)); book.setCategory(req.getParameter(category)); book.setSellPrice(new BigDecimal(req.getParameter(sellPrice))); Part cover req.getPart(cover); if (cover ! null cover.getSize() 0) { String fileName UUID.randomUUID().toString().replace(-, ) getSuffix(cover.getSubmittedFileName()); String savePath req.getServletContext().getRealPath(/upload) File.separator fileName; cover.write(savePath); book.setCover(upload/ fileName); } new BookService().addBook(book); resp.sendRedirect(req.getContextPath() /book/list?category URLEncoder.encode(book.getCategory(), UTF-8)); } private String getSuffix(String name) { return name.substring(name.lastIndexOf(.)); } }逻辑说明上传文件用UUID重命名避免两个用户上传同名图片互相覆盖。保存路径用getRealPath拿到部署目录下的upload文件夹数据库里只存相对路径upload/xxx.jpg页面上直接用${contextPath}/upload/xxx.jpg拼完整地址。这里两个参数要记住maxFileSize限制单张图片2MBmaxRequestSize限制整个请求5MB图书页面上如果还要传多张图5MB够用超过限制Tomcat会抛IllegalStateException前端记得用JS先校验文件类型和大小。参数方面sellPrice用BigDecimal接收不要用double涉及金额的字段用BigDecimal是答辩时的高频考点。前端采集的category如果为空服务端要兜底存“其他”这个校验写在Service的addBook方法开头。4.3 下单扣库存事务边界必须放在Service层下单是整个系统最核心的一段也是最容易拿高分的一段。核心逻辑是把图书状态从0在售改成1已售成功后再插入订单记录。两步必须在一个数据库事务里否则会出现“钱付了书还在卖”或“书卖了订单没生成”的情况。public class OrderService { public boolean createOrder(Order order, int bookId, int buyerId) { Connection conn null; PreparedStatement updateBook null; PreparedStatement insertOrder null; try { conn DbUtil.getConnection(); conn.setAutoCommit(false); // 条件更新只有图书还是“在售”才更新防止并发超卖 updateBook conn.prepareStatement( UPDATE tb_book SET status 1, click_count click_count 0 WHERE id ? AND status 0); updateBook.setInt(1, bookId); int rows updateBook.executeUpdate(); if (rows 0) { conn.rollback(); return false; // 图书已被人买走下单失败 } // 生成订单号并插入订单 order.setOrderNo(String.valueOf(System.currentTimeMillis()) buyerId); insertOrder conn.prepareStatement( INSERT INTO tb_order(order_no, buyer_id, book_id, price, receiver, phone, address, status) VALUES(?,?,?,?,?,?,?,0)); insertOrder.setString(1, order.getOrderNo()); insertOrder.setInt(2, buyerId); insertOrder.setInt(3, bookId); insertOrder.setBigDecimal(4, order.getPrice()); insertOrder.setString(5, order.getReceiver()); insertOrder.setString(6, order.getPhone()); insertOrder.setString(7, order.getAddress()); insertOrder.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { DbUtil.closeQuietly(updateBook, insertOrder, conn); } } }这一段讲清楚两层东西。第一层是事务setAutoCommit(false)之后update和insert要么都成功commit要么都失败rollback这就是数据库事务的ACID里原子性和一致性。第二层是防并发UPDATE语句里带AND status 0在事务提交前会锁住这条记录第二个用户同时下单时更新影响行数是0自然回滚“图书已售”。这两点是答辩时老师最愿意深挖的能讲明白基本就是高分区间。订单状态字段建议用数字状态机0待付款、1待发货、2待收货、3已完成、4已取消每次变更都检查前置状态比如只有0状态的订单能改成4。状态转换的校验放在Service里别放JSP里用超链接随便跳。4.4 DBUtil和MySQL连接参数一次配好不折腾DBUtil是JDBC方案的底座里面最容易出问题的是连接参数。MySQL 8.x必须用com.mysql.cj.jdbc.DriverMySQL 5.7用com.mysql.jdbc.Driver两者别混。public class DbUtil { private static final String URL jdbc:mysql://localhost:3306/book_market ?useSSLfalsecharacterEncodingutf8serverTimezoneAsia/Shanghai; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL驱动未找到请检查WEB-INF/lib, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void closeQuietly(AutoCloseable... resources) { for (AutoCloseable r : resources) { if (r ! null) { try { r.close(); } catch (Exception ignored) { } } } } }参数的三个重点characterEncodingutf8管中文不乱码serverTimezoneAsia/Shanghai管日期类型不出偏差MySQL 8.x不加这个参数直接报错useSSLfalse关掉安全连接告警。驱动Jar包要放到WEB-INF/lib下光在IDEA里Add as Library不够部署到Tomcat时没有一起打进去启动就ClassNotFound。5. 常见问题排查IDEA运行JavaWeb项目配置与编码的翻车现场5.1 现象IDEA配置Tomcat后一访问就是404这是JavaWeb课设里出现频率最高的翻车现场。启动没有报错Tomcat也起来了地址栏一敲localhost:8080就404。原因基本有三类一是IDEA里Run Configuration的Deployment没添加Artifact或者Artifact选成了war而不是war exploded二是Application context设置成了项目名访问路径得带前缀三是WEB-INF下的web.xml版本配置和Servlet注解没扫到。解决顺序打开Run/Debug ConfigurationsDeployment选项卡点加号选Artifact类型选“带exploded的那个”Application context建议直接设成/这样访问根路径就是首页。然后重新Build Artifact。还不行就检查classes输出目录是否包含编译产物IDEA里File Project Structure Modules把项目源码目录标记为Sources输出目录指向target/classes。这一串做完九成404都能救回来。剩下那一种是玄学关掉Tomcat进程重启IDEA的Tomcat配置经常要重启才生效。5.2 现象注册用户时中文全部变成问号数据库里看nickname是“???”而不是中文。这个坑十个人踩九个。原因分两层数据库或表的字符集不是utf8mb4连接字符串没带characterEncodingutf8。两层缺一不可。解决连接URL按4.4那样拼全参数。然后检查MySQL侧flock的建表SQL已经带了DEFAULT CHARSETutf8mb4如果库是早先建的用ALTER DATABASE和ALTER TABLE改。改完表结构还不行把现有乱码数据删掉重新插已写入的问号是恢复不回来的这也是为什么我建议初始化脚本一次性到位。5.3 现象Tomcat启动时ClassNotFound或驱动类不存在ClassNotFound基本分两种com.mysql.jdbc.Driver找不到和javax.servlet的类找不到。第一种是MySQL驱动Jar没进部署包IDEA里右键Jar选Add as Library只是编译期可用运行期还要确保Artifact里包含它。打开Project Structure Artifacts输出目录里必须有WEB-INF/lib/mysql-connector-java-x.jar。用Tomcat 9配了Tomcat 10的依赖也会有javax.servlet相关错误确认你用的还是javax版本不是jakarta版本。第二种是项目把servlet-api.jar塞进了WEB-INF/lib而Tomcat自带这套类库重复放会导致类加载冲突。解决就是把这个Jar从lib里删掉Tomcat的lib目录已经有它了。凡是Tomcat自带的Jar项目里不要重复引。5.4 现象图片上传成功刷新一下就没了图片上传后当时显示正常重启Tomcat再访问就404。这是因为Part.write写到了IDEA部署的临时目录里target目录下的upload在重启时被清掉了。解决别往getRealPath指向的部署目录里存文件。常见做法是配置一个项目外的绝对路径比如D:/bookmarket/upload数据库存upload/文件名页面上再拼绝对路径映射。但课设里改映射麻烦偷懒方案是把upload目录放到src/main/webapp下IDEA的war exploded部署会同步到target重启不清。另一个血泪经验不同机器路径不同老师验收换电脑时要么用相对路径方案要么把upload打包进war不然图全丢。5.5 现象数据库连接超时或连接数耗尽演示到中途报Connection is null或者Communications link failure。原因多半是DBUtil每次getConnection后没关连接或者关的顺序错了。解决所有DAO方法统一用finally关闭关闭顺序ResultSet、PreparedStatement、Connection倒着关。用我上面的closeQuietly工具方法能少写一堆重复代码。连接超时一般是MySQL的wait_timeout默认8小时长时间不操作旧连接失效解决方案是每次getConnection直接用新的连接别自己缓存Connection实例课设阶段不需要连接池。6. 让答辩现场从“会做”变成“懂做”三个加分证据演示的时候先把“注册→登录→发布图书→另一账号下单→卖家发货→确认收货→留言评论”这条主线完整走一遍每个页面停顿两秒让老师看清页面跳转。这条线走完功能分基本到手。第一个加分证据是讲订单表的价格快照。现场指着tb_order里的price字段说“这里没关联图书表原价而是下单时单独存了一份快照因为图书价格后续会改订单需要用当时的成交价结算。”这句话一出口老师就知道你理解“冗余”在交易系统里的作用。第二个加分证据是讲登录拦截和防注入。演示时故意在地址栏输入/login.jsp说“没登录直接访问会被Filter拦回”再说“DAO里全程用PreparedStatement参数化查询不拼接SQL字符串”。这两点属于JavaWeb安全基础很多做框架课设的同学被问住你点出来就是优势。第三个加分证据是给足“工程感”数据库脚本放在sql目录、README里写清楚启动步骤、idea运行JavaWeb项目配置写明白Tomcat版本和JDK版本。这些看着不起眼但老师翻文件时观感完全不同。这套项目做完顺手就把Java面试题里几个重点覆盖了Session生命周期、事务隔离级别、PreparedStatement防SQL注入、BigDecimal精度问题。我去面试遇到答辩问过的问题回答起来完全不虚。说到最后我做JavaWeb课设吃过最大的亏就是只写代码不写文档后来每次都先备份数据库脚本再动手改代码这是后悔药。希望帮到你。本文还有配套的精品资源点击获取