I'm switching from deprecated (unfortunately) Hibernate Criteria API to JPA Criteria API. We have a custom Order (from Hibernate) interface implementation to redefine SQL generated for it. The case is quite sophisticated as we need to use a giant SELECT with subqueries. We implemented toSqlString method of the interface to return this huge SQL and we need a way to migrate it to JPA Criteria API.
The question is: is there a way in JPA Criteria API to redefine the SQL generated? Or is there a weird way to use Hibernate Order with JPA Criteria API?
Thank you!
UPDATE Although #Tobias Liefke suggestion is quite interesting, my SQL varies too much to create a function class per SQL. I tried implementing a single function class and passing the SQL there as an argument but that didn't work (the rendered SQL was enclosed in single quotes thus it was sent to the database as parameter and not as part of the generated query)
You can't use SQL fragments in JPQL or criteria queries...
... except when ...
1. Calling a function
JPA and Hibernate allow to use functions in their expressions, for example:
... ORDER BY trim(entity.label) ASC
Resp.
query.orderBy(criteriaBuilder.asc(
criteriaBuilder.function("trim", String.class, root.get(ExampleEntity_.label))));
The problem is, that this is not really the call to the SQL function trim, but the call to a JPA function, which must be registered (Hibernate does this already for the most common SQL functions).
Fortunately you can define your own JPA functions in a DialectResolver:
public class MyDialectResolver implements DialectResolver {
public Dialect resolveDialect(final DialectResolutionInfo info) {
Dialect dialect = StandardDialectResolver.INSTANCE.resolve(info);
dialect.registerFunction("myOrderFunction", ...);
return dialect;
}
}
registerFunction takes two parameters, the first is the name of the function in JPA, the other is the mapping to SQL.
Don't forget to declare your dialect resolver in your persistence.xml:
<persistence-unit name="database">
<properties>
<property name="hibernate.dialect_resolvers"
value="my.package.MyDialectResolver" />
</properties>
</persistence-unit>
You could now create your own function in your SQL server which contains your huge SQL and register that as function:
dialect.registerFunction("myOrderFunction",
new StandardSQLFunction("myOrderFunctionInSQL", StringType.INSTANCE));
Or you could write your own mapping, which includes your huge SQL:
public class MyOrderFunction implements SQLFunction {
public String render((Type firstArgumentType, List arguments,
SessionFactoryImplementor factory) throws QueryException) {
return my_huge_SQL;
}
// ...
}
And register that one:
dialect.registerFunction("myOrderFunction", new MyOrderFunction());
Another advantage of this solution: you could define different SQLs depending on the actual database dialect.
2. Using a formula
You could use an additional attribute for your entity:
#Formula("my huge SQL")
private String orderAttribute;
You could now sort by this attribute:
... ORDER BY entity.orderAttribute ASC
Resp.
query.orderBy(criteriaBuilder.asc(root.get(ExampleEntity_.orderAttribute))));
I only recommend this solution, if you need the result of the huge SQL in your model anyway. Otherwise it will only pollute your entity model and add the SQL to every query of your entity (except you mark it with #Basic(fetch = FetchType.lazy) and use byte code instrumentation).
A similar solution would be to define a #Subselect entity with the huge SQL - with the same drawbacks.
Related
I just got started with Spring JPA and I am reviewing code written by my coworker.
I see that he has been using the following code to find a Login object using username:
public interface LoginDao extends JpaRepository<Login, Long> {
#Query("SELECT u FROM Login u WHERE LOWER(u.username) = LOWER(:username)")
Login findByUsername(#Param("username") String username);
}
Cant he just create a method like this:
#GET
#Path("{username}")
public Login getOne(#PathParam("username") String username) {
Login login = loginDao.findOne(username);
if (login == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
} else {
return login;
}
}
What are the fundamental advantages of using #Query rather than writing a method approach. Or am I plain wrong and both have different purposes.
I personally dont want to write queries inside the code. I think mixing java with sql queries can make code look uglier.
Our stack is java, JPA, Jersey, JAX-RS, Postgreql, Spring Boot, Hibernate
Regards
First, This is not an SQL query, this is a JPQL one. It would be a native SQL query if the nativeQuery attribute was set to true, which is not the case by default.
It is perfectly fine to create queries with JPQL, you will be able to switch from Hibernate to Eclipse Link or another JPA implementation without issues. You should also be able to switch from Postgres to MySQL, SQL Server...
You have to start to worry if your coworker creates queries with #Query(nativeQuery = true, value = "..."), otherwise it looks fine to me.
Second, when you look to your repository declaration, you can see that the ID field for your Login entity is of type Long. That means the loginDao.findOne(..) method wants a Long parameter. Probably a surrogate key which is not username.
This is why your coworker created this new method: to find a Login row by something else than the Long surrogate key. (Here by username which is most likely a business key, but I do not know your application.)
Just for your information: Spring automatically creates queries with the signature of your repository methods (derived queries). For example:
public interface LoginDao extends JpaRepository<Login, Long> {
Login findByUsername(String username);
}
Spring JPA will automatically create a query looking for a field named username and create a WHERE clause with it.
Notice that it is not the same than your coworker query because it will not use the LOWER function.
Some anchors in the JPA documentation about those derived queries:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repository-query-keywords
Another thing about your title:
"Why need #Query when I can write a method in Spring JPA"
Well this #Query writes a method in Spring JPA. :^)
JPARepository has come up with some of the Supported keywords which will write the queries themselves based on your entity.
If we are looking something out of box from what JPARepository provides #Query is useful like - Joining multiple queries, writing native queries etc.
From your code snippet both would do the same
For more info you can refer - https://docs.spring.io/spring-data/jpa/docs/1.4.2.RELEASE/reference/html/jpa.repositories.html
Let's suppose, am having two tables in my database and i have to write a join query using two tables. I mapped one of those tables as entity class in my MVC project, but there is no mapping for the other table as entity.
so when i run hql, will that join work?
if it doesn't, and if its necessary to have a mapping, should i specify the constraints (primary/foreign key) between those entities?
My application just reads the data from tables, hence i don't want to waste much time writing entity classes. Is there any easy approach using hibernate?
About your question: HQL only works with mapped entity, but can return not-mapped object with ResultTranformer, but is not your case. You can create minimal definition of unwanted entity with just relationships and property needed by your hql.
Another way to resolve is create the plain SQL query and return only mapped entity with session.createSQLQuery(yourQuerySQL).addEntity(YourMappedEntity.class).
Hibernate only knows what is there in session factory. If you have not defined some entity Hibernate would never know about is, so there is no question about writting hql involving that entity.
Alternatively you can get a connection from the session and then execute custom sql rather than hql.
To use plain sql you can use something like:
getSession().doWork(new Work() {
#Override
public void execute(Connection connection) throws SQLException {
// TODO Auto-generated method stub
}
})
Maybe I am missing something, but I simply want to (in my java program) get the query string from a javax.persistence.Query object?
The Query object itself doesn't seem to have a method to do that. Also I know that our manager doesn't want us to use Spring framework stuff (for example using their QueryUtils class).
Is there not a way to simply get the query string from javax.persistence.Query object (Again, in a java program) ?!
no problem. hibernate:
query.unwrap(org.hibernate.Query.class).getQueryString()
or eclipselink
query.unwrap(JpaQuery.class).getDatabaseQuery().getJPQLString(); // doesn't work for CriteriaQuery
query.unwrap(JpaQuery.class).getDatabaseQuery().getSQLString();
or open JPA
query.unwrap(org.apache.openjpa.persistence.QueryImpl.class).getQueryString()
...you get the idea....
There is no JPA-standard way, but some implementations have their own methods. For example DataNucleus JPA allows you to do
query.toString();
Look at the docs of your implementation for how they do it. See also this blog entry
http://antoniogoncalves.org/2012/05/24/how-to-get-the-jpqlsql-string-from-a-criteriaquery-in-jpa/
There is no standard way in JPA,
If you are using EclipseLink see,
http://wiki.eclipse.org/EclipseLink/FAQ/JPA#How_to_get_the_SQL_for_a_Query.3F
Edit your persistence properties file if JPA is provided by hibernate. Add the below section -
<property name="hibernate.show_sql" value="true"/>
Using OpenJPA I was able to do this:
OpenJPAQuery<MyEntity> q = (OpenJPAQuery<MyEntity>) query;
String mySQL = q.getQueryString();
The cast to OpenJPAQuery is important because the Query interface does not have the method getQueryString() on it.
The stored parameters are not filled in unfortunately but I'm working on figuring out how to do that myself.
If I were to define some function in the database (perhaps Postgres, or any other database):
create or replace function isValidCookie(ckie);
I would call it from SQL as:
select * from cookietable c where isValidCookie(c.cookie);
How can I call a custom function such as this from Hibernate?
If you want to use your custom function in HQL, you'll need to define it in appropriate Dialect
Take a look at PostgreSQLDialect (or any other, really) source, and you'll see a bunch of registerFunction() calls. You'll need to add one more :-) - for your own custom function.
You'll then have to specify your own dialect in Hibernate configuration.
As of Hibernate 5, if you don't want to depend on or customize the dialect, you can define a MetadataBuilderInitializer. For example, to use MySQL DATE_ADD with an INTERVAL from HQL, you can define a custom function called date_add_interval:
public class DateAddIntervalMetadataBuilderInitializer
implements MetadataBuilderInitializer {
#Override
public void contribute(MetadataBuilder metadataBuilder,
StandardServiceRegistry serviceRegistry) {
metadataBuilder.applySqlFunction("date_add_interval",
new SQLFunctionTemplate(DateType.INSTANCE,
"DATE_ADD(?1, INTERVAL ?2 ?3)"));
}
}
You would also need to put the name of the class in a JAR resource file called META-INF/services/org.hibernate.boot.spi.MetadataBuilderInitializer.
This approach is particularly useful when using Hibernate via a framework such as JPA and/or Spring, where the configuration is performed implicitly by the framework.
I'm running an application in JBoss and Using JPA.
For a report I need a group by query which I expect to return a result set with the following structure example:
count,idA,idB
I did not find a way to implement this in JPA.
What are my best options for implementing this considering I'm developing in JBoss 5, EJB3
You can use a custom holder class and use the NEW keyword in your query:
SELECT NEW com.mycompany.myapp.MyClass(count, idA, idB)
FROM ...
WHERE ...
Of course, MyClass needs to have the proper constructor defined.
In the case of Native queries, you can create a dummy entity into which the result set can be mapped to (Native query will not be mapped into an Object unless its a real managed entity).
The entity is a dummy as it will not be persisted and it only used for mapping the result set of the native query into this entity.