JPA 2.1 / Eclipselink: Reuse Criteria Queries as Named Queries - jpa

i am trying to reuse a dynamic query as a named query as described here:
https://wiki.eclipse.org/EclipseLink/Release/2.5/JPA21#Add_Named_Query
the goal is to build the criteria-query only once and then reuse it as a namedquery if parameter did not change.
public static List<User>getUserByParameter(ParameterMap parameter){
EntityManager em = getEntityManager();
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<User> criteriaQuery = builder.createQuery(User.class);
Root<User> user = criteriaQuery.from(User.class);
List<Predicate> predicates = new ArrayList<Predicate>();
//...build up the query depending on parameter
if (null != parameter.getStatus()){
predicates.add(builder.equal(user.<Integer>get("status"), parameter.getStatus()));
}
//etc.
criteriaQuery.select(user).where(predicates.toArray(new Predicate[]{}));
Query query = em.createQuery(criteriaQuery);
//now register this query as a namedQuery
em.getEntityManagerFactory().addNamedQuery("userByParameter", query);
return query.getResultList();
}
i thought about something like:
public static List<User>getUserByParameter(ParameterMap parameter){
Query userByParameter = em.createNamedQuery("userByParameter");
if (null != userByParameter){
return userByParameter.getResultList();
}else {
//build the dynamic query as above
}
}
this results in a nullpointer as the namedQuery doesn't exist the first time.
how can i reuse the query in the same method or in other words, how can i check in a clean way (without using try-catch) if a namedquery exists?

I'm not sure I understand the problem you are looking to solve. The getUserByParameter method is something that should be built on the EntityManagerFactory, when it is first initialized or obtained. Feel free to add properties to your factory if you wish to keep track of what you have added already, but these should be done only once, upfront during initialization.
What is confusing is that you are expecting the query results to be reused - named queries are designed to help reduce the cost of parsing and preparing queries. EclipseLink has a query cache feature that can return the results for you if the same parameters are used, without you needing to cache the query, its parameters and the results yourself.

Related

JPA search query using Criteria Builder for multiple columns

I am trying to make a universal search for my entity with criteria builder where a given text is matched with all the columns in the entity.
String likeSearchText = "%" + searchText + "%";
List<Customer> searchedCustomers = null;
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery query = builder.createQuery(Customer.class);
Root <Customer> root = query.from(Customer.class);
ArrayList<Predicate> conditions = new ArrayList<>();
conditions.add(builder.like(root.<String>get("firstName"), likeSearchText));
conditions.add(builder.like(root.<String>get("lastName"), likeSearchText));
conditions.add(builder.like(root.<String>get("middleName"), likeSearchText));
conditions.add(builder.like(root.<String>get("companyName"), likeSearchText));
conditions.add(builder.like(root.<String>get("industry"), likeSearchText));
query.where(builder.or(conditions.toArray(new Predicate[conditions.size()])));
query.select(root);
searchedCustomers = entityManager.createQuery(query).getResultList();
return searchedCustomers;
When I run this method I always get an empty list. I tried changing the like to notLike and that works perfectly fine by giving me a list containing elements which are not like the given search text so I am really confused as to what's wrong with my like method.
Any kind of help would be appreciated!
I had similar problems when I made some testing and had entities with the same (simple)name in the classpath. So for example there were entities like:
org.example.one.Customer
org.example.two.Customer
If you do not have explicitly set different table names like:
package org.example.one;
#Entity("customer_one")
public class Customer { ...
and
package org.example.two;
#Entity("customer_two")
public class Customer { ...
hibernate might:
mix stuff in the same table in db
try to find field from wrong table when constructing the query
Also I thibk you do not need this:
query.select(root);

EF deferred execution using SQLquery as input

I am trying to get an entity with EF by having an initial sql as input.
I tried the context.Entities.SQLQuery method but this returns a DBSet when I require an IQueriable.
I learned that I cannot transform DBSet to IQueryable because the first is already a result of data while the second is the container for the results of a "query" (executed yet or not). Correct me if i'm wrong :)
So I thought that when I write the following lambda I get the resulting query:
db.MyTable.Where(x => x.id == "123")
Becomes:
SELECT * FROM myTable WHERE id = '123'
With this I thought if I can set directly my query without needing to set my lambda...
Is that an option?
Or an alternative?
Thanks!
It's a bit unclear what you mean:
I am trying to get an entity with EF having an initial sql as input.
I'd interpret this, as that you have an SQL statement as input of something and you want to get an entity framework entity that would have this statement (whatever having a statement means)? Not understandable!
I learned that I cannot transform DBSet to IQueryable because the
first (the DbSet) is already a result of data while the second (the IQueryable) is the container for the results of a "query"
NOT!
Every DbSet<T> implements IQueryable<T>, meaning that if you have an object of class DbSet<t>, this object implements all functionality of IQueryable<T>. Just using this IQueryable does not execute the query. The query will only be executed once the first element of the sequence if requested.
using (var dbContext = new MyDbcontext())
{
var result = dbContext.MyItems
.Where(item => ...)
.Select(item => new
{
X = item.Property1,
Y = item.Property2,
...
};
Until here, the first element of the sequence is not asked, the query is not performed yet. No communication with the database was needed (except to create the dbContext object)
Only if you use execution functions like ToList(),Count(), First(), Max(), etc, the query is performed.
You can check this, because you get exceptoin if you do these kind of functions after the using block:
Wrong
IQueryable largeItems;
using (var dbContext = new MyDbcontext())
{
largeItems = dbContext.MyItems
.Where(item => item.Size > 1000);
// query not executed yet
}
int nrOfLargeItems = largeItems.Count();
// exception, query executed after dbContext is disposed
correct
int nrOfLargeItems;
using (var dbContext = new MyDbcontext())
{
var largeItems = dbContext.MyItems
.Where(item => item.Size > 1000);
// query not executed yet
nrOfLargeItems = largeItems.Count();
// the query is performed
}
Conclusion: users of a DbSet<T> inside a dbContext can use the DbSet<T> as if it was an IQueryable<T>, the query will not be executed until you perform any function that needs the first element of the query.
This includes complex functions like Join, GroupBy, OrderBy, etc. You can recognize these functions because MSDN add the following to the remarks section
This method is implemented by using deferred execution. The immediate return value is an object that stores all the information that is required to perform the action. The query represented by this method is not executed until the object is enumerated either by calling its GetEnumerator method directly or by using foreach.

How to support OData query syntax but return non-Edm models

Exposing my EF models to an API always seemed wrong. I'd like my API to return a custom entity model to the caller but use EF on the back.
So I may have PersonRestEntity and a controller for CRUD ops against that and a Person EF code-first entity behind in and map values.
When I do this, I can no longer use the following to allow ~/people?$top=10 etc. in the URL
[EnableQuery]
public IQueryable<Person> Get(ODataQueryOptions<Person> query) { ... }
Because that exposes Person which is private DB implementation.
How can I have my cake and eat it?
I found a way. The trick is not to just return the IQueryable from the controller, because you need to materialise the query first. This doesn't mean materialising the whole set into RAM, the query is still run at the database, but by explicitly applying the query and materialising the results you can return mapped entities thereafter.
Define this action, specifying the DbSet entity type:
public async Task<HttpResponseMessage> Get(ODataQueryOptions<Person> oDataQuery)
And then apply the query manually to the DbSet<Person> like so:
var queryable = oDataQuery.ApplyTo(queryableDbSet);
Then use the following to run the query and turn the results into the collection of entities you publicly expose:
var list = await queryable.ToListAsync(cancellationToken);
return list
.OfType<Person>()
.Select(p => MyEntityMapper.MapToRestEntity(p));
Then you can return the list in an HttpResponseMessage as normal.
That's it, though obviously where the property names between the entities don't match or are absent on either class, there's going to be some issues, so its probably best to ensure the properties you want to include in query options are named the same in both entities.
Else, I guess you could choose to not support filters and just allow $top and $skip and impose a default order yourself. This can be achieved like so, making sure to order the queryable first, then skip, then top. Something like:
IQueryable queryable = people
.GetQueryable(operationContext)
.OrderBy(r => r.Name);
if (oDataQuery.Skip != null)
queryable = oDataQuery.Skip.ApplyTo(queryable, new System.Web.OData.Query.ODataQuerySettings());
if (oDataQuery.Top != null)
queryable = oDataQuery.Top.ApplyTo(queryable, new System.Web.OData.Query.ODataQuerySettings());
var list = await queryable.ToListAsync(operationContext.CreateToken());
return list
.OfType<Person>()
.Select(i => this.BuildPersonEntity(i));
More information:
If you simply use the non-generic ODataQueryOptions you get
Cannot create an EDM model as the action 'Get' on controller 'People'
has a return type 'System.Net.Http.HttpResponseMessage' that does not
implement IEnumerable
And other errors occur under different circumstances.

Casting issue while returning value in method in EF4.0

I'm trying to load employees using Entity Framework.
The method is supposed to return employee list.
It' s giving this error:
Cannot implicit convert....<Class names and methods>.... An Explicit conversion exists.
I think the problem is related to casting.
Please check below code.
public List<Employee> LoadEmployees()
{
try
{
EMployeeDB1Entities EE = new EMployeeDB1Entities();
var Employees = EE.Employees.Where(p => p.Name.StartsWith("T"));
return Employees;
}
catch
{
return null;
}
}
var Employees = EE.Employees.Where(p => p.Name.StartsWith("T")).ToList();
Update your code to:
return Employees.ToList();
Also do note that this is the ToList() method that actually triggers the database query.
EE.Employees.Where(....) doesn't query the database. The DB is queried when the result of the Where() is enumerated, which is what .ToList() does.
Thanks it works...one more issue, suppose if I want to bind above list
to grid then how can I bind ?
Assuming you're using WPF or Silverlight:
To bind the result of your query on a datagrid, you could expose a public property of type ObservableCollection.
This collection accepts an IEnumerable<T> object as constructor.
You can write:
var myCollection = new ObservableCollection<Employee>(this.LoadEmployees());
Then bind the ItemSource property of your datagrid to your collection.
If you have more problems using bindings, I recommend you to ask another question, because the subject is quite different.

what is better: getSingleResult, or getResultList JPA

i need to retrieve single row from table, and i was interested what approach is better.
On the one side getSingleResult is designed for retrieving single result, but it raises exception. Does this method have benefit in performance related to getResultList with
query.setFirstResult(0);
query.setMaxResults(1);
According to Effective Java by Joshua Bloch:
Use checked exceptions for conditions from wich the caller can
reasonably be expected to recover. Use runtime exceptions to indicate
programming errors.
Credit to the source: Why you should never use getSingleResult() in JPA
#Entity
#NamedQuery(name = "Country.findByName",
query = "SELECT c FROM Country c WHERE c.name = :name"
public class Country {
#PersistenceContext
transient EntityManager entityManager;
public static Country findByName(String name) {
List<Country> results = entityManager
.createNamedQuery("Country.findByName", Country.class)
.setParameter("name", name).getResultList();
return results.isEmpty() ? null : results.get(0);
}
}
getSingleResult throws NonUniqueResultException, if there are multiple rows. It is designed to retrieve single result when there is truly a single result.
The way you did is fine and JPA is designed to handle this properly. At the same time, you cannot compare it against getSingleResult any way, since it won't work.
However, depend on the code you are working on, it is always better to refine the query to return single result, if that's all what you want - then you can just call getSingleResult.
There is an alternative which I would recommend:
Query query = em.createQuery("your query");
List<Element> elementList = query.getResultList();
return CollectionUtils.isEmpty(elementList ) ? null : elementList.get(0);
This safeguards against Null Pointer Exception, guarantees only 1 result is returned.
getSingleResult throws NonUniqueResultException, if there are multiple rows or no any rows . It is designed to retrieve single result when there is truly a single result.
In combination with fetch() the usage of setMaxResults(1) can lead to a partially initialised objects. For example,
CriteriaQuery<Individual> query = cb.createQuery(Individual.class);
Root<Individual> root = query.from(Individual.class);
root.fetch(Individual_.contacts);
query.where(cb.equal(root.get(Individual_.id), id));
Individual i = em.createQuery(query)
.setMaxResults(1) // assertion fails if individual has 2 contacts
.getResultList()
.get(0);
assertEquals(2, i.getContacts().size());
So, I am using getResultList() without limit -- a bit unsatisfying.