Native SQL select query using Spring JPA Data Annotation #Query, to cover non-empty, empty and null values at the same time - sql

I have something like this in my repository class in a Spring project:
#Query(value = "SELECT * FROM accounts WHERE (first_name LIKE %:firstName% AND last_name LIKE %:lastName%)", nativeQuery = true)
public List<Account> searchByFirstnameAndLastname(#Param("firstName")String firstName,#Param("lastName")String lastName);
I want it to return everything if the parameters are not provided. Even the ones with null firstname/lastname. And it ignores the null values because of the wildcard used. Since null is different from ''.
I was thinking of an if-statement structure and building the query in runtime based on the provided parameters and then setting the value for the #Query annotation.
I tried generating the where clause and passing it as a parameter but it didn't work. I guess the way Spring Data JPA processes the value of this annotation caused it.
Any idea what is the best solution to this?

Have you tried containing keyword like below :
List<Account> findByFirstnameContainingAndLastNameContaining(String firstName,String lastName);
Docs: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/

You cannot go far with #Query
For dynamic queries(with many optional filters), the way to go is using Criteria API or JPQL. I suggest the Criteria API as it is object oriented and suitable for dynamic queries.

I would suggest to use QueryDSL. It is mentioned in the docs JB Nizet already posted. There is is nice but quite old tutorial here.
With QueryDSL it is very convenient to create your queries dynamically and it is easier to understand than the JPA Criteria API.
The only difficulty in using QueryDSL is the need to automatically create the query objects from your entities but this can be automated by using maven.

There are two ways to handle your situation.
The hard way is using RepositoryFactoryBean as follow
create a custom RepositoryFactoryBean
public class DaoRepositoryFactoryBean, T, I extends Serializable>
extends JpaRepositoryFactoryBean
{
#Override
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager)
{
return new DaoRepositoryFactory(entityManager);
}
private static class DaoRepositoryFactory<E extends AbstractEntity, I extends Serializable> extends JpaRepositoryFactory
{
private EntityManager entityManager;
public DaoRepositoryFactory(EntityManager entityManager)
{
super(entityManager);
this.entityManager = entityManager;
}
#Override
protected Object getTargetRepository(RepositoryMetadata metadata)
{
return new DaoImpl<E>((Class<E>) metadata.getDomainType(), entityManager);
}
#Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata)
{
return Dao.class;
}
}
}
create Dao interface
#NoRepositoryBean
public interface Dao extends JpaRepository
{
List findByParamsOrAllWhenEmpty();
}
create your implementation
#Transactional(readOnly = true)
public class DaoImpl extends SimpleJpaRepository implements Dao
{
private EntityManager entityManager;
public DaoImpl(Class<E> domainClass, EntityManager em)
{
super(domainClass, em);
this.entityManager = em;
this.domainClass = domainClass;
}
List<E> findByParamsOrAllWhenEmpty()
{
//implement your custom query logic
//scan your domainClass methods for Query anotations and do the rest
}
}
introduce it to Spring Jpa Data
jpa:repositories
base-package=""
query-lookup-strategy="" factory-class="com.core.dao.DaoRepositoryFactoryBean"
The easy way using Custom Impl which in this case you can't use #Query annotation.

"coalesce" on MySQL or "IsNull" on SQL Server is my preferred solution. They return back the first non-null value of a list and you may use it as a trick to deal with an empty string just like a null:
#Query(value = "SELECT * FROM accounts WHERE (COALESCE(first_name,'') LIKE %:firstName% AND COALESCE(last_name,'') LIKE %:lastName%)", nativeQuery = true)
public List<Account> searchByFirstnameAndLastname(#Param("firstName")String firstName,#Param("lastName")String lastName);
Thanks to the questioner and the answerer :D at this page:
like '%' does not accept NULL value

Related

Spring JPA - Define SQL function for usage in custom query

Let's assume we've an Spring JPA based repository
#Repository
public interface MyClassRepository extends CrudRepository<MyClass, Long> {
}
with the entity
#Entity
#Table(name = "classes")
public class MyClass {
private double latitude;
private double longitude;
// Getter + Setter
}
I tried to find all stored records within a given instance around a referenced object. For calculating the distance between first object's position and second object's position I thought about a custom #Query in combination with the Haversine Formula.
#Query("SELECT c FROM MyClass c WHERE :distance >= haversine(c, :origin)")
public List<Passenger> findAll(#Param("origin") MyClass origin, #Param("distance") double distance);
Is it possible to define the SQL function haversine() in an additional query, e.g. #Query("CREATE FUNCTION haversine(...) ..."), because I've to use this function in multiple custom queries and I try to prevent code repetition. Any ideas about this?
You can add your Sql function through for example db migrations, as usual, and extend org.hibernate.dialect (see how it made for Postgis for example) and don't forget specify the dialect for your data source
public class YourDialect extends PostgreSQL95Dialect {
public YourDialect() {
super();
registerFunction("haversine", new StandardSQLFunction("haversine", StandardBasicTypes.DOUBLE));
}
}
You must create a function in your DB. Functions are "static" elements - they must be present in DB on the time of the query execution.
Also, you have some errors in your method.
In the query you select from the table "MyClass", but your method returns "List".

How to construct Spring Data repository query two Parameters with IN and same list?

This is my Entity:
#Data
#Entity
#IdClass(EtlJobExecutionTriggersId.class)
#Table(name = "ETL_JOB_EXEC_TRIGGERS")
public class EtlJobExecutionTriggers {
#Id private Long jobExecIdUs;
#Id private Long jobExecIdDs;
private LocalDate cobDate;
}
And here is the Composite Primary Key Class:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Embeddable
#EqualsAndHashCode
public class EtlJobExecutionTriggersId implements Serializable {
private Long jobExecIdUs;
private Long jobExecIdDs;
}
And here is my Spring Repo:
public interface EtlJobExecTriggersRepo extends JpaRepository<EtlJobExecutionTriggers, EtlJobExecutionTriggersId> {
String SQL_ = "select o from EtlJobExecutionTriggers o where o.jobExecIdDs in (:ids) or o.jobExecIdUs in (:ids) order by o.jobExecIdUs, o.jobExecIdDs";
#Query(EtlJobExecTriggersRepo.SQL_)
List<EtlJobExecutionTriggers> findAllByJobExecIdDsInAndJobExecIdUsInSQL(#Param("ids") List<Long> jobExecIdList);
}
The #Query works as expected, but I would like not to write any SQL and instead express the same Query using only Spring Data repository query.
I have tried the following (and other variants)
List<EtlJobExecutionTriggers> findAllByJobExecIdDsInAndJobExecIdUsInOrderByJobExecIdUsJobExecIdDs(List<Long> jobExecIdDsList)
But i keep getting errors when Booting. The above interface method yields the following exception for the OrderBy part:
org.springframework.data.mapping.PropertyReferenceException: No property jobExecIdDs found for type Long! Traversed path: EtlJobExecutionTriggers.jobExecIdUs.
So what am I doing wrong here? or is it not possible to express this particular query via Spring Data Repo query?
As I have written in my comment I fixed the Order by issue, but I am still unable to make it work with only one method parameter (List jobExecIdList)
When I make it with two (List jobExecIdDsList, List jobExecIdUsList)
Like this:
List<EtlJobExecutionTriggers> findAllByJobExecIdDsInAndJobExecIdUsInOrderByJobExecIdUsAscJobExecIdDsAsc(List<Long> jobExecIdDsList, List<Long> jobExecIdUsList);
it actually works but I can't get to work with only one list, as in the #Query("....") method
I think using your own custom id generator conflicts with Spring Data Repository query.
// You shoud have two parameters in your method as below.
List findAllByJobExecIdDsInAndJobExecIdUsInOrderByJobExecIdUsJobExecIdDs(List jobExecIdDsList,List jobExecIdUsList);

JPQL and Entities (java.lang.IllegalArgumentException)

I am creating a web application that needs a table of notifications to display to various users. For some reason the JPQL query I've written is throwing a java.lang.IllegalArgumentException. My application already has a transaction table, structured and queried using an identical approach (afaik), which works perfectly.
I have been shuffling the code around, changing variable names and character cases for hours trying to get this to work but I'm still getting the exception every time. Does anyone have any idea where I'm going wrong?
My NotificationEntity is as follows:
#Table(name="notificationentity")
#NamedQuery(name="fetch_user_notifications", query="SELECT n FROM NotificationEntity n WHERE n.notificationrecipient=:username")
#Entity
public class NotificationEntity implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
String notificationSender;
#NotNull
String notificationrecipient;
... other fields + methods
}
The JPQL query is called from an EJB (NotificationStorageServiceBean) that implements an interface (NotificationStorageService) with the following method:
#Override
public synchronized List<NotificationEntity> getUserNotificationList(String username)
{
List notifications;
notifications = em.createNamedQuery("fetch_user_notifications").setParameter("notificationrecipient", username).getResultList();
return notifications;
}
And the EJB method is called from a CDI backing bean for my .xhtml UI, using the FacesContext's currently logged in user to provide the argument for these methods.
#EJB
NotificationStorageService notificationStore;
public List<NotificationEntity> getUserNotificationList()
{
return notificationStore.getUserNotificationList(this.currentUser);
}
The exact error I get is:
java.lang.IllegalArgumentException: You have attempted to set a parameter value using a name of notificationrecipient that does not exist in the query string SELECT n FROM NotificationEntity n WHERE n.notificationrecipient=:username.
The parameter name in a JPQL query starts with a colon. So just use
setParameter("username", username)

How would I alter the SQL that Linq-to-Nhibernate generates for specific columns?

To take advantage of Full text indexing on MariaDB 10, I need to use this new "MATCH AGAINST" syntax in the sql string.
http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html#function_match
I think it would be really cool if, for certain columns only, I could override linq-to-nhibernate to change the sql it generates when I use
.Where(x => FullTextIndexedStringProperty.Contains("Some word")).ToList().
Who can give me some general directions on how to get started?
This will get you a very simple MATCH ... AGAINST clause. If you want to get more complex (more arguments, specifying the search modifier), you'll have to make some bigger changes. Hopefully this will get you started though:
Create a new dialect and register a simple MATCH (...) AGAINST (...) function:
public class CustomMySQLDialect : MySQLDialect
{
public CustomMySQLDialect()
{
this.RegisterFunction(
"matchagainst",
new SQLFunctionTemplate(
NHibernateUtil.Boolean,
"match (?1) against (?2)"));
}
}
Create a static extension method on string that you'll use in LINQ statements:
public static class LinqExtensions
{
public static bool MatchAgainst(this string source, string against)
{
throw new NotImplementedException();
}
}
Create a new LINQ to HQL generator class that associates the method with the SQL function we registered in the custom dialect:
public class MatchAgainstGenerator : BaseHqlGeneratorForMethod
{
public MatchAgainstGenerator()
{
this.SupportedMethods = new[]
{
ReflectionHelper.GetMethod(() => LinqExtensions.MatchAgainst(null, null))
};
}
public override HqlTreeNode BuildHql(
MethodInfo method,
System.Linq.Expressions.Expression targetObject,
ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
HqlTreeBuilder treeBuilder,
IHqlExpressionVisitor visitor)
{
return treeBuilder.BooleanMethodCall(
"matchagainst",
arguments.Select(visitor.Visit).Cast<HqlExpression>());
}
}
Create a custom LinqToHqlGeneratorsRegistry:
public class MyLinqToHqlRegistry : DefaultLinqToHqlGeneratorsRegistry
{
public MyLinqToHqlRegistry()
{
var generator = new MatchAgainstGenerator();
RegisterGenerator(typeof(LinqExtensions).GetMethod("MatchAgainst"), generator);
}
}
Use your custom dialect, and Linq to HQL registry either in your cfg.xml file or in code:
var cfg = new Configuration()
.DataBaseIntegration(db =>
{
db.Dialect<CustomMySQLDialect>();
})
.LinqToHqlGeneratorsRegistry<MyLinqToHqlRegistry>();
Finally, use your extension method in a LINQ-to-NHibernate query:
session.Query<Article>()
.Where(a => a.Body.MatchAgainst("configured"))
.ToList()
.Dump();
This will generate SQL that looks like this:
select
userquery_0_.Id as Id51_,
userquery_0_.Title as Title51_,
userquery_0_.Body as Body51_
from
articles userquery_0_
where
match (userquery_0_.Body) against ('configured');
Again, this won't help if you have more complicated requirements. But hopefully this is at least a good starting point.
In case anyone is curious about how to make this support more complex scenarios, here are the problems I think you'd run into:
Separating the arguments to MATCH from those to AGAINST.
Registering a custom SQL function with NHibernate that can take an arbitrary number of arguments in different places
Creating the correct HQL even after solving the two issues above.

Hibernate/Spring taking out class mapping. About reflection

Im trying to write an aplication with uses hibernate to write to database, however in some actions i have to use JDBC on data in tables made by HB.
JDBS is requred to give administrator ability to create SQL queries with will return statistic info about data in database like number of processed document of specified type, numbers of success/failed log in attempts or total value of products in orders.
To do that i've done an from that allows to create class that has override toString() with return nice sql query string.
All works but now im trying to make administrator live easier by hiving him an ability to choose of table/column names. And here is an problem, because they are created by hibernate. some by #column annotation other by field name.
How can i check how field mapping?
I know its all about reflections but didnt do much of that in java yet.
example
#Entity
#Table(name= "my_table_name" )
public class TableOFSomething implements Serializable{
//This field isn't mapped into database and info about it is not requred.
//In fact, info about it may cause an error.
private static final long serialVersionUID = 7L;
#Id
#Column(name="id")
private String id;
private String fieldOne;
#Column(name="field_two")
private String fieldTwo;
#Column(name="renamed_just_for_fun")
private int Number;
//code with getters & setters
}
How to write methods that will have definition like
public <T> String tableName(Class<T> Target); //returns name of table in database
public <T> ArrayList<String> tabelFields(Class<T> Target); //returns name of fields in database
Hibernate has API - getClassMetadata that can explore the mapping. The API might change and is now located in another place , but i will use it and not in reflection for this.
look on this post for more details:
Get the table name from the model in Hibernate
if you want reflection , so use this link
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import javax.persistence.Column;
import javax.persistence.Table;
import odi.beans.statistic.QueryBean;
public class ReflectionTest {
public static void main(String[] args) {
ReflectionTest test=new ReflectionTest();
System.out.println("Table name of "+QueryBean.class.getName()+" is "+test.getTableName(QueryBean.class));
System.out.println("Column names in this table are:");
for(String n: test.getColumnNames(QueryBean.class)){
System.out.println("\t"+n);
}
System.out.println("Good bye ;)");
}
public <T> ArrayList<String> getColumnNames(Class<T> target) {
ArrayList<String> ret=new ArrayList<>();
Field[] fields = target.getDeclaredFields();
String fieldName =null;
for (Field f : fields) {
//jump to next if if field is static
if (Modifier.isStatic(f.getModifiers()))
continue;
if (f.isAnnotationPresent(Column.class)) {
Column a = f.getAnnotation(Column.class);
fieldName = a.name();
} else {
fieldName = f.getName();
}
ret.add(fieldName);
}
return ret;
}
public <T> String getTableName(Class<T> target){
String ret=target.getSimpleName();
if (target.isAnnotationPresent(Table.class))
{
Table t=target.getAnnotation(Table.class);
ret=t.name();
}
return ret;
}
}
Is it cover all possibilities?
I know now that Hibernate way would be easier, but this is also about learning of very useful reflection mechanism :)
EDIT:
Important question:
Will this work only on annotations or also on xml mapping?