Beetlsql擴展之自定義Mapper

BeetlSql 有一個BaseMapper,提供了不少內置的Dao操做,如增刪改查等10幾個方法,用戶只須要些一個類繼承此接口便能很快的完成一個Dao,好比程序員

public interface UserDao extends BaseMapper<User>{
}

UserDao沒有包含任何方法,但集成了BaseMapper,所以具有了不少內置的Dao操做。若是這些操做不知足要求,可使用BeetlSql的MD文檔來維護複雜的SQL,好比,一個根據角色查詢全部用戶的操做sql

@SqlResource("common.user")
public interface UserDao extends BaseMapper<User>{
    List<User>  getUsersByRole(String role);
}

註解@SqlResource註明了sql文件的位置,位於common/user.md 文件,其中user.md 包含了sql查詢app

getUsersByRole
===

select u.* from user,user_role,role where ....

這是一個BeetlSql的基本概念,在這裏不詳細說明,有興趣的能夠參考官網文檔 如今有這樣一個需求,BaseMapper包含了太多的內置方法,有時候程序員對其中的某些方法並不敢興趣,有的內置方法則是不容許調用的,好比deleteById 方法,老是有一次物理刪除操做。應該在企業應用系統裏禁止這種調用,那麼,BeetlSql可否定製BaseMapper呢?ide

答案是能定製。只須要新寫一個接口,經過SQLManager註冊爲BaseMapper便可,而後,爲接口的每一個方法添加實現便可,實際上,自帶的BaseMapper就是這樣實現的。工具

以下是一個自定義的BaseMapper,爲了簡單起見,Dao只提供三個接口ui

public interface MyBaseMapper<T>  {
	public T single(Object key);
	public T single(Object key,List attrs);
	public List<T> allData();
}

第一個方法single和第三個all方法都是原有的BaseMapper自帶,第二個方法是根據主鍵獲取對象,但只獲取部分屬性。咱們將在博客最後講述如何實現,如今,咱們要實現allData方法。this

allData方法返回目標對象對應的數據全部值,這在原有的BaseMapper已經實現,是經過org.beetl.sql.core.mapper.internal.AllAmi 實現,所以咱們只須要複用這個實現。code

首先,告訴SQLManager,我要添加一個新的BaseMapper對象

SQLManager 	sql =   ....
MapperConfig config = sql.setBaseMapper(MyBaseMapper.class);

其次,添加allData實現繼承

config.getBuilder().addAmi("allData", new AllAmi());

這樣,就很是容易的定義了一個allData內置的Dao方法,使用了已經實現的AllAmi方法,AllAmi方法也很是簡單,直接使用SQLManager.all方法實現,其源碼以下

public class AllAmi implements MapperInvoke {

    @Override
    public Object call(SQLManager sm, Class entityClass, String sqlId, Method m, Object[] args) {
        if (args == null || args.length == 0) {
            return sm.all(entityClass);
        }

        return sm.all(entityClass, (Integer) args[0], (Integer) args[1]);
    }

}

這樣,你就具有初步自定義Dao的功能,好比以下代碼

MyUserMapper dao = sql.getMapper(MyUserMapper.class);
List<User> users =dao.allData();

如今,面臨新的挑戰,實現一個根據主鍵查詢,但僅僅返回須要的字段,也就是實現以下方法

public T single(Object key,List attrs);

這比較複雜,由於在BeetlSQL裏並未內置,須要咱們本身完成後註冊,完成思路很簡單,生成相似以下sql語句,並交給SQLManager 執行便可,好比

select id,name where id = #id#

咱們寫一個新的擴展,叫作SingleAmiExt(參考了自帶的SingleAmi),其腳手架以下

@Override
public Object call(SQLManager sm, Class entityClass, String sqlId, Method m, Object[] args) {
	if (args.length == 1) {
		return sm.single(entityClass, args[0]);
	}
	List attrs = (List) args[1];
	String sql  = getSingleSelect(entityClass,sm,attrs);

	Map paras = this.setIdsParas(sm, args[0], entityClass);
	List list = sm.execute(sql, entityClass, paras);
	if(list.size()==0) {
		return null;
	}else {
		return list.get(0);
	}

}

這段代碼前倆行先判斷參數,若是隻有一個參數,則認爲沒有屬性過濾,直接調用內置的single方法便可。不然,須要本身拼寫一個Sql模板,經過SQLManager.execute 來獲取

getSingleSelect方法用於生成目標sql模板,代碼以下

private String getSingleSelect(Class cls, SQLManager sm, List attrs) {
		NameConversion nameConversion = sm.getNc();
		String condition = appendIdCondition(sm, cls);
		StringBuilder sb = new StringBuilder("select ");
	
	for (Object o : attrs) {
		String attr = (String) o;
		String col = nameConversion.getColName(cls, attr);
		sb.append(col).append(" ,");
	
	}
	// 去掉最後一逗號
	sb.setLength(sb.length() - 1);
	sb.append(" from ").append(nameConversion.getTableName(cls)).append(condition);
		return sb.toString();
	}
	
	/* 參考了AbstractDBStyle的內置代碼生成辦法 */
	protected String appendIdCondition(SQLManager sm, Class<?> cls) {
	
		AbstractDBStyle style = (AbstractDBStyle) sm.getDbStyle();
		MetadataManager metadataManager = sm.getMetaDataManager();
		NameConversion nameConversion = sm.getNc();
		String tableName = nameConversion.getTableName(cls);
		StringBuilder condition = new StringBuilder(" where ");
	TableDesc table = metadataManager.getTable(tableName);
	ClassDesc classDesc = table.getClassDesc(cls, nameConversion);
	
	List<String> colIds = classDesc.getIdCols();
	List<String> propertieIds = classDesc.getIdAttrs();
	Iterator<String> colIt = colIds.iterator();
	Iterator<String> propertieIt = propertieIds.iterator();
	if (colIt.hasNext() && propertieIt.hasNext()) {
		String colId = colIt.next();
		String properId = propertieIt.next();
		condition.append(style.getKeyWordHandler().getCol(colId)).append(" = ").append(style.HOLDER_START)
				.append(properId).append(style.HOLDER_END);
		while (colIt.hasNext() && propertieIt.hasNext()) {
			colId = colIt.next();
			properId = propertieIt.next();
			condition.append(" and ").append(style.getKeyWordHandler().getCol(colId)).append(" = ")
						.append(style.HOLDER_START).append(properId).append(style.HOLDER_END);
			}
		}
	
		return condition.toString();
	}

代碼複雜是由於考慮到通用性,尤爲是符合主鍵這裏,若是你的表都是單一主鍵,或者主鍵都叫ID,那代碼就只有幾行

一樣的setIdsParas也由於複合主鍵緣由,也較爲複雜,內容以下

private Map setIdsParas(SQLManager sm, Object key, Class entityClass) {
		AbstractDBStyle style = (AbstractDBStyle) sm.getDbStyle();
		MetadataManager metadataManager = sm.getMetaDataManager();
		NameConversion nameConversion = sm.getNc();
		String tableName = nameConversion.getTableName(entityClass);

		TableDesc table = metadataManager.getTable(tableName);
		ClassDesc desc = table.getClassDesc(entityClass, nameConversion);
		Map paras = new HashMap();
		List<String> idAttrs = desc.getIdAttrs();
		if (idAttrs.size() == 1) {
			paras.put(idAttrs.get(0), key);
		} else {
			// 來自對象id的屬性.複合主鍵
			Map<String, Object> map = desc.getIdMethods();
			for (int i = 0; i < idAttrs.size(); i++) {
				String idCol = idAttrs.get(i);
				String idAttr = idAttrs.get(i);
				Method m = (Method) map.get(idAttr);
				try {
					Object os = m.invoke(key, new Object[0]);
					paras.put(idAttr, os);
				} catch (Exception ex) {
					throw new BeetlSQLException(BeetlSQLException.ID_VALUE_ERROR, "沒法設置複合主鍵:" + idCol, ex);
				}
			}

		}
		return paras;
	}

完成上訴代碼後,能夠添加這個實現

config.getBuilder().addAmi("single", new SingleAmiExt());

而後,可使用了,好比按照主鍵查詢,只關心id和name

dao.single(1,Arrays.asList("id","name"));

結束語: BaseMapper大部分擴展都較爲簡單,這裏的SingleAmiExt複雜是考慮到複合主鍵,且如今的BeetlSQL版本還沒法重用內置的代碼生成片斷致使的,叫來BeetlSQL版本會把內置代碼生成獨立出來,能夠供第三方工具使用,這樣,開發者就能輕易擴展BeetlSQL了。

關於BeetlSQL,請參考官網ibeetl.com

相關文章
相關標籤/搜索