在 Java 企業級開發中,資料存取層(DAO)的設計直接影響系統的可維護性與擴充性。本文展示一個完整的分層架構範例,結合 DAO 介面、實作類別、Proxy 代理類別 以及 Factory 工廠模式,實現資料庫連線管理、CRUD 操作與業務邏輯的清晰分離。
Emp (實體類別)
↑
IEmpDAO (DAO 介面)
↑
EmpDAOImpl (DAO 具體實作,處理 SQL)
↑
EmpDAOProxy (代理類別,管理連線與交易)
↑
DAOFactory (工廠類別,提供 DAO 實例)
↑
TestDAOInsert (測試主程式)
package com.example.dao;
import java.util.Date;
public class Emp {
private int empno;
private String ename;
private String job;
private Date hiredate;
private float sal;
// Getters & Setters 省略...
// 建議使用 Lombok @Data 簡化
}
package com.example.dao;
import java.sql.Connection;
import java.sql.DriverManager;
public class DatabaseConnection {
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String URL = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
private static final String USER = "root";
private static final String PASS = "password";
private Connection conn;
public DatabaseConnection() throws Exception {
Class.forName(DRIVER);
this.conn = DriverManager.getConnection(URL, USER, PASS);
}
public Connection getConnection() { return conn; }
public void close() throws Exception {
if (conn != null && !conn.isClosed()) conn.close();
}
}
提示:實務上建議使用連線池(HikariCP、Druid)替代直接建立連線。
package com.example.dao;
import java.util.List;
public interface IEmpDAO {
boolean doCreate(Emp emp) throws Exception;
List<Emp> findAll(String keyword) throws Exception;
Emp findById(int empno) throws Exception;
}
package com.example.dao;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class EmpDAOImpl implements IEmpDAO {
private Connection conn;
private PreparedStatement pstmt;
public EmpDAOImpl(Connection conn) { this.conn = conn; }
@Override
public boolean doCreate(Emp emp) throws Exception {
String sql = "INSERT INTO emp(empno, ename, job, hiredate, sal) VALUES(?,?,?,?,?)";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, emp.getEmpno());
pstmt.setString(2, emp.getEname());
pstmt.setString(3, emp.getJob());
pstmt.setDate(4, new java.sql.Date(emp.getHiredate().getTime()));
pstmt.setFloat(5, emp.getSal());
boolean flag = pstmt.executeUpdate() > 0;
pstmt.close();
return flag;
}
@Override
public List<Emp> findAll(String keyword) throws Exception {
List<Emp> list = new ArrayList<>();
String sql = "SELECT empno, ename, job, hiredate, sal FROM emp WHERE ename LIKE ? OR job LIKE ?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "%" + keyword + "%");
pstmt.setString(2, "%" + keyword + "%");
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
Emp e = new Emp();
e.setEmpno(rs.getInt("empno"));
e.setEname(rs.getString("ename"));
e.setJob(rs.getString("job"));
e.setHiredate(rs.getDate("hiredate"));
e.setSal(rs.getFloat("sal"));
list.add(e);
}
pstmt.close();
return list;
}
@Override
public Emp findById(int empno) throws Exception {
String sql = "SELECT empno, ename, job, hiredate, sal FROM emp WHERE empno=?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, empno);
ResultSet rs = pstmt.executeQuery();
Emp e = null;
if (rs.next()) {
e = new Emp();
e.setEmpno(rs.getInt("empno"));
e.setEname(rs.getString("ename"));
e.setJob(rs.getString("job"));
e.setHiredate(rs.getDate("hiredate"));
e.setSal(rs.getFloat("sal"));
}
pstmt.close();
return e;
}
}
負責連線建立、交易控制(commit/rollback)、資源關閉,對上層隱藏實作細節。
package com.example.dao;
import java.util.List;
public class EmpDAOProxy implements IEmpDAO {
private DatabaseConnection dbc;
private EmpDAOImpl dao;
public EmpDAOProxy() throws Exception {
this.dbc = new DatabaseConnection();
this.dao = new EmpDAOImpl(dbc.getConnection());
}
@Override
public boolean doCreate(Emp emp) throws Exception {
boolean flag = false;
try {
if (dao.findById(emp.getEmpno()) == null) { // 避免重複鍵
flag = dao.doCreate(emp);
}
} finally {
dbc.close(); // 確保連線關閉
}
return flag;
}
@Override
public List<Emp> findAll(String keyword) throws Exception {
try {
return dao.findAll(keyword);
} finally {
dbc.close();
}
}
@Override
public Emp findById(int empno) throws Exception {
try {
return dao.findById(empno);
} finally {
dbc.close();
}
}
}
package com.example.dao;
public class DAOFactory {
public static IEmpDAO getEmpDAOInstance() throws Exception {
return new EmpDAOProxy();
}
}
package com.example.dao;
public class TestDAOInsert {
public static void main(String[] args) throws Exception {
IEmpDAO dao = DAOFactory.getEmpDAOInstance();
for (int i = 0; i < 5; i++) {
Emp emp = new Emp();
emp.setEmpno(1000 + i);
emp.setEname("測試員工-" + i);
emp.setJob("工程師-" + i);
emp.setHiredate(new java.util.Date());
emp.setSal(5000 + i * 100);
dao.doCreate(emp);
}
System.out.println("資料新增完成");
}
}
| 模式 | 角色 | 優勢 |
|---|---|---|
| DAO | IEmpDAO / EmpDAOImpl | 抽象資料存取邏輯,支援多種資料來源切換(MySQL、Oracle、MongoDB 等) |
| Proxy | EmpDAOProxy | 集中管理連線生命週期、交易邊界、例外處理,業務層無需關心 JDBC 細節 |
| Factory | DAOFactory | 解耦客戶端與具體實作,未來可擴充為 Spring IoC 容器管理 |
DatabaseConnection 替換為 HikariCP DataSource,由 Proxy 從池取得/歸還連線。BaseDAO<T, ID> 減少重複 CRUD 程式碼。@Repository、@Transactional、JdbcTemplate 或 MyBatis 簡化開發。DAOException 封裝 SQLException,避免外層依賴 JDBC API。此分層架構雖屬傳統,但清晰展現了關注點分離的核心思想,是理解現代 ORM 框架(Hibernate、MyBatis)底層運作原理的最佳切入點。