java 通用搜索条件类型
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11771198/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Generic Search Criteria Type
提问by Swapnil
I've my application structured using presentation layer, service layer and data access layer. There are multiple services and APIs which often need to retrieve some data from the data access layer. To get such data, I often need to pass some search criteria from one layer to another. For example, I could be passing attributes like from_date, to_date, email_id, user_id etc. Now instead of passing a lot of individual parameters, I am trying to pass an object, say SearchCriteria. Again, different services/apis need different SearchCriterias.
我使用表示层、服务层和数据访问层构建了我的应用程序。有多个服务和 API 通常需要从数据访问层检索一些数据。为了获得这样的数据,我经常需要将一些搜索条件从一层传递到另一层。例如,我可以传递像 from_date、to_date、email_id、user_id 等属性。现在我不是传递很多单独的参数,而是试图传递一个对象,比如 SearchCriteria。同样,不同的服务/api 需要不同的 SearchCriteria。
The question is, how do I make it generic so as to make it usable by all services. One way is to have a SearchCriteria object which holds a map of SEARCH_KEY to value, where SEARCH_KEY could be an enum (having values like FROM_DATE, TO_DATE etc). Depending on the SEARCH_KEY, I could retrieve the key and use it. But this works only when you one layer knows about the type of the value (Integer, String) to expect from this map. One layer is coupled to another, which I suppose isn't good. I went through this question - Map with multiple value types with advantages of genericswhich solves a similar problem. Is that a good design? Are there any better alternatives?
问题是,我如何使它成为通用的以便所有服务都可以使用它。一种方法是拥有一个 SearchCriteria 对象,该对象保存 SEARCH_KEY 到值的映射,其中 SEARCH_KEY 可以是枚举(具有 FROM_DATE、TO_DATE 等值)。根据 SEARCH_KEY,我可以检索密钥并使用它。但这仅在您知道该地图所期望的值(整数、字符串)的类型时才有效。一层与另一层耦合,我认为这不好。我经历了这个问题 -具有多种值类型的映射,具有解决类似问题的泛型优势。这是一个好的设计吗?有没有更好的选择?
回答by jabu.10245
If you're using something like JPA you might want to look at the JPA Criteria API. With that you could write a basic filter class:
如果您使用的是 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();
This is just a very basic sample of a filter class, you can add more and more addXXX(Expression, ?)
methods and also features like sorting and paging to the filter. Note that you should set the EntityManager
by the DAO if you want to separate it from other layers, for example as a parameter in the getResultList(EntityManager)
method.
这只是过滤器类的一个非常基本的示例,您可以向过滤器添加越来越多的addXXX(Expression, ?)
方法以及排序和分页等功能。请注意,EntityManager
如果要将其与其他层分开,则应由 DAO设置,例如作为getResultList(EntityManager)
方法中的参数。
Another solution might be an example object like this:
另一个解决方案可能是这样的示例对象:
User example = new User();
example.setFirstName("J*");
example.setGender(Gender.Male);
List<User> users = userDao.find(example);
where your DAO implementation does the filtering itself.
您的 DAO 实现在哪里进行过滤。
回答by WilDrow
I just made this solution for a generic search on Hibernate 5 with the new criteria API. I got a solution similar to the FileFilter from JavaIO.
我刚刚使用新的标准 API 为 Hibernate 5 上的通用搜索制作了这个解决方案。我得到了一个类似于 JavaIO 的 FileFilter 的解决方案。
At the GenericDAO I implemented a method for the generic search:
在 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();
}
And for the SearchFilter class (implemented as a nested class for the GenericDAO), a really simple code:
对于 SearchFilter 类(作为 GenericDAO 的嵌套类实现),一个非常简单的代码:
abstract class SearchFilter {
abstract protected Predicate buildPredicate(CriteriaBuilder criteriaBuilder, Root<T> root);
}
Now for the usage, it looks like this:
现在的用法,它看起来像这样:
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);
}
});
}
This solution allowed to reduce the number of code lines for the of specific DAO searches with a simpler implementation.
该解决方案允许通过更简单的实现减少特定 DAO 搜索的代码行数。
回答by Byter
You can do something like this
你可以做这样的事情
Write Enum to hold your parameterKeys
编写 Enum 以保存您的参数键
enum ParameterKeys {
SERIAL_NUMBER,
SEARCH_KEY,
FROM_DATE,
ISVALID
}
write a class which hold the parameter Values
编写一个包含参数值的类
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);
}
}
now try making the parameter Map
现在尝试制作参数 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());
}
}
Output will be :
输出将是:
java.lang.Integer
java.lang.String
java.util.Date
java.lang.Boolean
You can pass this map around to different Levels
您可以将此地图传递到不同的级别
回答by Byter
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
参数键之间的类型不匹配:FROM_DATE 和值:true