在傳統 JDBC 開發中,業務邏輯往往夾雜著「取得連線 → 建立 Statement → 執行 SQL → 處理 ResultSet → 關閉資源」等樣板程式碼,導致維護困難、SQL 散落各處、測試成本高。資料存取層(Data Access Layer, DAL)的核心價值在於:將資料庫操作細節封裝起來,對上層暴露語義清晰的 CRUD 介面(如 insertUser()、findById()),實現關注點分離。
對應資料表 user(id, name, password, age),採用包裝類型(Integer)處理資料庫 NULL 值:
public class User {
private Integer id;
private String name;
private String pass;
private Integer age;
// 無參建構子(反射/框架必備)
public User() {}
// 業務建構子(不含自增 ID)
public User(String name, String pass, Integer age) {
this.name = name; this.pass = pass; this.age = age;
}
// 完整建構子
public User(Integer id, String name, String pass, Integer age) {
this(name, pass, age); this.id = id;
}
// Getter/Setter 省略...
}
集中管理 JDBC 參數,提供單點獲取連線,異常轉為非受檢異常簡化呼叫端:
public class ConnectionFactory {
private static final String URL = "jdbc:mysql://localhost:3306/testdb";
private static final String USER = "testuser";
private static final String PASS = "testpass";
static { try { Class.forName("com.mysql.jdbc.Driver"); } catch (Exception e) { throw new RuntimeException(e); } }
public static Connection getConnection() {
try { return DriverManager.getConnection(URL, USER, PASS); }
catch (SQLException e) { throw new RuntimeException("DB 連線失敗", e); }
}
}
最佳實踐:生產環境請改用連線池(HikariCP、Druid),並從設定檔讀取參數。
public interface UserDao {
User getUser(int id);
Set<User> getAllUsers();
User getUserByNameAndPass(String name, String pass);
boolean insertUser(User user);
boolean updateUser(User user);
boolean deleteUser(int id);
}
private User mapRow(ResultSet rs) throws SQLException {
User u = new User();
u.setId(rs.getInt("id"));
u.setName(rs.getString("name"));
u.setPass(rs.getString("pass"));
u.setAge(rs.getInt("age"));
return u;
}
public User getUser(int id) {
String sql = "SELECT * FROM user WHERE id = ?";
try (Connection c = ConnectionFactory.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setInt(1, id);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? mapRow(rs) : null;
}
} catch (SQLException e) { throw new RuntimeException(e); }
}
public User getUserByNameAndPass(String name, String pass) {
String sql = "SELECT * FROM user WHERE name = ? AND pass = ?";
try (Connection c = ConnectionFactory.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, name);
ps.setString(2, pass);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? mapRow(rs) : null;
}
} catch (SQLException e) { throw new RuntimeException(e); }
}
public Set<User> getAllUsers() {
String sql = "SELECT * FROM user";
Set<User> set = new HashSet<>();
try (Connection c = ConnectionFactory.getConnection();
Statement st = c.createStatement();
ResultSet rs = st.executeQuery(sql)) {
while (rs.next()) set.add(mapRow(rs));
} catch (SQLException e) { throw new RuntimeException(e); }
return set;
}
public boolean insertUser(User u) {
String sql = "INSERT INTO user(name, pass, age) VALUES (?, ?, ?)";
try (Connection c = ConnectionFactory.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, u.getName());
ps.setString(2, u.getPass());
ps.setInt(3, u.getAge());
return ps.executeUpdate() == 1;
} catch (SQLException e) { throw new RuntimeException(e); }
}
updateUser、deleteUser 同理,僅 SQL 不同。
上述所有範例均採用 try-with-resources 自動關閉 Connection、Statement、ResultSet,杜絕資源洩漏,程式碼更簡潔安全。
Connection.setAutoCommit(false),配合 ThreadLocal 管理連線。LIMIT ?,?、Oracle ROWNUM、SQL Server OFFSET FETCH。一個乾淨的 DAL 不僅能消除重複程式碼,更能為後續引入連線池、快取、讀寫分離、分庫分表奠定堅實基礎。建議從「介面定義 → 實體映射 → 基礎 CRUD → 事務整合」循序漸進建設,讓資料庫操作真正成為業務邏輯的隱形後盾。