Spring Data - PagingAndSortingRepository with custom query (HQL)? - spring-boot

Trying to mix PagingAndSortingRepository with custom queries, no luck..
Custom repo:
public interface SiteRepositoryCustom
{
public List<SitesDbRecord> getActiveSites();
}
Impl repo:
#Repository
public class SiteRepositoryImpl implements SiteRepositoryCustom
{
private static final Logger logger = ...
#PersistenceContext
private EntityManager em;
#Override
public List<SitesDbRecord> getActiveSites()
{
logger.info( "getActiveSites start" );
try
{
String hql = "select s from SitesDbRecord s where s.isActive = true";
return em.createQuery( hql ).setMaxResults( Integer.MAX_VALUE ).getResultList();
}
catch ( Exception e )
{
logger.error( "getActiveSites failed.", e );
return null;
}
}
}
The repo injected to the service:
public interface SiteRepository extends PagingAndSortingRepository<SitesDbRecord, Integer>, SiteRepositoryCustom {
public List<SitesDbRecord> getActiveSites( Pageable pageable );
public List<SitesDbRecord> getActiveSites();
}
If I just extend CrudRepository (without the Pageable method) then all is OK. Trying to extend PagingAndSortingRepository (with or without the Pageable method) then Spring fails to boot with
PropertyReferenceException: No property getActiveSites found for type SitesDbRecord!
What is the correct way to use PagingAndSortingRepository with custom queries? Probably got it wrong, but I assumed it's Spring responsibility to provide the handling of paging/sorting.

If SitesDbRecord has boolean property named active it should be:
public interface SiteRepository extends PagingAndSortingRepository<SitesDbRecord, Integer> {
public List<SitesDbRecord> findByActiveIsTrue( Pageable pageable );
public List<SitesDbRecord> findByActiveIsTrue();
}
There is no need to extend your Custom repository, just implement PagingAndSortingRepository

Related

Spring TransactionManager behavior with Spring Data and JpaRepository

I have a controller which does the following
A submit end point which save an entry in db and then call some external service asynchronously
Track the update of asynchronous call (this call updates an associated table) by watching the db and update the status of the entry created in step one
I was using the #Query Annotation to verify if step one entry exist in db and it was always returning empty. I tried changing it to the default spring method and it starts returning the inserted value.
I read about proxies, #Transactional and how non CRUD methods in a JPARepository are non transactional and tried few things like transaction propagation and self injection and even explicitly marking the repo method #Transactional. But none of them fixed the issue. Using spring data method solved it but I still don't understand what happened. Can someone help with an explanation of this behavior.
Basic code snippet is below
MyController
#RestController
public class MyController {
private final MyService myService;
private final MyRepository myRepository;
#Autowired
public MyController(MyService myService,
MyRepository myRepository) {
this.myService = myService;
this.myRepository = myRepository;
}
#PostMapping(value = "/submit")
public ResponseEntity<MyResponse> submit(#Valid #RequestBody MyRequest myRequest) {
return ResponseEntity
.accepted()
.body(MyResponse.success(myService.submit(myRequest), "SUBMITTED"));
}
/**
* This method is to update the status of the entry created by /submit endpoint
* if the asynchoronous process triggered by submit endpoint update an associated table
*/
#PostConstruct
private void trackUpdates() {
..
someObserver.subscribe(trackedAssociatedEntity -> {
myService.trackAndUpdateBasedOnAssociatedEntity(trackedAssociatedEntity);
});
}
}
MyService
#Service
#Transactional
public class MyService {
private final MyRepository myRepository;
#Autowired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
submit(MyRequest myRequest) {
myRepository.save(myEntity);
//makes that asynchronous call
}
public void trackAndUpdateBasedOnAssociatedEntity(#NotNull MyAssociatedEntity myassociatedEntity) {
// This commented call always return empty but the uncommented code works as expected
// List<MyEntity> existingEntity =
// myRepository.findEntityByField1AndField2(myassociatedEntity.getField1(),myassociatedEntity.getField2());
List<MyEntity> existingEntities =
myRepository.findByField1AndField2(myassociatedEntity.getField1(),myassociatedEntity.getField2());
if(existingEntities.isEmpty()){
//create new
}else{
//update
}
}
}
}
}
MyRepository
#Repository
public interface MyRepository extends JpaRepository<MyEntity, Long> {
#Query("SELECT e FROM MyEntity e WHERE e.field1 = ':field1' and e.field2 = ':field2' ")
List<MyEntity> findEntityByField1AndField2(String field1, String field2);
List<MyEntity> findByField1AndField2(String field1, String field2);
}
I believe that '' are not needed. Please try the following:
#Repository
public interface MyRepository extends JpaRepository<MyEntity, Long> {
#Query("SELECT e FROM MyEntity e WHERE e.field1 = :field1 and e.field2 = :field2")
List<MyEntity> findEntityByField1AndField2(String field1, String field2);
List<MyEntity> findByField1AndField2(String field1, String field2);
}

Query and Database performance when used with JpaRepository findAll() vs native query using JpaRepository

I am developing a spring boot project where i am having two functions for JPA on which i need to figure out which function will perform better and put less pressure on database query performance and utilise Hibernate caching. Please guide on which query to use.
My Repository interface:
#Repository
public interface CustomersRepository
extends JpaRepository<CustomersEntity, Long> {
#Query(nativeQuery = true, value = "SELECT * FROM customers WHERE c_mobile = ?1")
CustomersEntity findcustomerByMobile(String mobileNo);
#Override
List<CustomersEntity> findAll();
}
My Service class:
#Scope("request")
#Service
public class CustomerServiceImpl implements ICustomerService {
#Autowired
private CustomersRepository customersRepository;
#Override
public boolean findCustomerByMobile1(long mobileNo) {
CustomersEntity customersEntity = customersRepository.findcustomerByMobile(mobileNo);
if (customersEntity != null)
return true;
else
return false;
}
#Override
public boolean findCustomerByMobile2(long mobileNo) {
List<CustomersEntity> entityList = customersRepository.findAll();
for (CustomersEntity entity : entityList) {
if (entity.getcMobile() == mobileNo) {
return true;
}
}
return false;
}
}
There is no need to download all records from the database to your app and then filtering them. With thousands of records it will slow down.
Instead you should create an index on c_mobile field then use just like this simple method:
public interface CustomerRepo extends JpaRepository<CustomersEntity, Long> {
CustomersEntity findByMobileNo(String mobileNo);
}
It will work in a flash (with index).
More info about building query methods you can find here.

Get all documents from an index using spring-data-elasticsearch

I am trying to connect to my external ElasticSearch server with Spring Boot.
If I do a curl from command line, I get expected results.
curl "http://ipAddr:9200/indexName/TYPE/_search?pretty=true"
But getting this error when I try to access it via Spring Boot.
<html><body><h1>Whitelabel Error Page</h1><p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p><div id='created'>Mon Sep 11 12:39:15 IST 2017</div><div>There was an unexpected error (type=Internal Server Error, status=500).</div><div>Could not write JSON: (was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl["facets"])</div></body></html>
Not sure why a NullPointerException and what is aggregartion.impl
Here is my Spring Application:
Controller:
#RestController
public class PojoController {
#Autowired
PojoService pojoService;
#RequestMapping(value = "/", method=RequestMethod.GET)
public #ResponseBody String index() {
return new String("Welcome:)");
}
#RequestMapping(value = "/all", method = RequestMethod.GET,
produces = { MediaType.APPLICATION_JSON_VALUE })
#ResponseBody List<POJO> findAll() {
try {
List<POJO> pojoObj = pojoService.findAll();
return pojoObj;
} catch (Exception exp) {
exp.printStackTrace();
return null;
}
}
}
Repository:
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
List<POJO> findAll();
}
Service:
#Service
public class POJOServiceImpl implements POJOService{
private POJORepository pojoRepository;
private ElasticsearchTemplate elasticsearchTemplate;
#Autowired
public void setPojoRepository(PojoRepository pojoRepository) {
this.pojoRepository = pojoRepository;
}
public POJO findOne(String id) {
return pojoRepository.findOne(id);
}
public List<POJO> findAll() {
return (List<POJO>) pojoRepository.findAll();
}
}
POJO class:
#Document(indexName = "INDEX", type = "TYPE")
public class POJO {
#Id
private Integer id;
private String name;
public POJO(){
// empty
}
public POJO(Integerid, String name) {
super();
this.id = id;
this.name = name;
}
// getters and setters
}
I should be able to query all the documents in the index. Later on, I will try and use filters etc.
Any help is appreciated. Thanks :)
It looks like Jackson has a problem with handling your POJO (probably related to this issue: DATAES-274) - the problematic part is casting in repository from Iterable collection to List.
Update
In case of repositories, spring-data-elasticsearch behaves a bit different than you would expect. Taking your example:
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
List<POJO> findAll();
}
and after calling in your rest controller:
List<POJO> pojoObj = pojoService.findAll();
in debugger you will see something like this:
You would expect that pojoObj list contains objects of POJO class.
And here comes the surprise - pojoObj ArrayList contains one object of AggregatedPageImpl type and its content field is the right list that contains your POJO objects.
This is the reason why you get:
Could not write JSON: ... java.util.ArrayList[0]->org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl[\"facets\"])
As I wrote before, Jackson cannot handle this while serializing POJO objects.
Solution 1
Let repositories return Iterable collection (by default).
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
}
Move the conversion part to the service but use some utility method (here with Guava) in order to have it like this:
import com.google.common.collect.Lists;
public List<POJO> findAll() {
return Lists.newArrayList(pojoRepository.findAll());
}
Solution 2
Use Page in repository (here simplified version without parameters):
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
Page<TestDto> findAll();
}
If you still want to operate on list - get content from page in service:
public List<POJO> findAll() {
return testDtoRepository.findAll().getContent();
}

Spring data JPA locking

I need to use #Lock inside of my implementations:
#Lock(LockModeType.PESSIMISTIC_WRITE)
private Note findOneForUpdate(BigInteger id) {
return noteDao.findOne(id);
}
But other sources say it should be in interfaces:
#Repository
public interface NoteRepository extends JpaRepository<Note, BigInteger>, NoteDao {
#Lock(LockModeType.PESSIMISTIC_WRITE)
Note findOne(BigInteger id);
}
So, is first option possible? I tried it with spring-boot-starter-data-jpa 1.5.3.RELEASE, but lock did not work.
#Lock annotation is required in repository class
#Lock(LockModeType.PESSIMISTIC_WRITE) // not required
private Note findOneForUpdate(BigInteger id) {
return noteDao.findOne(id);
}
#Repository
public interface NoteRepository extends JpaRepository<Note, BigInteger>, NoteDao {
#Lock(LockModeType.PESSIMISTIC_WRITE) // required
Note findOne(BigInteger id);
}

Wiring Repository interfaces in service layer dynamically

The Service class and my repository classes in my spring MVC set up are something like this -
public class ObjectServiceImpl implements ObjectService {
#Autowired
Temp1Repo temp1Repo;
#Autowired
Temp2Repo temp2Repo;
...
}
public interface Temp1Repo extends CrudRepository<Temp1, Integer> {
}
public interface Temp2Repo extends CrudRepository<Temp2, Integer> {
}
Now, in my service class, i am getting a object of a type Temp1, I have to call temp1Repo.save(). If I get an object of Temp2, I have to call temp2Repo.save() and so on...
How do i achieve this?
Seems fairly simple to just have an if statement:
if(object instanceof Temp1) {
temp1Repo.save((Temp1) object);
} else if(object instanceof Temp2) {
temp2Repo.save((Temp2) object);
}
Or perhaps you are looking for a more generic way?
I suppose that you want to regroup all repositories in one. Something like
#SuppressWarnings("rawtypes")
public class ObjectServiceImpl {
#Autowired
private CrudRepository[] repositories;
private Map<Class<?>, CrudRepository> repositoryMap = new HashMap<Class<?>, CrudRepository>();
#PostConstruct
public void init() {
for (CrudRepository r : repositories)
repositoryMap.put(getType(r), r);
}
private Class<?> getType(CrudRepository repository) {
Type[] types = repository.getClass().getGenericInterfaces();
for (Type t : types) {
if (t instanceof ParameterizedType)
return (Class<?>) ((ParameterizedType) t).getActualTypeArguments()[0];
}
throw new IllegalStateException("Check repositories...");
}
public void save(Object entity) {
repositoryMap.get(entity.getClass()).save(entity);
}
public <T> T get(Object id, Class<T> clazz) {
return repositoryMap.get(clazz).findOne(id);
}
....
}
Consider to use EntityManager directly, but could be useful anyway...
Following the code you wrote, Spring will rise an exception at startup time if any injection is missing.
What you want to do is a dynamic Module load, depending on a condition you omitted within your question.
You probably have to use XML configuration style and create a by condition spring context and load the correct one to be used.
Cheers

Resources