從硬編碼連接到生產級數據訪問層,本文遞進式講解:配置文件解耦連接、連接工廠模式、Statement 三大執行方法、預處理防注入原理與實戰、自增主鍵回填、批處理優化、事務控制模板,附完整可運行代碼片段。
## MySQL driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/hncu?useUnicode=true&characterEncoding=UTF-8 username=root password=1234 ## Oracle (備選) #driver=oracle.jdbc.driver.OracleDriver #url=jdbc:oracle:thin:@192.168.31.12:1521:orcl #username=scott #password=tiger
public class ConnFactory {
private static Connection conn;
static {
try {
Properties p = new Properties();
p.load(ConnFactory.class.getClassLoader().getResourceAsStream("jdbc.properties"));
Class.forName(p.getProperty("driver"));
conn = DriverManager.getConnection(p.getProperty("url"), p.getProperty("username"), p.getProperty("password"));
System.out.println("已連接到數據庫..." + conn);
} catch (Exception e) {
throw new RuntimeException("讀取配置文件失敗", e);
}
}
public static Connection getConn() { return conn; }
}
優勢:切換數據庫只改配置,代碼零修改;生產環境請替換為成熟連接池。
@Test
public void getXXXDemo() throws Exception {
Statement st = ConnFactory.getConn().createStatement();
ResultSet rs = st.executeQuery("SELECT * FROM book");
while (rs.next()) {
Integer id = rs.getInt(1); // 列索引從 1 開始
String name = rs.getString(2);
Double price = rs.getDouble(3); // DECIMAL 取 getDouble,getObject 返回 BigDecimal
Object dateTime = rs.getObject(4); // DATETIME 取 Object 為 java.sql.Timestamp
System.out.println(id + "," + name + "," + price + "," + dateTime);
// 日期時間拆分
System.out.println(rs.getDate(4) + "--" + rs.getTime(4));
}
}
注意:自增列刪除後重新插入,序號不回退,會出現空號。
| 方法 | 適用 SQL | 返回值 | 備註 |
|---|---|---|---|
| executeQuery | SELECT | ResultSet | 只能查詢 |
| executeUpdate | INSERT/UPDATE/DELETE | int (影響行數) | 不能查詢 |
| execute | 任意 | boolean (true=有結果集) | 通用,查詢時需 getResultSet() 取結果 |
boolean hasRs = st.execute(sql);
if (hasRs) {
ResultSet rs = st.getResultSet();
// 處理結果
}
String sql = "SELECT COUNT(*) FROM stud WHERE id=" + id + " AND sname='" + name + "'"; // 惡意輸入:id=1002, name=1' OR '1'='1 → 條件恆真,繞過驗證
@Test
public void loginSafe() throws Exception {
Scanner sc = new Scanner(System.in);
Connection con = ConnFactory.getConn();
String sql = "SELECT COUNT(*) FROM stud WHERE id=? AND sname=?";
PreparedStatement pst = con.prepareStatement(sql);
pst.setInt(1, sc.nextInt());
pst.setString(2, sc.nextLine());
ResultSet rs = pst.executeQuery();
rs.next();
System.out.println(rs.getInt(1) > 0 ? "登錄成功" : "登錄失敗");
con.close();
}
原理:SQL 模板預編譯,參數作為數據傳遞,不參與語法解析,徹底杜絕注入。
@Test
public void saveAuto() throws Exception {
Connection con = ConnFactory.getConn();
// Statement 方式
Statement st = con.createStatement();
st.executeUpdate("INSERT INTO book(name,price,pub) VALUES('JavaEE',100.8,'2013-06-12 08:30:30')", Statement.RETURN_GENERATED_KEYS);
ResultSet rs = st.getGeneratedKeys();
if (rs.next()) System.out.println("自增ID: " + rs.getInt(1));
// PreparedStatement 方式
PreparedStatement pst = con.prepareStatement("INSERT INTO book(name,price) VALUES(?,?)", Statement.RETURN_GENERATED_KEYS);
pst.setString(1, "計算機基礎");
pst.setDouble(2, 28);
pst.executeUpdate();
ResultSet rs2 = pst.getGeneratedKeys();
if (rs2.next()) System.out.println("自增ID: " + rs2.getInt(1));
}
public void batch() throws Exception {
Connection con = ConnFactory.getConn();
Statement st = con.createStatement();
for (int i=0; i<10; i++) st.addBatch("INSERT INTO book(name,price,pub) VALUES('JavaEE',100.8,'2015-06-12 08:30:30')");
st.addBatch("UPDATE book SET price=price*1.1 WHERE price>100");
int[] counts = st.executeBatch();
for (int c : counts) System.out.println(c); // 每條影響行數
}
public void preparedBatch() throws Exception {
Connection con = ConnFactory.getConn();
PreparedStatement pst = con.prepareStatement("INSERT INTO book(name,price,pub) VALUES(?,?,?)");
for (int i=0; i<5; i++) {
pst.setString(1, "Java"+i);
pst.setDouble(2, 55.85+i);
pst.setString(3, "2016-12-10 07:07:08");
pst.addBatch(); // 不帶參數
}
int[] counts = pst.executeBatch();
for (int c : counts) System.out.println(c);
}
關鍵差異:PreparedStatement.addBatch() 無參數,參數已通過 setXxx 綁定;Statement.addBatch(sql) 需帶 SQL 字符串。
public void transactionTemplate() {
Connection con = null;
try {
con = ConnFactory.getConn();
con.setAutoCommit(false);
// 業務操作...
con.commit();
} catch (Exception e) {
if (con != null) con.rollback();
throw new RuntimeException("事務失敗", e);
} finally {
if (con != null) {
try { con.setAutoCommit(true); con.close(); } catch (SQLException ignore) {}
}
}
}
代碼規範落地,數據安全與性能才有保障。