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

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

Related

How to use different EntityManager based on condition at runtime?

My congfiguration is like below:
The abstract class:
public abstract class AbstractFacade<T> {
private Class<T> entityClass;
public AbstractFacade(Class<T> entityClass) {
this.entityClass = entityClass;
}
protected abstract EntityManager getEntityManager();
public T find(Object id) {
return getEntityManager().find(entityClass, id);
}
// other methods create(T), edit(T), ...
The Ejb that extends the abstract class (i have many others EJBs and about 12 different persistence units):
#Stateless
public class FilesDao extends AbstractFacade<Files> {
#PersistenceContext(unitName = "myfirstPU")
private EntityManager firstEm;
#PersistenceContext(unitName = "mysecondPU")
private EntityManager secondEm;
// i have more than two persistenceUnit ...
#Override
protected EntityManager getEntityManager() {
return firstEm; // or secondEm based on condition
}
public FilesDao() {
super(Files.class);
}
public Files findByFileref(String inFileRef) {
try {
Query q = firstEm.createNamedQuery("Files.findByFileref"); // or secondEm based on condition
q.setParameter("fileref", inFileRef);
Files file = (Files) q.getSingleResult();
return file;
} catch (NoResultException e) {
return null;
}
}
I want to use FilesDao like this :
#Stateless
#LocalBean
public class FileBusiness {
#EJB
FilesDao fileDao;
public void myMethod(){
if(condition1){
//use the FileDao with the EnityManager **firstEm**
}
else if(condition2){
//use the FileDao with the EnityManager **secondtEm**
}
...
}
Is there a way to achieve that ?
I read about using CDI with produces method.
CDI-Producers are not that hard, see the following example.
The following classes are CDI-Qualifier annotations, which are used to distinguish implementations.
#Retention(RetentionPolicy.RUNTIME)
#Target({ TYPE, METHOD, FIELD, PARAMETER, CONSTRUCTOR })
#Qualifier
public #interface MyfirstPUQualifier { }
#Retention(RetentionPolicy.RUNTIME)
#Target({ TYPE, METHOD, FIELD, PARAMETER, CONSTRUCTOR })
#Qualifier
public #interface MysecondPUQualifier { }
The following is a CDI-Bean which injects the different EntityManagers and implements producer methods, making the two EntityManagers available to CDI, distinguished via CDI-Qualifiers
#ApplicationScoped
class MYProducer {
#PersistenceContext(unitName = "myfirstPU")
private EntityManager firstEm;
#PersistenceContext(unitName = "mysecondPU")
private EntityManager secondEm;
#Produces
#RequestScoped
#MyfirstPUQualifier
public EntityManager produceMyfirstPU(){
return firstEm;
}
#Produces
#RequestScoped
#MysecondPUQualifier
public EntityManager produceMysecondPU(){
return secondEm;
}
}
The following shows a CDI-Bean injecting both EntityManagers. This could be extracted to an abstract base class, because maybe needed by other DAOs as well.
#Stateless
public class FileDao {
#Inject
#MyfirstPUQualifier
private EntityManager emFirst;
#Inject
#MysecondPUQualifier
private EntityManager emSecond;
public void myDaoMEthod(){
final EntityManager em = getEntityManager();
...
}
private EntityManager getEntityManager(){
if(condition1){
return emFirst;
}
else if(condition2){
return emSecond;
}
}
}
No can use the FileDao bean without taking care what EntityManager to use, because shouldn't be decided in this bean anyway
#Stateless
#LocalBean
public class FileBusiness {
#EJB
FilesDao fileDao;
public void myMethod(){
// FileDao will decide what entity manager to use
fileDao.doStruff();
}
}

#Autowired does not work in SimpleJpaRepository extension

When trying to override SimpleJpaRepository, adding other beans via #Autowired does not work. How can beans be injected in this case? Here is an implementation:
public class BaseDAO<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID>
implements IDAO<T, ID> {
#Autowired
private SomeBean someBean; // NULL!
}
Instances of BaseDAO are not Spring-managed beans in themselves and therefore autowiring through #Autowired will not work out-of-the-box. Dependencies therefore need to be injected into BaseDAO instances.
Step 1: Have a Spring ApplicationContext available somewhere
#Component
class SpringContext implements ApplicationContextAware {
private static ApplicationContext CONTEXT;
public void setApplicationContext(final ApplicationContext context) throws BeansException {
CONTEXT = context;
}
public static ApplicationContext getContext() { return CONTEXT; }
}
This will be required to autowire the dependencies for the custom repository implementation at the time of repository creation.
Step 2: Extend SimpleJpaRepository
class BaseDAO<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> {
#Autowired
private Dependency dependency;
}
Step 3: Autowire dependencies through a JpaRepositoryFactoryBean
class ExtendedJPARepositoryFactoryBean<R extends JpaRepository<T, ID>, T, ID extends Serializable>
extends JpaRepositoryFactoryBean<R, T, ID> {
private static class ExtendedJPARepositoryFactory<T, ID extends Serializable> extends JpaRepositoryFactory {
public ExtendedJPARepositoryFactory(final EntityManager entityManager) {
super(entityManager);
}
protected Class<?> getRepositoryBaseClass(final RepositoryMetadata metadata) {
return isQueryDslExecutor(metadata.getRepositoryInterface())
? QueryDSLJPARepository.class
// Let your implementation be used instead of SimpleJpaRepository.
: BaseDAO.class;
}
protected <T, ID extends Serializable> SimpleJpaRepository<?, ?> getTargetRepository(
RepositoryInformation information, EntityManager entityManager) {
// Let the base class create the repository.
final SimpleJpaRepository<?, ?> repository = super.getTargetRepository(information, entityManager);
// Autowire dependencies, as needed.
SpringContext.getContext()
.getAutowireCapableBeanFactory()
.autowireBean(repository);
// Return the fully set up repository.
return repository;
}
}
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
return new ExtendedJPARepositoryFactory(entityManager);
}
}
Step 4a: Configure Spring Data JPA to use your factory bean (XML configuration)
<jpa:repositories base-package="org.example.jpa"
factory-class="org.example.jpa.ExtendedJPARepositoryFactoryBean"/>
Step 4b: Configure Spring Data JPA to use your factory bean (Java configuration)
#EnableJpaRepositories(repositoryFactoryBeanClass = ExtendedJPARepositoryFactoryBean.class)
In order to let Spring know that it needs to inject something inside your DAO you need to annotate it with #Component.
You can read more about this here: http://docs.spring.io/autorepo/docs/spring-boot/current/reference/html/using-boot-spring-beans-and-dependency-injection.html.

Injection of #PersistenceContext in CDI-Unit

Here is the unit testing code. When we run unit test code (SampleServiceTest2); EntityManager injected in AbstractDao is always null! How can we inject em during unit test.
*** SampleServiceTest2.java
import javax.inject.Inject;
import org.jglue.cdiunit.CdiRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
#RunWith(CdiRunner.class)
public class SampleServiceTest2 {
#Inject SampleService greeter;
#Test
public void testGreeter() throws Exception {
System.out.println("before2");
greeter.addSampleData(new SampleDataDto(), new KullaniciDto());
System.out.println("after2");
}
}
*** SampleService.java
import javax.ejb.Stateless;
import javax.inject.Inject;
....
#Stateless
#SecuredBean
public class SampleService {
#Inject
SampleLogic sampleLogic;
#Yetki(tag="perm_add_sample_data")
public void addSampleData(SampleDataDto data, KullaniciDto aktifKullaniciDto){
SampleDataHelper sampleDataHelper = new SampleDataHelper();
SampleData sampleData = sampleDataHelper.getEntity(data);
KullaniciHelper kullaniciHelper = new KullaniciHelper();
Kullanici kullanici = kullaniciHelper.getEntity(aktifKullaniciDto);
sampleLogic.addData(sampleData, kullanici);
}
}
**** SampleLogic.java
import javax.inject.Inject;
....
public class SampleLogic {
#Inject
SampleDataDao sampleDataDao;
public void addData(SampleData data, Kullanici kullanici) {
addData1(data,kullanici);
System.out.println("SampleLogic : addData() called!");
}
public void addData1(SampleData data, Kullanici kullanici) {
sampleDataDao.create(data, kullanici);
}
}
**** SampleDataDao.java
public class SampleDataDao extends AbstractDao<SampleData> {
private static final long serialVersionUID = 1L;
}
**** AbstractDao.java
public abstract class AbstractDao<T extends BaseEntity> implements Serializable {
private static final long serialVersionUID = 1L;
#PersistenceContext(unitName="meopdb")
private EntityManager em;
protected EntityManager getEm() {
return em;
}
#SuppressWarnings("rawtypes")
private Class entityClass;
#SuppressWarnings("rawtypes")
private Class getEntityClass() {
if (entityClass == null) {
entityClass = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
return entityClass;
}
public T create(T t, Kullanici kullanici) {
if (t.getId() != null) {
throw new IllegalStateException("Create Operation: Oid should be null");
}
t.setId(getSeqNextValue(t));
t.setEklemeZamani(new Timestamp(Calendar.getInstance().getTimeInMillis()));
t.setEkleyenKullaniciId(kullanici.getId());
t.setDurumId(EnumDurum.AKTIF.getValue());
t = em.merge(t);
em.flush();
return t;
}
}
If you test with CDIUnit, the only thing you get is CDI injections, not the full power of Java EE. Injecting entityManager using #PersistenceContext into AbstractDAO is not part of standalone CDI, it is only supported when application is running within a Java EE application server.
The solution is to inject EntityManager using CDI mechanism and create a producer. The producer could be then switched for an alternative in unit tests to provide test entityManager. However, setting up JPA in a standalone unit test is not so straightforward, as you need to specify connection properties directly in persistence.xml file. Also, do not forget to add dependencies on a JPA implementation (hibernate, eclipselink) into your test dependencies.
However, if you do not want to adapt your application's code or you need more than CDI in your tests, you should have a look at Arquillian Java EE test framework.
Here is an example for CDIUnit:
public abstract class AbstractDao<T extends BaseEntity> implements Serializable {
...
#Inject
#Named("meopdb")
private EntityManager em;
...
}
// producer in application - just a wraper over `#PersisteneContext`
public class EntityManagerProducer {
#Produces
#PersistenceContext(unitName="meopdb")
#Named("meopdb")
private EntityManager em;
}
/* producer in your test sources - it creates entityManager via API calls instead of injecting via `#PersistenceContext`. Also, a different persistence unit is used so that it does not clash with main persistence unit, which requires datasource from app server
*/
public TestEntityManagerProducer {
#Produces
#ProducesAlternative // CDIUnit annotation to turn this on as an alternative automatically
#Named("meopdb")
public EntityManager getEm() {
return Persistence
.createEntityManagerFactory("meopdb-test")
.createEntityManager();
}
}
And it is not yet enough. You need to create a new persistence.xml in your test resources with the test persistence unit named "meopdb-test". For this unit you need to specify RESOURCE_LOCAL transaction-type, and specify connection information. And last thing not to forget - you need to list all your entities in the persistence.xml, or in external orm file. This is because your tests run outside of application server. Inside app server, JPA can find entities automatically.
As #OndroMih said, in CDI-Unit, the only thing you get is CDI injections. So you have to cheat a little.
You can use extension do add javax.inject.Inject annnotation to all #PersistenceContext injections
import java.util.Set;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.*;
import javax.inject.Inject;
import javax.persistence.PersistenceContext;
import org.apache.deltaspike.core.util.metadata.AnnotationInstanceProvider;
import org.apache.deltaspike.core.util.metadata.builder.AnnotatedTypeBuilder;
public class AddInjectToPersistenceContextInjectionsCdiExtension implements Extension {
<T> void processAnnotatedType(#Observes ProcessAnnotatedType<T> pat) {
Set<AnnotatedField<? super T>> fields = pat.getAnnotatedType().getFields();
for (AnnotatedField<? super T> field : fields) {
if (shouldInjectionAnnotationBeAddedToField(field)) {
AnnotatedType<T> at = pat.getAnnotatedType();
AnnotatedTypeBuilder<T> builder = new AnnotatedTypeBuilder<T>().readFromType(at);
Inject injectAnnotation = AnnotationInstanceProvider.of(Inject.class);
builder.addToField(field, injectAnnotation);
pat.setAnnotatedType(builder.create());
}
}
}
private <X> boolean shouldInjectionAnnotationBeAddedToField(AnnotatedField<? super X> field) {
return !field.isAnnotationPresent(Inject.class) &&
field.isAnnotationPresent(PersistenceContext.class);
}
}
and produce suitable EntityManager in test class
#RunWith(CdiRunner.class)
#AdditionalClasses(AddInjectToPersistenceContextInjectionsCdiExtension.class)
public class SampleServiceTest2 {
#Inject SampleService greeter;
EntityManagerFactory emf;
#PostConstruct
void init() {
emf = Persistence.createEntityManagerFactory("integration");
}
#Produces
EntityManager createEntityManager() {
return emf.createEntityManager();
}
#Test
public void testGreeter() throws Exception {
}
}
It's not exactly equivalent of what Java EE container does, but it's close enough more often than not.

How to pass jpql to query method in spring 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
}
}

PersistanceContext on NonServlet class

I have the following...
public class TestServlet extends HttpServlet {
#PersistenceContext
private EntityManager em;
...
}
#Stateless
#LocalBean
public class DeviceRegistrationPersister {
#PersistenceContext(unitName="deviceReg")
private EntityManager em;
...
}
The first one gets the entity manager just fine, however, the second the entity manager is null. Can someone help with what is going on?