In Spring Data JPA we can define a repository interface extending Repository and write a custom method.
If this method follows special syntax, Spring Data will generate the method body automatically.
For example (from the documentation):
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLastname(String lastname);
}
Is there a way to customize the method generation code to introduce new keywords into the syntax?
For example:
Person findExactlyOneById(Long id);
This method would either return the entity or throw a custom exception.
I know I can customize specific repositories as well as the base repository and achieve the effect from the above example, but I'm specifically asking for the automatic method of body generation.
Is there an extension point designed in the framework? Or is the only option to change the source code?
In your case, you can always use CrudRepository.findById(Long id) or JpaRepository.getOne(Long id).
I would suggest inheriting from the JpaRepository class because all types of repositories are included.
You can set nativeQuery = true in the #Query annotation from a Repository class like this:
public static final String FIND_PROJECTS = "SELECT projectId, projectName FROM projects";
#Query(value = FIND_PROJECTS, nativeQuery = true)
public List<Object[]> findProjects();
It's probably worth looking at the Spring data docs as well.
Some more example
1.
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
User findByEmailAddress(String emailAddress);
}
Related
I'm getting the following exception when trying to use a named query with Spring Data Elasticsearch.
ClassCastException: org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl cannot be cast to org.springframework.data.elasticsearch.core.SearchPage
The query I'm trying to make is:
public interface PlayerRepository extends ElasticsearchRepository<PlayerEntity, String> {
#Query("{\"bool\":{\"must\":[{\"terms\":{\"playerNumber.keyword\": ?0}}]}}")
SearchPage<PlayerEntity> fetchPlayers(JSONArray playerNumbers, Pageable pageable);
}
If I do not use the #Query annotation and instead let Spring derive the query from the method name like so:
SearchPage<PlayerEntity> findPlayerEntityByPlayerNumberIn(List<String> playerNumbers, Pageable pageable);
It works as expected. However, the PlayerNumber field is a #MultiField that supports the the field types of Text and Keyword like so:
#Document(indexName = "#{#playersIndexName}")
public class PlayerEntity {
#MultiField(
mainField = #Field(type = Text, name = "playerNumber"),
otherFields = {#InnerField(suffix = "keyword", type = Keyword)})
private String playerNumber;
...
}
And I need to use the keyword mapping here for the query and not the text mapping. As far as I can tell, Spring Data Elasticsearch cannot derive queries from method names on InnerField, which is why I went with the named query approach. But it seems like the using the declared query approach, detailed here, only supports a subset of return types as detailed here
In addition, I need to use the SearchPage return type as well, because there is metadata there that I need to make decisions on.
So I guess there are a couple of questions that come out of this:
Is it possible to use InnerFields in derived query methods? i.e. something like SearchPage<PlayerEntity> findPlayerEntityByPlayerNumber_KeywordIn(List<String> playerNumbers, Pageable pageable);
Is it possible for a named query to return a SearchPage? I think this might be possible with a custom Repository implementation, but if I could get either approach above to work that would be ideal.
Thanks for any help!!
spring-data-elasticsearch version: 4.0.3.RELEASE
spring-boot-starter-parent version: 2.3.3.RELEASE
elasticsearch version: 7.11.1
To answer your second question (Is it possible for a named query to return a SearchPage?): This is a bug that it does not work with #Query annotated methods. I fixed that yesterday for the main, 4.2.x, 4.1.x and 4.0.x branches so it will work when the next service releases are out.
To answer the first one, I will need to do some research and tests before I can say anything about that - it would be great if it would work. I think I' can give more information later this weekend.
Edit/Addition:
The query derivation from the method name is based on the properties of the Java class and is done in the Spring Data base which knows nothing about these inner fields that only exist in Elasticsearch.
But you can use the following custom repository fragment:
public interface CustomPlayerRepository {
SearchPage<PlayerEntity> findPlayerEntityByPlayerNumberKeywordIn(List<String> playerNumbers, Pageable pageable);
}
public class CustomPlayerRepositoryImpl implements CustomPlayerRepository {
private final ElasticsearchOperations operations;
public CustomPlayerRepositoryImpl(ElasticsearchOperations operations) {
this.operations = operations;
}
#Override
public SearchPage<PlayerEntity> findPlayerEntityByPlayerNumberKeywordIn(
List<String> playerNumbers, Pageable pageable) {
var criteriaQuery = new CriteriaQuery(new Criteria("playerNumber.keyword").in(playerNumbers), pageable);
var searchHits = operations.search(criteriaQuery, PlayerEntity.class);
return SearchHitSupport.searchPageFor(searchHits, pageable);
}
}
I would like my custom query method to return a specific Guava collection type (ImmutableSet in my example).
Example:
public interface MyRepository extends CrudRepository<User,UserId> {
#Query("SELECT DISTINCT u FROM User u WHERE... ORDER BY ...")
ImmutableSet<User> findByxxxx();
}
When I try it, I get :
Failed to convert from type [java.util.ArrayList<?>] to type [com.google.common.collect.ImmutableSet<?>]
...
Caused by: java.lang.IllegalArgumentException: Could not instantiate Collection type: com.google.common.collect.ImmutableSet
The documentation does not explicitly list the Guava collection types, so I am not sure if it is impossible without a new release, or if there is some configuration that is possible to make it work.
How can I instruct Spring Data JPA to use that type?
Spring data doesn't support collections from any third-party libraries and probably never do...
CrudRepository exposes an Iterable to you and if you want to materialize to custom collection it's up to you to do that.
By the way If you want your repository returns custom collection types you can decorate the spring data repository with yours. Like this
interface JpaUserRepository extends CrudRepository<User,UserId> {
#Query("SELECT DISTINCT u FROM User u WHERE... ORDER BY ...")
Iterable<User> findByxxxx();
}
public class MyRepository {
private final JpaUserRepository jpaUserRepository;
#Autowired
public MyRepository(final JpaUserRepository jpaUserRepository) {
this.jpaUserRepository = jpaUserRepository;
}
public ImmutableSet<User> findByxxxx() {
return ImmutableSet.copyOf(jpaUserRepository.findByXXX());
}
}
I have an entity that hold some logic data :
#Entity
public class Person {
private Long id.
private String name;
private int age;
private String address;
...
}
I create my Spring data interface
#Repository
public interface CardInventoryRepository extends JpaRepository<Person , Long> {
}
My purpose is to create a dynamic query based on the exist values of my entity for example
if the name is null the query is :
select * from Person p Where p.age=12 AND p.address="adress.."
When the address is null the query should be :
select * from Person p Where p.age=12 AND p.name="ALI"
I want to extract data using only the non empty fields ?
is there any solution suing spring data for building dynamic queries ?
Thanks in advance
Based on Spring doc https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example
Query by Example (QBE) is a user-friendly querying technique with a
simple interface. It allows dynamic query creation and does not
require you to write queries that contain field names. In fact, Query
by Example does not require you to write queries by using
store-specific query languages at all.
DEFINITION:
An Example takes a data object (usually the entity object or a sub-type of it) and a specification how to match properties. You can use Query by Example with JPA
Repositories.
To do so, let your repository interface extend QueryByExampleExecutor<T>, for example:
public interface PersonRepository extends CrudRepository<Person, String>, QueryByExampleExecutor<Person> {
}
Here are the available methods in QueryByExampleExecutor :
public interface QueryByExampleExecutor<T> {
<S extends T> S findOne(Example<S> example);
<S extends T> Iterable<S> findAll(Example<S> example);
// … more functionality omitted.
}
USAGES:
Example<Person> example = Example.of(new Person("Jon", "Snow"));
repo.findAll(example);
ExampleMatcher matcher = ExampleMatcher.matching().
.withMatcher("firstname", endsWith())
.withMatcher("lastname", startsWith().ignoreCase());
Example<Person> example = Example.of(new Person("Jon", "Snow"), matcher);
repo.count(example);
MORE INFO
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#query-by-example
https://github.com/spring-projects/spring-data-examples/tree/master/jpa/query-by-example
Spring Data JPA: Query by Example?
Yes, please take a look at the QueryDSL support for Spring Data. Your use case can be implemented via a Predicate. In a nutshell, you have to create a predicate in which you would pass the non null fields, and then pass that predicate to a findAll method that takes a Predicate as argument. Your repository interface also has to extend QueryDslPredicateExecutor
Need to extend repository from JpaSpecificationExecutor
#Repository
#Transactional
public interface EmployeeDAO extends CrudRepository<Employee,Long>,JpaSpecificationExecutor<Employee>{
}
Use specification and predicate like below
public List<Employee> findByCriteria(String employeeName,String employeeRole){
return employeeDAO.findAll(new Specification<Employee>() {
#Override
public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
if(employeeName!=null) {
predicates.add(criteriaBuilder.and(criteriaBuilder.like(root.get("employeeName"), "%"+employeeName+"%")));
}
if(employeeRole!=null){
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("employeeRole"), employeeRole)));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
});
}
Thank U, guys! I have found the solution:
#Repository
public interface BookRepository extends JpaRepository<Book, Integer>{
#Query(value = "select * from Book where find_in_set(:market,market)", nativeQuery = true)
public List<Book> findBooksByMarcket(#Param("market") String market);
}
Original question
I'm using the #Query annotation to create queries by using the JPA query language and to bind these queries directly to the methods of my repository interface.
My database is created correctly and I'm successful to create some queries except this one:
#Repository
public interface BookRepository extends JpaRepository<Book, Integer>{
#Query("select b from Book b where find_in_set(:market,b.market)")
public List<Book> findBooksByMarcket(#Param("market") String market);
}
I can get the correct result by using the find_in_set function when I check it though MySql. But I cannot reach to pass a variable in java. I have searched though the internet but I cannot find the correct format for it.
please help and thank you guys!
A quick solution is to transform the JPQL query to a native query (by setting the nativeQuery flag to true):
#Query(value = "select * from Book b where find_in_set(:market,b.market)", nativeQuery = true)
public List<Book> findBooksByMarcket(#Param("market") String market);
If you have a custom MySQL function and want to utilize it in a JPA repository, please take a look at tip 1
There is another way to do it using CriteriaBuilder (I used this mechanism along with JPA specification): tip 2
Key words for your search: custom db function, JPA specification, CriteriaBuilder, Criteria
Try this
#Repository
public interface BookRepository extends JpaRepository<Book, Integer>{
#Query("select b from Book b where find_in_set(?1,b.market)")
public List<Book> findBooksByMarcket(String market);
}
I am developing an application using Spring Boot using JPA.
In the application I am exposing a rest API. I do not want to use Spring data rest as I want to have full control of the data.
I am not able to figure out how to use EntityGraph dynamically.
Suppose I have following model taken from here
#Entity
class Product {
#ManyToMany
Set<Tag> tags;
// other properties omitted
}
interface ProductRepository extends Repository<Customer, Long> {
#EntityGraph(attributePaths = {"tags"})
Product findOneById(Long id);
}
I have following rest link to access Product
http://localhost:8090/product/1
It returns to me a product with id 1
Questions:
Will it by default fetch tags as we have mentioned #EntityGraph?
If yes, then can this be configured on demand? Say, if in the query
string I have include=tags, then only I want to fetch product with
its tags.
I found this article but not sure how this can be of help.
The definition of the EntityGraph in the Spring Data JPA Repository is static. If you want to have it dynamic you need to do this programatically like in the page you linked to:
EntityGraph<Product> graph = this.em.createEntityGraph(Product.class);
graph.addAttributeNodes("tags"); //here you can add or not the tags
Map<String, Object> hints = new HashMap<String, Object>();
hints.put("javax.persistence.loadgraph", graph);
this.em.find(Product.class, orderId, hints);
Also you can define the method with the EntityGraph in your JPA Repository.
interface ProductRepository extends Repository<Product, Long> {
#EntityGraph(attributePaths = {"tags"})
#Query("SELECT p FROM Product p WHERE p.id=:id")
Product findOneByIdWithEntityGraphTags(#Param("id") Long id);
}
And then have a method in your service which uses this method with the EntityGraph or the built in findOne(T id) without the EntityGraph:
Product findOneById(Long id, boolean withTags){
if(withTags){
return productRepository.findOneByIdWithEntityGraphTags(id);
} else {
return productRepository.findOne(id);
}
}
You can choose EntityGraph at runtime, by using Spring Data JPA EntityGraph.
Setup quite simple:
Add: implementation 'com.cosium.spring.data:spring-data-jpa-entity-graph:2.0.7' to build.gradle
Add: #EnableJpaRepositories(repositoryFactoryBeanClass = EntityGraphJpaRepositoryFactoryBean.class) bellow #SpringBootApplication
Now, you can choose the best EntityGraph at runtime. Example (this is the example from Spring Data JPA EntityGraph):
// This will apply 'Product.brand' named EntityGraph to findByLabel
productRepository.findByLabel("foo", EntityGraphs.named("Product.brand"));
// This will apply 'Product.supplier' named EntityGraph to findByLabel
productRepository.findByLabel("foo", EntityGraphs.named("Product.supplier"));
// This will apply 'supplier' attribute paths EntityGraph (don't need to define named EntityGraph) to findByLabel
productRepository.findByLabel("foo", EntityGraphUtils.fromAttributePaths("supplier"));
Please read the document for more information.
you can do this in the repository:
interface ProductRepository extends Repository<Product, Long> {
Product findOneById(Long id);
#EntityGraph(attributePaths = {"tags"})
Product findOneWithTagsById(Long id);
}
and create a service method as Robert Niestroj proposed.
You can add an Entity graph as bellow, make sure the entity product class has a relation with the tag class.
#EntityGraph(attributePaths = {
"tags"
})
#Query( value = "select product from product)
List<Product> findAllProduct();