BeetlSql 有一个BaseMapper,提供了很多内置的Dao操作,如增删改查等10几个方法,用户只需要些一个类继承此接口便能很快的完成一个Dao,比如
1public interface UserDao extends BaseMapper<User>{ 2}
UserDao没有包含任何方法,但集成了BaseMapper,因此具备了很多内置的Dao操作。如果这些操作不满足要求,可以使用BeetlSql的MD文档来维护复杂的SQL,比如,一个根据角色查询所有用户的操作
1@SqlResource("common.user") 2public interface UserDao extends BaseMapper<User>{ 3 List<User> getUsersByRole(String role); 4}
注解@SqlResource注明了sql文件的位置,位于common/user.md 文件,其中user.md 包含了sql查询
1getUsersByRole 2=== 3 4select u.* from user,user_role,role where ....
这是一个BeetlSql的基本概念,在这里不详细说明,有兴趣的可以参考官网文档 现在有这样一个需求,BaseMapper包含了太多的内置方法,有时候程序员对其中的某些方法并不敢兴趣,有的内置方法则是不允许调用的,比如deleteById 方法,总是有一次物理删除操作。应该在企业应用系统里禁止这种调用,那么,BeetlSql能否定制BaseMapper呢?
答案是能定制。只需要新写一个接口,通过SQLManager注册为BaseMapper即可,然后,为接口的每个方法添加实现即可,实际上,自带的BaseMapper就是这样实现的。
如下是一个自定义的BaseMapper,为了简单起见,Dao只提供三个接口
1public interface MyBaseMapper<T> { 2 public T single(Object key); 3 public T single(Object key,List attrs); 4 public List<T> allData(); 5} 6
第一个方法single和第三个all方法都是原有的BaseMapper自带,第二个方法是根据主键获取对象,但只获取部分属性。我们将在博客最后讲述如何实现,现在,我们要实现allData方法。
allData方法返回目标对象对应的数据所有值,这在原有的BaseMapper已经实现,是通过org.beetl.sql.core.mapper.internal.AllAmi 实现,因此我们只需要复用这个实现。
首先,告诉SQLManager,我要添加一个新的BaseMapper
1SQLManager sql = .... 2MapperConfig config = sql.setBaseMapper(MyBaseMapper.class);
其次,添加allData实现
config.getBuilder().addAmi("allData", new AllAmi());
这样,就非常容易的定义了一个allData内置的Dao方法,使用了已经实现的AllAmi方法,AllAmi方法也非常简单,直接使用SQLManager.all方法实现,其源码如下
1public class AllAmi implements MapperInvoke { 2 3 @Override 4 public Object call(SQLManager sm, Class entityClass, String sqlId, Method m, Object[] args) { 5 if (args == null || args.length == 0) { 6 return sm.all(entityClass); 7 } 8 9 return sm.all(entityClass, (Integer) args[0], (Integer) args[1]); 10 } 11 12} 13
这样,你就具备初步自定义Dao的功能,比如如下代码
1MyUserMapper dao = sql.getMapper(MyUserMapper.class); 2List<User> users =dao.allData();
现在,面临新的挑战,实现一个根据主键查询,但仅仅返回需要的字段,也就是实现如下方法
public T single(Object key,List attrs);
这比较复杂,因为在BeetlSQL里并未内置,需要我们自己完成后注册,完成思路很简单,生成类似如下sql语句,并交给SQLManager 执行即可,比如
select id,name where id = #id#
我们写一个新的扩展,叫做SingleAmiExt(参考了自带的SingleAmi),其脚手架如下
1@Override 2public Object call(SQLManager sm, Class entityClass, String sqlId, Method m, Object[] args) { 3 if (args.length == 1) { 4 return sm.single(entityClass, args[0]); 5 } 6 List attrs = (List) args[1]; 7 String sql = getSingleSelect(entityClass,sm,attrs); 8 9 Map paras = this.setIdsParas(sm, args[0], entityClass); 10 List list = sm.execute(sql, entityClass, paras); 11 if(list.size()==0) { 12 return null; 13 }else { 14 return list.get(0); 15 } 16 17}
这段代码前俩行先判断参数,如果只有一个参数,则认为没有属性过滤,直接调用内置的single方法即可。否则,需要自己拼写一个Sql模板,通过SQLManager.execute 来获取
getSingleSelect方法用于生成目标sql模板,代码如下
1 private String getSingleSelect(Class cls, SQLManager sm, List attrs) { 2 NameConversion nameConversion = sm.getNc(); 3 String condition = appendIdCondition(sm, cls); 4 StringBuilder sb = new StringBuilder("select "); 5 6 for (Object o : attrs) { 7 String attr = (String) o; 8 String col = nameConversion.getColName(cls, attr); 9 sb.append(col).append(" ,"); 10 11 } 12 // 去掉最后一逗号 13 sb.setLength(sb.length() - 1); 14 sb.append(" from ").append(nameConversion.getTableName(cls)).append(condition); 15 return sb.toString(); 16 } 17 18 /* 参考了AbstractDBStyle的内置代码生成办法 */ 19 protected String appendIdCondition(SQLManager sm, Class<?> cls) { 20 21 AbstractDBStyle style = (AbstractDBStyle) sm.getDbStyle(); 22 MetadataManager metadataManager = sm.getMetaDataManager(); 23 NameConversion nameConversion = sm.getNc(); 24 String tableName = nameConversion.getTableName(cls); 25 StringBuilder condition = new StringBuilder(" where "); 26 TableDesc table = metadataManager.getTable(tableName); 27 ClassDesc classDesc = table.getClassDesc(cls, nameConversion); 28 29 List<String> colIds = classDesc.getIdCols(); 30 List<String> propertieIds = classDesc.getIdAttrs(); 31 Iterator<String> colIt = colIds.iterator(); 32 Iterator<String> propertieIt = propertieIds.iterator(); 33 if (colIt.hasNext() && propertieIt.hasNext()) { 34 String colId = colIt.next(); 35 String properId = propertieIt.next(); 36 condition.append(style.getKeyWordHandler().getCol(colId)).append(" = ").append(style.HOLDER_START) 37 .append(properId).append(style.HOLDER_END); 38 while (colIt.hasNext() && propertieIt.hasNext()) { 39 colId = colIt.next(); 40 properId = propertieIt.next(); 41 condition.append(" and ").append(style.getKeyWordHandler().getCol(colId)).append(" = ") 42 .append(style.HOLDER_START).append(properId).append(style.HOLDER_END); 43 } 44 } 45 46 return condition.toString(); 47 }
代码复杂是因为考虑到通用性,尤其是符合主键这里,如果你的表都是单一主键,或者主键都叫ID,那代码就只有几行
同样的setIdsParas也因为复合主键原因,也较为复杂,内容如下
1 private Map setIdsParas(SQLManager sm, Object key, Class entityClass) { 2 AbstractDBStyle style = (AbstractDBStyle) sm.getDbStyle(); 3 MetadataManager metadataManager = sm.getMetaDataManager(); 4 NameConversion nameConversion = sm.getNc(); 5 String tableName = nameConversion.getTableName(entityClass); 6 7 TableDesc table = metadataManager.getTable(tableName); 8 ClassDesc desc = table.getClassDesc(entityClass, nameConversion); 9 Map paras = new HashMap(); 10 List<String> idAttrs = desc.getIdAttrs(); 11 if (idAttrs.size() == 1) { 12 paras.put(idAttrs.get(0), key); 13 } else { 14 // 来自对象id的属性.复合主键 15 Map<String, Object> map = desc.getIdMethods(); 16 for (int i = 0; i < idAttrs.size(); i++) { 17 String idCol = idAttrs.get(i); 18 String idAttr = idAttrs.get(i); 19 Method m = (Method) map.get(idAttr); 20 try { 21 Object os = m.invoke(key, new Object[0]); 22 paras.put(idAttr, os); 23 } catch (Exception ex) { 24 throw new BeetlSQLException(BeetlSQLException.ID_VALUE_ERROR, "无法设置复合主键:" + idCol, ex); 25 } 26 } 27 28 } 29 return paras; 30 } 31
完成上诉代码后,可以添加这个实现
config.getBuilder().addAmi("single", new SingleAmiExt());
然后,可以使用了,比如按照主键查询,只关心id和name
dao.single(1,Arrays.asList("id","name"));
结束语: BaseMapper大部分扩展都较为简单,这里的SingleAmiExt复杂是考虑到复合主键,且现在的BeetlSQL版本还无法重用内置的代码生成片段导致的,叫来BeetlSQL版本会把内置代码生成独立出来,可以供第三方工具使用,这样,开发者就能轻易扩展BeetlSQL了。
关于BeetlSQL,请参考官网ibeetl.com