恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Servlet+JSP全栈实践:夯实Java Web底层原理与开发流程

  • 首页
  • 资讯中心
  • /
  • Servlet+JSP全栈实践:夯实Java Web底层原理与开发流程

相关资讯

MATLAB相场模拟实操:从零搭建选择性激光烧结多颗粒模型 2026/10/13 4:45:53
Element Plus表格禁用勾选:selectable实现行级批量审批控制 2026/10/13 4:45:53
Java内卷观察:从八股文到微服务,我们为何越卷越累? 2026/10/13 4:40:53

最新资讯

AI论文工具测评:继续教育学员从选题到答辩的实战攻略
MACE 问题反馈指南:从 Issue 提交规范到系统化排障实践
GB/T4857.10标准解析:振动测试保障物流运输安全
Electron项目结构规划:主进程、预加载与IPC通信的职责划分
NSSM 2.24实战:把任意exe/bat脚本注册为稳定的Windows服务
PSO优化SVR超参数:风电功率预测回归拟合实战

今日推荐

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析
CAN总线错误处理机制深度解析:从位错误到Bus Off的硬件级调试

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

Servlet+JSP全栈实践:夯实Java Web底层原理与开发流程

发布时间:2026/10/13 4:45:53
Servlet+JSP全栈实践:夯实Java Web底层原理与开发流程 1. 项目概述一个看似“老派”却极富教学价值的全栈闭环实践你点开这个标题第一反应可能是“ServletJSPTomcat这不都是十年前的技术栈吗”——没错单看技术名词它确实带着浓重的教科书气息。但恰恰是这套被很多新人绕着走、甚至被部分面试官轻描淡写带过的组合构成了Java Web开发最底层、最扎实的“肌肉记忆”。它不依赖Spring Boot自动装配的魔法不靠前端框架封装好的API调用而是把HTTP请求怎么来、响应怎么回、数据库连接怎么建、数据怎么从一行SQL变成页面上一个表格里的文字全部掰开揉碎一环扣一环地摆在你面前。我带过不少刚从培训班出来的同学他们能熟练写MyBatis注解、能配置Spring Cloud网关但一问“request.setAttribute()和session.setAttribute()的区别在哪儿为什么JSP里用EL表达式取值要加${}而不是直接写变量名”很多人卡壳。这不是知识盲区而是对Web基础链路理解的断层。这个“用户信息列表展示”案例就是一块磨刀石它用最朴素的Servlet接收请求用最原始的JDBC Template管理连接背后是Druid连接池用BeanUtils完成对象属性映射最后用JSPJSTL在浏览器端渲染出一张干净的表格。整个过程没有黑盒每一步你都能在控制台看到日志在数据库里查到记录在浏览器Network面板里抓到完整的HTTP报文。它解决的不是“如何快速上线一个高并发系统”的问题而是“当所有高级框架都失效时你是否还知道Web的本质是什么”这个根本命题。适合谁适合所有想真正搞懂Java Web底层逻辑的初学者、转行者也适合那些已经会用Spring但想补全知识图谱的中级开发者。它不教你炫技只帮你把地基夯得再实一点。2. 整体架构设计与技术选型逻辑拆解2.1 为什么是ServletJSP而不是Spring MVC或Thymeleaf这个问题我每次讲到这个案例都会被问到。答案很实在可控性与教学透明度。Spring MVC的DispatcherServlet像一个精密的瑞士钟表它内部有HandlerMapping、HandlerAdapter、ViewResolver等十几种组件协同工作。你配好Component和RequestMapping请求就到了但中间发生了什么参数怎么绑定的ModelAndView怎么被解析成HTML的这些细节被层层封装对新手而言是“方便”但也是“迷雾”。而Servlet就是一个纯粹的Java类它只做一件事实现service()方法接收HttpServletRequest和HttpServletResponse两个对象。你必须手动调用request.getParameter(id)去取参数必须手动调用response.getWriter().write(html...)去写响应。这种“笨办法”强迫你直面HTTP协议本身。JSP同理。它本质是一个Servlet的语法糖JSP文件在第一次被访问时会被Tomcat的Jasper引擎编译成一个继承自HttpServlet的Java类。你在JSP里写的% user.getName() %最终会变成编译后Java类里的out.print(user.getName())。这意味着你写的每一行JSP都能在work/Catalina/localhost/yourapp/org/apache/jsp/目录下找到对应的.java源码。这种“所见即所得”的可追溯性是任何现代模板引擎都难以提供的教学优势。选择它不是守旧而是为了让你在学“开车”之前先亲手拆装一遍发动机。2.2 Druid连接池为何不可替代JDBC Template又解决了什么痛点很多人初学JDBC写的是这样的代码Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DriverManager.getConnection(url, user, pwd); ps conn.prepareStatement(SELECT * FROM user WHERE id ?); ps.setLong(1, userId); rs ps.executeQuery(); // 处理结果集... } catch (SQLException e) { e.printStackTrace(); } finally { if (rs ! null) rs.close(); if (ps ! null) ps.close(); if (conn ! null) conn.close(); // 这里极易出错 }这段代码的问题在于资源泄漏风险极高。close()方法可能抛出SQLException如果在finally块里再嵌套try-catch代码会变得极其臃肿。更致命的是DriverManager.getConnection()每次都会创建一个全新的物理连接而数据库连接是昂贵的资源频繁创建销毁会拖垮性能。Druid正是为了解决这两个问题而生。它不是一个简单的连接池而是一个集监控、防御、统计于一体的数据库连接管理器。它内部维护了一个连接池默认初始8个最大20个当你调用dataSource.getConnection()时它返回的不是物理连接而是一个DruidPooledConnection的代理对象。这个代理对象在你调用close()时并不会真的关闭物理连接而是将连接归还给池子供下一次复用。同时Druid内置了SQL防火墙能自动拦截 OR 11这类注入语句它还能实时监控慢SQL、连接泄漏、SQL执行时间分布这些数据都能通过一个内置的Web控制台http://localhost:8080/druid直观看到。而JDBC Template则是Spring对JDBC的一次优雅封装。它把上面那段冗长的try-catch-finally模板代码抽象成了一个execute()、query()、update()等方法。你只需要关心SQL和参数资源的获取、释放、异常转换将SQLException转为Spring的DataAccessException全部由它代劳。比如查询用户列表一行代码搞定ListUser users jdbcTemplate.query(SELECT * FROM user, new BeanPropertyRowMapper(User.class));这里BeanPropertyRowMapper就是利用反射自动将ResultSet的每一列按名字映射到User类的同名属性上。没有手动rs.getLong(id)、rs.getString(name)的繁琐。所以Druid JDBC Template的组合是“基础设施稳定可靠”与“业务代码简洁清晰”的完美平衡。2.3 BeanUtils那个被低估的“对象搬运工”在Web开发中一个高频场景是用户提交一个表单比如注册页后端需要把表单的name、email、age等参数赋值给一个User对象的对应属性。最原始的做法是User user new User(); user.setName(request.getParameter(name)); user.setEmail(request.getParameter(email)); user.setAge(Integer.parseInt(request.getParameter(age)));这不仅重复、易错类型转换容易抛NumberFormatException而且一旦User类的属性增多代码量会指数级增长。Apache Commons BeanUtils库就是为了解决这个“对象间属性拷贝”的通用问题。它的核心方法BeanUtils.copyProperties(dest, orig)能通过Java反射机制自动遍历orig对象的所有getter方法再在dest对象上寻找同名的setter方法完成赋值。它甚至能处理嵌套属性如user.getAddress().getCity()和类型转换自动将字符串25转为int。在这个案例里它被用于两个关键环节一是将HttpServletRequest中的参数一键填充到User对象用于新增或修改二是将数据库查询出的User对象列表传递给JSP页面时JSP里的EL表达式${user.name}能直接取值这背后依赖的正是JavaBean规范——User类必须有getName()和setName(String name)方法而BeanUtils正是基于此规范工作的。它不是一个炫酷的框架但它像空气一样无处不在让枯燥的“搬数据”工作变得无声无息。2.4 Tomcat不只是容器更是你的第一个“生产环境”模拟器很多初学者把Tomcat当成一个“运行Java Web项目的工具”仅此而已。但如果你深入进去会发现它本身就是一本活的HTTP服务器教科书。当你把一个user-list.war包丢进webapps目录Tomcat会自动解压、加载web.xml或扫描WebServlet注解、初始化ServletContext、启动Filter链、最后将请求路由到你的UserListServlet。这个过程完整复现了一个真实Web服务器的生命周期。更重要的是Tomcat的配置文件server.xml让你能亲手调整HTTP端口默认8080、设置虚拟主机、配置SSL证书虽然本案例不用、甚至定义不同的Connector来支持BIO/NIO/APR三种I/O模型。对于学习者而言调试Tomcat的过程就是调试整个Web请求链路的过程。你可以在logging.properties里把日志级别调成FINE然后在catalina.out里看到每一个请求的详细处理步骤你可以在web.xml里配置error-page观察404、500错误是如何被统一捕获和展示的。它不像Docker或K8s那样复杂但它足够真实足够让你建立起对“一个Web应用从启动到响应”的完整心智模型。把它当作一个“微型生产环境”来对待远比把它当成一个“一键启动的黑盒子”有价值得多。3. 核心模块详解与实操要点3.1 数据库设计从ER图到建表语句的落地思考一个看似简单的“用户信息列表”其数据库设计却暗藏玄机。我们不能只想着“建个user表放id、name、email就行”。实际开发中你需要考虑扩展性、一致性、可读性。我通常会这样设计字段名类型是否为空默认值注释idBIGINT UNSIGNEDNOT NULL-主键自增用BIGINT是为了未来数据量大时避免溢出usernameVARCHAR(50)NOT NULL用户名唯一索引不允许空字符串emailVARCHAR(100)NOT NULL邮箱唯一索引需校验格式passwordVARCHAR(100)NOT NULL密码必须加密存储MD5/BCrypt绝不存明文real_nameVARCHAR(20)NULLNULL真实姓名允许为空ageTINYINT UNSIGNEDNULLNULL年龄用TINYINT足够0-255statusTINYINTNOT NULL1状态1-启用0-禁用便于软删除create_timeDATETIMENOT NULLCURRENT_TIMESTAMP创建时间自动填充update_timeDATETIMENOT NULLCURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP更新时间自动更新建表语句如下MySQL 5.7CREATE TABLE user ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 主键ID, username varchar(50) NOT NULL DEFAULT COMMENT 用户名, email varchar(100) NOT NULL DEFAULT COMMENT 邮箱, password varchar(100) NOT NULL DEFAULT COMMENT 密码加密后, real_name varchar(20) DEFAULT NULL COMMENT 真实姓名, age tinyint(3) unsigned DEFAULT NULL COMMENT 年龄, status tinyint(1) NOT NULL DEFAULT 1 COMMENT 状态1-启用0-禁用, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) USING BTREE, UNIQUE KEY uk_email (email) USING BTREE, KEY idx_status (status) USING BTREE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户信息表;提示utf8mb4是必须的它支持完整的Unicode字符包括emoji。而旧的utf8在MySQL里其实是utf8mb3不支持4字节字符会导致插入微信昵称含emoji时报错。USING BTREE显式指定索引类型是良好习惯。为什么要有status字段而不是直接DELETE这是“软删除”的经典实践。物理删除会丢失历史数据且无法恢复。而将status设为0查询时加上WHERE status 1既保证了数据安全又不影响业务逻辑。create_time和update_time的自动填充省去了在Java代码里手动设置时间的麻烦且保证了时间戳的准确性由数据库服务器提供而非应用服务器。3.2 Druid连接池配置从XML到Java Config的平滑过渡Druid的配置有两种主流方式XML传统和Java ConfigSpring风格。本案例采用XML因为它更符合Servlet/JSP时代的原生风格也更容易被初学者理解。配置文件druid.properties放在src/main/resources目录下# 数据库连接基本信息 driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/your_database?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalse usernameroot password123456 # 连接池核心参数 initialSize5 minIdle5 maxActive20 maxWait60000 timeBetweenEvictionRunsMillis60000 minEvictableIdleTimeMillis300000 validationQuerySELECT 1 testWhileIdletrue testOnBorrowfalse testOnReturnfalse # 配置监控统计拦截的filters去掉后监控界面sql无法统计 filtersstat,wall,log4j # 配置slowSqlMonitor slowSqlMillis1000关键参数解读initialSize5应用启动时池子里就准备好5个连接避免首次请求时的连接创建延迟。minIdle5池中最小空闲连接数。当空闲连接少于5个时Druid会自动创建新连接补充。maxActive20池中最大活跃连接数。超过20个请求后续请求会进入等待队列直到有连接被释放。timeBetweenEvictionRunsMillis60000每60秒运行一次“空闲连接回收”任务检查哪些连接空闲太久minEvictableIdleTimeMillis300000即5分钟并将其关闭。validationQuerySELECT 1这是健康检查SQL。Druid会定期执行它确保连接还活着。MySQL用SELECT 1Oracle用SELECT 1 FROM DUAL。filtersstat,wall,log4j这是Druid的灵魂。stat开启监控统计wall是SQL防火墙能防御基本SQL注入log4j将日志输出到Log4j。注意serverTimezoneAsia/Shanghai是必须的否则Java程序读取MySQL的DATETIME字段时会因时区不一致导致时间错乱比如数据库存的是2023-10-01 10:00:00Java里读出来却是2023-10-01 18:00:00。allowPublicKeyRetrievaltrueuseSSLfalse是MySQL 8.0驱动的新要求用于简化本地开发的SSL配置。在UserDao类中我们通过DruidDataSourceFactory.createDataSource()方法加载这个配置public class UserDao { private static DataSource dataSource; static { try { // 加载druid.properties文件 Properties props new Properties(); InputStream is UserDao.class.getClassLoader().getResourceAsStream(druid.properties); props.load(is); // 创建数据源 dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new RuntimeException(Druid DataSource init failed, e); } } // 后续的query、update方法都使用这个dataSource }这个静态代码块保证了dataSource在UserDao类第一次被加载时就初始化完成且全局唯一符合连接池的设计初衷。3.3 Servlet核心逻辑从URL映射到MVC分层的演进UserListServlet是整个流程的入口。它的职责非常明确接收HTTP GET请求调用DAO获取数据将数据存入request域然后转发forward到JSP页面。代码结构如下WebServlet(/user/list) public class UserListServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 调用DAO查询所有用户 ListUser userList userDao.findAll(); // 2. 将数据存入request域供JSP页面使用 request.setAttribute(userList, userList); // 3. 请求转发到user_list.jsp注意是forward不是redirect request.getRequestDispatcher(/WEB-INF/jsp/user_list.jsp).forward(request, response); } }这里有几个关键点必须厘清WebServlet(/user/list)这是一个注解它告诉Tomcat当收到/user/list路径的请求时就交给这个Servlet处理。它替代了古老的web.xml配置是Servlet 3.0的标准。request.setAttribute()vsrequest.getSession().setAttribute()前者是将数据绑定到当前这次HTTP请求的生命周期内数据只在本次请求-响应周期有效转发forward时可以传递给下一个资源JSP。后者是将数据绑定到用户的Session会话中数据会一直存在直到Session超时或被手动销毁。列表展示是典型的“一次请求一次展示”用request域完全足够且更安全避免数据污染Session。forward()vsredirect()forward()是服务器端的内部跳转浏览器地址栏的URL不会改变且request域中的数据可以传递过去。redirect()是客户端重定向服务器会返回一个302状态码和新的Location头浏览器会发起第二次请求因此request域数据会丢失。本案例必须用forward()否则JSP页面拿不到userList。实操心得我见过太多同学在这里踩坑。他们用response.sendRedirect(/user_list.jsp)结果JSP页面里${userList}取不到值然后疯狂查EL表达式语法其实根源在跳转方式错了。记住一个口诀“数据要传就用forwardURL要变才用redirect”。3.4 JSP页面渲染从原始HTML到JSTLEL的现代化写法user_list.jsp位于/WEB-INF/jsp/目录下放在WEB-INF里是为了安全防止用户直接通过URL访问JSP源码。它的核心任务是将request域里的userList集合渲染成一个HTML表格。早期的JSP写法是混杂Java代码的% ListUser userList (ListUser) request.getAttribute(userList); % table trthID/thth用户名/thth邮箱/thth操作/th/tr % for (User user : userList) { % tr td% user.getId() %/td td% user.getUsername() %/td td% user.getEmail() %/td tda hrefdelete?id% user.getId() %删除/a/td /tr % } % /table这种写法的问题是Java代码和HTML严重耦合难以维护且容易产生XSS漏洞如果user.getUsername()里包含script标签就会被执行。现代JSP的最佳实践是彻底剥离Java代码只用JSTLJSP Standard Tag Library和ELExpression Language。首先在JSP顶部声明taglib% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt %然后用JSTL的c:forEach标签遍历集合table border1 thead tr thID/th th用户名/th th邮箱/th th真实姓名/th th年龄/th th状态/th th创建时间/th th操作/th /tr /thead tbody c:choose c:when test${empty userList} trtd colspan8 aligncenter暂无用户数据/td/tr /c:when c:otherwise c:forEach items${userList} varuser tr td${user.id}/td td${user.username}/td td${user.email}/td td${user.realName}/td td${user.age}/td td c:if test${user.status 1}启用/c:if c:if test${user.status 0}禁用/c:if /td tdfmt:formatDate value${user.createTime} patternyyyy-MM-dd HH:mm:ss//td td a hrefdelete?id${user.id}删除/a nbsp;|nbsp; a hrefedit?id${user.id}编辑/a /td /tr /c:forEach /c:otherwise /c:choose /tbody /table这里体现了几个重要思想c:forEach替代了for循环items指向EL表达式${userList}var定义了循环变量user在循环体内就可以用${user.xxx}取值。c:choose/c:when/c:otherwise替代了if-else用于条件判断。test${empty userList}是JSTL的空值判断比userList null || userList.size() 0更简洁安全。fmt:formatDate专门用于日期格式化。value是Date对象pattern指定了显示格式。这比在Java代码里用SimpleDateFormat格式化后再传给JSP要优雅得多。EL表达式的自动转义${user.username}默认会对特殊字符如,进行HTML转义输出为lt;、gt;从而天然防御XSS攻击。这是JSP 2.0的默认行为无需额外配置。注意JSTL库需要在pom.xml中引入dependency groupIdjstl/groupId artifactIdjstl/artifactId version1.2/version /dependency否则JSP页面会报org.apache.jasper.JasperException: The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved错误。4. 完整实操流程与关键环节实现4.1 项目结构搭建Maven骨架与目录约定一个规范的Java Web项目其目录结构是工程化的第一步。我们使用Maven来管理依赖和构建项目结构如下user-management/ ├── pom.xml # Maven核心配置文件 ├── src/ │ ├── main/ │ │ ├── java/ # Java源代码 │ │ │ └── com/example/web/ # Servlet、DAO、实体类 │ │ │ ├── servlet/ │ │ │ │ └── UserListServlet.java │ │ │ ├── dao/ │ │ │ │ └── UserDao.java │ │ │ ├── domain/ │ │ │ │ └── User.java # JavaBean实体类 │ │ │ └── util/ │ │ │ └── JdbcUtils.java # 可选自定义JDBC工具类 │ │ ├── resources/ # 配置文件 │ │ │ ├── druid.properties # Druid连接池配置 │ │ │ └── log4j.properties # 日志配置 │ │ └── webapp/ # Web资源根目录 │ │ ├── WEB-INF/ │ │ │ ├── web.xml # 可选Servlet 2.5的部署描述符 │ │ │ └── jsp/ # JSP页面存放目录 │ │ │ ├── user_list.jsp │ │ │ └── user_edit.jsp │ │ └── static/ # 静态资源CSS, JS, 图片 │ │ ├── css/ │ │ │ └── style.css │ │ └── js/ │ │ └── common.js │ └── test/ # 单元测试代码 └── target/ # Maven编译输出目录自动生成pom.xml是整个项目的“心脏”它定义了项目坐标、依赖、插件和构建配置。以下是本案例的核心依赖片段dependencies !-- Servlet API -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope !-- provided表示由Tomcat提供打包时不包含 -- /dependency !-- JSP API -- dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency !-- JSTL -- dependency groupIdjstl/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- MySQL Driver -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- Druid Connection Pool -- dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.20/version /dependency !-- Spring JDBC Template -- dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version5.3.31/version /dependency !-- Apache Commons BeanUtils -- dependency groupIdcommons-beanutils/groupId artifactIdcommons-beanutils/artifactId version1.9.4/version /dependency !-- Log4j for logging -- dependency groupIdlog4j/groupId artifactIdlog4j/artifactId version1.2.17/version /dependency /dependencies build plugins !-- Maven Compiler Plugin: 指定Java版本 -- plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.11.0/version configuration source8/source target8/target /configuration /plugin !-- Maven War Plugin: 打包成WAR文件 -- plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-war-plugin/artifactId version3.3.2/version /plugin /plugins /build关键说明scopeprovided/scope用于javax.servlet-api和javax.servlet.jsp-api。因为Tomcat本身就提供了这些API的实现如果打包时也包含进去会导致类冲突ClassCastException。Maven会识别这个scope在编译和测试时提供该依赖但在最终的WAR包中不包含它。4.2 User实体类严格遵循JavaBean规范的编写User.java是整个数据流转的载体它必须严格遵守JavaBean规范否则BeanUtils和JSP EL都无法正常工作。规范的核心是私有属性private公共的无参构造方法public User()每个属性都有对应的公共getter和setter方法public String getName(), public void setName(String name)getter/setter方法的命名必须符合驼峰规则package com.example.web.domain; import java.util.Date; // 必须有无参构造方法 public class User { private Long id; private String username; private String email; private String password; private String realName; private Integer age; private Integer status; private Date createTime; private Date updateTime; // 无参构造方法必须有 public User() {} // getter和setter方法必须一一对应且首字母大写 public Long getId() { return id; } public void setId(Long id) { this.id id; } public String getUsername() { return username; } public void setUsername(String username) { this.username username; } public String getEmail() { return email; } public void setEmail(String email) { this.email email; } public String getPassword() { return password; } public void setPassword(String password) { this.password password; } public String getRealName() { return realName; } public void setRealName(String realName) { this.realName realName; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age age; } public Integer getStatus() { return status; } public void setStatus(Integer status) { this.status status; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime createTime; } public Date getUpdateTime() { return updateTime; } public void setUpdateTime(Date updateTime) { this.updateTime updateTime; } // 可选重写toString()方便调试 Override public String toString() { return User{ id id , username username \ , email email \ }; } }注意Integer和Long是包装类它们可以为null而int和long是基本类型不能为null。在数据库中age和status字段允许为NULLNULLABLE所以Java中必须用包装类否则ResultSet.getInt(age)遇到NULL时会抛出SQLException。Date类型同理对应数据库的DATETIME。4.3 UserDao实现JDBC Template的实战应用UserDao是数据访问层它封装了所有与数据库交互的逻辑。我们使用JdbcTemplate来简化JDBC操作。JdbcTemplate本身不管理连接它需要一个DataSource。我们已经在UserDao的静态代码块中初始化了Druid的DataSource现在只需在构造方法中注入它package com.example.web.dao; import com.example.web.domain.User; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import javax.sql.DataSource; import java.util.List; public class UserDao { private JdbcTemplate template; // 构造方法注入DataSource public UserDao(DataSource dataSource) { this.template new JdbcTemplate(dataSource); } // 查询所有用户 public ListUser findAll() { String sql SELECT * FROM user WHERE status 1 ORDER BY create_time DESC; return template.query(sql, new BeanPropertyRowMapper(User.class)); } // 根据ID查询单个用户 public User findById(Long id) { String sql SELECT * FROM user WHERE id ? AND status 1; try { return template.queryForObject(sql, new BeanPropertyRowMapper(User.class), id); } catch (EmptyResultDataAccessException e) { // 如果没查到返回null而不是抛异常 return null; } } // 新增用户 public int save(User user) { String sql INSERT INTO user(username, email, password, real_name, age, status, create_time, update_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?); return template.update(sql, user.getUsername(), user.getEmail(), user.getPassword(), // 注意此处应为加密后的密码 user.getRealName(), user.getAge(), user.getStatus(), user.getCreateTime(), user.getUpdateTime()); } // 更新用户 public int update(User user) { String sql UPDATE user SET username?, email?, real_name?, age?, status?, update_time? WHERE id?; return template.update(sql, user.getUsername(), user.getEmail(), user.getRealName(), user.getAge(), user.getStatus(), user.getUpdateTime(), user.getId()); } // 删除用户软删除 public int delete(Long id) { String sql UPDATE user SET status0, update_time? WHERE id?; return template.update(sql, new Date(), id); } }关键点解析template.query()用于查询返回一个ListT。BeanPropertyRowMapper是核心它根据User.class的属性名自动匹配ResultSet的列名。template.queryForObject()用于查询单个对象。如果ResultSet为空即没查到它会抛出EmptyResultDataAccessException。我们在findById()中捕获它并返回null这是一种更友好的API设计。template.update()用于INSERT、UPDATE、DELETE操作返回受影响的行数int。SQL中的?占位符这是预编译Prepared Statement

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号