Servlet 数据库访问

JDBC 连接、预编译语句、连接池,以及在 Servlet 中的正确位置。

Intermediate
Key PointsServlet 里不写 JDBC,只调 DAO;SQL 一律用 PreparedStatement 传参,绝不拼字符串。

分层:Servlet 不该碰 SQL

Text
Browser
   ↓ HTTP
Servlet          ← 取参数、校验、调 Service、选视图
   ↓
Service          ← 业务规则、事务边界
   ↓
DAO              ← SQL、ResultSet 映射
   ↓
DataSource       ← 连接池

Writing JDBC in a servlet makes "change a page, change the SQL" the norm and kills testability. Push SQL into a DAO so the servlet only sees objects.

DAO 的写法

Java
public class UserDao {
    private final DataSource ds;

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

    public User findById(int id) throws SQLException {
        String sql = "SELECT id, name, email FROM users WHERE id = ?";
        try (Connection c = ds.getConnection();
             PreparedStatement ps = c.prepareStatement(sql)) {
            ps.setInt(1, id);
            try (ResultSet rs = ps.executeQuery()) {
                if (!rs.next()) return null;
                User u = new User();
                u.setId(rs.getInt("id"));
                u.setName(rs.getString("name"));
                u.setEmail(rs.getString("email"));
                return u;
            }
        }
    }

    public int insert(User u) throws SQLException {
        String sql = "INSERT INTO users(name, email) VALUES (?, ?)";
        try (Connection c = ds.getConnection();
             PreparedStatement ps = c.prepareStatement(sql,
                     Statement.RETURN_GENERATED_KEYS)) {
            ps.setString(1, u.getName());
            ps.setString(2, u.getEmail());
            ps.executeUpdate();
            try (ResultSet keys = ps.getGeneratedKeys()) {
                keys.next();
                return keys.getInt(1);
            }
        }
    }
}

为什么必须用预编译语句

Java
// 错误:拼字符串,参数里有 ' OR '1'='1 就直接绕过
String bad = "SELECT * FROM users WHERE name = '" + name + "'";

// 正确:参数化,驱动负责转义,值永远当值处理
String good = "SELECT * FROM users WHERE name = ?";
ps.setString(1, name);
Text
输入:' OR '1'='1

拼接后:SELECT * FROM users WHERE name = '' OR '1'='1'
结果  :返回全表 —— 注入成功

参数化后:值整体作为字符串比较,查不到任何行

连接池

XML
<Resource name="jdbc/app" auth="Container"
          type="javax.sql.DataSource"
          maxTotal="20" maxIdle="10" maxWaitMillis="3000"
          username="app" password="secret"
          driverClassName="com.mysql.cj.jdbc.Driver"
          url="jdbc:mysql://localhost:3306/app?useUnicode=true&amp;characterEncoding=utf8"/>

Opening a connection per request costs a handshake plus authentication — tens of milliseconds, and high concurrency exhausts the database. A pool keeps connections warm and hands them back instead of closing.

Always close connections in finally, or use try-with-resources. Leaked connections drain the pool and every request blocks until timeout — a failure that only shows up under load.

Friends & Partners