個人博客網:https://wushaopei.github.io/ (你想要這裏多有)
1、預編譯:statement 與 PreparedStatement
1.1 sql提供訪問的接口:
- 數據庫連接被用於向數據庫服務器發送命令和 SQL 語句,在連接建立後,需要對數據庫進行訪問,執行 sql 語句
- 在 java.sql 包中有 3 個接口分別定義了對數據庫的調用的不同方式:
|----- Statement
|------PreparedStatement
|------CallableStatement
使用statement 進行預編譯可能會導致SQL注入的發生
1.2 SQL 注入攻擊
- SQL 注入是利用某些系統沒有對用戶輸入的數據進行充分的檢查,而在用戶輸入數據中注入非法的 SQL 語句段或命令(如:SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1') ,從而利用系統的 SQL 引擎完成惡意行爲的做法
- 對於 Java 而言,要防範 SQL 注入,只要用 PreparedStatement(從Statement擴展而來) 取代 Statement 就可以了
1.3 PreparedStatement
- 可以通過調用 Connection 對象的 preparedStatement() 方法獲取 PreparedStatement 對象
- PreparedStatement 接口是 Statement 的子接口,它表示一條預編譯過的 SQL 語句
- PreparedStatement 對象所代表的 SQL 語句中的參數用問號(?)來表示,調用 PreparedStatement 對象的 setXxx() 方法來設置這些參數. setXxx() 方法有兩個參數,第一個參數是要設置的 SQL 語句中的參數的索引(從 1 開始),第二個是設置的 SQL 語句中的參數的值
1.4 PreparedStatement vs Statement
- 代碼的可讀性和可維護性.
- PreparedStatement 能最大可能提高性能:
DBServer會對預編譯語句提供性能優化。因爲預編譯語句有可能被重複調用,所以語句在被DBServer的編譯器編譯後的執行代碼被緩存下來,那麼下次調用時只要是相同的預編譯語句就不需要編譯,只要將參數直接傳入編譯過的語句執行代碼中就會得到執行。
在statement語句中,即使是相同操作但因爲數據內容不一樣,所以整個語句本身不能匹配,沒有緩存語句的意義.事實是沒有數據庫會對普通語句編譯後的執行代碼緩存.這樣每執行一次都要對傳入的語句編譯一次.
(語法檢查,語義檢查,翻譯成二進制命令,緩存)
- PreparedStatement 可以防止 SQL 注入防止 SQL 注入
1.5 數據類型轉換表
1.6 連接數據庫、操作表的步驟:
注意:
- 釋放ResultSet, Statement,Connection。
- 數據庫連接(Connection)是非常稀有的資源,用完後必須馬上釋放,如果Connection不能及時正確的關閉將導致系統宕機。Connection的使用原則是儘量晚創建,儘量早的釋放。
2 、ResultSet
2.1 ResultSet 的作用:
- 通過調用 PreparedStatement 對象的 excuteQuery() 方法創建該對象
- ResultSet 對象以邏輯表格的形式封裝了執行數據庫操作的結果集,ResultSet 接口由數據庫廠商實現
- ResultSet 對象維護了一個指向當前數據行的遊標,初始的時候,遊標在第一行之前,可以通過 ResultSet 對象的 next() 方法移動到下一行
ResultSet 接口的常用方法:
-
boolean next()
getString( )
2.2 案例
(1)
(2)
2.3 ResultSet的說明
1. 查詢需要調用Prepared Statement 的 executeQuery() 方法,查詢結果是一個 ResultSet 對象
2. 關於 ResultSet:代表結果集
- ResultSet: 結果集. 封裝了使用 JDBC 進行查詢的結果.
- 調用 PreparedStatement 對象的 executeQuery() 可以得到結果集.
- ResultSet 返回的實際上就是一張數據表. 有一個指針指向數據表的第一條記錄的前面.
3.可以調用 next() 方法檢測下一行是否有效. 若有效該方法返回 true, 且指針下移. 相當於Iterator 對象的 hasNext() 和 next() 方法的結合體
4.當指針指向一行時, 可以通過調用 getXxx(int index) 或 getXxx(int columnName) 獲取每一列的值.
例如: getInt(1), getString("name")
5.ResultSet 當然也需要進行關閉.
3、 普通的增刪查改
3.1 創建一個類customer
public class Customer {
private int id;
private String name;
private String email;
private Date birth;
public Customer() {
super();
}
public Customer(int id, String name, String email, Date birth) {
super();
this.id = id;
this.name = name;
this.email = email;
this.birth = birth;
}
3.2 創建jdbc連接,並封裝爲方法
/* * 獲取數據庫的連接
*/
public static Connection getConnection() {
// 數據庫的連接
Connection connection = null;
InputStream is = null;
try {
is = JDBCUtils.class.getClassLoader().getResourceAsStream("sqlDriver.properties");
Properties ps = new Properties();
ps.load(is);
String user = ps.getProperty("user");
String password = ps.getProperty("password");
String driverClass = ps.getProperty("driverClass");
String url = ps.getProperty("url");
// 創建Driver對象
Class clazz = Class.forName(driverClass);
Object obj = clazz.newInstance();
Driver driver = (Driver) obj;
// 註冊驅動
DriverManager.registerDriver(driver);
connection = DriverManager.getConnection(url, user, password);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return connection;
}
3.3 在操作完成後要進行連接的關閉
public static void close(Connection connection, PreparedStatement ps) {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection connection, PreparedStatement ps, ResultSet rs) {
close(connection,ps);
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3.4 增刪查改操作
/*
* 對數據庫的 增 ,刪,改,查
*/
public class CRUDTest {
/*
* 從數據庫中查找一條數據
*/
@Test
public void select() throws Exception {
Customer customer = getCustomerById();
System.out.println(customer);
List<Customer> customers = getCustomers();
for (Customer customer2 : customers) {
System.out.println(customer2);
}
}
/*
* 查詢多條數據
*/
public List<Customer> getCustomers() throws Exception {
// 1.獲取數據庫的連接
Connection connection = JDBCUtils.getConnection();
String sql = "select * from customers";
// 2.預編譯 -- 如果沒有佔位符可以不填充數據
PreparedStatement ps = connection.prepareStatement(sql);
// 4.執行sql語句
ResultSet rs = ps.executeQuery(); // 查找的結果都已經放到ResultSet中了
List<Customer> list = new ArrayList<>();
while(rs.next()){
//獲取數據
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
Date birth = rs.getDate("birth");
//將每條數據放入集合中
list.add(new Customer(id, name, email, birth));
}
// 6.關閉資源
JDBCUtils.close(connection, ps, rs);
return list;
}
/*
* 查詢一條數據
*/
public Customer getCustomerById() throws Exception {
// 1.獲取數據庫的連接
Connection connection = JDBCUtils.getConnection();
String sql = "select * from customers where id = ?";
// 2.預編譯
PreparedStatement ps = connection.prepareStatement(sql);
// 3.填充數據
ps.setInt(1, 20);
// 4.執行sql語句
ResultSet rs = ps.executeQuery(); // 查找的結果都已經放到ResultSet中了
Customer customer = null;
// 5.取數據
if (rs.next()) { // 如果有數據就返回true,否則返回false
// 獲取列上值的第一種方式
// int id = rs.getInt(1); //根據列的索引獲取對應的列上的值
// String name = rs.getString(2);
// String email = rs.getString(3);
// Date date = rs.getDate(4);
// 獲取列上值的第二種方式
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
Date birth = rs.getDate("birth");
// 考慮將數據進行封裝
customer = new Customer(id, name, email, birth);
// 輸出獲取到的內容
System.out.println(id + " " + name + " " + email + " " + birth);
}
// 6.關閉資源
JDBCUtils.close(connection, ps, rs);
return customer;
}
/*
* 向數據庫中插入一條數據
*/
@Test
public void insert() {
Connection connection = null;
PreparedStatement ps = null;
try {
// 1 : 連接數據庫
connection = JDBCUtils.getConnection();
// sql語句
String sql = "INSERT INTO customers(NAME,email,birth) " + "VALUES(?,?,?);";
// 2.預編譯
ps = connection.prepareStatement(sql);
// 3.設置內容
ps.setString(1, "強哥"); // 第一個參數:第幾個問號 第二個參數:內容
ps.setString(2, "[email protected]");
// 獲取一個sql下的Date
Date date = new Date(new java.util.Date().getTime());
ps.setDate(3, date);
// 4.執行sql語句
ps.execute(); // 如果是查詢語句返回true
} catch (Exception e) {
e.printStackTrace();
} finally {
// 5.關閉資源
JDBCUtils.close(connection, ps);
}
}
/*
* 刪除一條件數
*/
@Test
public void delete() throws Exception {
// 1. 獲取數據庫的連接
Connection connection = JDBCUtils.getConnection();
String sql = "DELETE FROM customers WHERE id = ?";
// 2. 預編譯
PreparedStatement ps = connection.prepareStatement(sql);
// 3.填充數據
ps.setInt(1, 19);
// 4.執行sql語句
int executeUpdate = ps.executeUpdate();
System.out.println(executeUpdate + "條受到影響");
// 5.關閉資源
JDBCUtils.close(connection, ps);
}
/*
* 修改一條件數
*/
@Test
public void update() throws Exception {
Connection connection = null;
PreparedStatement ps = null;
try {
connection = JDBCUtils.getConnection();
String sql = "UPDATE customers SET NAME = ? WHERE id = ?";
ps = connection.prepareStatement(sql);
ps.setString(1, "成哥");
ps.setInt(2, 18);
int executeUpdate = ps.executeUpdate();
System.out.println(executeUpdate + "條數據受到影響");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.close(connection, ps);
}
}
}
4、通用增刪查改
創建類 User.java
public class User {
private int id;
private String name;
private String address;
private String phone;
4.1 創建通用連接方案 - 通用增刪改
/*
* 通用的增刪改
*/
public static int UID(String sql,Object ...objects) {
// 1. 獲取數據庫的連接
Connection connection = null;
// 2. 預編譯
PreparedStatement ps = null;
// 4.執行sql語句
int executeUpdate = -1;
try {
connection = JDBCUtils.getConnection();
ps = connection.prepareStatement(sql);
// 3.填充數據
for (int i = 0; i < objects.length; i++) { // i 表示索引
ps.setObject(i + 1, objects[i]); // i + 1表示的是第幾個佔位符
}
executeUpdate = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally{
// 5.關閉資源
JDBCUtils.close(connection, ps);
}
return executeUpdate;
}
測試代碼:
/*
* 測試通用的增刪改
*/
@Test
public void insert(){
String sql = "INSERT INTO customers(NAME,email,birth) VALUES(?,?,?)";
Date date = new java.sql.Date(new java.util.Date().getTime());
int uid = JDBCUtils.UID(sql, "強哥","[email protected]",date);
System.out.println(uid + "條數據受到影響");
}
4.2 通用查找(一條或多條)
只查一條數據:
/*
* 通用的查找 (只有一條數據)
*
* Class<T> clazz : 運行時類的對象
*/
public static <T> T getObject(Class<T> clazz,String sql,Object ...objects) throws Exception{
//1.獲取數據庫的連接
Connection connection = JDBCUtils.getConnection();
//2.預編譯
PreparedStatement ps = connection.prepareStatement(sql);
//3.填充數據
for (int i = 0; i < objects.length; i++) {
ps.setObject(i + 1, objects[i]);
}
//4.執行sql語句
ResultSet rs = ps.executeQuery();
ResultSetMetaData md = rs.getMetaData(); //獲取元數據。
//獲取列的數量
int columnCount = md.getColumnCount();
//創建對象
T t = clazz.newInstance();
while(rs.next()){ //遍歷每一條數據
/*
* 第一個問題 : 如何獲取到表的列數
* 第二個問題 : 需要知道類中的屬性
* 第三個問題 : 對象中屬性的名字怎麼來
*/
for (int i = 0; i < columnCount; i++) {
//獲取某列的列名(如果有別名獲取的是別名)
String columnLabel = md.getColumnLabel(i + 1);
//通過列名獲取列中的數據。
Object value = rs.getObject(columnLabel);
//封裝
//通過反射 - 給對象中的屬性進行賦值
//將表中的列名當作類中的屬性名。如果列名和屬性名不一樣,可以通過別名的方式(別名 = 屬性名)
//通過列名就獲取到了類中的對應的屬性
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);
/*
* 第一個參數 : 是給哪個對象進行賦值
* 第二個參數 : 屬性值
*/
declaredField.set(t, value);
}
}
//關閉資源
JDBCUtils.close(connection, ps, rs);
return t;
}
查多條數據:
/*
* 通用的查找 (返回多條數據)
*
*/
public static <T> List<T> getObjects(Class<T> clazz,String sql,Object ...objects) throws Exception{
//1.獲取數據庫的連接
Connection connection = JDBCUtils.getConnection();
//2.預編譯
PreparedStatement ps = connection.prepareStatement(sql);
//3.填充數據
for (int i = 0; i < objects.length; i++) {
ps.setObject(i + 1, objects[i]);
}
//4.執行sql語句
ResultSet rs = ps.executeQuery();
ResultSetMetaData md = rs.getMetaData(); //獲取元數據。
//獲取列的數量
int columnCount = md.getColumnCount();
//創建一個集合
List<T> list = new ArrayList<>();
while(rs.next()){ //遍歷每一條數據
//創建對象
T t = clazz.newInstance();
/*
* 第一個問題 : 如何獲取到表的列數
* 第二個問題 : 需要知道類中的屬性
* 第三個問題 : 對象中屬性的名字怎麼來
*/
for (int i = 0; i < columnCount; i++) {
//獲取某列的列名(如果有別名獲取的是別名)
String columnLabel = md.getColumnLabel(i + 1);
//通過列名獲取列中的數據。
Object value = rs.getObject(columnLabel);
//封裝
//通過反射 - 給對象中的屬性進行賦值
//將表中的列名當作類中的屬性名。如果列名和屬性名不一樣,可以通過別名的方式(別名 = 屬性名)
//通過列名就獲取到了類中的對應的屬性
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);
/*
* 第一個參數 : 是給哪個對象進行賦值
* 第二個參數 : 屬性值
*/
declaredField.set(t, value);
}
//將對象放入到集合中
list.add(t);
}
//關閉資源
JDBCUtils.close(connection, ps, rs);
return list;
}
測試代碼:
String sql = "select id,name,email,birth from customers where id = ?";
Customer customer = getObject(Customer.class, sql, 5);
System.out.println(customer);
sql = "select * from users where id = ?";
User object = getObject(User.class, sql, 1);
System.out.println(object);
sql = "select * from users";
List<User> users = getObjects(User.class, sql);
for (User user : users) {
System.out.println(user);
}