JSP 连接数据库

JDBC 在 JSP 里的写法、为什么不该写在页面里、以及正确分层。

Intermediate
Key Points页面里不写 JDBC。用 Servlet 或 Service 查好数据放进 request,JSP 只负责渲染。

先看错误示范

JSP
<%-- 能跑,但不要这么写 --%>
<%@ page import="java.sql.*" %>
<%
    Class.forName("com.mysql.cj.jdbc.Driver");
    Connection c = DriverManager.getConnection(
            "jdbc:mysql://localhost:3306/app", "root", "123456");
    Statement st = c.createStatement();
    ResultSet rs = st.executeQuery(
            "SELECT * FROM users WHERE name = '" + request.getParameter("name") + "'");
    while (rs.next()) {
        out.println("<p>" + rs.getString("name") + "</p>");
    }
    rs.close(); st.close(); c.close();
%>

This snippet has four defects: the password is hardcoded in the page, string-concatenated SQL invites injection, the connection has no exception safety, and unescaped output enables XSS. Any one is enough to compromise the system.

正确做法:分层

Java
// UserDao.java —— SQL 只出现在这里
public class UserDao {
    private final DataSource ds;

    public UserDao(DataSource ds) { this.ds = ds; }

    public List<User> search(String keyword) throws SQLException {
        String sql = "SELECT id, name, email FROM users WHERE name LIKE ?";
        List<User> list = new ArrayList<>();
        try (Connection c = ds.getConnection();
             PreparedStatement ps = c.prepareStatement(sql)) {
            ps.setString(1, "%" + keyword + "%");
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    User u = new User();
                    u.setId(rs.getInt("id"));
                    u.setName(rs.getString("name"));
                    u.setEmail(rs.getString("email"));
                    list.add(u);
                }
            }
        }
        return list;
    }
}
Java
// UserServlet.java —— 取参数、调 DAO、放进 request、转发
@WebServlet("/users")
public class UserServlet extends HttpServlet {

    private UserDao dao;

    @Override
    public void init() {
        DataSource ds = (DataSource) getServletContext().getAttribute("ds");
        dao = new UserDao(ds);
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String kw = req.getParameter("kw");
        try {
            req.setAttribute("users", dao.search(kw == null ? "" : kw));
        } catch (SQLException e) {
            throw new ServletException(e);
        }
        req.getRequestDispatcher("/WEB-INF/views/users.jsp").forward(req, resp);
    }
}
JSP
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<table>
  <c:forEach var="u" items="${users}">
    <tr>
      <td>${u.id}</td>
      <td><c:out value="${u.name}"/></td>
      <td><c:out value="${u.email}"/></td>
    </tr>
  </c:forEach>
</table>

数据源配置

XML
<Resource name="jdbc/app" auth="Container"
          type="javax.sql.DataSource"
          maxTotal="20" maxIdle="10"
          username="app" password="secret"
          driverClassName="com.mysql.cj.jdbc.Driver"
          url="jdbc:mysql://localhost:3306/app?useUnicode=true&amp;characterEncoding=utf8"/>
XML
<resource-ref>
  <res-ref-name>jdbc/app</res-ref-name>
  <res-type>javax.sql.DataSource</res-type>
  <res-auth>Container</res-auth>
</resource-ref>
DriverManager.getConnection per request costs a handshake and authentication — tens of milliseconds — and high concurrency exhausts the database. A pool keeps connections warm and returns them instead of closing. Non-negotiable in production.

关闭顺序

Java
try (Connection c = ds.getConnection();
     PreparedStatement ps = c.prepareStatement(sql);
     ResultSet rs = ps.executeQuery()) {
    // 用 rs
}   // 逆序自动关闭:rs → ps → c

try-with-resources closes in reverse declaration order, which matches JDBC's requirement. A hand-written finally in the wrong order (closing the Connection first) makes rs.close() throw and swallow the real error.

Friends & Partners