Spring Data JDBC not generating derived Query - spring-data-jdbc

I am using Spring Data JDBC (v1.1.1) automatically pulled in by SpringBoot 2.2.1.RELEASE. For the repository below, the method should automatically derive the Query at start-up time.
interface AccountRepository extends CrudRepository<Account, Long> {
long countByLastName(String lastName);
...
Instead I get this..
Caused by: java.lang.IllegalStateException: No query specified on countByLastName
at org.springframework.data.jdbc.repository.support.JdbcRepositoryQuery.determineQuery(JdbcRepositoryQuery.java:200) ~[spring-data-jdbc-1.1.1.RELEASE.jar:1.1.1.RELEASE]
My entity looks like..
public class Account {
#Id
private Long id;
private final String firstName;
private final String lastName;
Any idea why I am getting this issue?

Support for query derivation for Spring Data JDBC came with version 2.0.0.
Note that query derivation currently only supports properties which get stored in the table of the aggregate root for filtering and sorting.

Related

Not able to search data in redis cache using spring crud repository by passing list of values for a property of the model saved in cache

We have model class saved in Redis as mentioned below:-
#Data
#NoArgsConstructor
#AllArgsConstructor
#RedisHash("book")
public class Book implements Serializable {
private static final long serialVersionUID = 2208852329346517265L;
#Id
private Integer bookID;
#Indexed
private String title;
#Indexed
private String authors;
private String averageRating;
private String isbn;
private String languageCode;
private String ratingsCount;
private BigDecimal price;
}
We have title and authors as our indexed property.
Now we wanted to search all the records from Redis by passing title and a list of authors using the spring crud repository as mentioned below.
public interface BookSpringRepository extends CrudRepository<Book, String> {
List<Book> findAllByTitleAndAuthors(String title, List<String> authors);
}
Service layer:-
#Override
public Optional<List<Book>> searchBooksByTitleAndAuthorNames(String title, List<String>
autherNames) {
return Optional.ofNullable(bookSpringRepository.findAllByTitleAndAuthors(title,
autherNames));
}
Here we are getting below exception
Unable to fetch data from Spring data Redis cache using List of Integer or
String.
Getting error while fetching - "Resolved
[org.springframework.core.convert.ConversionFailedException: Failed to convert from type
[java.lang.String] to type [byte] for value 'Ronak';
nested exception is java.lang.NumberFormatException: For input string: "Ronak"]."
We would not want to convert the list of string/integer to byte as it is a time-consuming process and as we tried took so much amount of time. Also when the results are retrieved we will again have to convert back to normal integer or string values.
The other option is to loop through the list and pass a single value at a time to the Redis crud repository and this time Redis crud repository is happy but that will be a loop call to Redis and network latency.
We cannot add ID attributes on authors' property as these can be duplicate records.
Does the spring crud repository support the LIKE query in search that way we can create a unique id having these authors' names and make put ID annotation on that new derived property to search the records using spring crud repository using LIKE or contains kind of query.
Any suggestions here are highly appreciated!!
Try to add serialization to your redis key and value. This might help :
https://medium.com/#betul5634/redis-serialization-with-spring-redis-data-lettuce-codec-1a1d2bc73d26

How to make #Indexed as unique property for Redis model using Spring JPA Repository?

I have a model class that I store in Redis and I use Jpa Repository with Spring java. Normally(not with redis) jpa repository is saving the new data or updates(conditionally) if the given model is already exist in Db. Here, I want to add new item to redis but if it is not already exists on db otherwise update it just like usual Jpa implementation.
Here is my model:
#Getter
#Setter
#RedisHash("MyRecord")
public class MyRecordRedisModel {
private String id;
#Id
#Indexed
private String recordName;
private Date startDate;
private Date endDate;
}
And my repository class is just a normal spring jpa repo as follows:
#Repository
public interface IFRecordRedisRepository extends JpaRepository<IFRecordRedisModel, String> {
Page<IFRecordRedisModel> findAll(Pageable pageable);
}
Unique key must be the name (I totally do not care about uniquiness of the id). Thus, if the name is already exist in Db than do not add it again. I marked it as Indexed but still it is adding same data (with same recordName).
How can I make it unique?
This would require an additional query, but I think this solution would work for you. You can use query by Example to check if there exists a record with that name, and save conditionally, or do something else if it already exists.
IFRecordRedisModel exampleRecord = new IFRecordRedisModel();
exampleRecord.setRecordName(inputRecord.getRecordName());
if (!repository.exists(Example.of(exampleModel)))
repository.save(inputRecord);
else ..... // do something else

Why does graphql java query all fields in entity when I only ask for a few?

I created a basic graphql-java app with the spring boot starter and using the graphql spqr library against an MSSQL database utilizing Hibernate and Jpa.
I have an entity called "Task" with 5 fields. I have a simple Jpa repository and a simple Jpa service that calls a "findAllTasks" method. It works great, but if I specify, for example, only one field to query with graphiql, I can see through my SQL log that the select command executed is querying for ALL fields in my Task entity/table, rather than the one I want. Is this expected? I thought graphql only selects the fields you specify in the query command?
Here is my code:
Entity
#Entity
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class Task {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#Column
public String desc;
#Column
public LocalDateTime createdOn;
#Column
public LocalDateTime modifiedOn;
#Column
public String owner;
}
Repository
#Repository
public interface TaskRepository extends JpaRepository<Task, Long> {}
Service
#GraphQLApi
#Service
public class TaskService {
private TaskRepository taskRepo;
#Autowired
public TaskService(TaskRepository taskRepo) {
this.taskRepo = taskRepo;
}
#GraphQLQuery
public List<Task> findAllTasks() {
return taskRepo.findAll();
}
}
When I run the following in graphiql:
query {
findAllTasks {
id
}
}
I get the following SQL statement that was generated from my log:
select task0_.id as Task1_1_0_. task0_.desc as Task1_2_0, task0_.createdOn as Task1_3_0, task0_.modifiedOn as Task1_4_0, task0_.owner as Task1_4_0 from Task as task0_
You have to make a distinction between your GraphQL API and your database. You defined a query method GraphQL that is called findAllTasks. In consequence, when you call this GraphQL query with any number of fields, it will call the Java method implementation findAllTasks.
You can see that the implementation of this Java method calls taskRepo.findAll(). Therefore, you will fetch all data from your tasks in database.
GraphQL will then filter the data from the tasks fetched by your Java method to match what is asked in the GraphQL query.
In a nutshell, GraphQL is in charge in returning just the fields that you requested, but your implementation is in charge of getting the data from the database.
Disclaimer: I'm not an expert of graphql-spqr, so the upcoming information might not work in your case (as it applied to graphql-java).
If you feel that your implementation is however not efficient enough (here we are really talking about efficiency), you could look into dataloaders.

Spring Data MongoDB repository method delete by list of id

I have the following document:
#Document(collection = "decision_analysis")
public class DecisionAnalysis implements Serializable {
#Id
private String id;
...
}
I need to delete multiple MongoDB documents via List<String> decisionAnalysisIds
How to properly write Spring Data MongoDB repository method in order to do it?
The following doesn't work :
void deleteByIds(List<String> decisionAnalysisIds); - error: No property ids found for type DecisionAnalysis! Did you mean 'id'?
void deleteById(List<String> decisionAnalysisIds); - works, but delete only one document
Use the in clause like this:
void deleteByIdIn(List<String> ids);

How to use projection interfaces with pagination in Spring Data JPA?

I'm trying to get a page of a partial entity (NetworkSimple) using the new feature of spring data, projections
I've checked the documentation and if I request only:
Collection<NetworkSimple> findAllProjectedBy();
It works, but if I'm using pageable:
Page<NetworkSimple> findAllProjectedBy(Pageable pageable);
It throws an error:
org.hibernate.jpa.criteria.expression.function.AggregationFunction$COUNT cannot be cast to org.hibernate.jpa.criteria.expression.CompoundSelectionImpl
Any one has already work with this ?
My NetworkSimple class is the following:
public interface NetworkSimple {
Long getId();
String getNetworkName();
Boolean getIsActive();
}
Note: This feature should work in the way described by the original poster but due to this bug it didn't. The bug has been fixed for the Hopper SR2 release, if you're stuck on an earlier version then the workaround below will work.
It is possible to use Pageable with the new query projection features introduced in Spring Data JPA 1.10 (Hopper). You will need to use the #Query annotation and manually write a query for the fields you require, they must also be aliased using AS to allow Spring Data to figure out how to project the results. There is a good example in spring-boot-samples part of the spring boot repository.
In your example it would be quite simple:
#Query("SELECT n.id AS id, n.name AS networkName, n.active AS isActive FROM Network n")
Page<NetworkSimple> findAllProjectedBy(Pageable pageable);
I have made the assumption that your entity looks something like this:
#Entity
public class Network
{
#Id
#GeneratedValue
private Long id;
#Column
private String name;
#Column
private boolean active;
...
}
Spring Data will derive a count query automatically for the paging information. It is also possible to make use of joins in the query to fetch associations and then summarise them in the projection.
I think you need create findAllProjectedBy() as specification.Then you can use findAll() method like this.
example :findAll(findAllProjectedBy(),pageable)
Following link may be help to find how to create specification in spring.
https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
The issue may come from the method name. The by keyword means that you ae filterig data by a specific property: findByName for example. Its called query creation from method name:
http://docs.spring.io/spring-data/jpa/docs/1.10.1.RELEASE/reference/html/#repositories.query-methods.query-creation
So try with Page<NetworkSimple> findAll(Pageable pageable);
Even with spring-data-jpa 1.11.4, something like
public interface NetworkRepository extends JpaRepository<Network, String> {
Page<NetworkSimple> findAll(Pageable pageable);
}
would not compile; reporting
findAll(org.springframework.data.domain.Pageable) in NetworkRepository clashes with findAll(org.springframework.data.domain.Pageable) in org.springframework.data.repository.PagingAndSortingRepository
return type org.springframework.data.domain.Page<NetworkSimple> is not compatible with org.springframework.data.domain.Page<Network>
The workaround we found was to rename findAll to findAllBy, e.g.
public interface NetworkRepository extends JpaRepository<Network, String> {
Page<NetworkSimple> findAllBy(Pageable pageable);
}
You can use interface projection with Pageable like this :
Page<NetworkSimple> findPagedProjectedBy(Pageable pageable);
with some parameter :
Page<NetworkSimple> findPagedProjectedByName(String name, Pageable pageable);
Implementing interface projection with pagination
1. Our ResourceEntity.java class
#Getter
#Setter
#NoArgsConstructor
#Entity
public class ResourceEntity{
private Long id;
private String name;
}
2. Creating projection Interface name ProjectedResource.java, which maps data collected by the SQL query from repository layer method
public interface ProjectedResource {
Long getId();
String getName();
String getAnotherProperty();
}
3. Creating Repository layer method: getProjectedResources()
We are considering the database table name is resource.
We are only fetching id and name here.
#Query(name="select id, name, anotherProperty from resource", countQuery="select count(*) from resource", nativeQuery=true)
Page<ProjectedResource> getProjectedResources(Pageable page);
Hope the issue will be resolved!
You can use:
#Query("SELECT n FROM Network n")
Page<? extends NetworkSimple> findAllProjectedBy(Pageable pageable);

Resources