How to write a custom #Query in Spring Data JDBC? - java

In Spring Data JDBC examples, how do I write a simple query in #Query annotation?
e.g. In LegoSet Repository, how do I add a simple findByName query?
When I tried
#Query("select * from lego_set where name = :name")
List<LegoSet> findByName(#Param("name") String name);
it throws following error:
org.springframework.data.mapping.MappingException: Could not read property #org.springframework.data.annotation.Id() #org.springframework.data.relational.core.mapping.Column(value=handbuch_id, keyColumn=)private java.lang.Long example.springdata.jdbc.basics.aggregate.Manual.id from result set!
...
> Caused by: org.hsqldb.HsqlException: Column not found: manual_handbuch_idat org.hsqldb.error.Error.error(Unknown Source) at org.hsqldb.error.Error.error(Unknown Source) `
Also, the reference document seems to be copied from some generic spring data document since it mentioned derived query which doesn't exist in spring data jdbc yet.

Just as a completion of #jens-schauder's answer:
The query should be:
#Query("SELECT ls.id, ls.name, ls.min_age, ls.max_age, " +
"h.handbuch_id AS manual_handbuch_id, h.author AS manual_author, h.text AS manual_text " +
"FROM lego_set ls JOIN handbuch h ON ls.id = h.handbuch_id " +
"WHERE name = :name")
List<LegoSet> findByName(#Param("name") String name);
Using this method the following test passes:
#Test
public void so_52978700() {
// prepare
LegoSet cars = createLegoSet("Small Car - 01", 5, 10);
cars.setManual(new Manual("Just put all the pieces together in the right order", "Jens Schauder"));
repository.save(cars);
// execute
List<LegoSet> actual = repository.findByName("Small Car - 01");
// verify
assertThat(actual).hasSize(1);
assertThat(actual.get(0).getName()).isEqualTo("Small Car - 01");
assertThat(actual.get(0).getManual().getText()).isEqualTo("Just put all the pieces together in the right order");
}

The LegoSet entity has a 1:1 relationship to a Manual.
Spring Data JDBC selects such a construct using a join and expects the representative columns in the ResultSet.
Note that it expects the columns representing the Manual entity itself plus the one forming the back-reference to the LegoSet.
Also, all column names are to be prefixed by the property name +_, i.e. manual_ in this case.
The error message actually tells you about the missing column (modulo a missing space): Column not found: manual_handbuch_id.
Alternatively, you can also provide your own RowMapper
Regarding the documentation:
You are kind of right.
The documentation of (almost) all Spring Data modules includes a generic part which easily leads to confusion. There is a ticket for comming up with a better solution.

I think you are trying to execute a native query.So,try as below
#Query( value = "SELECT * FROM lego_set ls where ls.name = :name",
nativeQuery = true)
List<LegoSet> findByName(#Param("name") String name);
This should work.

Related

jpa order records descending and select first record

I have a spring boot app connected to oracle DB.
I am trying to order a list of records and select the top most record.
I wrote a JPA query as below but it fails.
#Query("SELECT id FROM UploadedFile uploadedFile "
+ "WHERE uploadedFile.p_file_type = 'branch' "
+ "and p_file_status='Processed' "
+ "and p_is_file_process_with_error = 0 "
+ "order by c_created_date desc "
+ "FETCH FIRST 1 rows only ")
public String findLatestBranchCodeFile();
The error received was
creating bean with name 'uploadedFileRepo': Invocation of init method
failed; nested exception is java.lang.IllegalArgumentException:
Validation failed for query for method public abstract
java.lang.String
com.rhb.pintas.repo.UploadedFileRepo.findLatestBranchCodeFile()!
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token:
FETCH near line 1, column 204 [SELECT id FROM
com.rhb.pintas.entities.UploadedFile uploadedFile WHERE
uploadedFile.p_file_type = 'branch' and p_file_status='Processed' and
p_is_file_process_with_error = 0 order by c_created_date desc FETCH
FIRST 1 rows only ] -> [Help 1]
The issue seems to be with fetch,not sure whats wrong.
It seems you are mixing HQL and native query dialects:
If this will be a naviveQuery (like most of the columns would mention), then replace the entity name with table name and add nativeQuery option. And because You are using only a single table, You can skip the alias name:
#Query("SELECT id FROM uploaded_file "
+ "WHERE p_file_type = 'branch' and p_file_status='Processed' and "
+ "p_is_file_process_with_error = 0 "
+ "order by c_created_date desc "
+ "FETCH FIRST 1 rows only ", nativeQuery = true)
public String findLatestBranchCodeFile();
If You want to keep it as a HQL, then replace all column names with entity property names, like p_file_type > fileType (I guess column names). Secondly You will need to pass a Pageable parameter, to replace Your 'Fetch first' statement.
You can find more materials here:
Bealdung
NativeQ
StackOverflow
You are trying to execute SQL query, in this case you need to add nativeQuery=true attribute to #Query annotation
UPD.
got confused because FETCH FIRST - is a SQL syntax, for JPA way please check another solution - it is possible to return list with at most one element.
I guess, you can try passing pagable to limit result set size and unlimit your query:
public String findLatestBranchCodeFile(Pageable pageable); // pass new PageRequest(0,1)

spring-data-jdbc: query containing entity with a 1-n relation

How can I write Queries for entities containing a 1-n reference?
Based on the spring-data-jdbc examples I will explain it with the following unit-test:
#Test
public void customQuery_ReferenceMultipleInstances() {
// prepare
LegoSet smallCar = createLegoSet("Small Car 01", 5, 12);
smallCar.setManual(new Manual("Just put all the pieces together in the right order", "Jens Schauder"));
smallCar.addModel("suv", "SUV with sliding doors.");
smallCar.addModel("roadster", "Slick red roadster.");
repository.save(smallCar);
// execute
List<LegoSet> actual = repository.findByName("Small Car 01");
Iterable<LegoSet> compare = repository.findAll();
// verify
assertThat(actual).as("same number of lego sets").hasSize(Lists.newArrayList(compare).size());
assertThat(actual.get(0).getModels()).as("same number of models").hasSize(Lists.newArrayList(compare).get(0).getModels().size());
assertThat(actual.get(0).getModels().get(0)).as("model must not be null").isNotNull();
assertThat(actual.get(0).getModels().get(0).getName()).as("model must have a name").isNotEmpty();
}
This models a LegoSet referencing 2 Models. The repository.findByName() is annotated with a custom query; the repository.findAll() is the standard spring-boot-data method of the CrudRepository (just as reference).
The custom query in version 1:
#Query("SELECT ls.id, ls.name, ls.min_age, ls.max_age, " +
"h.handbuch_id AS manual_handbuch_id, h.author AS manual_author, h.text AS manual_text " +
"FROM lego_set ls JOIN handbuch h ON ls.id = h.handbuch_id " +
"WHERE name = :name")
List<LegoSet> findByName(#Param("name") String name);
In this version the test fails w/
java.lang.AssertionError: [model must not be null]
Expecting actual not to be null
Okay, after that I add another JOIN to model:
#Query("SELECT ls.id, ls.name, ls.min_age, ls.max_age, " +
"h.handbuch_id AS manual_handbuch_id, h.author AS manual_author, h.text AS manual_text, " +
"m.* " +
"FROM lego_set ls JOIN handbuch h ON ls.id = h.handbuch_id " +
"JOIN model m ON ls.id = m.lego_set " +
"WHERE name = :name")
List<LegoSet> findByName(#Param("name") String name);
Now the test fails w/
java.lang.AssertionError: [same number of lego sets]
Expected size:<1> but was:<2> in:
<[LegoSet(id=1, name=Small Car 01, minimumAge=P5Y, maximumAge=P12Y, manual=Manual(id=1, author=Jens Schauder , text=Just put all the pieces together in the right order), models={suv=Model(name=suv, description=SUV with sliding doors.), roadster=Model(name=roadster, description=Slick red roadster.)}),
LegoSet(id=1, name=Small Car 01, minimumAge=P5Y, maximumAge=P12Y, manual=Manual(id=1, author=Jens Schauder
So how do I have to write that query correctly?
The first query is actually the correct one and it is working fine.
The problem is in your test. models is a Map, but you treat it like a List which compiles because a list index is also a valid map key.
If you change the last two assertions in your test like this they will succeed:
assertThat(actual.get(0).getModels().get("suv")).as("model must not be null").isNotNull();
assertThat(actual.get(0).getModels().get("suv").getName()).as("model must have a name").isNotEmpty();
// ----------------------------------------^
An alternative is to use the second query but with a custom ResultSetExtractor to collect multiple rows for the multiple models into a single LegoSet.
I'm in a similar situation, but i don't see what is different in my situation.
I have three models:
Client: id, name..
Project: id, name, clientId, List
ProjectMember
Here is my had coded query
#Query("""
select
project.id,
project.legacy_id,
project.code,
project.client_id,
project.is_archived,
project.name,
project.budget_type,
project.hours_budget_max,
project.money_budget_max
from project
join client on client.id = project.client_id
where client.name ilike '%:name%'
""")
List<Project> findByClientNameContainingIgnoreCase(final String name);
As you can see i'm looking for project where client name (using a join) match a pattern.
Executing the request by hand give me the expected results. But going through spring data jdbc give me no results

Hibernate NamedNativeQuery with PostgreSQL returns 'relation does not exist'

I created a bean class to fit an entry on list in my webapp, because this bean should use data from many db tables I decided to use #NamedNativeQuery annotation, so I can fetch excactly the data is needed (for decrease list loading time).
When I call:
getSession().createCriteria(ProjectListEntry.class).list();
I get the following exception:
org.hibernate.exception.SQLGrammarException: ERROR: relation "projectlistentry" does not exist
Position: 29
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:122)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:125)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:110)
at org.hibernate.engine.jdbc.internal.proxy.AbstractStatementProxyHandler.continueInvocation(AbstractStatementProxyHandler.java:129)
at org.hibernate.engine.jdbc.internal.proxy.AbstractProxyHandler.invoke(AbstractProxyHandler.java:81)
at com.sun.proxy.$Proxy123.executeQuery(Unknown Source)
at org.hibernate.loader.Loader.getResultSet(Loader.java:1897)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1698)
at org.hibernate.loader.Loader.doQuery(Loader.java:832)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:293)
at org.hibernate.loader.Loader.doList(Loader.java:2382)
at org.hibernate.loader.Loader.doList(Loader.java:2368)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2198)
at org.hibernate.loader.Loader.list(Loader.java:2193)
at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:122)
at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1622)
at org.hibernate.internal.CriteriaImpl.__xr__list(CriteriaImpl.java:374)
at org.hibernate.internal.CriteriaImpl.list(CriteriaImpl.java)
at org.hibernate.internal.CriteriaImpl.__xr__uniqueResult(CriteriaImpl.java:396)
at org.hibernate.internal.CriteriaImpl.uniqueResult(CriteriaImpl.java)
...
Of course I don't have projectlistentry table in my database, because I want to create this entity from the query.
My ProjectListEntity class looks like this:
#Entity
#SqlResultSetMapping(
name = "compositeKey",
entities = #EntityResult(
entityClass = <path>.ProjectListEntry.class,
fields = {
#FieldResult(name = "id", column = "p_id"),
#FieldResult(name = "title", column = "p_title"),
...
}
))
#NamedNativeQuery(
name = "fetchProjectListEntry",
query = "SELECT p.id AS p_id, " +
"p.title AS p_title, " +
... many fields and many joins here ...,
resultSetMapping = "compositeKey")
public class ProjectListEntry {
#Id
private Long id;
private String title;
...
}
Query in the query parameter when copied to pgadmin is working correctly, showing a list of all data I want to put in this class instances.
I must use this class with Criteria API, so
getSession().createCriteria(ProjectListEntry.class).list();
must work properly, giving me list of results exactly the same as query in pgadmin.
I am also open to suggestions which, would lead me to solve this problem even not using #NamedNativeQuery annotation. If there is a need i can provide some more informations.
Try to create view in database with your query and then map it to your entity.
This solution is not perfect because if you want to change your bean you must change view too.
This method is not using #NamedNativeQuery annotation but probably will work.

Ebean query using setDistinct() does not work

I'm using an ebean query in the play! framework to find a list of records based on a distinct column. It seems like a pretty simple query but the problem is the ebean method setDistinct(true) isn't actually setting the query to distinct.
My query is:
List<Song> allSongs = Song.find.select("artistName").setDistinct(true).findList();
In my results I get duplicate artist names.
From what I've seen I believe this is the correct syntax but I could be wrong. I'd appreciate any help. Thank you.
I just faced the same issue out of the blue and can not figure it out. As hfs said its been fixed in a later version but if you are stuck for a while you can use
findSet()
So in your example use
List<Song> allSongs = Song.find.select("artistName").setDistinct(true).findSet();
According to issue #158: Add support for using setDistinct (by excluding id property from generated sql) on the Ebean bug tracker, the problem is that an ID column is added to the beginning of the select query implicitly. That makes the distinct keyword act on the ID column, which will always be distinct.
This is supposed to be fixed in Ebean 4.1.2.
As an alternative you can use a native SQL query (SqlQuery).
The mechanism is described here:
https://ebean-orm.github.io/apidocs/com/avaje/ebean/SqlQuery.html
This is from the documentation:
public interface SqlQuery
extends Serializable
Query object for performing native SQL queries that return SqlRow's.
Firstly note that you can use your own sql queries with entity beans by using the SqlSelect annotation. This should be your first approach when wanting to use your own SQL queries.
If ORM Mapping is too tight and constraining for your problem then SqlQuery could be a good approach.
The returned SqlRow objects are similar to a LinkedHashMap with some type conversion support added.
// its typically a good idea to use a named query
// and put the sql in the orm.xml instead of in your code
String sql = "select id, name from customer where name like :name and status_code = :status";
SqlQuery sqlQuery = Ebean.createSqlQuery(sql);
sqlQuery.setParameter("name", "Acme%");
sqlQuery.setParameter("status", "ACTIVE");
// execute the query returning a List of MapBean objects
List<SqlRow> list = sqlQuery.findList();
i have a solution for it:-
RawSql rawSql = RawSqlBuilder
.parse("SELECT distinct CASE WHEN PARENT_EQUIPMENT_NUMBER IS NULL THEN EQUIPMENT_NUMBER ELSE PARENT_EQUIPMENT_NUMBER END AS PARENT_EQUIPMENT_NUMBER " +
"FROM TOOLS_DETAILS").create();
Query<ToolsDetail> query = Ebean.find(ToolsDetail.class);
ExpressionList<ToolsDetail> expressionList = query.setRawSql(rawSql).where();//ToolsDetail.find.where();
if (StringUtils.isNotBlank(sortBy)) {
if (StringUtils.isNotBlank(sortMode) && sortMode.equals("descending")) {
expressionList.setOrderBy("LPAD("+sortBy+", 20) "+"desc");
//expressionList.orderBy().asc(sortBy);
}else if (StringUtils.isNotBlank(sortMode) && sortMode.equals("ascending")) {
expressionList.setOrderBy("LPAD("+sortBy+", 20) "+"asc");
// expressionList.orderBy().asc(sortBy);
} else {
expressionList.setOrderBy("LPAD("+sortBy+", 20) "+"desc");
}
}
if (StringUtils.isNotBlank(fullTextSearch)) {
fullTextSearch = fullTextSearch.replaceAll("\\*","%");
expressionList.disjunction()
.ilike("customerSerialNumber", fullTextSearch)
.ilike("organizationalReference", fullTextSearch)
.ilike("costCentre", fullTextSearch)
.ilike("inventoryKey", fullTextSearch)
.ilike("toolType", fullTextSearch);
}
//add filters for date range
String fromContractStartdate = Controller.request().getQueryString("fm_contract_start_date_from");
String toContractStartdate = Controller.request().getQueryString("fm_contract_start_date_to");
String fromContractEndtdate = Controller.request().getQueryString("fm_contract_end_date_from");
String toContractEnddate = Controller.request().getQueryString("fm_contract_end_date_to");
if(StringUtils.isNotBlank(fromContractStartdate) && StringUtils.isNotBlank(toContractStartdate))
{
Date fromSqlStartDate=new Date(AppUtils.convertStringToDate(fromContractStartdate).getTime());
Date toSqlStartDate=new Date(AppUtils.convertStringToDate(toContractStartdate).getTime());
expressionList.between("fmContractStartDate",fromSqlStartDate,toSqlStartDate);
}if(StringUtils.isNotBlank(fromContractEndtdate) && StringUtils.isNotBlank(toContractEnddate))
{
Date fromSqlEndDate=new Date(AppUtils.convertStringToDate(fromContractEndtdate).getTime());
Date toSqlEndDate=new Date(AppUtils.convertStringToDate(toContractEnddate).getTime());
expressionList.between("fmContractEndDate",fromSqlEndDate,toSqlEndDate);
}
PagedList pagedList = ToolsQueryFilter.getFilter().applyFilters(expressionList).findPagedList(pageNo-1, pageSize);
ToolsListCount toolsListCount = new ToolsListCount();
toolsListCount.setList(pagedList.getList());
toolsListCount.setCount(pagedList.getTotalRowCount());
return toolsListCount;

Why EclipseLink Query cache only works when I use query.getSingleResult()?

My entity has a named query which looks like this:
#NamedQuery(name = "Person.find", query = "select p from Organization p where p.name=:NAME")
In my code I want to set the query cache hint:
query.setHint("eclipselink.cache-usage", "CheckCacheThenDatabase");
If I try to get whole result list:
List<Person> result = query.getResultList();
EclipseLink throws an exception:
Exception [EclipseLink-6124] (Eclipse Persistence Services - 1.0.1 (Build 20080905)): org.eclipse.persistence.exceptions.QueryException
Exception Description: Required query of class org.eclipse.persistence.queries.ReadAllQuery, found class org.eclipse.persistence.queries.ReadObjectQuery
If I try to get only a single result, it works:
Person person = query.getSingleResult();
If I remove the query hint, then getResultList() works also.
I don't understand the exception - isn't it saying that it is specifically expecting getResultList()?? What am I doing wrong?
EclipseLink documentation says:
"EclipseLink does not support the cache usage for native queries or queries that have complex result sets such as returning data or multiple objects."
Also the documentation says:
"CheckCacheThenDatabase – You can configure any read-object query to check the cache completely before you resort to accessing the database."
So the behaviour seems to be ok, I just found the exception misleading.
EDIT: Try something like this in the entity definition, that should be enough: (Coded on the web page so there can be errors)
Entity
#Cache(expiry = 3600000, // 1 hour size = 10000)
#NamedQueries({
#NamedQuery(name = "Person.findByPK", query = "select p from Person p " +
"where p.name=:NAME",
hints = {
#QueryHint(name = QueryHints.CACHE_USAGE,
value = CacheUsage.CheckCacheThenDatabase),
#QueryHint(name = QueryHints.QUERY_TYPE, value = QueryType.ReadObject)
}
})

Categories