How to pass jpql to query method in spring jpa? - jpa

Our project involves hundreds of tables/entities, so it's boring to create a single repository for every entity. We want to create a generic repository for common query use which might have a below look:
#Repository
public interface GenericRepo extends JpaRepository<Ctmpdis,Integer> {
public List findByQl(String jpql,Map params);
}
I want to pass concrete jpql to the method on the fly so that we don't have to create so many repos just need one to do all the variable queries.The problem of this idea is I don't know how to pass a query to repo and make it work. Does anybody know how to do it and is it possible? Thanks

You could implement a custom repository, here is an example
public interface MyRepository<T, ID extends Serializable>
extends JpaRepository<T, ID> {
public List findByQl(String jpql,Map params);
}
public class MyRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID> {
#PersistenceContext
private EntityManager entityManager;
public MyRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
this.entityManager = entityManager;
}
public List findByQl(String jpql,Map params) {
// implementation goes here
}
}

Related

Spring Data 2.x: adding customer behavior to all repositories

I have a web app that was built with Spring Data 1.6.1.RELEASE. It adds custom behavior too all repositories by following the example in the Spring Data' online documentation here:
1.3.2 Adding custom behavior to all repositories
https://docs.spring.io/spring-data/data-commons/docs/1.6.1.RELEASE/reference/html/repositories.html
Basically, it extends JpaRepository with custom behavior shown below:
public interface MyRepository<T, ID extends Serializable>
extends JpaRepository<T, ID> {
void sharedCustomMethod(ID id);
}
Here is the code for implementation of this new interface:
public class MyRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID> {
private EntityManager entityManager;
public MyRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
super(domainClass, entityManager);
this.entityManager = entityManager;
}
public void sharedCustomMethod(ID id) {
// implementation goes here
}
}
Custom repository factory bean:
public class MyRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable>
extends JpaRepositoryFactoryBean<R, T, I> {
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new MyRepositoryFactory(entityManager);
}
private static class MyRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory {
private EntityManager entityManager;
public MyRepositoryFactory(EntityManager entityManager) {
super(entityManager);
this.entityManager = entityManager;
}
protected Object getTargetRepository(RepositoryMetadata metadata) {
return new MyRepositoryImpl<T, I>((Class<T>) metadata.getDomainClass(), entityManager);
}
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
// The RepositoryMetadata can be safely ignored, it is used by the JpaRepositoryFactory
//to check for QueryDslJpaRepository's which is out of scope.
return MyRepository.class;
}
}
}
Here is the declaration of the factory bean:
<repositories base-package="com.acme.repository"
factory-class="com.acme.MyRepositoryFactoryBean" />
Everything works well with Spring Data 1.6.1.RELEASE. However, I got the following exception when starting Jetty in Eclipse after I switched to Spring Data 2.3.2.RELEASE:
java.lang.IllegalStateException: No suitable constructor found on interface com.acme.repository.MyRepository to match the given arguments: [class org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation, class com.sun.proxy.$Proxy52]. Make sure you implement a constructor taking these
at org.springframework.data.repository.core.support.RepositoryFactorySupport.lambda$getTargetRepositoryViaReflection$4(RepositoryFactorySupport.java:522)
at java.util.Optional.orElseThrow(Optional.java:290)
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getTargetRepositoryViaReflection(RepositoryFactorySupport.java:522)
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getTargetRepositoryViaReflection(RepositoryFactorySupport.java:506)
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:180)
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:162)
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:72)
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:309)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:297)
at org.springframework.data.util.Lazy.getNullable(Lazy.java:212)
at org.springframework.data.util.Lazy.get(Lazy.java:94)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:300)
at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:144)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1853)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1790)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:878)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:401)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:292)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:103)
at org.eclipse.jetty.server.handler.ContextHandler.callContextInitialized(ContextHandler.java:843)
at org.eclipse.jetty.servlet.ServletContextHandler.callContextInitialized(ServletContextHandler.java:533)
at org.eclipse.jetty.server.handler.ContextHandler.startContext(ContextHandler.java:816)
at org.eclipse.jetty.servlet.ServletContextHandler.startContext(ServletContextHandler.java:345)
at org.eclipse.jetty.webapp.WebAppContext.startWebapp(WebAppContext.java:1406)
at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1368)
at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:778)
at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:262)
at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:522)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
at org.eclipse.jetty.util.component.ContainerLifeCycle.start(ContainerLifeCycle.java:131)
at org.eclipse.jetty.server.Server.start(Server.java:427)
at org.eclipse.jetty.util.component.ContainerLifeCycle.doStart(ContainerLifeCycle.java:105)
at org.eclipse.jetty.server.handler.AbstractHandler.doStart(AbstractHandler.java:61)
at org.eclipse.jetty.server.Server.doStart(Server.java:394)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
at net.sourceforge.eclipsejetty.starter.jetty9.Jetty9Adapter.start(Jetty9Adapter.java:68)
at net.sourceforge.eclipsejetty.starter.common.AbstractJettyLauncherMain.launch(AbstractJettyLauncherMain.java:84)
at net.sourceforge.eclipsejetty.starter.jetty9.Jetty9LauncherMain.main(Jetty9LauncherMain.java:42)
What can I try to fix this?
UPDATE
I added the following constructor but still have the same problem:
public MyRepositoryImpl(
JpaMetamodelEntityInformation<T, ID> entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
this.em = entityManager;
}
I just setup an application with 2.3.2.RELEASE and its look like it has been simplified.
Delete MyRepositoryFactoryBean
Replace <repositories base-package="com.acme.repository... with
<repositories base-package="com.acme.repository"
base-class="….MyRepositoryImpl" />
MyRepositoryImpl
public class MyRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID> {
private EntityManager entityManager;
MyRepositoryImpl(JpaEntityInformation entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
// Keep the EntityManager around to used from the newly introduced
this.entityManager = entityManager;
}
public void sharedCustomMethod(ID id) {
// implementation goes here
}
}
Make sure MyRepository is outside the package base-package i.e com.acme.repository.
Note
#EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class) on a class annotated #Configuration is equivalent to point 2. As long as the package of that configuration class is com.acme.repository
#NoRepositoryBean annotation on MyRepository is equivalent to point 4
Sample Spring boot app with java config for reference
https://github.com/kavi-kanap/stackoveflow-63223076
Import it as maven project to your IDE. Right click on AccessingDataJpaApplication and run, it will start, save some entities and call your custom method

Spring Data Jpa Query methods are not invoking the repositoryBaseClass

I have a repository base class as defined below.
#NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
}
public class BaseRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> implements BaseRepository<T, ID> {
public BaseRepositoryImpl(JpaEntityInformation<T, ?> entityInfo, EntityManager entityMgr) {
super(entityInfo, entityMgr);
}
// ...
}
#Configuration
#EnableJpaRepositories(basePackages = "org.example",
repositoryBaseClass = BaseRepositoryImpl.class)
public class BaseConfig {
// additional JPA Configuration
}
I have defined a business repository class and a query method as seen below.
#Repository
public interface CarRepository extends BaseRepository<Car, Long> {
#Query("SELECT c FROM Car c Where active = 1")
List<Car> findAllActiveCars();
}
I have a test class which invokes the findAllActiveCars(). I am getting the expected results. But, that query method is not invoking any of the methods in BaseRepository class. How to customize the return values of the query methods?
You didn't show the methods that you did implement, so it is not clear why they don't get called, but since you want to decrypt entity fields, consider listening to JPAs entity lifecycle events. #PostLoad should be able to do the trick.
https://docs.jboss.org/hibernate/core/4.0/hem/en-US/html/listeners.html

Mixing Spring Data Envers and QueryDSL

I'm using a global custom repository in my project which extends QueryDslJpaRepository:
public class CustomPagingAndSortingRepositoryImpl<T, ID extends Serializable> extends QueryDslJpaRepository<T, ID>
implements CustomPagingAndSortingRepository<T, ID> {
And the interface:
public interface CustomPagingAndSortingRepository<T, ID extends Serializable>
extends JpaRepository<T, ID>, QueryDslPredicateExecutor<T> {
And then on my configuration I annotate it with:
#EnableJpaRepositories(repositoryBaseClass = CustomPagingAndSortingRepositoryImpl.class)
All is working fine, but now I was trying to add auditing support to my entities by using spring-data-envers and according to the docs I should use a specific repository factory bean class :
#EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class, repositoryBaseClass = CustomPagingAndSortingRepositoryImpl.class)
Now obviously if I do this things won't work because my repositories will now be created through the EnversRevisionRepositoryFactoryBean class and will no longer be of CustomPagingAndSortingRepositoryImpl type.
How can I support something like this? I'm not seeing how since my custom repository need to extend from QueryDslJpaRepository already.
I think the relevant part for you is this method of EnversRevisionRepositoryFactoryBean:
#Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return EnversRevisionRepositoryImpl.class;
}
Here you really want your CustomPagingAndSortingRepositoryImpl returned. So I would try the following:
extend EnversRevisionRepositoryFactoryBean and overwrite getRepositoryBaseClass to return your CustomPagingAndSortingRepositoryImpl.
Make CustomPagingAndSortingRepositoryImpl extend EnversRevisionRepositoryImpl.

Add customer behaviour to all spring data Jpa repositories in CDI context

Am successfully injecting jpa repositories using CDI. I wanted to add custom behaviour(soft deletes) to all repositories. When using spring I can enable customer behaviour by specifying the repository base class
#EnableJpaRepositories(repositoryBaseClass = StagedRepositoryImpl.class)
How do I specify the same in CDI? Thanks in advance.
To add custom behaviour to Jpa Repositories(in your case for delete),
1. Create a base repository like below:
#NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
#Override
default void delete(T entity){
// your implementation
}
}
2. Now inherit Jpa Repositories from custom repository(i.e BaseRepository) like below:
public interface EmployeeRepository extends BaseRepository<Employee, Long> {
}
3. Inject your repository into Service class and call the delete method.
#Service
class EmployeeService {
#Inject
private EmployeeRepository employeeRepository;
public void delete(Long id) {
employeeRepository.delete(id);
}
}
Now whenever you call delete on repositories which are child of BaseRepository, your custom implementation for delete will be invoked.
Here is the way to add custom logic to your repositories:
http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations
Basically you create a custom repository named {YourRepositoryName}Custom
interface UserRepositoryCustom {
public void someCustomMethod(User user);
}
And implement it:
class UserRepositoryImpl implements UserRepositoryCustom {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
Your main repository should extend the custom one.
Hope this helps!

Spring Data Rest CrudRepository vs ReadOnlyRepository

I noticed an anomaly in the way Spring Data Rest repositories are behaving. I have two types of entities. in my application - readonly entities (for reference data like statecodes, country codes, zip codes etc.). I don't want to let the end user change these. So I implemented the following ReadOnly repository.
#NoRepositoryBean
public interface ReadOnlyRepository<T, ID extends Serializable> extends Repository<T, ID> {
T findOne(ID id);
Iterable<T> findAll();
}
#Repository
public interface StateRepository extends ReadOnlyRepository<State, Long> {
}
Now, all other entities have CrudRepositories associated with them because they are editable entities like addresses which reference the states and zip codes.
Here's an example.
#Repository
public interface CustomerRepository extends CrudRepository<Address, Long> {
}
I have a controller for both readonly and editable entities, with a pass-through call to the repositories.
#RestController
#RequestMapping(value = "/addresses", produces = MediaType.APPLICATION_JSON_VALUE)
public class AddressController {
#Autowired
private AddressRepository addressRepository;
#RequestMapping(method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public Iterable<Address> getAllAddresses() {
return addressRepository.findAll();
}
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public Address getAddress(#PathVariable("id") Long id) {
return addressRepository.findOne(id);
}
}
I have an identical Controller corresponding to the State entity.
Funnily enough, the request to StateController gives me a HATEOAS json response, while the request to Address gives me a non HATEOAS json response. What gives?
My bad. My application server did not redeploy certain repositories. This is a non-issue.
So for those running into these issues, you are likely using hot-code replace feature of your IDE. Consider restarting your app and it should be a non-issue.