JPA criteria builder - in human readable language - jpa

I understand the advantages of using a JPA criteria builder above the Java Persistence Query Language.
Is there an easy way to explain how to build up this kind of queries?
I need a more human readable explanation to build up my queries, this to have a kind of intuitive approach to query my database.
Example:
SQL:
SELECT id,status,created_at from transactions where status='1'
and currency='USD' and appId='123' order by id
Critera Builder with MetaModel:
Map<SingularAttribute<Transaction, ?>, Object> params = ...;
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Transaction> r = cq.from(Transaction.class);
Predicate p= cb.conjunction();
for (Map.Entry<SingularAttribute<Transaction, ?>, Object> param: params.entrySet())
p = cb.and(p, cb.equal(r.get(param.getKey()), param.getValue()));
cq.multiselect(r.get(Transaction_.id), r.get(Transaction_.status),
r.get(Transaction_.created_at))
.where(p)
.orderBy(cb.asc(r.get(Transaction_.id)));
List<Tuple> result = em.createQuery(cq).getResultList();
This example was based on another question:
Complex queries with JPA criteria builder

I don't think there's an more clean way to write that kind of query following the standards and not using an hand wrote JPQL query, anyway out of the standard the are many query builders like: query dsl or Torpedo query or Object Query that allow to write query in a more clean way if that can help :)

Criteria query have some advantages of JPQL, such as: type safety, write SQL querias based on Java Programming model and make it portable. The easiest way in which I understand this when I started to work with JPA is think in the main objects that you need to use and their features.
CriteriaBuilder: Any statement that can be done using SQL like functions, reserved works, operations, predicates are part of this class, so builder need to be used to create those and apply them to the criteriaQuery.
CriteriaQuery: Any statement to have as goal to create a formal SQL statement are here, think on this as the boilerplate of the query definition, it have from, select, where, group by and all the statements, so this is the query itself and must be use to determine what you are really looking for from the database.
Now think to do a query you always must to use a Root, this mean select what will be your main table in your query definitions, based on that and helping from CriteriaQuery and CriteriaBuilder objects you can redefine the search to be whatever you want.

Related

Writing custom n1ql queries in spring

I'm trying to query a spring-data couchbase repository using N1QL queries. I have two doubts:
I'm using #Query annotation to generate queries, my code looks like this:
#Query("#{#n1ql.selectEntity} WHERE $0 = $1 AND #{#n1ql.filter}")
public Page<GsJsonStore> matchJson(String term, String value, Pageable pageable);
//Query
Page<GsJsonStore> p = repo.matchJson("_object.details.status", "ready", pg);
This query doesn't return any results. However, when I run the same query (below) in cbq I get the desired result:
select * from default where _object.details.status = 'ready';
How can I view the query string generated by the Couchbase repository? I'm using spring-boot. Am I correct in using the #Query annotation for this use-case?
Also, how can I perform n1QL queries on CouchbaseOperations template? I know that there's a findByN1QL method, but I didn't find any good documentation about it. Could someone please explain how to use this?
The query looks ok. You did persist your GsJsonStore entities using the Spring Data Couchbase repository did you?
In order to log all the queries generated and executed by the framework (including inline queries like in your case), you can configure the logger like so in a logback.xml configuration:
<logger name="org.springframework.data.couchbase.repository.query" level="debug"/>
You'll see that the query that got executed and the query that you ran in cbq are not the same, since at least you didn't use a WHERE clause.
In CouchbaseOperations there are two methods relative to N1QL queries:
findByN1QL: this expects specific structure of the query, in order to make sure all data necessary to correct deserialization of a Spring Data annotated entity is selected (which is the purpose of the #n1ql.selectEntity and #n1ql.filter SpEL).
findByN1QLProjection is more free-form. If Jackson can deserialize the results of the provided query to the requested Class, then it will. As such, the SELECT clause is much less implicitly restricted in this method.
To use both, you must pass in a N1qlQuery object from the SDK. Such queries can be constructed using factory methods of the N1qlQuery class, for example:
//a version of the query that is constructed from positional parameters
N1qlQuery queryWithParameter = N1qlQuery.parameterized("SELECT name FROM `beer-sample` WHERE name LIKE $0", JsonArray.from("%dog%"));
//let Spring Data execute the query, projecting to the String class
List<String> beerNamesContainingDog = template.findByN1QLProjection(queryWithParameter, String.class);

Dynamic EF Query

I am thinking of designing a business rule engine which basically generates an EF query from a set of string values stored in a database.
For e.g. I will store the connection string, table name, the where condition predicate, and select predicate as string fields in a db and would like to construct the EF query dynamically. For e.g.
var db = new DbContext(“connectionstring”);
var wherePredicate = Expression.FromString(“p => p.StartDate > new DateTime(2014,5,1))
var selectPredicate = Expression.FromString(“p => p”)
var results = db.Set(“Projects”).Where(wherepredicate).Select(selectPredicate)
For constructing the predicates I can use DynamicExpression or Dynamic LINQ library.
However how do I access db.Set(“Projects”) where Projects is the entity name and apply the where and select predicates? (or something like db[“Projects”].Where().Select).
I tried the non-generic version of the DbContext.Set(Type entityttype) method, however couldn’t figure out how to apply Where and Select predicates to the returned object.
I am trying to avoid generating SQL queries and instead rely on dynamically generated EF code.
This doesn't make much sense. You can create method that will work on string instead of generic type using reflection, but you'd have to return DbSet not DBSet<T>. And on that one you cannot execute LINQ's methods (basically), because there's no type (during compilation). Of course you can do it all the way using reflection, but then, why??? You're loosing 90% of what O/R mapper does for you.

Entity Framework method to query based on a complete object

How can I achieve following query method in Entity Framework,
below is a snippet from NHibernate documentation http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/querycriteria.html
Example example = Example.create(cat)
.excludeZeroes() //exclude zero valued properties
.excludeProperty("color") //exclude the property named "color"
.ignoreCase() //perform case insensitive string comparisons
.enableLike(); //use like for string comparisons
List results = session.createCriteria(Cat.class)
.add(example)
.list();
Entity framework is LINQ based. Linq is said to be a declarative language, which means so much as telling what to do in stead of how to do it (imperative). A statement like
context.Orders.Select(o => o.OrderDate).Distinct();
is a declarative shortcut, if you like, for a 'ceremonial' foreach statement in which OrderDates are added to a list if they were not added to it before.
I'm not an expert in NHibernate or its criteria API, but the criteria API seems to be even more declarative than linq. That makes it hard to compare them. A few differences:
The main one: query by example is not possible in EF.
There is no way in linq to set behaviours for a whole query. For instance, if you want to exclude zero valued properties, you'll have to specify each one of them in a where predicate (which is closer to telling how to filter).
Case sensitivity is downright underdeveloped in EF. For example, a statement like
People.Where(c => string.Compare( c.Name, "z", false) > 0)
will generate the same SQL as
People.Where(c => string.Compare( c.Name, "z", true) > 0)
The database collation determines the case sensitiveness of string comparisons.
You can do LIKE queries, but, again, specified for each individual predicate:
People.Where (c => c.Name.Contains("a"))
(again: no differentiation in case)
So I can't really give a linq translation of your criteria query. I'd have to know the class properties to be able to specify all individual predicates.

CriteriaBuilder and using alias for select cause

I would like to create a query with CriteriaBuilder for this kind of sql;
SELECT myDefinedAlias.id, myDefinedAlias.name, myDefinedAlias.aFieldForFK select from Person as myDefinedAlias where myDefinedAlias.name = ?1
How can i accomplish defining an alias for this?
I can create queries without aliases but i cannot define aliases...
CriteriaQuery<Person> cq = criteriBuilder.createQuery(Person.class);
Root<Person> person = cq.from(Person.class);
cq = cq.select(person);
cq = cq.where(criteriaBuilder.equal(person.get(Person_.name), "Chivas")))
I need this for QueryHints, batch fetch.
.setHint(QueryHints.BATCH, "myDefinedAlias.aFieldForFK.itsNestedAttribute");
I am stuck and couldn't find anything regarding my problem. Anyone?
Regards
Doing cq.select(person).alias("myDefinedAlias") assigns your alias which can subsequently be used in batch/fetch query hints. Eclipselink supports nested fetch joins as long as you do not transfer to-Many relations (collections).
I.e..setHint(QueryHints.BATCH, myDefinedAlias.toOneRelation.toManyRelation") works while .setHint(QueryHints.BATCH, .setHint(QueryHints.BATCH, "myDefinedAlias.toManyRelation.toOneRelation") shouldn't.
I think you are going about this the wrong way. JPA needs the sql-statement-aliases for itself to use when generating sql-statements.
For the nested query hints to work, the relationship needs to be specified in the entities.
For example, if your Person entity have a OneToMany mapping to a House entity - and the property name in the Person class is livedInHouses. The query hint would become:
.setHint(QueryHints.BATCH, "Person.livedInHouses"). Its damn near impossible to use FKs that exists in the database but are not annotated as relations on/in entities in JPA.

Adding OrderBy clause to a named query

Is it possible to add an OrderBy clause in to JPA Named query at runtime?
Named queries are processed by the persistence provider when the EntityManagerFactory is created. You can't change/modify/append anything about a named query dynamically at runtime.
If you are using JPA 2.0 and you need a way to do high-performance dynamic queries at runtime, you should look into the Criteria API.
From Java EE 5 Documentation : "Is used to specify a named query in the Java Persistence query language, which is a static query expressed in metadata. Query names are scoped to the persistence unit".
As it says, it is static & you can't change the query at runtime.
Rather use custom query or if ordering element is fixed then you can use annotation;
Field:
#OrderBy(value="nickname")
List<Person> friends;
Method:
#OrderBy("nickname ASC")
public List<Person> getFriends() {...};
Even if you can't add order-by clause to your named query, you can provide a parametric orderBy. The following is a perfectly valid query:
SELECT u FROM User u ORDER BY :orderBy
And you are going to change ordering with something like:
query.setParameter("orderBy", "id");