1. 程式人生 > >專案收穫-多表查詢的sql語句的拼接+反射

專案收穫-多表查詢的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
看來也就第一次耗時,後面的肯定是做了處理,本來想做個代理快取了,現在看來也不用了