介紹
簡介
我剛學JDBC的時候,在DAO層中獲取數據是這樣寫的:
但如果使用過DButils包或者Spring JDBCTemplate,就知道,在JavaBean裏,代碼可以這樣寫:
所以相比之下,第一種方法看起來就像原始人一樣…
接下來我將介紹一下,如何自己寫一個簡單的類來模擬這個功能。Ps:這裏我用到了數據庫連接池。
準備
JDBC驅動:mysql-connector-java-xxx.jar
C3P0數據庫連接池:c3p0-xxx.jar
實現
先創建一個名爲JDBCTools的類
連接
創建數據庫連接池
首先,你需要一個配置文檔,取名爲c3p0-config
,內容寫法如下:
<c3p0-config>
<named-config name="起個名字">
<!-- 指定連接數據源的基本屬性 -->
<property name="user">root</property>
<property name="password">自己寫你的密碼去</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/你的Database</property>
<!-- 下面是可寫可不寫的 -->
<!-- 若數據庫中連接數不足時, 一次向數據庫服務器申請多少個連接 -->
<property name="acquireIncrement">5</property>
<!-- 初始化數據庫連接池時連接的數量 -->
<property name="initialPoolSize">20</property>
<!-- 數據庫連接池中的最小的數據庫連接數 -->
<property name="minPoolSize">5</property>
<!-- 數據庫連接池中的最大的數據庫連接數 -->
<property name="maxPoolSize">50</property>
<!-- C3P0 數據庫連接池可以維護的 Statement 的個數 -->
<property name="maxStatements">30</property>
<!-- 每個連接同時可以使用的 Statement 對象的個數 -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
由於你這個類中所有的操作都是基於實例化了這個數據庫連接池之後才能做事的,所以:
1.這個類需要有一個數據源DataSource作爲成員變量。
2.需要先使用靜態代碼塊來先實例化。(關於爲什麼不使用構造函數呢,因爲這個工具類的所有方法都將被寫爲static方法,本類不會被實例化,所以不需要寫構造函數)
實現如下:
private static DataSource dataSource = null;
static{
dataSource = new ComboPooledDataSource("寫你剛起的那個名字");
}
獲取連接
有了一個DataSource,獲取連接這件事情就變得很簡單了:在數據庫連接池中之前已經創建好了很多個Connection了,現在你只需要找它要一個就對了。
public static Connection getConnection() throws SQLException{
return dataSource.getConnection();
}
釋放
Java7裏的自動釋放
Java7裏面增加了個語法糖:try-with-resources。用法是在try後面加上括號,括號裏面new一個對象(但是要求實現java.lang.AutoCloseable接口),編譯器在這個對象使用完後自動檢測null然後關閉。不過鑑於我們這裏的有些new操作是封裝了的,所以這樣似乎不太方便,所以,可以利用:Connection,Statement,resultSet都實現java.lang.AutoCloseable接口的特性來寫一個通用的關閉方法:
public void close(AutoCloseable auto)
{
if(auto!=null)
{
try {
auto.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
傳統的方法
不過我個人寫的是另外一種方法:一下把Connection,Statement,resultSet都關了的方法。
public static void releaseDB(ResultSet resultSet, Statement statement,
Connection connection) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Connection,Statement,resultSet這三個是在各種操作中最常見的,但是如果你只需要用到兩個類,你用null來佔位置就行了。
特別說明:在使用了數據庫連接池之後,Connection的close()操作就不再是真正意義上的關閉連接了,而是把這個連接放回數據庫連接池中,再次變成空閒狀態。
更新
在執行增,刪,改這類操作的時候,只用執行SQL語句即可,不需要從數據庫中的得到什麼,所以我們需要的只是
1.統一地補全PreparedStatement語句
2.執行
在補全語句的時候,爲了使得代碼的耦合性降低,給幾個參數就補全幾個,所以這裏使用可變參數 Object ... args
來。
(這裏多說一句,不管SQL語句有沒有佔位符?
最好用PreparedStatement,因爲Statement在執行的時候有可能被SQL注入)
public static void executeSql(String sql, Object ... args)
{
Connection conn = null;
try {
conn = getConnection();
} catch (Exception e1) {
e1.printStackTrace();
}
PreparedStatement ps = null;
try
{
ps = conn.prepareStatement(sql);
//逐個填寫
for(int i=0;i<args.length;i++)
{
ps.setObject(i+1, args[i]);
}
ps.execute();
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
releaseDB(null, ps, conn);
}
}
查詢
與更新不同的是,查詢會返回某個值。
獲取一組對象
由於獲取的對象的類可能是各種各樣的,這裏需要用到泛型和反射的知識,使得這個方法可以對所有的類都通用。
此外還需JDBC結果集的元數據(ResultSetMetaData)。
所以,
1.同上面那個更新方法一樣的執行操作,得到resultSet
2.利用resultSet得到元數據(讓你知道哪行的哪個值對應的屬性名字是什麼)
3.創建一個目標類的List,準備接受內容。
4.把每行的值和名字都依次放到一個HashMap<String,Object>裏面去
5.這樣你會得到一個HashMap的數組,數組裏每個HashMap代表的是實際每個對象的信息結果
6.對這個數組,進行循環操作:
(1)先利用反射實例化一個對象
(2)通過HashMap中儲存的名字和值來給這個對象賦值
(3)把賦值好了的對象加入到第3步中的List中
7.返出List即可
對於第6步中的(2),我們得到的“名字”實際上是數據庫的表裏面的首行的名字,而不一定和JavaBean中的變量名字一樣,這樣會導致賦值失敗。例如數據庫中的是user_id,而JavaBean中是userId。
所以我們可以
1.把表頭的屬性名字取的和JavaBean的一樣(但是這個方法不推薦,雖然我就是這樣做的)
2.利用AS語法:SELECT user_id AS userId,……
而利用名字給對象屬性賦值也有兩種方法
1.反射賦值,就是我下面代碼些的那種。
2.懶人使用commons-beanutils
工具類,點此下載。
還需要commons-logging.jar
,點此下載。
然後,使用方法如下:
BeanUtils.setProperty(對象的名字, “屬性的名字”, 屬性的值);
下面是正式代碼:
public static <T> List<T> executeQuery(Class clazz, String sql, Object ... args)
{
Connection conn = null;
try {
conn = getConnection();
} catch (Exception e1) {
e1.printStackTrace();
}
PreparedStatement ps = null;
ResultSet rs = null;
List<T> list = new ArrayList<T>();
try
{
ps = conn.prepareStatement(sql);
//填寫語句
for(int i=0;i<args.length;i++)
{
ps.setObject(i+1, args[i]);
}
//執行查詢
rs = ps.executeQuery();
//獲得元數據
ResultSetMetaData rsmd= rs.getMetaData();
//元數據的使用方法
while(rs.next())
{
Map<String,Object> map = new HashMap<String,Object>();
for(int i = 1;i<=rsmd.getColumnCount();i++)
{
map.put(rsmd.getColumnLabel(i), rs.getObject(i));
}
//利用反射創建一個實例
T entity = (T) clazz.newInstance();
//利用反射賦值
for(Map.Entry<String, Object> entry : map.entrySet())
{
String fieldName = entry.getKey();
Object fieldValue = entry.getValue();
//反射賦值,如果你用了BeanUtils就替換下面三句
Field f = entity.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
f.set(entity, fieldValue);
}
list.add(entity);
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
releaseDB(rs, ps, conn);
}
return list;
}
不過這裏有個小小的Bug。當你只需要返回一個對象的時候,這個方法返回給你的是個只有一個對象的List,所以嘛,就手動加點代碼啦,例如:
public Article ViewArticle(int id) {
String sql = "SELECT * FROM ARTICLE WHERE ID = ?";
List<User> list = JDBCTools.executeQuery(User.class, sql, id);
//手動獲取第一個對象XD
User user = list.get(0);
return user;
}
獲取某個值
本質上和上面那個一樣,但是返回的就是個值,所以簡單多了。
public static int executeGetSum(String sql, Object ... args)
{
Connection conn = null;
try {
conn = getConnection();
} catch (Exception e1) {
e1.printStackTrace();
}
PreparedStatement ps = null;
ResultSet rs = null;
int value = 0;
try
{
ps = conn.prepareStatement(sql);
//填寫語句
for(int i=0;i<args.length;i++)
{
ps.setObject(i+1, args[i]);
}
rs = ps.executeQuery();
while(rs.next())
{
//這裏實際看你列名怎麼取的如果你用了AS XXX那這裏記得改
value = rs.getInt("COUNT(*)");
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
releaseDB(null, ps, conn);
}
return value;
}
我的JDBCTools類的完整代碼
dbq我還沒寫註釋文檔…
package com.dao;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.sql.DataSource;
import org.junit.Test;
import com.mchange.v2.c3p0.ComboPooledDataSource;
/**
* JDBC 的工具類
*
* 其中包含: 獲取數據庫連接, 關閉數據庫資源等方法.
*/
public class JDBCTools {
private static DataSource dataSource = null;
//數據庫連接池應只被初始化一次. 自動創建了一堆Conn然後來管理
//然後這裏的conn.close的含義 就變爲了釋放 而不是關閉!
static{
dataSource = new ComboPooledDataSource("LehrC3P0");
}
public static Connection getConnection() throws SQLException{
return dataSource.getConnection();
}
public static void releaseDB(ResultSet resultSet, Statement statement,
Connection connection) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
//執行一條sql,沒有返回值
@Test
public static void executeSql(String sql, Object ... args)
{
Connection conn = null;
try {
conn = getConnection();
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
PreparedStatement ps = null;
try
{
ps = conn.prepareStatement(sql);
//填寫語句
for(int i=0;i<args.length;i++)
{
ps.setObject(i+1, args[i]);
}
ps.execute();
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
releaseDB(null, ps, conn);
}
}
//執行Select語句
/**
* @param <T>
* @param sql
* @param args
* @return
*/
@SuppressWarnings({ "rawtypes" })
public static <T> List<T> executeQuery(Class clazz, String sql, Object ... args)
{
Connection conn = null;
try {
conn = getConnection();
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
PreparedStatement ps = null;
ResultSet rs = null;
List<T> list = new ArrayList<T>();
try
{
ps = conn.prepareCall(sql);
//填寫語句
for(int i=0;i<args.length;i++)
{
ps.setObject(i+1, args[i]);
}
//執行查詢
rs = ps.executeQuery();
//獲得元數據
ResultSetMetaData rsmd= rs.getMetaData();
while(rs.next())
{
Map<String,Object> map = new HashMap<String,Object>();
for(int i = 1;i<=rsmd.getColumnCount();i++)
{
map.put(rsmd.getColumnLabel(i), rs.getObject(i));
}
//利用反射創建一個實例
@SuppressWarnings("unchecked")
T entity = (T) clazz.newInstance();
//利用反射賦值
for(Map.Entry<String, Object> entry : map.entrySet())
{
String fieldName = entry.getKey();
Object fieldValue = entry.getValue();
//直接對屬性操作!但是這裏時間類和Long那個有點小問題
Field f = entity.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
f.set(entity, fieldValue);
//其實這裏也可以導入Beanutils包來快速操作的
}
list.add(entity);
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
releaseDB(rs, ps, conn);
}
return list;
}
//不用加AS NUM !!!
public static int executeGetSum(String sql, Object ... args)
{
Connection conn = null;
try {
conn = getConnection();
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
PreparedStatement ps = null;
ResultSet rs = null;
int value = 0;
try
{
ps = conn.prepareStatement(sql);
//填寫語句
for(int i=0;i<args.length;i++)
{
ps.setObject(i+1, args[i]);
}
rs = ps.executeQuery();
while(rs.next())
{
value = rs.getInt("COUNT(*)");
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
releaseDB(null, ps, conn);
}
return value;
}
}