在實際的開發中,如果直接使用JDBC開發,是非常繁瑣且麻煩的,所以現在的企業在開發web程序時,連接數據庫一定會使用一些JDBC的框架。
在學習框架之前,得先掌握一些基礎知識。
- JDBC元數據(編寫JDBC框架的基礎)
首先就來學習一下JDBC元數據。
元數據就是數據庫、表、列的定義信息。
元數據相關類(DataBaseMetaData),可以通過Connetion接口中的getMetaData()方法來獲得一個DataBaseMetaData對象。
通過實例感受一下:
新建一個web項目,名為demo
因為在之前已經學習了數據庫連接池技術,所以之后有關數據庫操作的部分都可以使用連接池,推薦使用c3p0,因為它相較於別的連接池更加簡單和人性化。
重新編寫工具類JDBCUtils
/**
* JDBC 工具類,抽取公共方法
*
* @author seawind
*
*/
public class JDBCUtils {
//獲得數據庫的連接 通過c3p0連接池
private static DataSource dataSource = new ComboPooledDataSource();
public static Connection getConnection() throws SQLException{
return dataSource.getConnection();
}
// 釋放資源
public static void release(ResultSet rs, Statement stmt, Connection conn) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
release(stmt, conn);
}
public static void release(Statement stmt, Connection conn) {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
新建測試類MetaDataTest,編寫測試代碼
@Test
public void demo1() throws SQLException{
//通過Connection對象獲得DataBaseMetaData
Connection conn = JDBCUtils.getConnection();
DatabaseMetaData databaseMetaData = conn.getMetaData();
//通過DatabaseMetaData可以獲得JDBC連接的參數信息
System.out.println(databaseMetaData.getURL());
System.out.println(databaseMetaData.getDriverName());
System.out.println(databaseMetaData.getUserName());
//獲得某張表的主鍵信息
ResultSet rs = databaseMetaData.getPrimaryKeys(null, null, "account");
while(rs.next()){
//獲得表名
System.out.println(rs.getString("TABLE_NAME"));
//獲得列名
System.out.println(rs.getString(4));
}
}
都是一些十分簡單的API,沒有什么可說的,自己看看API文檔應該就能明白。
接下來是第二個內容,參數元數據(ParameterMataData),可以獲得預編譯sql語句中?的信息。
通過PreparedStatement調用getParameterMataData()得到。
演示一下。
編寫測試代碼
@Test
public void demo2() throws SQLException{
Connection conn = JDBCUtils.getConnection();
String sql = "select * from account where name = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
//通過ParameterMataData獲得?的相關信息
ParameterMetaData parameterMetaData = stmt.getParameterMetaData();
//獲得參數個數
int count = parameterMetaData.getParameterCount();
System.out.println(count);
//獲得參數類型 該方法並不是所有的數據庫都支持 MySQL不支持
int type = parameterMetaData.getParameterType(1);
System.out.println(type);
}
其中的getParameterType()方法並不是所有的數據庫都支持,而恰好MySQL數據就不支持該方法。那如何讓該方法在MySQL數據庫被支持呢?可以在配置Url的時候在后面加上?generateSimpleParameterMetadata=true
,然后運行測試代碼,該方法返回的值是12,通過查看源碼,得知12代表的是數據庫中的varchar類型。
/**
* <P>The constant in the Java programming language, sometimes referred
* to as a type code, that identifies the generic SQL type
* <code>VARCHAR</code>.
*/
public final static int VARCHAR = 12;
但是,請注意了,其實不管你的預編譯sql語句的參數是什么類型,在MySQL數據庫用該方法查詢得到的數據類型永遠都會是12,也就是varchar類型。
接下來是第三個元數據,叫做結果集元數據(ResultSetMetaData)
編寫測試代碼
@Test
public void demo3() throws SQLException{
//測試結果集元數據ResultSetMetaData
Connection conn = JDBCUtils.getConnection();
String sql = "select * from account";
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery();
//獲得結果集元數據
ResultSetMetaData resultSetMetaData = rs.getMetaData();
//獲得列數
int count = resultSetMetaData.getColumnCount();
//打印數據表的第一行
for(int i = 1;i <= count;i++){
System.out.print(resultSetMetaData.getColumnName(i) + "\t");
}
System.out.println();
//打印每列類型
for(int i = 1;i <= count;i++){
System.out.print(resultSetMetaData.getColumnTypeName(i) + "\t");
}
System.out.println();
//打印表數據
while(rs.next()){
for(int i = 1;i <= count;i++){
System.out.print(rs.getObject(i) + "\t");
}
System.out.println();
}
}
有了元數據的基礎之后,我們就可以自己來編寫JDBC框架了。
先創建一個張表,並初始化數據
create table account(
id int primary key not null auto_increment,
name varchar(40),
money double
);
insert into account values(null,'aaa',1000);
insert into account values(null,'bbb',1000);
insert into account values(null,'ccc',1000);
現在就來為該表編寫DAO程序。
新建com.wang.domain包,然后在該包下創建實體類Account。
/**
* 屬性和列對應
* @author Administrator
*
*/
public class Account {
private int id;
private String name;
private double money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
然后新建com.wang.dao包,在該包下創建類AccountDao。
/**
* 插入一個賬戶的數據
* @param account
*/
public void insertAccount(Account account){
Connection conn = null;
PreparedStatement stmt = null;
try {
conn = JDBCUtils.getConnection();
String sql = "insert into account values(null,?,?)";
stmt = conn.prepareStatement(sql);
//設置參數
stmt.setString(1, account.getName());
stmt.setDouble(2, account.getMoney());
stmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally{
JDBCUtils.release(stmt, conn);
}
}
編寫測試代碼,調用執行
public static void main(String[] args) {
Account account = new Account();
account.setName("ddd");
account.setMoney(1000);
new AccountDao().insertAccount(account);
}
查詢數據庫,數據被成功添加到數據庫。
更新和刪除的方法和插入類似,不作過多贅述。
假設這個時候有很多的數據庫表,那我們都要給每一個數據庫表編寫相對應的方法,會發現,重復代碼非常的多,怎么樣能夠簡化它呢?我們應該抽取通用的方法代碼。
新建包com.wang.framework,在該包下新建類JDBCFramework。
/**
* 自定義的JDBC框架
* @author Administrator
*
*/
public class JDBCFramework {
/**
* 通用的insert,update和delete方法
* @param sql 預編譯需要的sql
* @param args 根據sql中的?准備的參數
*/
public static void update(String sql,Object... args){
Connection conn = null;
PreparedStatement stmt = null;
try {
conn = JDBCUtils.getConnection();
stmt = conn.prepareStatement(sql);
//設置參數 根據?設置參數
ParameterMetaData parameterMetaData = stmt.getParameterMetaData();
//獲得參數個數
int count = parameterMetaData.getParameterCount();
for(int i = 1;i <= count;i++){
stmt.setObject(i, args[i - 1]);
}
stmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally{
JDBCUtils.release(stmt, conn);
}
}
}
然后回到類AccountDao中,編寫一個刪除的方法。
public void deleteAccount(Account account) {
String sql = "delete from account where id = ?";
JDBCFramework.update(sql, account.getId());
}
因為現在有了自己編寫好的框架,所以實現一個刪除方法是非常簡單的。
編寫測試代碼
public static void main(String[] args) {
Account account = new Account();
account.setId(3);
new AccountDao().deleteAccount(account);
}
運行測試代碼
id為3的用戶數據被成功刪除。
插入、修改方法和刪除類似。但是該框架無法用於查詢,因為沒有查詢所對應的結果集。
我們來抽取一個查詢方法,用於數據庫表的通用查詢。
在com.wang.framework包下新建接口MyResultSetHandler。
public interface MyResultSetHandler {
/**
* 將結果集中的數據封裝成對象
* @param rs
* @return
*/
public Object handle(ResultSet rs);
}
在JDBCFramework類中添加方法。
/**
* 通用的查詢方法
*/
public static Object query(String sql,MyResultSetHandler handler,Object... args){
Object obj = null;
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
stmt = conn.prepareStatement(sql);
//設置參數
ParameterMetaData parameterMetaData = stmt.getParameterMetaData();
int count = parameterMetaData.getParameterCount();
for(int i = 1;i <= count;i++){
stmt.setObject(i, args[i - 1]);
}
rs = stmt.executeQuery();
obj = handler.handle(rs);
} catch (SQLException e) {
e.printStackTrace();
}finally{
JDBCUtils.release(rs, stmt, conn);
}
return obj;
}
這樣查詢框架就寫好了,當調用者調用該方法時需要自己實現接口,完成查詢。
public Account findById(int id){
//使用自定義框架查詢
String sql = "select * from account where id = ?";
MyResultSetHandler handler = new MyResultSetHandler() {
public Object handle(ResultSet rs) {
try {
if(rs.next()){
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getDouble("money"));
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
};
return (Account) JDBCFramework.query(sql, handler, id);
}
編寫測試代碼。
public static void main(String[] args){
Account account = new AccountDao().findById(1);
System.out.println(account.getName());
}
運行測試代碼,成功查詢到表數據。
會發現,要想實現查詢,調用者就必須實現接口,接下來我們利用泛型和反射幫助調用者實現接口。
這塊內容有點復雜,就直接貼代碼了。
修改MyResultSetHandler接口
public interface MyResultSetHandler<T> {
/**
* 將結果集中的數據封裝成對象
* @param rs
* @return
*/
public T handle(ResultSet rs);
}
新建類MyBeanHandler
public class MyBeanHandler<T> implements MyResultSetHandler<T>{
private Class<T> domainClass;
public MyBeanHandler(Class<T> domainClass){
this.domainClass = domainClass;
}
public T handle(ResultSet rs) {
try{
//獲得結果集元數據
ResultSetMetaData resultSetMetaData = rs.getMetaData();
int count =resultSetMetaData.getColumnCount();
BeanInfo beanInfo = Introspector.getBeanInfo(domainClass);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
if(rs.next()){
//獲得實例
T t = domainClass.newInstance() ;
for(int i = 1;i <= count;i++){
String columnName = resultSetMetaData.getColumnName(i);
//獲得列名 需要去查找匹配屬性
for(PropertyDescriptor descriptor : propertyDescriptors){
if(columnName.equals(descriptor.getName())){
//列名存在一個同名的屬性
//將列的值存入屬性
Method method = descriptor.getWriteMethod();
method.invoke(t,rs.getObject(columnName));
}
}
}
return t;
}
}catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
修改類JDBCFramework的查詢方法
/**
* 通用的查詢方法
*/
public static <T>T query(String sql,MyResultSetHandler<T> handler,Object... args){
T obj = null;
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
stmt = conn.prepareStatement(sql);
//設置參數
ParameterMetaData parameterMetaData = stmt.getParameterMetaData();
int count = parameterMetaData.getParameterCount();
for(int i = 1;i <= count;i++){
stmt.setObject(i, args[i - 1]);
}
rs = stmt.executeQuery();
obj = handler.handle(rs);
} catch (SQLException e) {
e.printStackTrace();
}finally{
JDBCUtils.release(rs, stmt, conn);
}
return obj;
}
在AccountD類中修改查詢方法
public Account findById(int id){
String sql = "select * from account where id = ?";
return JDBCFramework.query(sql, new MyBeanHandler<Account>(Account.class),id);
}
重新運行測試代碼,查詢成功。