2

我使用表示层、服务层和数据访问层构建了我的应用程序。有多种服务和 API 通常需要从数据访问层检索一些数据。为了获得这样的数据,我经常需要将一些搜索条件从一层传递到另一层。例如,我可以传递 from_date、to_date、email_id、user_id 等属性。现在,我尝试传递一个对象,比如 SearchCriteria,而不是传递很多单独的参数。同样,不同的服务/api 需要不同的 SearchCriterias。

问题是,我如何使其通用,以便所有服务都可以使用它。一种方法是拥有一个 SearchCriteria 对象,该对象包含一个 SEARCH_KEY 到值的映射,其中 SEARCH_KEY 可以是一个枚举(具有 FROM_DATE、TO_DATE 等值)。根据 SEARCH_KEY,我可以检索密钥并使用它。但这仅在您的一层知道从该映射中期望的值的类型(整数,字符串)时才有效。一层耦合到另一层,我认为这不好。我经历了这个问题 -具有多种值类型的映射,具有解决类似问题的泛型优势。这是一个好的设计吗?有没有更好的选择?

4

4 回答 4

7

如果您使用 JPA 之类的东西,您可能需要查看JPA Criteria API。有了它,您可以编写一个基本的过滤器类:

public abstract class AbstractFilter<T> {
    private EntityManager em;
    private Class<T> entityClass;
    private List<Predicate> predicates;
    private CriteriaBuilder cb;

    public AbstractFilter(EntityManager em, Class<T> clazz) {...}

    protected void add(Predicate predicate) {
        if (predicates == null) predicates = new ArrayList<>();
        if (predicate != null) predicates.add(predicate);
    }

    protected <X> void addEqual(Expression<X> expression, X value) {
        if (expression != null && value != null && cb != null)
            add(cb.equal(expression, value));
    }

    protected abstract void buildPredicates(Root<T> root);

    public List<T> getResultList() {
        this.cb = em.getCriteriaBuilder();
        CriteriaQuery<T> query = cb.createCriteria(entityClass);
        Root<T> root = query.from(entityClass);
        buildPredicates(root);
        return em.createQuery(query.select(root).where(predicates)).getResultList();
    }
}

public class UserFilter<User> {
    private String firstName, lastName;  // plus getter and setter
    private Gender gender;               // plus getter and setter

    public UserFilter(EntityManager em) { super(em, User.class); }

    protected void buildPredicates(Root<User> user) {
        addEqual(user.get(User_.firstName), getFirstName());
        addEqual(user.get(User_.lastName), getLastName());
        addEqual(user.get(User_.gender), getGender());
    }
}

------

UserFilter filter = new UserFilter(entityManager);
filter.setFirstName("Joe");
filter.setGender(Gender.Male);
List<User> users = filter.getResultList();

这只是过滤器类的一个非常基本的示例,您可以向过滤器添加越来越多的addXXX(Expression, ?)方法以及排序和分页等功能。请注意,EntityManager如果要将其与其他层分开,则应由 DAO 设置,例如作为getResultList(EntityManager)方法中的参数。


另一种解决方案可能是这样的示例对象:

User example = new User();
example.setFirstName("J*");
example.setGender(Gender.Male);
List<User> users = userDao.find(example);

您的 DAO 实现本身在哪里进行过滤。

于 2012-08-02T06:31:56.947 回答
3

我刚刚使用新的条件 API 为 Hibernate 5 上的通用搜索制作了这个解决方案。我得到了一个类似于 JavaIO 的 FileFilter 的解决方案。

在 GenericDAO,我实现了一个通用搜索的方法:

public List<T> search(SearchFilter searchFilter) {
    CriteriaBuilder criteriaBuilder = getSession().getCriteriaBuilder();
    CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(getPersistentClass());
    Root<T> entityRoot = criteriaQuery.from(getPersistentClass());
    criteriaQuery.select(entityRoot);
    criteriaQuery.where(searchFilter.buildPredicate(criteriaBuilder, entityRoot));
    Query<T> query = getSession()
            .createQuery(criteriaQuery);
    return query.getResultList();
}

对于 SearchFilter 类(作为 GenericDAO 的嵌套类实现),一个非常简单的代码:

abstract class SearchFilter {
    abstract protected Predicate buildPredicate(CriteriaBuilder criteriaBuilder, Root<T> root);
}

现在对于用法,它看起来像这样:

public List<Product> findByCategory(ProductCategory productCategory) {
    return search(new SearchFilter() {
        @Override
        protected Predicate buildPredicate(CriteriaBuilder criteriaBuilder, Root<Product> root) {
            return criteriaBuilder.equal(root.get("productCategory"), productCategory);
        }
    });
}

该解决方案允许通过更简单的实现来减少特定 DAO 搜索的代码行数。

于 2018-02-18T14:15:27.437 回答
0

If handling at Runtime is enough you can try this

public enum ParameterKeys {

  SERIAL_NUMBER {
      public Class<?> getType() {
          return Integer.class;
      }
  },
  SEARCH_KEY{
      public Class<?> getType() {
          return String.class;
      }
  },
  FROM_DATE{
      public Class<?> getType() {
          return Date.class;
      }
  },
  ISVALID{
      public Class<?> getType() {
          return Boolean.class;
      }
  };

public abstract Class<?> getType();

}

public class Parameter {

private Object value;
private Class<?> clazz;
private ParameterKeys key;

public Parameter(Object value,ParameterKeys key) throws Exception {
    this.key = key;
    setValue(value);
}

public void setValue(Object value) throws Exception{
    this.value = value;
    this.clazz = value.getClass();
    if(!clazz.getCanonicalName().equals(key.getType().getCanonicalName())) {
        throw new Exception("Type Mismatch between Parameter Key : " + key + " and Value : " + value);
    }
}

public Object getValue() {
    return clazz.cast(value);
}

}

public class Main {

public static void main(String[] args) {
    Map<ParameterKeys,Parameter> _params = new HashMap<ParameterKeys, Parameter>();
    try {
    _params.put(ParameterKeys.SERIAL_NUMBER, new Parameter(1,ParameterKeys.SERIAL_NUMBER));
    _params.put(ParameterKeys.SEARCH_KEY, new Parameter("query",ParameterKeys.SEARCH_KEY));
    _params.put(ParameterKeys.FROM_DATE, new Parameter(new Date(),ParameterKeys.FROM_DATE));
    _params.put(ParameterKeys.FROM_DATE, new Parameter(true,ParameterKeys.FROM_DATE));
    } catch(Exception e) {
        System.out.println(e.getMessage());
    }

}

}

This will print out the following exception

Type Mismatch between Parameter Key : FROM_DATE and Value : true

于 2012-08-02T07:48:06.740 回答
0

你可以做这样的事情

写枚举来保存你的参数键

enum ParameterKeys {

  SERIAL_NUMBER,
  SEARCH_KEY,
  FROM_DATE,
  ISVALID

}

编写一个包含参数值的类

public class Parameter {

    private Object value;
    private Class<?> clazz;

    public Parameter(Object value) {
        setValue(value);
    }

    public void setValue(Object value) {
        this.value = value;
        this.clazz = value.getClass();
    }

    public Object getValue() {
        return clazz.cast(value);
    }

}

现在尝试制作参数 Map

 public class Main {

        public static void main(String[] args) {
            Map<String,Parameter> _params = new HashMap<String, Parameter>();
            _params.put(ParameterKeys.SERIAL_NUMBER, new Parameter(1));
                 _params.put(ParameterKeys.SEARCH_KEY, new Parameter("query"));
            _params.put(ParameterKeys.FROM_DATE, new Parameter(new Date()));
            _params.put(ParameterKeys.ISVALID, new Parameter(1));
        System.out.println(_params.get(ParameterKeys.SERIAL_NUMBER).getValue().getClass().getCanonicalName());
    System.out.println(_params.get(ParameterKeys.SEARCH_KEY).getValue().getClass().getCanonicalName());     System.out.println(_params.get(ParameterKeys.FROM_DATE).getValue().getClass().getCanonicalName());    System.out.println(_params.get(ParameterKeys.FROM_DATE).getValue().getClass().getCanonicalName());

        }

    }

输出将是:

java.lang.Integer
java.lang.String
java.util.Date
java.lang.Boolean

您可以将此地图传递到不同的关卡

于 2012-08-02T05:20:22.330 回答