鋼炮級 持久層 —— 上篇

Title:核心處理基類

平時接到很多規模不大的小型Web項目,很多部門提出要求說需要XXX和YYY等功能,他們之間既不能保持統一操作規範或習慣,也不能統一業務和數據,在這種的情況下,悲劇的重複,帶來了悲劇的工作量。

所以,我研究出一套應對我所遇到的不同需求的相同的解決方案(恩,這個詞彙受到了VS的影響,以前是解決思路),爲了解決編碼過程中可能遇到的問題,我定了幾個整體目標:

1. 系統應具有足夠的訪問性能,並儘量保持最少的資源消耗。
2. 持久層的重複編寫是不能接受的,應達到只需 Ctrl + V 或者包引用即可解決,且無修改。
3. 該持久層內部實現,應較低的耦合,具備高兼容性、高擴展性。
4. 內部實現應儘量簡單可讀,計算效率低、且計算複雜同樣不能接受!

具體在編碼階段,爲了控制訪問方式、避免Sql氾濫,我也設置幾個目標:
1. 與數據庫無關
2. 與表無關,實現具體表與相應的操作相分離
3. 與業務邏輯無關

要滿足以上要求,不會太困難,相信罈子裏也很多,好吧,廢話不錯說,上代碼,首先介紹 DBConnection.java
package com.pub.db;

import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

/***
* tomcat中的配置方式:
* 1. 在tomcat/conf/context.xml中,<context></context>中配置<Resource ... />
* 2. 在相同目錄下,配置server.xml,在<GlobalNamingResources>標籤裏配置相同的
* <Resource ... />,然後再context.xml中的<context>標籤裏,配置<ResourceLink />去引用它
*
* @author db2admin
*
*/
public class DBConnection {

private static Connection con = null;

/***
* 默認連接
* @return
*/
public static Connection getConnection() {
InitialContext initCtx = null;
Context context = null;
DataSource ds = null;
try {

initCtx = new InitialContext();
context = (Context) initCtx.lookup("java:comp/env");
ds = (DataSource) context.lookup("JDBC/SQL");
con = ds.getConnection();
} catch (SQLException e) {
e.printStackTrace();
} catch (NamingException e) {
e.printStackTrace();
}finally{
try {
if(context != null){
context.close();
}
if(initCtx != null){
initCtx.close();
}
} catch (NamingException e) {
e.printStackTrace();
}
}
return con;
}
}


注:以上代碼註釋中提到的tomcat配置,請參考網友xiejin2008的文章[url]http://xiejin2008.iteye.com/blog/370584[/url],講得很詳細

把這個類貼上來,我比較猶豫,大家都看了千百遍了,但這也確實是夠用的小成本連接池配置,能對付併發至少20的訪問量,對於我所處理的項目來說,併發數達到20是比較少見的,所以這種方式對於我來說屢試不爽啊。。 :D

然後是核心的邏輯處理類,PublicDao.java
package com.pub.dao;

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.HashMap;
import java.util.List;
import java.util.Map;

import com.pub.db.DBConnection;

/***
* 核心邏輯處理類
* 缺點: 1. 拼湊條件、鍵值對對象操作複雜;
* 2. 建立結果集(表)與實體類的映射關係耦合程度高
* 3. 與具體數據類型的耦合度程度高
* @author db2admin
* 2010-12-01
*/
public class PublicDao {

/**當前操作狀態 4*/
public final static int ADD = 4;
/**當前操作狀態 3*/
public final static int UPDATE = 3;
/**當前操作狀態 2*/
public final static int DELETE = 2;
/**當前操作狀態 1*/
public final static int SELECT = 1;
/**當前操作狀態:直接執行 0*/
public final static int CONDITION = 0;
/**當前操作狀態:直接執行無返回值 -1*/
public final static int CONDITION_1 = -1;

/***
* 該方法返回用於執行的SQL
*
* 通過Map_k和Map_v來執行生成SQL,增 Add 4,刪 Del 2,改 Upd 3,查 1,直接執行的SQL:condition<br>
*
* 情況零:<br>
* condition爲主要執行Sql,tableName,mk和mv爲空。<br>
* 情況一:Select<br>
* mk爲@cols的key,mv爲@where的key(或者完整"Key=Value"),condition(若不爲空)爲@where值【condition針對多條件臨時採用", "號分隔,其順序依次對應mv中的key】<br>
* 情況二:delete<br>
* mk中爲@where的key,mv中爲@where的value,其中條件中符號同樣在mv中<br>
* 情況三:update<br>
* mk中爲@value的key,mv中爲@values的value,其中條件中符號同樣在mv中,condition爲查詢條件<br>
* 情況四:insert<br>
* mk中爲@cols,mv中爲@value,與condition無關<br>
* @param type
* @param className
* @param mk
* @param mv
* @param condition
* @return
*/
public String createSql(int type,String tableName,Map<Object, String> mk,Map<Object, Object> mv,String condition){

//用於記錄列名
StringBuffer cols = new StringBuffer();
//用於記錄條件
StringBuffer where = new StringBuffer();
//用於記錄列值,僅在插入時使用
StringBuffer value = new StringBuffer();

if(type <= 0){//直接返回Sql
return condition;
} else if(type == 1){//單表查詢
//循環配置列名
for(int i=1; i <= mk.size();i++){
cols.append( mk.get(i) );
if(i != mk.size()){
cols.append(",");
}
}
//配置條件
where.append(" where 1=1 and ");
if("".equals(condition) || condition == null){
for(int i=1; i <= mv.size();i++){
where.append(mv.get(i));
if(i != mv.size()){
where.append(" and ");
}
}
}else{
for(int i=1; i <= mv.size();i++){
where.append( mv.get(i)+condition.split(", ")[i-1] );//逗號+空格
if(i != mv.size()){
where.append(" and ");
}
}
}
//查詢SQL
String sql = "select @cols from "+tableName+" @where";
//替換查詢列名
sql = sql.replace("@cols", ("".equals(cols))?"*":cols);
//替換查詢條件
sql = sql.replace("@where", (mv.size()==0)?"":where);
return sql;
} else if(type == 2){//刪除
//配置條件
where.append(" where 1=1 and ");
for(int i=1;i<=mk.size();i++){
where.append(mk.get(i) + mv.get(mk.get(i)));
if(i != mk.size()){
where.append(" and ");
}
}
//刪除SQL
String sql_del = "delete from "+tableName+" @where";
//替換刪除條件
sql_del = sql_del.replace("@where", where);
return sql_del;
} else if(type == 3){//修改
//配置要修改的列和值
for(int i=1;i<=mk.size();i++){
cols.append(mk.get(i)+" = "+mv.get(mk.get(i)));
if(i != mk.size()){
cols.append(",");
}
}
//配置條件
where.append(" where 1=1 and ");
if("".equals(condition) || null == condition){
where = new StringBuffer();
} else {//如果condition爲空,則條件爲空
where.append(condition);
}
//更新SQL
String sql_upd = "update "+tableName+" set @values @where";
//替換要更新的列和值
sql_upd = sql_upd.replace("@values", cols);
//替換更新條件
sql_upd = sql_upd.replace("@where", where);
return sql_upd;
} else if(type == 4){//插入、新增
//配置列名,以及列所對應的值
for(int i=1;i<=mk.size();i++){
cols.append(mk.get(i));
value.append(mv.get(mk.get(i)));
if(i != mk.size()){
cols.append(",");
value.append(",");
}
}
//新增SQL
String sql_add = "insert into "+tableName+" (@cols) values (@value)";
//替換列名
sql_add = sql_add.replace("@cols", cols);
//替換值
sql_add = sql_add.replace("@value", value);
return sql_add;
}
return "";
}

/***
* 執行Sql<br>
* 當type不爲0,1時,直接執行返回空,反之則返回結果集,結構爲:<br>
* List[
* List[
* Map(index,name(i)), List[Map(name(i),value(i))]
* ]]
* @param type 操作類型
* @param sql 執行SQL
*/
public List executeSql(int type , String sql){
//獲取連接
Connection con = DBConnection.getConnection();
PreparedStatement ps = null;
ResultSet rs = null;

try {
con = DBConnection.getConnection();
ps = con.prepareStatement(sql);
//判斷查詢類型
if(type == SELECT || type == CONDITION){
rs = ps.executeQuery();
} else {
ps.execute();
return null;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if(type != SELECT && type != CONDITION){
//根據查詢類型判斷是否關閉連接等對象
this.close(con, ps, rs);
}
}
return priseResultSet(rs,ps,con);
}


/***
* 解析執行SQL後返回的固定結構的數據集
*
* 返回的List中有兩個對象:ListM和ListMV,ListMV中包含有rs的數據行數所對應的Map數,
* 每一個Map中都存有一行完整的數據庫數據
*
* @param rs 查詢得到數據集
* @return List 返回一份從數據庫查詢出來,由rs解析得到的數據集
*/
public List priseResultSet(ResultSet rss,PreparedStatement ps,Connection con){
List<Object> list = new ArrayList<Object>();
if(rss == null){
return null;
}
//結果集
ResultSet rs = rss;

try {
//由結果集得到數據庫表、字段信息
ResultSetMetaData rsmd = rs.getMetaData();
//數據庫表字段數量
int count = rsmd.getColumnCount();
//封裝列名
Map<Object,String> mk = new HashMap<Object,String>();
for(int i=1;i<=count;i++){
//鍵值對的格式爲:index, 列名
mk.put(i, rsmd.getColumnName(i));
}
list.add(mk);//添加封裝列名的Map
//封裝列名和值的集合
List<Map> listm = new ArrayList<Map>();
while(rs.next()){
//通過遍歷結果集,得到與結果集對應的Map數量
Map<Object,Object> m = new HashMap<Object,Object>();
//表字段的數量,循環查找對應列類型,及賦值給Map,這裏一個Map對象封裝的是: 列名, 列值
for(int i=1;i<=count;i++){
//列類型
int colType = rsmd.getColumnType(i);
//列名
String colName = rsmd.getColumnName(i);

//字符串類
if(colType == java.sql.Types.CHAR){
m.put(colName, rs.getString(colName));
}
if(colType == java.sql.Types.VARCHAR){
m.put(colName, rs.getString(colName));
}
//日期類
if(colType == java.sql.Types.DATE){
m.put(colName, rs.getDate(colName));
}
if(colType == java.sql.Types.TIMESTAMP){
m.put(colName, rs.getDate(colName));
}
//浮點數類
if(colType == java.sql.Types.DECIMAL){
m.put(colName, rs.getDouble(colName));
}
if(colType == java.sql.Types.DOUBLE){
m.put(colName, rs.getDouble(colName));
}
if(colType == java.sql.Types.FLOAT){
m.put(colName, rs.getFloat(colName));
}
//整形類
if(colType == java.sql.Types.INTEGER){
m.put(colName, rs.getInt(colName));
}
if(colType == java.sql.Types.SMALLINT){
m.put(colName, rs.getInt(colName));
}
if(colType == java.sql.Types.TINYINT){
m.put(colName, rs.getInt(colName));
}
if(colType == java.sql.Types.BIGINT){
m.put(colName, rs.getInt(colName));
}
}
listm.add(m);
}
list.add(listm);//添加值
} catch (SQLException e) {
e.printStackTrace();
} finally {
this.close(con, ps, rs);
}
return list;
}

/***
* 關閉
* @param con
* @param ps
* @param rs
*/
public void close(Connection con, PreparedStatement ps, ResultSet rs){
try {
if(rs != null){
rs.close();
}
if(ps != null){
ps.close();
}
if(con != null){
con.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}

然後測試
public static void main(String[] args) {
PublicDao dao = new PublicDao();
Map<Object, String> mk = new HashMap<Object, String>();
Map<Object, Object> mv = new HashMap<Object, Object>();

mk.put(1, "A");
mk.put(2, "B");
mk.put(3, "C");
mk.put(4, "D");
mk.put(5, "E");
mk.put(6, "F");
mv.put(1, "A");
mv.put(2, "B");
mv.put(3, "C");
String condition = "=1, =2, <3";

String sql = dao.createSql(PublicDao.SELECT, "TableName", mk, mv, condition);
System.out.println(sql);
}

得到結果:
select A,B,C,D,E,F from TableName  where 1=1 and A=1 and B=2 and C<3


有了這些基本的處理方法,就能滿足比較簡單的增刪改查和簡單的多表查詢的操作。

這個類實現以下幾個目標:
1. 要查詢的表在調用時指定,所以跟具體的表實現了分離;
2. 在調用該類執行操作時,需要指定操作類型(createSql方法的第一個參數),由此具體的操作類型我們可以不再關心了。
3. 邏輯比較簡單:構造SQL——>執行Execute(解析結果集)——>關閉對象
4. createSql方法規範了調用方式,不會再看到氾濫的SQL,SQL被固定在了持久層,當然類型CONDITION的操作除外。

但是也有一些問題,如下:
1. 爲了支持多表查詢,還是妥協的暴露的一個操作類型CDONDITION,createSql方法目前還不能強大到執行複雜度太高的SQL。
2. 調用的方法顯得有些複雜,做過很多思考,儘可能讓createSql方法的參數簡單,很遺憾,智商有限啊
3. 不能支持分頁查詢SQL構造,這個需求將在下一篇介紹,畢竟只是個雛形啊,哪能一蹴而就

小總結了一下,還有一些小優點:
1. 調試。你所有的SQL只會在同一個地方執行(executeSql方法),你只需要打一個斷點,Ok,錯誤們,找到你們更easy了。
2. 這裏返回的是List和Map,不是實體類對象,你可以隨便造出任何實體類對象,來跟List裏面的數據耦合。當然,這些實體類要實現自動與List裝配,必須有一些規範,後面我也會做介紹。

總結,以上對付的,只是基於簡單的數據操作,我在使用之後發現我的持久層就孤零零的幾個類,代碼結構簡潔了許多。之後我做了一些擴展,增加分頁查詢的支持是必須的,對多表查詢的支持我也會盡量做到(我考慮過,由於操作比較複雜,還不如直接寫SQL,性價比不高```)。有任何問題和建議,希望各位看官大大的提出來,我會繼續努力實現儘可能的操作簡單化。

~~~平時看別人的博客沒怎麼在意,今天寫這些居然花了一下午,En,多多鍛鍊````
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章