在學習JDBC的時候,項目開發的過程中,總會遇到對數據庫表數據增刪改查的操作,而很多基本的JDBC代碼都會出現大量的重複編寫,不僅對DAO的編寫造成了繁瑣,也對以後的維護增加了困難。當然,很多開源的框架可以解決這些問題,比如Mybatis,Hibernate等這些好的框架可以很好的實現相應的效果,但處在學習基礎的過程中,這些框架不建議使用,那麼最好的方法就是可以封裝原來重複的代碼,這樣的話,一個可以適用學習的過程,其次也可以提高對Java基礎的掌握程度,再而可以通過自己的封裝適用最基本的項目開發,而且很容易,很方便的進行自己的項目維護開發。
注:本博文所使用的代碼可以通過以下鏈接進行獲取http://download.csdn.net/detail/songdeitao/6753631
下面就來看看最基本的JDBC輕量型封裝項目的實例。
首先來看一下最終的效果。
比如僅僅對數據庫中數據增加的DAO操作,代碼需要這樣寫:
UserDao中實現User數據的新增操作
public boolean doCreate(User user) {
// 創建標誌位
boolean flag = false;
// 創建sql語句
String sql = "insert into t_user values(?,?,?,?,?)";
// 填充參數(主鍵不需要填寫,自增)
Object[] parameters = new Object[] { null, user.getUserName(),
user.getAge(), user.getBirthday(), user.getIsVip() };
flag = DaoHandle.executeDML(sql, parameters) > 0 ? true : false;
return flag;
}
首先創建sql語句,這裏的sql語句需要通過預處理的方式進行操作,通過佔位符填寫相應的參數,然後將需要的參數(和佔位符個數相同)創建成Object對象數組,數組中包含將要對佔位符操作的數據,而輕量型封裝的DaoHandle這個類,就主要負責了對數據庫數據增刪改查的操作。
調用的測試代碼這樣編寫:
/**
* 測試增加一條記錄到數據庫中
*/
public static void addUser() {
// 創建對象
User user = new User("steven", 23, new Date(), true);
// 創建一條記錄
boolean flag = userDao.doCreate(user);
// 是否成功創建記錄
System.out.println(flag);
}
這個時候就可以在數據庫表中新增一條數據。
下面就來進行主要代碼的實現過程編寫:
本項目中主要採用MyEclipse8.5+Mysql5.0開發環境來實現開發。首先給出項目的結構圖,如圖1所示:
圖1
在這個圖中,可以看到,這是一個最基本的項目搭建結構,而在這個過程中本文主要對dao進行封裝,而所使用的輕量型封裝的代碼主要是DaoHandle這個類,所以以後的功能擴展,代碼可靠性的提高,主要就是對此類進行修改。這裏就按照項目搭建的過程一步步講解。
首先創建數據庫,而用到的數據庫代碼都在mysql.sql文件,
mysql.sql
--創建數據庫steven
create database steven;
--應用數據庫
use steven;
--創建表
create table t_user(
userId int(10) not null primary key auto_increment,
userName varchar(100) not null,
age int(2),
birthday datetime,
isVip boolean
);
--顯示錶結構
desc t_user;
--顯示錶中數據
select * from t_user;
--提交
commit;
這個時候可以將這些代碼如圖2所示進行執行:
圖2
這個時候數據庫已經創建成功。
注:小技巧,在windows的命令提示符中如果想如上圖所示改變字體和背景的顏色,可以通過color f4這個命令來實現,不清楚的可以動手試試。後面的f4分別指代的是背景和字體的顏色。
然後給出User實體的java代碼:
User.java
package com.steven.entity;
import java.util.Date;
/**
* 實體類
*
* @author Steven
*
*/
public class User {
// 用戶Id
private int userId;
// 用戶名
private String userName;
// 用戶年齡
private int age;
// 用戶生日
private Date birthday;
// 用戶是否是會員 true:是 false:不是
private boolean isVip;
public User() {
}
// 有參構造
public User(String userName, int age, Date birthday, boolean isVip) {
this.userName = userName;
this.age = age;
this.birthday = birthday;
this.isVip = isVip;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public boolean getIsVip() {
return isVip;
}
public void setIsVip(boolean isVip) {
this.isVip = isVip;
}
@Override
public String toString() {
return "用戶信息: [年齡是" + this.getAge() + ", 生日是" + this.getBirthday()
+ ", 用戶編號是" + this.getUserId() + ", 用戶名是" + userName
+ isVip(this.getIsVip()) + "]";
}
public String isVip(boolean isVip) {
return ", " + (isVip == true ? "是" : "不是") + "會員";
}
}
這個時候給出數據庫資源文件的代碼,這個資源文件的獲取有很多種方式,感興趣的可以參照(http://blog.csdn.net/songdeitao/article/details/17447627)這篇博文進行學習。
jdbc.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/steven
user=root
password=mysqladmin
連接Mysql數據庫的代碼,這裏的連接驅動的jar包已經附屬到項目中,
DatabaseConnection.java
package com.steven.dbc;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* 進行數據庫連接的操作
*
* @author Steven
*
*/
public class DatabaseConnection {
public static String driver;// 驅動
public static String url;// url
public static String user;// 用戶名
public static String password;// 密碼
public DatabaseConnection() {
}
static {
File file = new File("src/jdbc.properties");
Properties pro = new Properties();
try {
FileInputStream in = new FileInputStream(file);
pro.load(in);// 讀取屬性配置文件
driver = pro.getProperty("driver");
url = pro.getProperty("url");
user = pro.getProperty("user");
password = pro.getProperty("password");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 獲取數據庫連接
*
* @return
*/
public static Connection getConnection() {
Connection conn = null;
try {
Class.forName(driver);
conn = DriverManager.getConnection(url, user, password);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
/**
* 關閉數據庫對象
*
* @param con
* @param stmt
* @param rs
*/
public static void closeAll(Connection con, Statement stmt, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
rs = null;
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
stmt = null;
}
if (con != null) {
try {
con.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
con = null;
}
}
/**
* 測試數據庫連接
*
* @param args
*/
public static void main(String[] args) {
Connection conn = DatabaseConnection.getConnection();
System.out.println(conn);
}
}
注:一般情況下如果寫完了連接數據庫的代碼,都要進行一次測試,如果成功可以進行接下來的編碼,如果失敗了,就要進行錯誤糾正,這樣可以保證以後的代碼編寫不會因爲之前的錯誤而影響到,也減少了更改bug的難度。這種遞進式的代碼開發,是常用的開發模式。尤其在大的項目中,一定要對自己的每一個模塊可以進行測試,確保項目的正確性。測試結果:
com.mysql.jdbc.JDBC4Connection@f11404
獲取了連接對象,是正確的。 下面將給出博文的核心,也是輕量型JDBC代碼的封裝的具體實現,DaoHandle類的具體實現如下所示:
DaoHandle.java
package com.steven.util;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.steven.dbc.DatabaseConnection;
/**
* DAO數據操作的輔助類
*
* @author Steven
*
*/
public class DaoHandle {
private static Connection con;
private static PreparedStatement pstmt;
private static ResultSet rs;
/**
* 執行所有的DML操作
*
* @param sql
* @param parameters
* @return
*/
public static int executeDML(String sql, Object[] parameters) {
int count = -1;
// 獲取連接
con = DatabaseConnection.getConnection();
if (con != null) {
try {
// 獲取處理器對象
pstmt = con.prepareStatement(sql);
// 注入參數
for (int i = 0; i < parameters.length; i++) {
// 根據參數的類型判斷調用注入方法
// 參數類型不安全
pstmt.setObject(i + 1, parameters[i]);
}
// 執行SQL語句
count = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
DatabaseConnection.closeAll(con, pstmt, null);
}
}
return count;
}
/**
* 查詢獲取單個對象
*
* @param <T>
* @param sql
* @param paramters
* @param objClass
* @return
*/
public static <T> T executeQueryForSingle(String sql, Object[] parameters,
Class<T> objClass) {
T t = null;
// 獲取連接
con = DatabaseConnection.getConnection();
if (con != null) {
try {
// 獲取處理器
pstmt = con.prepareStatement(sql);
// 注入參數
if (parameters != null) {
for (int i = 0; i < parameters.length; i++) {
pstmt.setObject(i + 1, parameters[i]);
}
}
// 執行查詢
rs = pstmt.executeQuery();
// 獲取結果集的元數據
ResultSetMetaData metaData = rs.getMetaData();
// 獲取所有列名
String[] colNames = getColNames(metaData);
// 獲取所有列的數據類型
// int[] colTypes = getColTypes(metaData);
// 使用反射獲取當前類的方法
Method[] methods = objClass.getDeclaredMethods();
// 獲取結果集的數據
while (rs.next()) {
// 獲取類的實例
t = objClass.newInstance();
// 循環判斷每列的數據類型
for (int i = 0; i < colNames.length; i++) {
// 獲取每列的結果
Object value = null;
// 進行所有參數類型的賦值,不能保證類型安全
value = rs.getObject(i + 1);
// 遍歷每個方法
for (Method m : methods) {
if (value != null) {
// 如果是和該列同名的set方法,則調用該方法
if (m.getName().equalsIgnoreCase(
"set" + colNames[i])) {
// 進行對set方法的調用,向其中置值
m.invoke(t, value);
}
}
}
}
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
DatabaseConnection.closeAll(con, pstmt, rs);
}
}
// 返回對象
return t;
}
/**
* 通過元數據獲取列名數組
*
* @param metaData
* @return
*/
private static String[] getColNames(ResultSetMetaData metaData) {
String[] colNames = null;
try {
// 獲取結果集的列數
int colCount = metaData.getColumnCount();
// 創建數組
colNames = new String[metaData.getColumnCount()];
// 遍歷每列
for (int i = 1; i <= colCount; i++) {
// 獲取列名
colNames[i - 1] = metaData.getColumnLabel(i);
}
} catch (SQLException e) {
e.printStackTrace();
}
return colNames;
}
/**
* 根據元數據獲取所有列的數據類型
*
* @param metaData
* @return
*/
private static int[] getColTypes(ResultSetMetaData metaData) {
int[] colNames = null;
try {
// 獲取列數
int colCount = metaData.getColumnCount();
// 創建數組
colNames = new int[colCount];
for (int i = 1; i <= colCount; i++) {
// colNames[i-1] = metaData.getColumnTypeName(i);
colNames[i - 1] = metaData.getColumnType(i);
}
} catch (SQLException e) {
e.printStackTrace();
}
return colNames;
}
/**
* 查詢多行對象
*
* @param <T>
* @param sql
* @param paramters
* @param objClass
* @return
*/
public static <T> List<T> executeQueryForMultiple(String sql,
Object[] parameters, Class<T> objClass) {
List<T> list = new ArrayList<T>();
// 獲取連接
con = DatabaseConnection.getConnection();
if (con != null) {
try {
// 獲取處理器
pstmt = con.prepareStatement(sql);
if (parameters != null) {
// 注入參數
for (int i = 0; i < parameters.length; i++) {
pstmt.setObject(i + 1, parameters[i]);
}
}
// 執行查詢
rs = pstmt.executeQuery();
// 獲取結果集的元數據
ResultSetMetaData metaData = rs.getMetaData();
// 獲取所有列名
String[] colNames = getColNames(metaData);
// 獲取所有列的數據類型
// int[] colTypes = getColTypes(metaData);
// 使用反射獲取當前類的方法
Method[] methods = objClass.getDeclaredMethods();
// 獲取結果集的數據
while (rs.next()) {
T t = null;
// 獲取類的實例
t = objClass.newInstance();
// 循環判斷每列的數據類型
for (int i = 0; i < colNames.length; i++) {
// 獲取每列的結果
Object value = null;
// 進行所有參數類型的賦值,不能保證類型安全
value = rs.getObject(i + 1);
// 遍歷每個方法
for (Method m : methods) {
if (value != null) {
// 如果是和該列同名的set方法,則調用該方法
if (m.getName().equalsIgnoreCase(
"set" + colNames[i])) {
// 進行對set方法的調用,向其中置值
m.invoke(t, value);
}
}
}
}
// 向集合中添加數據
list.add(t);
}
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} finally {
DatabaseConnection.closeAll(con, pstmt, rs);
}
}
// 返回集合
return list;
}
/**
* 返回數據表中數據的條數
*
* @param sql
* @param paramters
* @return
*/
public static int executeQueryForCount(String sql, Object[] parameters) {
int count = 0;
// 獲取連接
con = DatabaseConnection.getConnection();
try {
// 獲取處理器
pstmt = con.prepareStatement(sql);
if (parameters != null) {
// 注入參數
for (int i = 0; i < parameters.length; i++) {
pstmt.setObject(i + 1, parameters[i]);
}
}
// 執行查詢
rs = pstmt.executeQuery();
// 遍歷查找的結果集
while (rs.next()) {
count = rs.getInt(1);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DatabaseConnection.closeAll(con, pstmt, rs);
}
return count;
}
}
注:這個過程中用到了反射機制,也是java底層代碼實現的最基本應用,功能很強大,通過相應的方法,可以進行動態的方法調用,實現數據庫表元素和對象之間的互相映射。其實在數據庫框架中,也大量使用這反射機制。
然後給出Dao層中,iface和impl的實現代碼:
IBaseDao.java
package com.steven.dao.iface;
import java.util.List;
/**
* 通過泛型機制爲所有的實現類提供一下方法
*
* @author Steven
*
*/
public interface IBaseDao<T> {
/**
* 新增一條數據
*
* @param t
* @return
*/
public boolean doCreate(T t);
/**
* 根據編號(主鍵)進行刪除一條數據
*
* @param id
* @return
*/
public boolean delete(int id);
/**
* 修改數據
*
* @param t
* @return
*/
public boolean update(T t);
/**
* 根據ID查找到該信息
*
* @param id
* @return
*/
public T findById(int id);
/**
* 返回所有的信息
* @return
*/
public List<T> findAll();
}
IUserDao.java
package com.steven.dao.iface;
import com.steven.entity.User;
public interface IUserDao extends IBaseDao<User> {
}
UserDao.java
package com.steven.dao.impl;
import java.util.List;
import com.steven.dao.iface.IUserDao;
import com.steven.entity.User;
import com.steven.util.DaoHandle;
public class UserDao implements IUserDao {
@Override
public boolean delete(int id) {
// 創建標誌位
boolean flag = false;
// 創建sql語句
String sql = "delete from t_user where userId=?";
// 填充參數
Object[] parameters = new Object[] { id };
// 執行數據庫數據刪除操作
flag = DaoHandle.executeDML(sql, parameters) > 0 ? true : false;
return flag;
}
@Override
public boolean doCreate(User user) {
// 創建標誌位
boolean flag = false;
// 創建sql語句
String sql = "insert into t_user values(?,?,?,?,?)";
// 填充參數(主鍵不需要填寫,自增)
Object[] parameters = new Object[] { null, user.getUserName(),
user.getAge(), user.getBirthday(), user.getIsVip() };
flag = DaoHandle.executeDML(sql, parameters) > 0 ? true : false;
return flag;
}
@Override
public List<User> findAll() {
// 創建sql語句
String sql = "select * from t_user";
// 查詢所有的記錄
List<User> list = (List<User>) DaoHandle.executeQueryForMultiple(sql,
null, User.class);
return list;
}
@Override
public User findById(int id) {
// 創建sql語句
String sql = "select * from t_user where userId = ?";
// 填充參數
Object[] parameters = new Object[] { id };
// 查找單個記錄
User user = DaoHandle
.executeQueryForSingle(sql, parameters, User.class);
return user;
}
@Override
public boolean update(User user) {
// 創建標誌位
boolean flag = false;
// 創建sql語句
String sql = "update t_user set userName=?,age=?,birthday=?,isVip=? where userId=?";
// 注入參數
Object[] parameters = new Object[] { user.getUserName(), user.getAge(),
user.getBirthday(), user.getIsVip(), user.getUserId() };
flag = DaoHandle.executeDML(sql, parameters) > 0 ? true : false;
return flag;
}
}
接下來將給出UserDao的測試代碼,也是對輕量型封裝的DaoHandle的檢驗,
CRUDTest.java
package com.steven.test;
import java.util.Date;
import java.util.List;
import com.steven.dao.impl.UserDao;
import com.steven.entity.User;
public class CRUDTest {
private static UserDao userDao = new UserDao();
/**
* @param args
*/
public static void main(String[] args) {
// addUser();
// retrieveUser();
// deleteUser();
// updateUser();
// retrieveAll();
}
/**
* 測試增加一條記錄到數據庫中
*/
public static void addUser() {
// 創建對象
User user = new User("steven", 23, new Date(), true);
// 創建一條記錄
boolean flag = userDao.doCreate(user);
// 是否成功創建記錄
System.out.println(flag);
}
/**
* 測試從數據庫中讀取一條記錄
*/
public static void retrieveUser() {
// 從數據庫中讀取一條記錄
User user = userDao.findById(1);
// 將此記錄的相應對象輸出
System.out.println(user);
}
/**
* 從數據庫表中刪除一條數據
*/
public static void deleteUser() {
// 首先從數據庫中查找出一條記錄
User user = userDao.findById(1);
// 然後根據查找後的對象id從數據庫中刪除此記錄
boolean flag = false;
if (user != null) {
flag = userDao.delete(user.getUserId());
}
// 輸出刪除的成功與否
System.out.println(flag);
}
/**
* 更新數據操作
*/
public static void updateUser() {
// 從數據表中讀取一條數據
User user = userDao.findById(2);
// 更改數據
user.setIsVip(false);
// 執行更新操作
boolean flag = userDao.update(user);
// 成功與否
System.out.println(flag);
}
public static void retrieveAll() {
// 獲取所有記錄的集合
List<User> userList = userDao.findAll();
// 輸出所有對象信息
for (User user : userList) {
System.out.println(user);
}
}
}
然後給出測試的結果,這裏僅僅給出前兩個測試效果,所有的測試都是正確的,可以自己動手試試。
addUser()
控制檯輸出
true
數據庫中的結果如圖3所示:
圖3
retrieveUser();
控制檯輸出:
用戶信息: [年齡是23, 生日是2013-12-22 16:10:25.0, 用戶編號是1, 用戶名是steven, 是會員]
到這裏,輕量型的JDBC封裝的實現和測試已經全部給出,下面就開始編寫JavaSE的具體項目邏輯了,這時候可以在建一個包com.steven.business層(可以爲其他的層次)進行業務邏輯的操作,這樣一個簡答的使用封裝後的JDBC項目就可以實現了,當然這也可以用到web項目中,具體的項目編寫,還是大家實現吧,這個封裝僅僅是簡單的實現,如果有需要擴展的,比如分頁查找,搜索字段,都是可以實現的。
在此恭祝大家學習愉快!