This is more of a design than implementation question and it's going to be long so bear with me. It's best explained with an example:
Let's say I have a business entity called Product with a bunch of properties (name, price, vendor, etc...).
It's represented by an interface (Product) and implementation (ProductImpl, mapped in Hibernate) as well as basic CRUD service interface (ProductService) and implementation (ProductServiceImpl).
Product and ProductService are exposed as API, their implementations are not.
I want to add a List findProducts(QueryCriteria criteria) method to ProductService that would return a list of products satisfying given criteria. The requirements are:
product.price gt 50.0
)product.vendor.name = "Oracle"
)order by product.vendor.name desc, product.price asc"
)product.vendor.name = "Microsoft"
, query in (2) above should produce empty result set. The question, therefore, is what should QueryCriteria interface used by such a method look like? I can think of 3 solutions and I don't like either one of them:
Any comments on the validity of the above approaches or - fingers crossed - simple elegant solutions that didn't occur to me would be highly appreciated.
P.S. In my specific case, all API clients are internal and "semi-trusted" - that is I'm not as much concerned with someone trying to deliberately break something as with poor programming resulting in Cartesian product of 5 tables :-) However, it'd be nice to come up with a solution that would withstand API exposure to public.
Criteria is a simplified API for retrieving entities by composing Criterion objects. This is a very convenient approach for functionality like "search" screens where there is a variable number of conditions to be placed upon the result set. The Session is a factory for Criteria.
Criteria in Hibernate can be used for join queries by joining multiple tables, useful methods for Hibernate criteria join are createAlias(), setFetchMode() and setProjection() Criteria in Hibernate API can be used for fetching results with conditions, useful methods are add() where we can add Restrictions.
Add an ordering to the result set. Join an association, assigning an alias to the joined association. Join an association using the specified join-type, assigning an alias to the joined association. Create a new Criteria, "rooted" at the associated entity.
The actual solution I've implemented uses a hybrid approach.
Methods that use well-defined queries (e.g. methods that are used internally by other services, predefined reports, etc.) have signature similar to HibernateTemplate's findBy methods:
public List<Entity> findEntities(String queryName, QueryParameters parameters);
where QueryParameters
is a convenience class for specifying named parameters explicitly or taking them from a bean. Sample usage is:
List<Product> products = findProducts("latestUpdates",
new QueryParameters()
.add("vendor", "Oracle")
.add("price", "50.0")
);
or
List<Product> products = findProducts("latestUpdates",
new QueryParameters(product, "vendor", "price"));
Access to such methods is limited to "trusted" code; queries used obviously must obviously be defined in Hibernate mappings. Filters are built into query or defined as session filters. The benefits are cleaner code (no Criteria-like stuff spread across half a page) and clearly defined HQL (easier to optimize and deal with cache if necessary).
Methods that are exposed to UI or otherwise need to be more dynamic use Search interface from Hibernate-Generic-DAO project. It's somewhat similar to Hibernate's DetachedCriteria but has several advantages:
It can be created without being tied to particular entity. It's a big deal for me because entity interface (part of API visible to users) and implementation (POJO mapped in Hibernate) are two different classes and implementation is not available to user at compile time.
It's a well thought out open interface; quite unlike DetachedCriteria from which it's nearly impossible to extract anything (yes, I know DC wasn't designed for that; but still)
Built-in pagination / results with total count / bunch of other little niceties.
No explicit ties to Hibernate (though I personally don't really care about this; I'm not going to suddenly drop Hibernate and go with EclipseLink tomorrow); there are both Hibernate and generic JPA implementations available.
Filters can be added to Search on service side; that's when entity class is specified as well. The only thing's missing is quick-fail on client side if invalid property name is specified and that can be resolved by writing my own ISearch / IMutableSearch implementation but I didn't get to that yet.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With