JavaWeb购物车源码:Session链路与事务金额避坑

发布时间:2026/11/2 6:55:53
JavaWeb购物车源码:Session链路与事务金额避坑 简介这是一套面向JavaWeb初学者和进阶学习者的简易购物车项目源码适合用于课程设计、入门练习或小型Web项目参考帮助理解Servlet、JSP、Session与数据库协作完成购物车业务的过程。压缩包共54个文件约1.58MB以14个Java源文件、14个class文件、6个JSP页面为主体并配有properties配置、xml描述、jar依赖、图片及说明文档源码与笔记、数据库资料相对完整。已有4129人学习说明其在JavaWeb入门资料中有一定参考价值。项目围绕商品展示、加入购物车、删除商品和更新数量等操作展开读者可从中观察HTTP请求处理、Session状态保持、JDBC增删改查、MVC分层以及JSTL和EL在页面中的数据绑定方式。配套笔记对关键代码、数据库表设计和安装运行作了说明便于结合源码梳理从页面请求到数据落库的完整流程并留意异常处理、SQL注入防范等细节适合作为理解JavaWeb基础组件与业务流程的实践底稿。1. 先看链路再动手java购物车源码里到底跑着哪几段逻辑搜 java购物车 的人多半不是来听 MVC 概念的而是手里有一个压缩包想知道它能不能跑、购物车逻辑是不是藏在 Session 里、改数量会不会把总价算错。这份 javaWeb 简易购物车源代码走的是典型 Servlet JSP JDBC 路线商品列表从数据库读加入购物车先落 Session改数量和删除走 Servlet结算再写订单表。它适合课程设计、毕业设计原型、JavaWeb 入门练手也适合需要一份能改的购物车底稿的开发者。后面按「先跑通首屏—再看 Session 链路—最后处理事务和金额」的顺序拆重点放在参数、目录和翻车点不铺概念。2. 环境落地从压缩包到登录页的依赖、数据库与部署顺序拿到源码包先别急着点启动。JavaWeb 项目最常见的翻车不是代码写错而是目录认错、依赖放错、数据库连接串和源码里的键名对不上。先花十分钟把目录、依赖、SQL 文件、Web 根目录四件事认清楚比后面盲改一整天有效。2.1 先认目录与依赖pom.xml、WEB-INF/lib 和 SQL 文件谁决定启动方式JavaWeb 源码一般有两种组织方式Maven 结构把页面放在src/main/webapp依赖写在pom.xml老式 Eclipse 结构常见WebContent或WebRoot依赖直接丢在WEB-INF/lib。这两种结构的启动方式不一样先确认属于哪一种再决定是让构建工具拉依赖还是手工补齐 jar。把压缩包解压后先在根目录做一次文件盘点# 在源码根目录执行先找构建文件、Web 配置、SQL 和依赖目录 find . -maxdepth 4 -type f \( -name pom.xml -o -name web.xml -o -name *.sql \) -print find . -maxdepth 4 -type d \( -name webapp -o -name WebContent -o -name WebRoot -o -name lib \) -print这段命令只做定位不改文件。-maxdepth 4是防止在很深的目标里翻太久-name里的三个条件分别对应 Maven 构建文件、Servlet 部署描述符和建表脚本。输出里如果出现pom.xml说明项目大概率用 Maven 管理依赖如果只看到WebContent/WEB-INF/lib说明依赖是手工放进 war 的。Windows 下用资源管理器搜索同名文件也行不必死记命令。接着看 JDK 编译级别。Maven 项目看pom.xml里的maven.compiler.source、maven.compiler.target老项目看.classpath或.settings里的 JRE 容器。编译级别高于本机 JDK 时启动会报UnsupportedClassVersionError这类错误不是代码逻辑问题换 JDK 或改编译级别即可。Servlet API 一般由容器提供不要把它重复打进WEB-INF/libJDBC 驱动则必须让应用能加载到Maven 项目放dependencies非 Maven 项目放WEB-INF/lib。我一般会先把WEB-INF/lib里的 jar 列一遍确认没有两个同名不同版本的驱动也没有把容器自带的 Servlet API 塞进去这一步能省掉很多ClassNotFoundException和类冲突。提示源码包里的包名、表名、字段名都以实际文件为准。下面出现的demo.cart、cart_demo只是占位示例照抄前先和源码对齐。2.2 数据库不是「导入就完事」库名、字符集、账号与连接串对齐数据库这一步最容易出现的误区是「看到 SQL 就执行看到连接串就改密码」结果表建好了DAO 里查的字段却不是这个名。正确顺序是反向的先打开 DAO 或实体类找 SQL 语句里的表名、字段名、占位符再看源码包里的.sql文件是否一致不一致时以 Java 代码里的 SQL 为准或者把两边改成同一套。下面是一份常见的商品表和连接配置示例表名和字段名要按源码替换-- 示例表结构字段名必须和 DAO 里的 SQL 对齐 CREATE DATABASE cart_demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE TABLE product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(120) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 1 ); INSERT INTO product(name, price, stock, status) VALUES (模拟商品A, 19.90, 100, 1);DECIMAL(10,2)不是随便写的金额用定点数后面用BigDecimal接能避开double的尾数问题。status用来做上下架过滤stock用来做加购上限和下单扣减。库名cart_demo、表名product只是示例真正执行前先看源码 DAO 里写的是product、goods还是item字段是price还是unit_price。字符集用utf8mb4不要只改数据库默认字符集而漏掉表和连接串。连接配置一般放在db.properties、jdbc.properties或工具类常量里。键名可能是jdbc.url、url、driver不要自己新造一套键名然后怪源码不生效# 示例键名以源码读取的为准不要直接覆盖原有键 jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://127.0.0.1:3306/cart_demo?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernamecart_user jdbc.passwordchange_mejdbc.driver要跟驱动包的大版本对应老驱动常见com.mysql.jdbc.Driver新驱动常见com.mysql.cj.jdbc.Driver写错会报ClassNotFoundException。URL 里的3306是数据库端口cart_demo是库名useUnicodetrue和characterEncodingUTF-8负责连接层编码serverTimezone影响时间字段useSSLfalse只适合本机练习。账号不要图省事用超级用户建一个只对cart_demo有增删改查权限的账号后面排查连接问题时也更容易定位。如果启动后报No suitable driver先别改代码按顺序查三件事驱动 jar 是否在WEB-INF/lib或 Maven 依赖里、Class.forName的类名是否和驱动大版本一致、URL 是否以jdbc:mysql://开头。可以用一段最小连接测试把问题从 Web 层剥离出来// 最小连接测试先确认驱动和 URL再回去启动 Web 应用 Class.forName(com.mysql.cj.jdbc.Driver); try (Connection conn DriverManager.getConnection( jdbc:mysql://127.0.0.1:3306/cart_demo?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai, cart_user, change_me)) { System.out.println(conn.getMetaData().getURL()); }try-with-resources会自动关闭连接避免测试代码本身把连接池占满。输出能打印出 URL说明驱动、账号、库名这条链路通了如果这里就失败问题不在 JSP 和 Servlet。测试通过后再把同样的 URL 和账号写回配置文件不要一边改 Web 代码一边猜数据库。2.3 部署与首屏验证上下文路径、映射和日志三处同时看部署时先确认 war 包名或项目名它通常决定访问的上下文路径。比如应用部署后叫cart首屏不是http://127.0.0.1:8080/而是http://127.0.0.1:8080/cart/。很多 404 不是 Servlet 没写而是访问地址少了应用名或者 JSP 里的表单 action 写成了绝对根路径。Servlet 映射有两种常见写法web.xml里配servlet-mapping或者 Java 类上用WebServlet。两者选一种不要同一路径重复配!-- 示例包名和 url-pattern 都要与源码一致用注解时删掉这段 -- servlet servlet-namecartServlet/servlet-name servlet-classdemo.cart.web.CartServlet/servlet-class /servlet servlet-mapping servlet-namecartServlet/servlet-name url-pattern/cart/url-pattern /servlet-mappingservlet-class写的是完整包名包名错一个字母就会在部署时抛ClassNotFoundException。url-pattern要和 JSP 表单的action、页面链接对得上如果 JSP 里写${pageContext.request.contextPath}/cart/addServlet 上却只映射了/add请求就会 404。用注解时WebServlet(/cart/add)和web.xml二选一重复映射在不同容器里表现不一样有的直接部署失败有的后配的覆盖前配的排查时很费时间。启动后不要只看首页有没有出来按三处看日志容器启动日志里应用是否部署成功、应用日志里数据库连接是否初始化、浏览器控制台和页面是否报静态资源 404。首屏能出商品列表说明数据库、DAO、JSP 这条线通了首屏 500 但日志里有 SQL 异常回到 2.2 查表名和字段首屏 404 但容器日志显示应用已启动回到上下文路径和映射。把这三处分开看排错就不会变成玄学。3. 购物车核心链路Session 暂存与数据库落单的 Servlet 实现简易购物车源码的核心不在页面多漂亮而在「加购、改数量、删除、算总价」这四个动作有没有统一的数据结构。很多包能打开页面但一改数量就重复加行、一刷新总价就变根子在于购物车用了 List 又没做去重或者金额在 JSP 里用double直接加。先把数据结构定住再写 Servlet后面改起来才不痛苦。3.1 购物车数据模型CartItem、MapLong, CartItem 与 Session 作用域购物车项至少要有商品 ID、商品名、单价、数量四个字段。商品 ID 用Long对应数据库BIGINT单价用BigDecimal对应DECIMAL数量用int并在服务端限制上限。购物车容器我一般用MapLong, CartItem键是商品 ID值是购物车项这样同一商品重复加购时是合并数量不是新增一行。用List也能写但每次加购都要遍历查重改数量还要按索引找后面容易出重复行。public class CartItem implements Serializable { private Long productId; private String name; private BigDecimal price; private int quantity; // 小计在模型里算JSP 只负责显示 public BigDecimal getSubtotal() { return price.multiply(BigDecimal.valueOf(quantity)); } // getter/setter 省略实际源码里补齐 }implements Serializable不是摆设Session 在容器重启、会话钝化时可能要求对象可序列化简易项目单机跑不一定立刻报错但加上更稳。getSubtotal()用price.multiply(BigDecimal.valueOf(quantity))不要用price * quantity这种基本类型运算。quantity的合法范围要在 Servlet 里卡住比如 1 到 99前端min/max只是体验不能当校验。Session 属性名要统一。源码里可能叫cart也可能叫shoppingCart一旦 JSP 读sessionScope.cart、Servlet 写session.setAttribute(shoppingCart, ...)页面就会永远空。我习惯在工具类里放一个常量CART_SESSION_KEY cart所有读写都引用它比散落在 JSP 和 Servlet 里手写字符串可靠。Session 方案适合单机、单浏览器会话的简易购物车如果要做登录后多端同步、长期保存应该把购物车落到数据库表而不是无限往 Session 里堆。3.2 加入、改数量、删除三个 Servlet 的请求参数与幂等边界加入购物车是一个 POST 动作参数通常只有productId和quantity。这里有一条血泪经验价格不要从请求参数里取。前端传price看似方便实际用户可以改正确做法是按productId查数据库拿商品名和单价回填购物车项。下面的AddCartServlet保留了核心逻辑包名和映射按源码调整WebServlet(/cart/add) public class AddCartServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); long productId Long.parseLong(req.getParameter(productId)); int qty Integer.parseInt(req.getParameter(quantity)); // 服务端校验数量非法直接 400不往后走 if (qty 0 || qty 99) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, quantity out of range); return; } SuppressWarnings(unchecked) MapLong, CartItem cart (MapLong, CartItem) req.getSession().getAttribute(cart); if (cart null) { cart new LinkedHashMap(); req.getSession().setAttribute(cart, cart); } CartItem item cart.get(productId); if (item null) { // 实际源码应在这里按 productId 查数据库回填 name 和 price item new CartItem(productId, 待回填, BigDecimal.ZERO, 0); cart.put(productId, item); } int next Math.min(item.getQuantity() qty, 99); item.setQuantity(next); // 重定向避免刷新时重复提交加购 resp.sendRedirect(req.getContextPath() /cart.jsp); } }req.setCharacterEncoding(UTF-8)只对 POST 请求体有效写在读取参数之前。Long.parseLong和Integer.parseInt遇到空值或非数字会抛异常实际项目里要包一层校验或统一异常处理别让 500 直接暴露给用户。Math.min(..., 99)是服务端上限前端max被绕过时仍然有效。最后用sendRedirect而不是forward刷新页面不会重复执行加购如果业务要求防重复点击还要加 token 或按钮禁用单靠重定向只能解决刷新重复提交。改数量和删除通常共用一个/cart/update或分开两个 Servlet。核心参数是productId和新的quantity。数量小于等于 0 时可以直接移除该项大于上限时截断。删除就是按productId从 Map 里remove然后重定向回购物车页。这里要注意无论改数量还是删除都要先判断 Session 里有没有cart不能直接getAttribute(...).get(...)否则空会话访问会 NPE。// 改数量片段quantity 0 视为删除避免页面出现 0 件商品 long productId Long.parseLong(req.getParameter(productId)); int qty Integer.parseInt(req.getParameter(quantity)); MapLong, CartItem cart (MapLong, CartItem) req.getSession().getAttribute(cart); if (cart ! null cart.containsKey(productId)) { if (qty 0) { cart.remove(productId); } else { cart.get(productId).setQuantity(Math.min(qty, 99)); } } resp.sendRedirect(req.getContextPath() /cart.jsp);这段片段没有查数据库因为改数量和删除只动 Session 里的数量不再信任价格。参数productId必须和加购时用的键类型一致一个是Long一个用long自动装箱也能匹配但不要一边存字符串一边用 Long 取。containsKey先判断再取能避开空指针Math.min(qty, 99)和加购保持同一上限避免两处规则不一致。3.3 JSP 渲染与金额计算别在页面里做业务判断购物车页面只做三件事遍历 Session 里的购物车、显示小计、提交改数量和删除表单。遍历用 JSTL 的c:forEach金额格式化用fmt:formatNumber不要在 JSP 里写 Java 脚本片段的加减乘除。下面的片段假设 Session 属性名是cart并且CartItem有对应的 getter% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % c:forEach items${sessionScope.cart.values} varitem tr td${item.name}/td tdfmt:formatNumber value${item.price} pattern0.00//td td form action${pageContext.request.contextPath}/cart/update methodpost input typehidden nameproductId value${item.productId} input typenumber namequantity value${item.quantity} min1 max99 button typesubmit改数量/button /form /td tdfmt:formatNumber value${item.subtotal} pattern0.00//td /tr /c:forEachsessionScope.cart.values依赖cart是 Map如果源码里是 List就要改成直接遍历 List。fmt:formatNumber只负责显示两位小数不改变底层BigDecimal的精度。表单 action 用${pageContext.request.contextPath}拼上下文路径应用名变化时不用改页面。min和max是浏览器约束服务端仍然要校验别把安全边界放在 HTML 属性上。总价建议在 Servlet 或服务层算好放进请求域再给 JSP 显示而不是在 JSP 里循环累加。JSP 里做业务判断会让后面加优惠、运费、库存校验时无处下手。如果源码已经在 JSP 里用 EL 求和先别急着大改至少确认它没有用double有double的地方逐步换成BigDecimal这是后面第 5 章要处理的边界。4. 避坑排查五个高频翻车点从现象追到解决简易购物车源码的坑很集中路径、依赖、编码、Session、金额。下面五条都是实际排错时反复出现的现象每条按「现象 → 原因 → 解决」走看到类似日志可以直接对号入座。4.1 启动与路径类404、空白页、依赖缺失现象一容器启动正常访问根路径 404访问/cart也 404。原因通常不是 Servlet 没编译而是上下文路径不对。war 包名或项目名决定应用访问前缀根路径/访问的是容器默认页不是你的应用。另一种情况是web.xml的url-pattern和 JSP 表单 action 不一致比如页面提交/cart/addServlet 只映射了/add。还有一种是注解和web.xml同时配了同一路径部署日志里出现映射冲突应用没起来。解决先看容器启动日志里的应用上下文按http://127.0.0.1:端口/应用名/访问再打开浏览器开发者工具看表单实际提交到哪个 URL最后检查WebServlet和web.xml是否重复。页面里的链接统一用${pageContext.request.contextPath}拼不要写死/cart。现象二JSP 能打开一点加购就 500日志里ClassNotFoundException或NoClassDefFoundError。原因多半是依赖没进WEB-INF/lib或者 Maven 依赖 scope 写错。Servlet API 由容器提供如果被打进 war可能和容器自带版本冲突JSTL 标签库缺失时页面上的c:forEach会原样输出或直接报错。JDBC 驱动缺失则会在连接数据库时炸。解决先看WEB-INF/lib里有没有驱动和 JSTLMaven 项目跑一次依赖树确认 Servlet API 是provided再看容器日志第一个Caused by不要只看最后一行。改完依赖后清一次容器工作目录和编译输出避免旧 class 残留。这个坑我翻过不止一次后来养成习惯每次换依赖先清 work 目录再启动。4.2 数据与状态类乱码、Session 丢失、金额错乱现象三商品名显示问号表单提交中文变乱码。原因不是单一位置而是 JSP 的pageEncoding、响应contentType、请求setCharacterEncoding、JDBC URL 的characterEncoding、数据库表和字段字符集五处不一致。只改 JSP 头数据库连接仍可能用旧编码只改连接串GET 查询串又可能按容器默认编码解。解决加一个统一编码过滤器把请求和响应都设为 UTF-8JSP 头写contentTypetext/html;charsetUTF-8和pageEncodingUTF-8JDBC URL 带useUnicodetruecharacterEncodingUTF-8建库建表用utf8mb4。改完重启应用不要只刷新页面连接池里的旧连接可能还带着旧编码。现象四加入购物车后刷新数量翻倍或者换个页面购物车就空了。数量翻倍通常是加购后用了forward刷新时浏览器重复提交最后一次 POST也可能是按钮没有防重复点击。购物车空了通常是 Session 属性名不一致Servlet 写shoppingCartJSP 读cart或者用了请求域request.setAttribute重定向后请求域丢了。浏览器禁用 cookie 时JSESSIONID带不上每次请求都是新会话也会表现为加购不保存。解决加购成功后用sendRedirect重定向到购物车页Session 键名统一成常量检查浏览器是否禁 cookie需要防重复提交时加 token 或提交后禁用按钮。先看响应头有没有Set-Cookie: JSESSIONID再看服务端日志里 Session ID 是否每次变化能快速区分是业务问题还是会话问题。现象五总价出现0.30000000000000004或者库存扣成负数。金额尾数异常一般是double或float参与计算JSP 里用 EL 直接加也会踩到库存负数则是先查库存再扣减中间没有事务和条件更新并发下单时两个请求都查到有库存最后都扣。解决金额字段用DECIMALJava 用BigDecimalsetScale(2, RoundingMode.HALF_UP)统一两位库存扣减用UPDATE product SET stock stock - ? WHERE id ? AND stock ?看受影响行数是否为 0为 0 就回滚并提示库存不足。数量和库存都要在服务端校验前端min/max只能算体验。注意排错时先看第一个异常不要从最后一行ServletException开始猜。容器日志里的Caused by往往才是根因。5. 改得动才算能用过滤器、事务与金额精度的参数边界源码能跑只是第一步真正要用还得改得动。简易购物车最容易在三个地方失控编码和登录判断散落在每个 JSP、下单扣库存没有事务、金额和分页参数随手拼字符串。把这三处收口后面加优惠、加订单列表、加库存校验才有地方下手。5.1 编码与登录过滤器把重复判断从 JSP 挪到 Filter编码过滤器要放在所有请求前面统一设置请求和响应字符集。WebFilter(/*)表示拦截所有路径req.setCharacterEncoding(UTF-8)只对 POST 请求体生效响应字符集要和 JSP 的contentType配合。不要在每个 Servlet 里重复写这两行后面漏一个就是乱码。WebFilter(/*) public class EncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); resp.setCharacterEncoding(UTF-8); chain.doFilter(req, resp); } }chain.doFilter之前的代码在请求进入 Servlet 前执行之后的代码在响应返回后执行。编码设置要放在chain.doFilter之前否则参数已经按默认编码解析完再设就晚了。响应字符集设了之后JSP 里仍要写contentType两者不冲突。过滤器顺序如果和登录过滤器同时存在编码过滤器应该排在前面避免登录校验读取参数时已经乱码。登录过滤器解决的是「未登录也能直接访问购物车和下单」的问题。简易源码可能没有登录或者只在 JSP 里判断 Session这种判断容易被绕过。过滤器里取 Session 时用getSession(false)没有会话就不要新建WebFilter(/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String path request.getRequestURI().substring(request.getContextPath().length()); // 登录页、静态资源、登录接口放行其余路径检查会话 boolean open path.startsWith(/login) || path.startsWith(/static/); HttpSession session request.getSession(false); if (!open (session null || session.getAttribute(loginUser) null)) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }getRequestURI()包含上下文路径先减掉getContextPath()再判断否则应用名里带login字样会误放行。放行列表按实际项目调整CSS、JS、图片这些静态资源如果被拦登录页会样式丢失。session.getAttribute(loginUser)的键名要和登录 Servlet 写入时一致。过滤器里不要做重业务查询只做会话判断避免每个请求都查一次数据库。5.2 下单事务与库存扣减JDBC 自动提交的坑与手动回滚从购物车到下单至少涉及扣库存和写订单两张表。JDBC 默认自动提交两个executeUpdate各自提交第一步成功、第二步失败时库存已经扣了订单却没写。正确做法是把setAutoCommit(false)放在服务层所有 DAO 共用同一个连接成功一起提交异常一起回滚。Connection conn null; try { conn dataSource.getConnection(); conn.setAutoCommit(false); // 条件更新库存足够才扣受影响行数为 0 说明库存不足 int rows updateStock(conn, productId, qty); if (rows 0) { conn.rollback(); resp.sendError(409, 库存不足); return; } insertOrder(conn, userId, productId, qty); conn.commit(); } catch (Exception e) { if (conn ! null) conn.rollback(); throw new ServletException(e); } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } }dataSource.getConnection()拿到的连接必须在整个事务里复用不能updateStock里开一个连接、insertOrder里再开一个否则两个连接各自提交事务边界就散了。updateStock的 SQL 要带AND stock ?用受影响行数判断是否扣成功不要先SELECT stock再判断再更新。finally里恢复自动提交再关闭连接回到池里时状态才干净。异常分支里rollback之后不要继续写响应抛出去交给统一错误页处理更稳。金额在事务里也要统一精度。订单表金额字段用DECIMALJava 里setScale(2, RoundingMode.HALF_UP)不要一半double一半BigDecimal。库存扣减和订单写入如果跨多个商品要把循环放在同一个事务里任何一行失败整体回滚不能扣了 A 商品、B 商品失败还继续提交。5.3 金额精度、分页与搜索BigDecimal、LIMIT 参数化的边界金额计算用BigDecimal之后还要注意比较和除法。equals会比较 scale1.0和1.00不相等金额比较用compareTo。除法要指定精度和舍入模式否则除不尽会抛ArithmeticException。下面是一个常用的金额工具片段BigDecimal price new BigDecimal(19.90); BigDecimal qty new BigDecimal(3); BigDecimal total price.multiply(qty).setScale(2, RoundingMode.HALF_UP); // 比较金额用 compareTo不要用 equals if (total.compareTo(new BigDecimal(100.00)) 0) { // 满足满减门槛 }new BigDecimal(19.90)用字符串构造不要用new BigDecimal(19.90)后者会把二进制浮点误差带进来。setScale(2, RoundingMode.HALF_UP)明确两位小数和四舍五入规则财务场景要看业务要求选舍入模式。compareTo返回负数、0、正数分别表示小于、等于、大于判断门槛时不要写equals。商品列表的分页和搜索参数也要收口。page和size要在服务端限制范围size不能由前端无限传大LIMIT后面的占位符用PreparedStatement设置不要字符串拼接。搜索关键字用LIKE时把%拼在参数值里不要拼在 SQL 里int page Math.max(1, Integer.parseInt(req.getParameter(page))); int size Math.min(50, Math.max(1, Integer.parseInt(req.getParameter(size)))); int offset (page - 1) * size; String sql SELECT id, name, price, stock FROM product WHERE status 1 AND name LIKE ? ORDER BY id DESC LIMIT ?, ?; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ps.setInt(2, offset); ps.setInt(3, size); // 执行查询并映射结果集 }page小于 1 时归一到 1size限制在 1 到 50避免一次拉全表。offset由页码和每页条数算出来数据量大时深分页会慢简易项目够用。LIKE的参数值里拼%SQL 模板里保留?这样关键字里的特殊字符不会变成 SQL 语法。排序字段如果允许用户传要用白名单映射不能直接把请求参数拼进ORDER BY。6. 验收与二次开发用三条断言把简易购物车跑成可交付样例改完代码别急着交付先用三条断言压一遍。第一条全新会话打开商品列表加购同一商品两次购物车只出现一行数量为两次之和本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询