專案收穫-多表查詢的sql語句的拼接+反射
在專案中,涉及到了多表查詢,總共有6張表,在前端頁面中顯示的輸入框的又多,獲得的引數不一定是有值的,語句拼接非常麻煩。
在編寫專案程式碼的時候,我們已經給各個實體類寫好了一一對應的dao,為了拼接sql語句,我也寫好了單個物件拼接sql語句的工具方法。
在多表多條件查詢的時候,我們之前的做法是從第一個類開始,查出符合該類約束的所有物件,然後通過已知的物件集合,查出與之相關的資料(約束條件=類本事約束條件+已知外資料的外來鍵約束),這樣到了最後,得到了要查詢的資料。
再根據查的的資料往回約束查詢,也找到其他類資料。
這樣做雖然要求是達到了,但是感覺根本沒有效率可言,最好的方法當然是用一條語句來查詢出來,然後再把返回資料分成多個物件來返回。
現在就想辦法來設計一個通用的多表查詢的方法:
返回值:
平常我們返回的應該是List<Object>型別的資料,現在我們要返回的是多個這樣的List,那麼這裡就用Map<Class,List<Object>>用類物件去取其中的集合。
引數:
要傳入查詢的條件,那就要傳入 資料庫中列的名字,列的值,還有表與表之間的聯絡。列的名字可以查詢對映配置檔案來得到,所以可以選擇傳入類中屬性的名字,表與表之間的聯絡只要知道了類也可以通過配置檔案獲取,那也不傳了。也就是說要傳的是屬性的名字與屬性的值,再封裝一下,直接傳物件,完事。
現在方法的大概樣子應該是這樣的
public Map<Class, List<Object>> multiClassQuery(Object[] objs){ }
對應的sql語句應該是:
select * from t1 [(join t2 on ...) (join t3 on ...)...] [ where conditions];
所以說第一個表與其他的表還是有點不一樣的,所以將方法改成這樣:
public Map<Class, List<Object>> multiClassQuery(Object firstObj,Object... objs){
}
具體實現:
sql語句應該拆成2部分
1.select * from t1後面來一個表新增一個 “join tx on (...)”
2.where 後面一部分,來一個表,填一些有的限制
其他:
為了防止sql注入,還是使用"?"佔位符,將實際的值存到List中,到時候再set到PreparedStatment中
結果處理:
最後結果返回的應該是幾行資料,資料的列數是所有類屬性數之和。
每一個ResultSet.nex()對應的一大行資料,每一列的通過ResultSet.get(index++)來獲取,並且放到物件裡去。
------------------------------------------------------------------------------------------------------------
程式碼部分:
上面提到了配置資訊,這裡了用這個封裝類來表示某個類與資料庫對映的的資訊。
public class DbMapping {
private Class clazz;
private DbMapping(Class clazz) {
this.clazz = clazz;
}
private Map<Class, Map<String, String>> wholeMap = new LinkedHashMap<Class, Map<String, String>>();
public String getForeignKey() {
return "";
}
public String getPrimaryKey(){
return "";
}
public Map<String, String> getMapping() {
return wholeMap.get(clazz);
}
public String getTableName() {
return wholeMap.get(clazz).get(clazz.getSimpleName());
}
public String getColumnMapping(Field field) {
return wholeMap.get(clazz).get(field.getName());
}
public static DbMapping obtain(Class clazz) {
return new DbMapping(clazz);
}
}
這裡的例項化用obtain()方法獲取物件,在後面改動的時候可以少產生物件。
接著是這個方法:
public Map<Class, List<Object>> multiClassQuery(Object firstObj,
Object... objs) throws Exception {
checkArguments(firstObj,objs);
// 這個用來粗放返回值
Map<Class, List<Object>> container = new HashMap<Class, List<Object>>();
// 以下三行用來存放生成的params與sql語句
List<Object> params = new ArrayList<Object>();
StringBuffer headSql = new StringBuffer("select * from ");
StringBuffer tailSql = new StringBuffer(" where ");
// //////////以下是處理第一個物件
// 獲取對映分裝類
DbMapping dbMapping = DbMapping.obtain(firstObj.getClass());
// 得到表名,生成sql
String tableName = dbMapping.getTableName();
headSql.append(tableName);
// 將物件中的屬性新增到sql中的約束部分,並且新增params
Field[] fields = firstObj.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Object fieldValue = field.get(firstObj);
// 那就說明這個屬性需要轉化成sql語句
if (fieldValue != null || !"".equals(fieldValue)) {
String columnName = dbMapping.getColumnMapping(field);
tailSql.append(columnName).append("=? and ");
params.add(fieldValue);
}
}
List<Object> firstValues = new ArrayList<Object>();
container.put(firstObj.getClass(), firstValues);
// //////第一個物件處理完畢
// //////---下面處理後面的物件
for (Object obj : objs) {
String oldTableName = tableName;
String oldPrimaryKey = dbMapping.getPrimaryKey();
dbMapping = DbMapping.obtain(obj.getClass());
// 先處理headSql
// 這裡要把自己表名加上,還要把表與表之間的聯絡加上
tableName = dbMapping.getTableName();
String newForeignKey = dbMapping.getForeignKey();
headSql.append(" join ").append(tableName).append(" on ") //
.append(oldTableName).append(".").append(oldPrimaryKey) //
.append("=") //
.append(tableName).append(".").append(newForeignKey);
// 再來第二段sql語句
fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Object fieldValue = field.get(firstObj);
if (fieldValue != null || !"".equals(fieldValue)) {
String columnName = dbMapping.getColumnMapping(field);
tailSql.append(columnName).append("=? and ");
params.add(fieldValue);
}
}
List<Object> values = new ArrayList<Object>();
container.put(obj.getClass(), values);
}
String finalSql = headSql.append(
tailSql.delete(tailSql.length() - 4, tailSql.length() - 1))
.toString();
System.out.println("sql = " + finalSql);
System.out.println("params = " + params);
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = ConnectionPool.getConnection();
ps = conn.prepareStatement(finalSql);
for (int i = 0; i < params.size(); i++) {
ps.setObject(i + 1, params.get(i));
}
rs = ps.executeQuery();
while (rs.next()) {
int rsCursor = 1;
Object instance = firstObj.getClass().newInstance();
fields = firstObj.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
field.set(instance, rs.getObject(rsCursor++));
}
container.get(firstObj.getClass()).add(instance);
for (Object obj : objs) {
instance = obj.getClass().newInstance();
fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
field.set(instance, rs.getObject(rsCursor++));
}
container.get(obj.getClass()).add(instance);
}
}
} finally {
rs.close();
ps.close();
conn.close();
}
return null;
}
/**
* 檢查引數異常,丟擲。
* */
private void checkArguments(Object firstObj, Object[] objs) {
if(firstObj==null){
throw new RuntimeException();
}
}
這就寫完了,至於可行性,下次我再試試。。。。。。。。。。。
這裡用到了好多次的getDeclaredFields()方法,本來聽說反射耗時,而且是在for迴圈裡使用,所以我就很擔心效率會變得特別差。
於是就做了測試:
public static void main(String[] args) throws InterruptedException {
System.out.println(System.currentTimeMillis());
Field[] fields = PersonalBase.class.getDeclaredFields();
System.out.println(System.currentTimeMillis());
Thread.sleep(10000);
System.out.println(System.currentTimeMillis());
for (int i = 0; i < 100; i++) {
fields = PersonalBase.class.getDeclaredFields();
}
System.out.println(System.currentTimeMillis());
}
結果是這樣的:
1436091716807
1436091716809
1436091726810
1436091726812
看來也就第一次耗時,後面的肯定是做了處理,本來想做個代理快取了,現在看來也不用了