JDBC之使用Statement與PreparedStatement實現增刪改查

上一文中,我們對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編程步驟拿來,我們按照分層的思想,來對其步驟進行分解。

  1. 加載並註冊數據驅動
  2. 通過DriverManager獲取數據庫的連接Connection
  3. 通過Connection對象獲取Statment對象
  4. 使用獲取的Statment對象執行SQL語句
  5. 處理返回結果
  6. 關閉連接,釋放資源

​ 這裏,我們將步驟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。。。

參考閱讀:

  1. Java Web數據庫開發(MySQL)之環境準備
  2. 寫給求甚解的你—JDBC詳解

​ 又到了分隔線以下,本文到此就結束了,本文內容全部都是由博主自己進行整理並結合自身的理解進行總結,如果有什麼錯誤,還請批評指正。

​ Java web這一專欄會是一個系列博客,喜歡的話可以持續關注,如果本文對你有所幫助,還請還請點贊、評論加關注。

​ 有任何疑問,可以評論區留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章