Mybatis 通用Crud-設計思路

更新日誌

<!--  2016-11-13更新 start -->java

1 新增批量操做數據方法:批量插入,根據條件刪除,根據條件更新指定的列名-字段值。mysql

2 新增高級查詢方法:可設置查詢列,查詢條件,排序,分頁。git

3 根據一、2更新接口。github

4 更改dao接口方法實現方式,統一採用GeneralMapper.xml編寫sql,棄用GeneralDaoProvider。sql

<!--  2016-11-13更新 end  -->數據庫

一 關於Mybatis

1.1 mybatis 的優勢

1 輕量級ORM 。緩存

2 提供了完善的緩存機制。mybatis

3 mapper.xml 原聲SQL更清晰靈活,且sql便於SQL調優。oracle

4 resultType resultMap 處理返回結果集,與pojo解耦。app

1.2 mybatis的使用體驗

這裏只將Mybatis不便於使用之處作以說明。

1 須要爲每張表寫一個dao接口和mapper.xml,這對於開發者來說就不是很友好了,假設系統有30張業務表,呵呵。

2 雖然有generator 工具,能夠自動生成dao 接口和mapper.xml,是能夠不用本身去寫這些代碼了,可是仍是有2個缺點:

(1) generator只是提供了基礎的增刪改查功能,複雜的sql仍是要本身去添加。

(2) 仍是上面的問題,每張表都要有dao接口和mapper.xml。

1.3 關於物理分頁

對於分頁的功能,仁者見仁,智者見智吧,就我我的的觀點來說,我以爲不必。

上面也描述了mybatis的優勢之一就是mapper.xml中的原聲SQL,特別是對於複雜的SQL語句,原聲SQL頗有魅力。

若是你分頁的sql封裝成以下的格式,

<!-- mysql page封裝 -->
select count(1) as 'totalNum',t.*  from (select  user_id , user_name from user ) t limit startRowNo,rowNum

<!-- mysql 原生分頁 -->
select count(1) as 'totalNum', user_id , user_name from user limit startRowNo,rowNum

我以爲,仍是算了吧,不如原聲SQL來的實在。select * 我就不講了,select * from (sql) 根本就不必嘛。

若是說你但願實現一個靈活的查詢方法,有分頁參數就按分頁查詢,沒有分頁參數就查詢所有。那麼個人觀點是,mybatis 提供了<if></if>標籤,代碼以下:

<!-- mapper.xml -->
   <select id="selectByPage" parameterType="map" resultType="hashmap">
    
    select count(1) as 'totalNum',user_id,user_name
    from user
    
    <if test="pageParam != null" >
    limit #{pageParam.startRowNo},#{pageParam.pageSize}
    </if>
    
	</select>

若是說你但願分頁方法可以更靈活,但願實現一個方法對任何一張表均可以分頁,代碼能夠這樣寫

<select id="selectByPage" parameterType="map" resultType="hashmap">
    
    select ${queryColumn}
    from ${tableName}
    
    <if test="page != null" >
    limit #{page.startRowNo},#{page.pageSize}
    </if>
    
	</select>

關於分頁,就這麼多,僅我的觀點,歡迎批評、建議或討論。

二 Mybatis通用Crud設計

2.1 需求

基於1.2,但願提供相似於hibernate中的load、insert、delete、save方法,對於基礎的數據訪問層操做,系統只須要存在一處,便於統一的管理,減小沒必要要的代碼。

基於1.1,避免使用java封裝字符串sql,mapper.xml,@select,@selectProvider三選一。

2.2 設計

2.2.1 通用crud接口

但願實現的功能方法接口,因篇幅有限,這裏就只給出主鍵查詢的接口。

所有接口聲明能夠查看對應代碼,或者,

查看上一篇博文Mybatis 通用Crud-接口預覽,https://my.oschina.net/LittleNewbie/blog/785947

/**
 * 通用Crud 數據訪問層接口
 * 
 * @author svili
 * @date 2016年11月11日
 *
 */
public interface CrudServiceInter {

	/**
	 * 根據主鍵查詢
	 * @param <T> pojo類
	 * @param clazz pojo類-class對象
	 * @param primaryValue  主鍵值
	 * @return pojo對象
	 */
	<T> T selectByPrimaryKey(Class<T> clazz, Object primaryValue) throws Exception;
}

2.2.2 根據主鍵查詢

1 首先,明確接口

<T> T selectByPrimaryKey(Class<T> clazz, Object primaryValue) throws Exception;

<2017-02-18更新>

主鍵類型不作限制。

2 分析下mapper.xml中的sql

<select id="selectByPrimaryKey" parameterType="map" resultType="hashmap">
    select 
    <foreach item="columnName" index="index" collection="queryColumn" separator="," >
        ${columnName}
    </foreach>
    from ${tableName}
    where ${primaryKey} = #{primaryValue}
</select>

注意:不要使用<![CDATA[  ]]>去嘗試對這段sql進行轉義,會致使<foreach></foreach>標籤不被解析處理。

關於xml中的<![CDATA[  ]]> ,自行百度get噢。

3 明確sql中的參數

sql中須要傳遞的參數有4個,分別是:tableName(表名),queryColumn(查詢字段集),primaryKey(主鍵列名),primaryValue(主鍵值)。

4  接口參數與sql參數轉化

(1)tableName

從Java類到數據庫的表名,咱們能夠獲取兩個信息,一個是類名,第二個是JPA的@Table註解。

要注意兩點,1.註解優先,2.Java的命名規則爲駝峯,數據庫的命名規則爲下劃線。若是你不按套路出牌,仍是要好好想一想如何去對應轉換。

//獲取pojo表名
public static String getTableName(Class<?> clazz) {
		// 駝峯轉下劃線
		String tableName = StringUtil.camelToUnderline(clazz.getName());
		// 判斷是否有Table註解
		if (clazz.isAnnotationPresent(Table.class)) {
			// 獲取註解對象
			Table table = clazz.getAnnotation(Table.class);
			// 設置了name屬性
			if (!table.name().trim().equals("")) {
				return table.name();
			}
		}
		return tableName;
}

(2)queryColumn

直接獲取字段名就好,注意駝峯轉下劃線。

//獲取全部字段名
public static List<String> getAllColumns(Class<?> clazz) {
		List<String> columns = new ArrayList<String>();
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			columns.add(StringUtil.camelToUnderline(field.getName()));
		}
		return columns;
}

(3)primaryKey

這隻能靠JPA的@Id了。

固然你可本身定製一套標準,好比每張表都固定一個主鍵字段名爲table_id。可是我以爲徹底不必,既然有JPA的標準,爲何要去本身費腦經呢。若是說你使用oracle的guid,沒脾氣,呵呵。

//獲取主鍵字段
public static Field getPrimaryFieldNotCareNull(Class<?> clazz) {
		Field field = FieldReflectUtil.findField(clazz, Id.class);
		if (field != null) {
			return field;
		} else {
			return null;
		}
}
//獲取指定註解類型的字段
public static Field findField(Class<?> clazz, Class<? extends Annotation> annotationType) {
		Class<?> searchType = clazz;
		while (!Object.class.equals(searchType) && searchType != null) {
			Field[] fields = searchType.getDeclaredFields();
			for (Field field : fields) {
				if (field.isAnnotationPresent(annotationType)) {
					return field;
				}
			}
			searchType = searchType.getSuperclass();
		}
		return null;
}

2.2.3 其餘接口

關於其餘接口,其實大同小異,這裏就不作說明了,只須要按照2.2.2 中的思路去查看源碼就OK了。

三 源碼

源碼地址:https://github.com/LittleNewbie/portal

如何閱讀源碼:

主要4個文件 GeneralDao,GeneralMapper.xml,GeneralDaoProvider,CrudServiceImpl

GeneralDao聲明瞭數據訪問層接口;

GeneralMapper.xml 和GeneralDaoProvider(已棄用2016-11-13)配合GeneralDao。

GeneralDaoProvider已棄用 from 2016-11-13

CrudServiceImpl 實現由接口參數轉化爲GeneralDao參數,並封裝返回類型。

GeneralQueryParam 查詢參數封裝,start from 2016-11-13。

相關文章
相關標籤/搜索