Hutool之對JDBC的ORM封裝

開篇

端午沒事兒幹擴充了下Hutool的DB部分,原來只是一個簡單的SQL運行器,如今加入了方言支持,封裝了增刪改查,引入Session從而支持事務,能夠說工程量巨大,在封裝過程總我仍是參考了Jodd的DbOom、Jfinal的ActiveRecord、Apache Commons-DbUtils,吸收優勢,剔除我以爲沒用的,再加入些本身的想法,儘可能作到簡單和靈活。mysql

版本支持

因爲上次已經將1.0.0版的Hutool提交到了Maven中央庫,且此次也是一個重大的改進,因此把此次的更新所有放到1.1.0版本中,考慮到未徹底測試,因此你能夠在Github上clone下來使用,或者看下個人代碼,體會下個人思想以及設計哲學(哲學這個詞好有逼格……)。git

由來

考慮到Hibernate作ORM的複雜性,它想把一個對象映射到數據庫,再加上各類外鍵的複雜對應關係,當時學習的時候整的我焦頭爛額,並且其數據庫鏈接配置所有放在xml裏,須要鏈接池插件去爲它開發對應的插件,顯然這樣作太霸道了,總之這種靈活性的缺失,致使在使用Hibernate的時候必須按照它指定的思路和方式走,痛苦萬分啊,例如你執行一條SQL語句,查詢的結果是個讓人百思不得其解的列表,難用的要死。後來我便傾向於Apache Commons-DbUtils,誰讓業務簡單呢,沒幾張表,簡簡的看了其源碼,作了些簡單的改進放到個人Hutool裏來了,而後就看了Jfinal的ActiveRecord,那段時間正在看Python的一個框架Django,發現其殊途同歸之妙的ORM方式:將數據庫表映射爲一個Map,而不是一個對象。這樣的靈活性大大的增長,字段也更加靈活。因而按照這個思想開始動工,封裝增刪改查的經常使用方法。github

對象解釋

1. Entity

在ORM中,我把一張表中的一條數據映射成爲一個叫作Entity的類,繼承自HashMap,key是字段名,value是Object類型,字段值,這樣一個Entity對象就是數據庫表中的一條記錄,固然這個對象中還有個字段是表的名字,方便以後的操做。以後對數據庫增刪改查操做的對象大可能是這個。sql

這個對象充當着兩種角色,一個是數據的載體,表示一條數據,另外一個就是where語句的條件,固然,Entity對象只支持 = 操做,更復雜的操做我之後再想別的辦法。充當where條件時,key依舊是字段名,value是字段條件值。例如:數據庫

Entity where = Entity.create(TABLE_NAME).set("條件1", "條件值");

表示的where語句是:apache

WHERE `條件1` = 條件值

固然到時候會用PreparedStatement,不會出現SQL注入。django

2. Table Column

這兩個對象主要是描述數據庫表結構的,暫時和ORM自己沒啥關係,只是當你想得到一些字段信息的時候,這樣來得到表結構信息:session

/**
 * 得到表的元數據
 * 
 * @param ds 數據源
 */
private static void getTableMetaInfo(DataSource ds) {
	// 得到當前庫的全部表的表名
	List<String> tableNames = DbUtil.getTables(ds);
	Log.info("{}", tableNames);

	/*
	 * 得到表結構 表結構封裝爲一個表對象,裏面有Column對象表示一列,列中有列名、類型、大小、是否容許爲空等信息
	 */
	Table table = DbUtil.getTableMeta(ds, TABLE_NAME);
	Log.info("{}", table);
}

總體的架構

總體分爲幾部分架構

  1. 數據源 DataSource
  2. SQL執行器 SqlExecutor
  3. CRUD的封裝 SqlConnRunner SqlRunner
  4. 支持事務的CRUD封裝 Session
  5. 各類結果集處理類 handler
  6. 數據庫的一些工具方法彙總 DbUtil

還有就是沒有列出來的dialect(數據庫方言),我會根據給定的DataSource、Connection等對象自動識別是什麼數據庫,而後使用不一樣的方言構造SQL語句,暫時支持的數據庫有MySQL、Oracle、SqlLite3,固然若是識別失敗會用ANSI SQL,這樣遇到不支持的數據,能夠搞定大部分方法。框架

下面解釋下:

1. 數據源

不管是JDNI仍是數據庫鏈接池,最終給用戶的都是一個DataSource,那麼,我執行SQL的鏈接直接從數據源裏拿就能夠,至於數據源是JNDI仍是哪一種鏈接池我是無論的,這樣大大提升了靈活性。在ds包中,我還本身封裝了SimpleDataSource對象,這是不用數據庫鏈接池的數據源,鏈接直接問DriverManager中拿,徹底是JDBC原生的數據庫鏈接獲取操做,固然這個類因爲沒有鏈接池,僅供測試或打開關閉鏈接很是少的場合使用。固然,我還提供了一個DruidDS把數據庫配置以及鏈接池配置放在配置文件裏,更加方便。

/**
 * @return 得到數據源樣例方法
 */
private static DataSource getDataSource() {
	/*
	 * 得到數據源,可使用Druid、DBCP或者C3P0數據源
	 * 我封裝了Druid的數據源,在classpath下放置db.setting和druid.setting文件 
	 * 詳細格式請參考doc/db-example.setting和doc/db/example.setting 
	 * 若是沒有druid.setting文件,使用鏈接池默認的參數 能夠配置多個數據源,用分組隔離
	 */
	DataSource ds = DruidDS.getDataSource("test");

	//固然,若是你不喜歡用DruidDS類,你也能夠本身去實例化鏈接池的數據源 具體的配置參數請參閱Druid官方文檔
	DruidDataSource ds2 = new DruidDataSource();
	ds2.setUrl("jdbc:mysql://fedora.vmware:3306/extractor");
	ds2.setUsername("root");
	ds2.setPassword("123456");
	ds = ds2;

	return ds;
}

2. SQL執行器 SqlExecutor

這是一個靜態類,裏面的靜態方法只有兩種:執行非查詢的SQL語句和查詢的SQL語句

/**
 * SqlExecutor樣例方法<br>
 * 若是你只是執行SQL語句,使用SqlExecutor類裏的靜態方法便可
 * 
 * @param ds 數據源
 */
private static void sqlExecutorDemo(DataSource ds) {
	Connection conn = null;
	try {
		conn = ds.getConnection();
		// 執行非查詢語句,返回影響的行數
		int count = SqlExecutor.execute(conn, "UPDATE " + TABLE_NAME + " set field1 = ? where id = ?", 0, 0);
		log.info("影響行數:{}", count);
		// 執行非查詢語句,返回自增的鍵,若是有多個自增鍵,只返回第一個
		Long generatedKey = SqlExecutor.executeForGeneratedKey(conn, "UPDATE " + TABLE_NAME + " set field1 = ? where id = ?", 0, 0);
		log.info("主鍵:{}", generatedKey);

		/* 執行查詢語句,返回實體列表,一個Entity對象表示一行的數據,Entity對象是一個繼承自HashMap的對象,存儲的key爲字段名,value爲字段值 */
		List<Entity> entityList = SqlExecutor.query(conn, "select * from " + TABLE_NAME + " where param1 = ?", new EntityHandler(), "值");
		log.info("{}", entityList);
	} catch (SQLException e) {
		Log.error(log, e, "SQL error!");
	} finally {
		DbUtil.close(conn);
	}
}

3. CRUD的封裝 SqlConnRunner SqlRunner

這兩個類有些類似,裏面都封裝了增、刪、改、查、分頁、個數方法,差異是SqlConnRunner須要每一個方法都傳Connection對象,而SqlRunner繼承自SqlConnRunner,在傳入DataSource會自動獲取Connection對象。Demo以下:

/**
 * SqlRunner是繼承自SqlConnRunner的(SqlConnRunner繼承自SqlExecutor),因此相應的方法也繼承了下來,能夠像SqlExecutor同樣使用靜態方法<br>
 * 固然,SqlRunner更強大的功能在於對Entity對象作CRUD,避免寫SQL語句。 SqlRunner須要實例化
 * 
 * SqlRunner同時提供了帶Connection參數的CRUD方法,方便外部提供Connection對象而由使用者提供事務的操做
 * 
 * @param ds 數據源
 */
private static void sqlRunnerDemo(DataSource ds) {
	Entity entity = Entity.create(TABLE_NAME).set("字段1", "值").set("字段2", 2);
	Entity where = Entity.create(TABLE_NAME).set("條件1", "條件值");

	try {
		SqlRunner runner = SqlRunner.create(ds);
		// 指定數據庫方言,在此爲MySQL
		runner = SqlRunner.create(ds);

		// 增,生成SQL爲 INSERT INTO `table_name` SET(`字段1`, `字段2`) VALUES(?,?)
		runner.insert(entity);

		// 刪,生成SQL爲 DELETE FROM `table_name` WHERE `條件1` = ?
		runner.del(where);

		// 改,生成SQL爲 UPDATE `table_name` SET `字段1` = ?, `字段2` = ? WHERE `條件1` = ?
		runner.update(entity, where);

		// 查,生成SQL爲 SELECT * FROM `table_name` WHERE WHERE `條件1` = ? 第一個參數爲返回的字段列表,若是null則返回全部字段
		List<Entity> entityList = runner.find(null, where, new EntityHandler());
		log.info("{}", entityList);

		// 分頁,注意,ANSI SQL中不支持分頁!
		List<Entity> pagedEntityList = runner.page(null, where, 0, 20, new EntityHandler());
		log.info("{}", pagedEntityList);

		// 知足條件的結果數,生成SQL爲 SELECT count(1) FROM `table_name` WHERE WHERE `條件1` = ?
		int count = runner.count(where);
		log.info("count: {}", count);
	} catch (SQLException e) {
		Log.error(log, e, "SQL error!");
	} finally {
	}
}

4. 支持事務的CRUD封裝 Session

Session很是相似於SqlRunner,差異是Session對象中只有一個Connection,全部操做也是用這個Connection,便於事務操做,而SqlRunner每執行一個方法都要從DataSource中去要Connection。樣例以下:

private static void sessionDemo(DataSource ds) {
	Entity entity = Entity.create(TABLE_NAME).set("字段1", "值").set("字段2", 2);
	Entity where = Entity.create(TABLE_NAME).set("條件1", "條件值");

	Session session = Session.create(ds);
	try {
		session.beginTransaction();

		// 增,生成SQL爲 INSERT INTO `table_name` SET(`字段1`, `字段2`) VALUES(?,?)
		session.insert(entity);

		// 刪,生成SQL爲 DELETE FROM `table_name` WHERE `條件1` = ?
		session.del(where);

		// 改,生成SQL爲 UPDATE `table_name` SET `字段1` = ?, `字段2` = ? WHERE `條件1` = ?
		session.update(entity, where);

		// 查,生成SQL爲 SELECT * FROM `table_name` WHERE WHERE `條件1` = ? 第一個參數爲返回的字段列表,若是null則返回全部字段
		List<Entity> entityList = session.find(null, where, new EntityHandler());
		log.info("{}", entityList);

		// 分頁,注意,ANSI SQL中不支持分頁!
		List<Entity> pagedEntityList = session.page(null, where, 0, 20, new EntityHandler());
		log.info("{}", pagedEntityList);

		session.commit();
	} catch (Exception e) {
		session.quietRollback();
	} finally {
		session.close();
	}
}

5. 各類結果集處理類 handler

此包中有個叫作RsHandler的接口,傳入ResultSet對象,返回什麼則在handle方法中本身指定。 實現的類有:

  1. EntityHandler 轉換爲Entity列表

  2. NumberHandler 當使用select count(1)這類語句的時候,或者返回只有一個結果,且爲數字結果的時候,用這個handler

  3. SingleEntityHandler 返回一條記錄的時候用這個

  4. 數據庫的一些工具方法彙總 DbUtil 提供一些工具方法,最經常使用的就是close方法了,因爲JDK7才把ResultSet``Statement``PreparedStatement``Connection這幾個接口實現了Closeable接口,因此以前只能判斷類型再去關閉,這樣一個close方法能夠關閉多個對象。

相關文章
相關標籤/搜索