在现代Web开发中,将后台数据库中的数据动态地展示给用户,是一项核心且基础的任务,对于使用Java技术栈的开发者而言,JSP(JavaServer Pages)与Servlet的组合是经典的解决方案,理解如何将从数据库中查询到的数据高效、安全地传递到JSP前台页面进行渲染,是掌握Java Web开发的关键一环,本文将详细拆解这一过程,遵循MVC(Model-View-Controller)设计模式,阐述其核心思想与实现步骤。
核心思想:分离关注点,遵循MVC模式
在讨论具体技术之前,我们必须明确其背后的指导思想——MVC模式,这种模式将应用程序清晰地划分为三个部分,以实现高内聚、低耦合:
- Model(模型):负责业务逻辑和数据封装,在我们的场景中,它通常指代一个JavaBean(POJO),用于封装从数据库中取出的一行数据(例如一个用户信息),或者是一个包含多个JavaBean的集合(例如用户列表)。
- View(视图):负责数据显示,它不包含任何业务逻辑,仅负责将模型中的数据以美观的HTML格式呈现给用户,JSP页面就扮演着视图的角色。
- Controller(控制器):作为模型和视图之间的协调者,它接收用户的请求,调用模型处理业务逻辑(如查询数据库),然后选择合适的视图进行响应,并将处理结果(数据)传递给视图,Servlet正是控制器的最佳实现。
遵循MVC模式,数据传递的路径就变得非常清晰:数据库 → Servlet(Controller) → 数据模型 → JSP(View)。
第一步:后端数据准备(Servlet的工作)
Servlet是整个数据流转的起点和指挥中心,它的主要职责包括连接数据库、执行查询、封装数据,并将数据“放置”在一个JSP可以访问到的地方。
1 数据库连接与查询
Servlet需要使用JDBC(Java Database Connectivity)技术来连接数据库并执行SQL查询,这是一个标准流程,通常包括加载驱动、建立连接、创建Statement或PreparedStatement对象、执行查询并获取ResultSet
。
// 示例代码片段 Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; List<User> userList = new ArrayList<>(); // 创建一个List用于存放用户数据 try { // 1. 获取数据库连接(实际项目中通常使用连接池) conn = dataSource.getConnection(); String sql = "SELECT id, name, email FROM users"; // 2. 创建PreparedStatement pstmt = conn.prepareStatement(sql); // 3. 执行查询 rs = pstmt.executeQuery(); // 4. 处理结果集 while (rs.next()) { User user = new User(); // 创建User模型对象 user.setId(rs.getInt("id")); user.setName(rs.getString("name")); user.setEmail(rs.getString("email")); userList.add(user); // 将封装好的User对象添加到List中 } } catch (SQLException e) { e.printStackTrace(); } finally { // 5. 关闭资源(非常重要) // ... 关闭 rs, pstmt, conn }
2 数据封装
直接将ResultSet
对象传递给JSP是一种非常糟糕的做法。ResultSet
与数据库连接紧密绑定,如果在JSP页面中处理它,会导致数据库连接长时间占用,并且严重违反MVC原则,使视图层混杂了数据访问逻辑。
正确的做法是,如上例所示,遍历ResultSet
,将每一行数据封装成一个独立的JavaBean对象(如User
),然后将所有这些对象存入一个List
集合中,这个List<User>
就是我们的“模型”,它是一个纯粹的、与数据库无关的Java对象集合。
3 设置请求作用域属性
数据封装完毕后,Servlet需要将这个userList
传递给JSP,传递的“桥梁”就是HTTP请求对象(HttpServletRequest
),Servlet可以通过调用setAttribute
方法,将任何Java对象存入请求作用域中。
// 将userList存入request作用域,键名为"userList" request.setAttribute("userList", userList);
这里,"userList"
是一个字符串键,JSP将通过这个键来获取数据;userList
则是我们之前创建的包含所有用户数据的List
对象。
第二步:请求转发
设置完属性后,Servlet需要将请求转发给指定的JSP页面来处理响应,这里必须使用请求转发,而不是重定向。
// 将请求转发到showUsers.jsp页面 request.getRequestDispatcher("showUsers.jsp").forward(request, response);
forward()
方法会将同一个request
和response
对象传递给showUsers.jsp
,这意味着,我们刚刚存入request
中的userList
属性,在JSP页面中是完全可以访问的,而重定向会告知浏览器发起一个新的请求,原请求中的所有属性都会丢失。
第三步:前端数据渲染(JSP的工作)
JSP页面的任务单一而纯粹:从请求作用域中取出数据,并将其渲染成HTML。
1 使用EL和JSTL(推荐做法)
为了保持JSP页面的整洁,避免在页面中嵌入大量的Java代码(即Scriptlet <% ... %>
),强烈推荐使用表达式语言(EL)和JSP标准标签库(JSTL)。
需要在JSP页面顶部引入JSTL核心库:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
2 遍历并展示数据
我们可以使用JSTL的<c:forEach>
标签来遍历Servlet传递过来的userList
,并使用EL表达式 来访问每个User
对象的属性。
<table border="1"> <thead> <tr> <th>ID</th> <th>姓名</th> <th>邮箱</th> </tr> </thead> <tbody> <%-- 使用c:forEach遍历request作用域中的userList --%> <c:forEach items="${userList}" var="user"> <tr> <%-- 使用EL表达式访问User对象的属性 --%> <td>${user.id}</td> <td>${user.name}</td> <td>${user.email}</td> </tr> </c:forEach> </tbody> </table>
items="${userList}"
:items
属性指定要遍历的集合。${userList}
是EL表达式,它会自动在page、request、session、application作用域中查找名为userList
的属性,由于我们是在Servlet中将其存入request作用域的,所以这里能正确找到。var="user"
:var
属性定义了一个临时变量名,在每次循环中,user
会引用集合中的当前元素(即一个User
对象)。${user.id}
:EL表达式会调用user
对象的getId()
方法(注意:EL会自动将属性名首字母大写并加上get
前缀来寻找对应的getter方法),并将其返回值输出到页面。
浏览器会收到一个包含完整用户数据的HTML表格。
将数据库数据传递到JSP前台的完整流程,可以概括为以下几个关键步骤:
步骤 | 执行者 | 核心操作 | 目的 |
---|---|---|---|
1 | Servlet (Controller) | JDBC查询数据库 | 获取原始数据 |
2 | Servlet (Controller) | 遍历ResultSet,封装为JavaBean集合 | 创建与数据库无关的模型数据 |
3 | Servlet (Controller) | request.setAttribute("key", data) | 将模型数据放入请求作用域 |
4 | Servlet (Controller) | request.getRequestDispatcher().forward() | 将请求(包含数据)转发给JSP |
5 | JSP (View) | 使用JSTL的<c:forEach> 和EL | 从请求中获取数据并渲染成HTML |
通过这一套严谨的流程,我们实现了业务逻辑、数据访问和视图展示的彻底分离,使得代码结构清晰、易于维护和扩展,是Java Web开发中值得遵循的最佳实践。
相关问答FAQs
问题1:为什么不应该在JSP页面中直接写JDBC代码来查询数据库?
解答: 直接在JSP中编写JDBC代码是一种严重的反模式,主要弊端如下:
- 违反MVC原则:它将数据访问逻辑(模型)和视图展示逻辑(视图)混杂在一起,导致代码混乱,职责不清。
- 难以维护:一旦数据库连接信息或SQL语句需要变更,就必须修改JSP文件,增加了维护成本和风险,业务逻辑和页面逻辑的耦合使得任何一方的改动都可能影响另一方。
- 资源管理风险:在JSP中管理数据库连接的打开和关闭非常容易出错,稍有不慎就可能导致连接泄漏,最终耗尽数据库资源,使整个应用崩溃。
- 安全性与可测试性差:代码分散且难以进行单元测试,同时也更容易引入SQL注入等安全漏洞。
通过Servlet作为控制器来处理所有后台逻辑,可以完美地解决以上所有问题。
问题2:request.setAttribute()
, session.setAttribute()
, 和 application.setAttribute()
有什么区别?
解答: 这三个方法都用于在Web应用中存储数据,但它们的作用域(生命周期和可见范围)完全不同,选择哪个取决于数据的使用场景。
request.setAttribute()
:- 作用域:请求作用域。
- 生命周期:仅在单次HTTP请求-响应周期内有效,当服务器将响应发送给客户端后,该请求结束,其中存储的所有数据都会被销毁。
- 用途:最常用于在Servlet和JSP之间传递数据,将查询结果从Servlet传递给用于展示的JSP页面。
session.setAttribute()
:- 作用域:会话作用域。
- 生命周期:在整个用户会话期间有效,会话从用户首次访问网站开始,直到浏览器关闭或会话超时(例如30分钟无操作)。
- 用途:用于存储与特定用户相关的数据,用户的登录信息、购物车内容等,这些数据需要在用户浏览不同页面时持续可用。
application.setAttribute()
:- 作用域:应用作用域。
- 生命周期:在整个Web应用程序的生命周期内有效,从服务器启动应用开始,直到服务器关闭或应用被卸载。
- 用途:用于存储全局共享的数据,网站的配置参数、所有用户共享的缓存数据、在线用户计数器等,此作用域内的数据对所有用户的所有会话都是可见的,因此在使用时需考虑线程安全问题。
【版权声明】:本站所有内容均来自网络,若无意侵犯到您的权利,请及时与我们联系将尽快删除相关内容!
发表回复