在上一文中,我們對JDBC進行了詳細的分析,介紹了使用JDBC操作數據庫的步驟,上篇文章更多的是一個理論分析,今天我們就動手來搞代碼,兩相印證下,加深我們對JDBC的理解。
廢話不多說了,擼起袖子,碼起來。
1.Statement與PreparedStatement的關係與區別
一個尷尬又不是禮貌的微笑🙂。。在開始碼代碼之前,還是要更簡單的講解下Statement與PreparedStatement,這兩個接口都是JDBC中提供的重要接口,也是我們用來執行增刪改查的主力軍(CallableStatement更多的用來執行存儲過程)。
首先我們來講下兩者的聯繫,PreparedStatement接口繼承(extends
)了Satement接口,也就是說數據庫驅動幫我們實例化的Satement對象和PreparedStatement對象就像是父類和子類之間的關係,PreparedStatement就像一個超集,其中實現了更多的方法,可以提供更強勁的功能。
兩者之間最主要的區別就是PreparedStatement相比於Satement多了一個SQL預編譯的功能,並且SQL中還可以使用?
作爲佔位符,只需在SQL執行前設置具體的參數即可。而這個功能則使PreparedStatement能非常方便處理批量數據的變更,不僅可以更好的獲得執行SQL,可以節省每次執行時SQL編譯的時間,可以讓此次數據操作更快的完成,並且PreparedStatement對象創建時就已經對SQL進行了預編譯,因此還可有效的防止SQL注入攻擊。
2.一個好的JDBC操作規範
一個好的後端代碼應該是分層的,上層的代碼去調用下層封裝的方法,增加代碼的複用率,並且方便管理,更少出錯。
這裏我們把上文中JDBC編程步驟拿來,我們按照分層的思想,來對其步驟進行分解。
- 加載並註冊數據驅動
- 通過
DriverManager
獲取數據庫的連接Connection - 通過Connection對象獲取
Statment
對象 - 使用獲取的
Statment
對象執行SQL語句 - 處理返回結果
- 關閉連接,釋放資源
這裏,我們將步驟1、2封裝成爲獲取連接的方法,步驟6封裝成關閉連接、釋放資源的方法,並將這兩個方法封裝成一個Util類(工具類);在dao層中,封裝增、刪、改、查的方法,並在每個方法中執行步驟3、4、5;在sevice層實現各個功能(方法),每個方法中調用dao的方法;在Controller(Servlet)層中調用sevice中的方法。各層的調用關係如下如所示:
這裏將獲取連接和關閉連接的方法封裝在JDBCUtil中下面我們給出JDBCUtil.java的代碼:
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
public class JDBCUtil {
/**
* 獲取連接
*
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public static Connection getConnection() throws ClassNotFoundException, SQLException {
//步驟一,註冊驅動
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/java_web?useSSL=false&characterEncoding=utf-8";
String userName = "root";
String password = "123456";
//步驟二,創建連接
Connection connection = DriverManager.getConnection(url, userName, password);
return connection;
}
/**
* 步驟六:關閉連接,釋放資源
*
* @param statement
* @param connection
*/
public static void release(ResultSet resultSet, Statement statement, Connection connection) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet = null;
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
}
}
3.增刪改查的實現
下面我們就以用戶(User)對象來實現dao和簡單的實現Service。
User的創建SQL如下:
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL,
`email` varchar(32) DEFAULT NULL,
`birthday` date DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
我們創建與之對應的Java類User.java代碼如下(省略了getter、setter和toString方法):
import java.util.Date;
/**
* 用戶類
*
* @author lizishu
*
*/
public class User {
private int id;
private String userName;
private String password;
private String email;
private Date birthday;
//getXxx getter方法
//setXxx setter方法
//toString
}
爲了充分的展示如何使用Statement和PreparedStatement中的方法實現對users的增刪改查的,UserDao中實現瞭如下方法,方法截圖如下:
UserDao的代碼如下:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import com.zzxy.web.domain.User;
import com.zzxy.web.utils.JDBCUtil;
public class UserDao {
/**
* 使用Statement來進行用戶查詢
* 根據入參的id查詢對應的用戶信息
*
* @param id
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public User findUserById(int id) throws ClassNotFoundException, SQLException {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 獲取數據庫連接
connection = JDBCUtil.getConnection();
// 創建Statement對象
statement = connection.createStatement();
String sql = "SELECT * FROM users WHERE id = " + id;
// 獲取查詢結果集
resultSet = statement.executeQuery(sql);
User user = new User();
while (resultSet.next()) {
user.setId(resultSet.getInt("id"));
user.setUserName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
user.setEmail(resultSet.getString("email"));
user.setBirthday(resultSet.getDate("birthday"));
}
return user;
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw e;
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
JDBCUtil.release(resultSet, statement, connection);
}
}
/**
* 使用Statement查找表中的所有員工
* 注:一般此功能需要增加分頁
*
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public List<User> findAllUsers() throws ClassNotFoundException, SQLException {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 獲取數據庫連接
connection = JDBCUtil.getConnection();
// 創建Statement對象 加上其中的兩個參數可以讓ResultSet中的遊標可以自由移動
statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
String sql = "SELECT * FROM users";
// 獲取查詢結果集
resultSet = statement.executeQuery(sql);
// 這些方法都可使用,具體功能可參考上篇博文
// resultSet.previous();
// resultSet.relative(1);
// resultSet.absolute(1);
// resultSet.beforeFirst();
// resultSet.afterLast();
// resultSet.first();
// resultSet.last();
List<User> userList = new ArrayList<User>();
// 迭代獲取所有的用戶
while (resultSet.next()) {
// 注意此處User必須重新new一個,否則list中所有的user都是最後一個user
User user = new User();
user.setId(resultSet.getInt("id"));
user.setUserName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
user.setEmail(resultSet.getString("email"));
user.setBirthday(resultSet.getDate("birthday"));
// 添加到列表中
userList.add(user);
}
// System.out.println("獲取的用戶信息爲:" + user);
return userList;
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw e;
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
JDBCUtil.release(resultSet, statement, connection);
}
}
/**
* 使用Statement新增一個用戶
*
* @param userList
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public boolean insertUser(User user) throws ClassNotFoundException, SQLException {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 獲取數據庫連接
connection = JDBCUtil.getConnection();
// 創建PreparedStatement對象
statement = connection.createStatement();
// 拼接SQL
// 注意下面java.tile.Date 轉換爲 java.sql.Date
String sql = "INSERT INTO users (name, password, email, birthday) VALUES ('" + user.getUserName() + "', '"
+ user.getPassword() + "', '" + user.getEmail() + "', '"
+ new java.sql.Date(user.getBirthday().getTime()) + "');";
// 執行SQL,並獲取影響的行數
int result = statement.executeUpdate(sql);
// 如果影響的行數大於0,表示此用戶插入成功
if (result > 0) {
return true;
}
// 返回插入成功的數量
return false;
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw e;
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
JDBCUtil.release(resultSet, statement, connection);
}
}
/**
* 使用PreparedStatement預編譯sql
* 一次新增多個用戶
*
* @param userList
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public int insertUser(List<User> userList) throws ClassNotFoundException, SQLException {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
// 獲取數據庫連接
connection = JDBCUtil.getConnection();
// 構建Sql,參數使用佔位符?代替
String sql = "INSERT INTO users (name, password, email, birthday) VALUES (?, ?, ?, ?);";
// 創建PreparedStatement對象
statement = connection.prepareStatement(sql);
// 總的插入結果
int resultCount = 0;
// 循環設置SQL中的參數
for (User tmpUser : userList) {
// 設置sql中佔位符對應的值,index從1開始
statement.setString(1, tmpUser.getUserName());
statement.setString(2, tmpUser.getPassword());
statement.setString(3, tmpUser.getEmail());
statement.setDate(4, new java.sql.Date(tmpUser.getBirthday().getTime()));
// 執行SQL,並獲取影響的行數
int result = statement.executeUpdate();
// 如果影響的行數大於0,表示此用戶插入成功
if (result > 0) {
resultCount++;
}
}
return resultCount;
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw e;
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
JDBCUtil.release(resultSet, statement, connection);
}
}
/**
* 使用PreparedStatement更新用戶
* 只更新User中的非空字段
* 拼接SQL中參數個數不確定時的情況
*
* @param user
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public boolean updateUser(User user) throws ClassNotFoundException, SQLException {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
// 獲取數據庫連接
connection = JDBCUtil.getConnection();
// 構建Sql,參數使用佔位符代替
// 手動拼接SQL
String sql = "update users set ";
List<Object> paramList = new ArrayList<Object>();
//如果某個屬性爲null,則跳過,不更新此字段
if (user.getUserName() != null) {
sql += "name = ?,";
paramList.add(user.getUserName());
}
if (user.getPassword() != null) {
sql += "password = ?,";
paramList.add(user.getPassword());
}
if (user.getEmail() != null) {
sql += "email = ?,";
paramList.add(user.getEmail());
}
if (user.getBirthday() != null) {
sql += "birthday = ?,";
paramList.add(user.getBirthday());
}
// 去除多餘的逗號
sql = sql.substring(0, sql.length() - 1);
sql += " where id = ?";
// 添加id到參數列表中
paramList.add(user.getId());
// 創建PreparedStatement對象
statement = connection.prepareStatement(sql);
// 設置sql中佔位符對應的值
for (int i = 0; i < paramList.size(); i++) {
statement.setObject(i + 1, paramList.get(i));
}
int result = statement.executeUpdate();
// 如果影響的行數大於0,表示此用戶更新成功
if (result > 0) {
return true;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw e;
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
JDBCUtil.release(resultSet, statement, connection);
}
return false;
}
/**
* 使用PreparedStatement刪除用戶
* SQL中無佔位符
*
* @param id
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public boolean deleteUser(int id) throws ClassNotFoundException, SQLException {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
// 獲取數據庫連接
connection = JDBCUtil.getConnection();
// 構建Sql,手動拼接SQL
String sql = "delete from users where id = " + id;
// 創建PreparedStatement對象
statement = connection.prepareStatement(sql);
int result = statement.executeUpdate();
// 如果影響的行數大於0,表示此用戶更新成功
if (result > 0) {
return true;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw e;
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
JDBCUtil.release(resultSet, statement, connection);
}
return false;
}
}
上面,我們分別實現了findUserById(int)
、findAllUsers()
、insertUser(User)
、insertUser(List<User>)
、updateUser(User)
、deleteUser(int)
這六個方法,使用Satement、PreparedStatement實現對user的增刪改查,並且演示了SQL拼接、具體佔位符數量未知如何寫SQL、批量插入、ResultSet中游標的使用等。
下面我們簡單的實現一個UserService類(具體的Service方法還需要根據業務來寫,此處的Service主要是爲了測試),其中實現的方法如下:
其代碼如下,這裏使用了單例模式,使用懶漢式創建UserService。
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.zzxy.web.dao.UserDao;
import com.zzxy.web.domain.User;
/**
* User的Service類 使用單例模式,保證service在整個項目中唯一
*
* @author lizishu
*
*/
public class UserService {
private static UserService instance;
private static UserDao userDao = null;
// 私有的構造函數,在UserService類外部無法調用
private UserService() {
}
// 單例 懶漢式
public static UserService getInstance() {
// 第一次調用時初始化UserService、UserDao
if (instance == null) {
instance = new UserService();
userDao = new UserDao();
}
return instance;
}
/**
* 根據id查詢用戶測試
*
* @param id
* @throws SQLException
* @throws ClassNotFoundException
*/
public void testSelectUserById(int id) throws ClassNotFoundException, SQLException {
System.out.println("查用用戶,入參爲:" + id);
User user = userDao.findUserById(id);
// ...
System.out.println("用戶查詢成功,查詢到的用戶爲:" + user);
}
/**
* 查詢所有用戶測試
*
* @throws ClassNotFoundException
* @throws SQLException
*/
public List<User> testFindAllUsers() throws ClassNotFoundException, SQLException {
System.out.println("查用所有用戶");
List<User> userList = userDao.findAllUsers();
// ...
System.out.println("FindAllUsers執行成功,查詢到的用戶數量爲:" + userList.size());
return userList;
}
/**
* 測試使用Statement一次插入一個用戶
*
* @throws SQLException
* @throws ClassNotFoundException
*/
public void testInsertUser(User user) throws ClassNotFoundException, SQLException {
System.out.println("新增用戶");
boolean result = userDao.insertUser(user);
// ...
System.out.println("新增用戶執行結果爲" + result);
}
/**
* 測試使用PreparedStatement一次插入多個用戶
*
* @throws SQLException
* @throws ClassNotFoundException
*/
public void testInsertUser(List<User> userList) throws ClassNotFoundException, SQLException {
System.out.println("批量插入多個用戶,開始時間爲:" + System.currentTimeMillis());
long beginTimes = System.currentTimeMillis();
// 如果傳入的list爲null或其中無數據,方法結束
if (userList == null || userList.isEmpty()) {
return;
}
int result = userDao.insertUser(userList);
// ...
System.out.println("批量插入執行成功,結束時間爲:" + System.currentTimeMillis() + ", 插入結果爲" + result);
long millis = System.currentTimeMillis() - beginTimes;
System.out.println("PreparedStatement新增用戶成功,耗時:" + millis + "豪秒");
}
/**
* 測試更新用戶
*
* @param user
* @throws SQLException
* @throws ClassNotFoundException
*/
public void testUpdateUser(User user) throws ClassNotFoundException, SQLException {
System.out.println("更新指定用戶信息");
boolean result = userDao.updateUser(user);
// ...
System.out.println("用戶更新執行結果爲:" + result);
}
/**
* 測試刪除用戶
*
* @param user
* @throws SQLException
* @throws ClassNotFoundException
*/
public void testDeleteUser(int id) throws ClassNotFoundException, SQLException {
System.out.println("刪除指定用戶信息:id=" + id);
boolean result = userDao.deleteUser(id);
// ...
System.out.println("刪除用戶執行結果爲:" + result);
}
}
4.測試
關於User類的操作,dao層、Service層都已實現,一般來說,就可以在Servlet中進行Service的加載,並調用其中封裝好的方法,完成業務邏輯並將處理結果返回給客戶端即可。
這裏爲了方便測試,我們就使用最簡單的main函數來進行調用(這裏推薦使用單元測試–Junit),下面代碼的測試順序爲:用戶的插入–>查詢所有用戶–>更新某個用戶–>查詢更新用戶–>刪除某個用戶。
public static void main(String[] args) {
UserService userService = UserService.getInstance();
// 批量構造10個user,並存入userList中
List<User> userList = new ArrayList<User>();
String initName = "test-";
for (int i = 1; i <= 10; i++) {
User user = new User();
user.setUserName(initName + i);
user.setPassword("123456");
user.setEmail("xxxxxxx");
user.setBirthday(new Date());
userList.add(user);
}
// 一般來講,異常拋到Controller層就需要處理了,否則發生異常時會將異常的堆棧信息拋給用戶
try {
// 使用過Statement對象插入單用戶--該對象是從userList中獲取的隊首的用戶
userService.testInsertUser(userList.get(0));
// 使用過PreparedStatement對象插入用戶列表
userService.testInsertUser(userList);
// 查詢所有用戶,即上面新插入的十一條用戶數據
userList = userService.testFindAllUsers();
for (User tmp : userList) {
System.out.println("查詢到的用戶:" + tmp);
}
// 更新userList中index爲5數據
User user = userList.get(5);
System.out.println("更新數據的id爲:" + user.getId());
user.setPassword("11111");
// 注意""與null 在UserDao中updateUser中產生的不同效果
user.setEmail("");
user.setBirthday(null);
userService.testUpdateUser(user);
// 查詢上一條更新後的數據
userService.testSelectUserById(user.getId());
// 刪除userList中index爲7數據
userService.testDeleteUser(userList.get(7).getId());
} catch (ClassNotFoundException | SQLException e) {
// 記錄異常信息,一般使用error級別
System.out.println("方法執行異常,異常信息爲:" + e.getMessage());
e.printStackTrace();
}
}
上面測試代碼運行後,控制檯輸出信息如下,從下圖可以看到,單次插入了一個用戶,批量插入了10個用戶,因此查詢處的所有用戶工11個,對id爲6的員工進行更新,查詢後可以發現,password順利修改了,而email和birthday也是一個被更新爲空字符串,一個無變化,最後刪除了id爲8的用戶。
爲了展示上面代碼對數據庫的操作,我們來看下數據庫中users表的截圖,可以看到,id爲6的用戶已經被更新了,而id爲8的用戶也已被刪除。
4.總結
本文簡單的介紹了Statement與PreparedStatement對象,簡述了兩者的關係與區別;介紹了軟件開發中的分層思想,較好的代碼分層,不僅可以增加代碼複用性,也因爲這種分層的實現讓代碼的魯棒性也更好;最後簡單的師範了下如何分層開發代碼,並演示了使用Statement與PreparedStatement完成數據庫的增刪改查。
在使用JDBC操作數據庫時,Statement與PreparedStatement兩個相比,推薦大家使用PreparedStatement,因爲其更方便、更高效、更安全。
Statement效果一般,PreparedStatement效果不錯,但是若要和ORM框架比起來,我還是選Mybatis。。。
參考閱讀:
又到了分隔線以下,本文到此就結束了,本文內容全部都是由博主自己進行整理並結合自身的理解進行總結,如果有什麼錯誤,還請批評指正。
Java web這一專欄會是一個系列博客,喜歡的話可以持續關注,如果本文對你有所幫助,還請還請點贊、評論加關注。
有任何疑問,可以評論區留言。