如何让QueryBuilder查询无结果时返回默认实体?
解决方案思路
1. 装饰器模式包装Dao层(推荐)
创建通用Dao装饰类,统一拦截unique()调用,处理无结果异常并返回默认实体。这种方式无需修改原有Dao代码,仅需替换注入的Dao实例为装饰后的版本,侵入性极低。
代码示例:
首先定义默认实体生成接口,让所有需要默认值的实体实现它:
public interface Defaultable<T> { T getDefaultInstance(); } // 实体类实现接口,定义自身默认值 public class MyEntity implements Defaultable<MyEntity> { // 原有字段定义... @Override public MyEntity getDefaultInstance() { MyEntity defaultEntity = new MyEntity(); defaultEntity.setId(-1); defaultEntity.setName("默认内容"); // 补充其他字段的默认值 return defaultEntity; } }
实现Dao装饰类,包装原QueryBuilder的unique()方法:
public class DefaultableDaoDecorator<T extends Defaultable<T>, DAO extends AbstractDao<T, Long>> implements DAO { private final DAO delegateDao; public DefaultableDaoDecorator(DAO delegateDao) { this.delegateDao = delegateDao; } @Override public QueryBuilder<T> queryBuilder() { return new DefaultableQueryBuilder<>(delegateDao.queryBuilder()); } // 其他Dao方法直接委托给原Dao实现 @Override public void insert(T entity) { delegateDao.insert(entity); } // 按需重写其他需要处理的Dao方法... // 自定义QueryBuilder,重写unique()做统一异常处理 private static class DefaultableQueryBuilder<T extends Defaultable<T>> extends QueryBuilder<T> { private final QueryBuilder<T> delegateQuery; public DefaultableQueryBuilder(QueryBuilder<T> delegateQuery) { super(delegateQuery.getDao()); this.delegateQuery = delegateQuery; } @Override public T unique() { try { return delegateQuery.unique(); } catch (DaoException e) { // 判断是否为无结果异常(匹配GreenDao无结果时的异常信息) if (e.getMessage() != null && e.getMessage().contains("No entity found for query")) { try { return getDao().getEntityClass().newInstance().getDefaultInstance(); } catch (InstantiationException | IllegalAccessException ex) { throw new RuntimeException("创建默认实体失败", ex); } } // 非无结果异常,继续抛出 throw e; } } } }
使用时替换原Dao实例:
// 获取原始Dao MyEntityDao originalDao = daoSession.getMyEntityDao(); // 包装为带默认值返回的Dao MyEntityDao defaultableDao = new DefaultableDaoDecorator<>(originalDao); // 调用方式与原有代码完全一致,无结果时自动返回默认实体 MyEntity entity = defaultableDao.queryBuilder().where(MyEntityDao.Properties.Id.eq(100)).unique();
2. 泛型工具类封装查询逻辑
如果不想包装Dao,可创建通用工具类,封装带默认返回的查询逻辑,所有查询通过工具类调用即可。
代码示例:
public class QueryUtils { public static <T extends Defaultable<T>> T queryWithDefault(QueryBuilder<T> queryBuilder) { try { return queryBuilder.unique(); } catch (DaoException e) { if (e.getMessage() != null && e.getMessage().contains("No entity found for query")) { try { return queryBuilder.getDao().getEntityClass().newInstance().getDefaultInstance(); } catch (InstantiationException | IllegalAccessException ex) { throw new RuntimeException("创建默认实体失败", ex); } } throw e; } } }
使用方式:
MyEntity entity = QueryUtils.queryWithDefault( myDao.queryBuilder().where(MyEntityDao.Properties.Id.eq(100)) );
3. 自定义Dao基类(适合新项目)
若项目处于初期阶段,可让所有Dao继承自定义基类,在基类中重写queryBuilder()方法,返回自定义QueryBuilder统一处理unique()逻辑。
代码示例:
public abstract class DefaultableBaseDao<T extends Defaultable<T>, K> extends AbstractDao<T, K> { protected DefaultableBaseDao(DaoConfig config) { super(config); } @Override public QueryBuilder<T> queryBuilder() { return new DefaultableQueryBuilder<>(super.queryBuilder()); } private static class DefaultableQueryBuilder<T extends Defaultable<T>> extends QueryBuilder<T> { private final QueryBuilder<T> delegate; public DefaultableQueryBuilder(QueryBuilder<T> delegate) { super(delegate.getDao()); this.delegate = delegate; } @Override public T unique() { try { return delegate.unique(); } catch (DaoException e) { if (e.getMessage() != null && e.getMessage().contains("No entity found for query")) { try { return getDao().getEntityClass().newInstance().getDefaultInstance(); } catch (Exception ex) { throw new RuntimeException("创建默认实体失败", ex); } } throw e; } } } }
后续生成Dao时,指定继承该基类(可通过GreenDao的生成配置设置)即可实现全局统一处理。
注意事项
- 异常判断:GreenDao无结果时抛出的
DaoException,可通过异常信息中的关键词No entity found for query识别,确保仅处理无结果场景,其他异常正常抛出。 - 默认实体创建:通过
getEntityClass().newInstance()创建实例时,需确保实体类存在无参构造函数,否则会抛出初始化异常。 - 场景适配:装饰器模式适合已有大量Dao实现的老项目,自定义基类适合新项目统一规范,工具类则是轻量化的折中方案。
内容的提问来源于stack exchange,提问作者Jacks
相关产品推荐
相关产品推荐

