Generics and Spring Data JPARepository - spring

This is a follow-up question based on Oliver Gierke's suggestion.
We have two tables (almost same information) but for some external reasons, cannot use a common single table. I am getting an error that the base class is not a mapped entity. Oliver Gierke has mentioned in his response that it would work only for Single Table. I am assuming that is the reason. if so, could someone explain why such limitation and how can I make the following work.
Base entity:
#MappedSuperclass
public abstract class DecisionEntity {
Inherited classes:
#Entity
#Table(name="DM_INSP_TASKING_RULES_RSLT")
public class DmInspTaskingRulesRslt extends DecisionEntity implements Serializable {
#Entity
#Table(name="DM_UW_REF_RULES_RSLT")
public class DmUwRefRulesRslt extends DecisionEntity implements Serializable {
The Repository
#Repository
public interface DecisionManagementRepository<T extends DecisionEntity> extends JpaRepository<DecisionEntity, Long> {
Have defined 'packagesToScan' and also listed all the 3 classes in persistence.xml.
I am getting the 'Non an Managed Entity' for 'DecisionEntity' class.
I tried Inheritence Type - 'TABLE_PER_CLASS

This is not supported by Spring Data JPA and Java Persistance API Specification.
Spring Data JPA Issue DATAJPA-264
Repositories: throw exception at startup if entity is a not an #Entity (e.g. for #MappedSuperclass)
Status: Investigating
Resolution: Unresolved
The JPA specifications says:
A mapped superclass, unlike an entity, is not queryable and must not be passed as an argument to
EntityManager or Query operations. Persistent relationships defined by a mapped superclass must
be unidirectional.

Related

What is the difference between #Entity and #Document in spring boot?

Can you use both annotations on your database tables?
id, just like some clarification on there differences. thank you
#Entity is used to map a class to a relational database, it represents a database table.
#Document is used to map a class to noSQL database (specifically mongoDB), it represents a MongoDB documents.
You can use both JPA or MongoRepository if you are using both databases by creating different entities and repositories for each database.
I recommend you to have a look at spring documentation (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/)
#Document is a Spring Data Mongo annotation while #Entity is part of Java Persistence API (JPA).
You can check both documentations:
Spring Data Mongo docs
JPA docs
Where into "Example 10. Repository definitions using domain classes with annotations" there is this piece of code:
interface PersonRepository extends Repository<Person, Long> { … }
#Entity
class Person { … }
interface UserRepository extends Repository<User, Long> { … }
#Document
class User { … }
And documentation says:
PersonRepository references Person, which is annotated with the JPA #Entity annotation, so this repository clearly belongs to Spring Data JPA. UserRepository references User, which is annotated with Spring Data MongoDB’s #Document annotation.
So you can see here the difference.

Spring Boot 2.5 and Spring Data: #NoRepositoryBean unexpected behaviour in multi-module project

I'm facing the following issue in a legacy code that I can't change. I have a multi module project which defines in the commons module a Spring Data interface as below:
package commons;
...
#NoRepositoryBean
public interface MyCustomRepository<P, I extends Number> extends JpaRepository<MyEntity, Integer>
{
MyEntity getOneAndCheck();
}
In another module I extend this interface as follows:
package data;
...
#Repository
public interface MyRepository extends MyCustomRepository<MyEntity, Integer>
{
...
}
So, the idea is that I don't want that Spring Data generates any implementation for the MyEntity getOneAndCheck() method 'cause it is implemented like this:
package data;
...
public class MyCustomRepositoryImpl implements MyCustomRepository
{
...
#Override
public MyEntity getOneAndCheck()
{
...
}
...
}
However, when I'm starting the application, I get the following exception:
...
Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract MyEntity commons.MyCustomRepository.getOneAndCheck()! No property getOne found for type MyEntity!
...
So what it seems to happen is that Spring Data tries to generate a Query for the MyEntity getOneAndCheck() method, despite the #NoRepositoryBean annotation. This works as expected in the application I'm gonna migrate from Spring 3 with Spring Data to Spring Boot 2.5.
Not sure if the described behavior has anything to do with the fact that there are multiple Maven modules and that the repositories, the entities and the DTOs are in different modules. Not sure neither if there should be any difference between the way it runs currently with Spring and the one with Spring Boot. But the result is that all of the dozens of repositories in this legacy application are failing with the mentioned exception.
It might be important to mention that the main class needs to use annotations in order to tune the scanning:
#SpringBootApplication(scanBasePackages = "...")
#EnableJpaRepositories(basePackages={"...", "..."})
#EntityScan(basePackages= {"...", "..."})
public class MyApp
{
public static void main(String[] args)
{
SpringApplication.run(MyApp.class, args);
}
}
Not sure whether these annotations are supposed to change anything from the point of view of #NoRepositoryBean but the issue appeared as soon as I added this Spring Boot main class. It worked okay previously without Spring Boot.
Any suggestion please ?
Many thanks in advance.
Kind regards,
Seymour
There are two things that play together:
Spring Data's default custom implementation
Repository fragments
None of these apply because:
The default custom implementation follows the name of the actual repository. In your case, the implementation is named MyCustomRepositoryImpl whereas the repository name is MyRepository. Renaming the implementation to MyRepositoryImpl would address the issue
Since Spring Data 2.0, the repository detection considers interfaces defined at the repository level as fragment candidates where each interface can contribute a fragment implementation. While the implementation name follows the fragment interface name (MyCustomRepository -> MyCustomRepositoryImpl), only interfaces without #NoRepositoryBean are considered.
You have three options:
extracting your custom method into its own fragment interface and providing an implementation class that follows the fragment name:
interface MyCustomFragement {
MyEntity getOneAndCheck();
}
class MyCustomFragementImpl implements MyCustomFragement {
public MyEntity getOneAndCheck() {…}
}
public interface MyRepository extends MyCustomRepository<MyEntity, Integer>, MyCustomFragment {…}
Set the repositoryBaseClass via #EnableJpaRepositories(repositoryBaseClass = …) to a class that implements the custom method.
If you cannot change the existing code, you could implement a BeanPostProcessor to inspect and update the bean definition for the JpaRepositoryFactoryBean by updating repositoryFragments and adding the implementation yourself. This path is rather complex and requires the use of reflection since bean factory internals aren't exposed.

Writing generic spring jpa repository for all entities

I am working on Spring Boot application.
We have Service Layer,Rest Controller and Dao as repository.
I have 20 to 30 tables in my database and I dont want to create repository for each entity and extends that to CrudRepository.
ex : User is an Entity, to perform persistance operations on User, I have to create UserRepository which extends CrudRepository.
Same with Department, Company etc...
What i want to do is, I will write a BaseRepository which gonna extend CrudRepository, base repository should accept all entities and do persistance operations.
Is there a way to that ??
Don't extend CrudRepository it's functionality is all tied to the generic type, it'd be hacky to extend it for a generic implementation. You probably just want something simple which uses the JPA entity manager directly:
import javax.persistence.EntityManager;
import org.springframework.beans.factory.annotation.Autowired;
public class GenericRepository {
#Autowired
private EntityManager entityManager;
public <T, ID> T findById(Class<T> type, ID id) {
return entityManager.find(type, id);
}
}

Spring Boot detects 2 identical repository beans

I am using Spring Boot with Spring Data JPA, there is only one #SpringBootApplication. And I have also a repository classes, for example:
package com.so;
public interface SORepository {
//methods
}
And impl
#Repository("qualifier")
#Transactional(readOnly = true)
public class SORepositoryImpl implements SORepository {
//methods
}
The proplem is, when I start the application, I get following error:
Parameter 0 of constructor in com.so.SomeComponent required a single bean, but 2 were found:
- qualifier: defined in file [path\to\SORepositoryImpl.class]
- soRepositoryImpl: defined in file [path\to\SORepositoryImpl.class]
So, as you see, somehow 2 beans of one repository class are created. How can I fix this?
You can use Spring Data JPA methods having created Proxy element and than inject it into public class SORepositoryImpl:
public interface Proxy() extends JpaRepository<Element, Long>{
Element saveElement (Element element); //ore other methods if you want}
And than:
#Repository
#Transactional(readOnly = true)
public class SORepositoryImpl implements SORepository {
#Autowired
private Proxy proxy;
//end realisation of methods from interface SORepository
}
Try taking the #Repository annotation off the SORepositoryImpl class
e.g.
#Transactional(readOnly = true)
public class SORepositoryImpl implements SORepository {
//methods
}
The error message is implying you have two beans, one named "qualifier" and one named "soRepositoryImpl", which is probably in a Config class.
I guess you should share your SomeComponent class supposing you have no extra configuration class/xml. My take is that you are injecting as 'soRepositoryImpl' there where you have defined as 'qualifier'. Having two options them. I would say to just remove the annotation parameter 'qualifier' and it should work.
Moreover, unless you want do specify an custom DAO implementation you can avoid #Repository at all (That's an annotation you use to make it injectable for your services). You can just create an interface extending Spring interface and define methods for queries.
For example:
public interface PersonRepository extends Repository<User, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
Then you can just inject it in your services/controller directly.
private final PersonRepository personRepository;
public PersonController(final PersonRepository personRepository) {
this.personRepository = personRepository;
}
check samples:
https://spring.io/guides/gs/accessing-data-jpa/
http://docs.spring.io/spring-data/data-commons/docs/1.6.1.RELEASE/reference/html/repositories.html
OK, I've found the issue.
I just couldn't understand, how Spring creates the second bean (soRepositoryImpl), because I've never told it, neither explicitly nor in config classes. But I figured out that the second bean us created during the instantiation of my another SORepository (which is in the different package com.another and which extends JpaRepository).
So, when Spring tries to resolve all dependencies of com.another.SORepository it somehow finds my com.so.SORepositoryImpl (which has nothing familiar with com.another.SORepository - not extending\implementing, not jpa stuff, only similar names!).
Well it seems like a Spring bug to me, because it doesn't check the real inheritance of dependent classes of repositories, only name + Impl (even in different package) suits for him.
The only thing that I should do is to rename `com.so.SORepositoryImpl and that it, no 2 beans anymore.
Thanks everyone for answers!

Spring MVC + Hibernate DAOs : unable to wire beans

I'm currently working on a Spring MVC project in which I integrated Hibernate. The pure Spring MVC part (DispatcherServlet + request mapping) works fine. Now, the problem I have to cope with is quite strange : I've read "Java Persistence with Hibernate" and I am trying to design my persistence layer in a similar way than explained in the book. That is, I've designed it in two parallel hierarchies : one for implementation classes and a second for the interfaces.
So, I have an abstract class named GenericDaoImpl, that implements the GenericDao interface. Then I have a concrete class named AdvertisementDaoImpl, that extends GenericDaoImpl and that implements the AdvertisementDao interface (which extends GenericDao).
Then, in a service bean (class marked with #Service), I'll have my dao class autowired.
Here's my problem :
autowiring a DAO class that implements an interface but does not extends my abstract GenericDaoImpl class : OK
autowiring my AdvertisementDaoImpl that implements the AdvertisementDao interface and extends my abstract GenericDaoImpl class : leads to bean initialization exception.
The abstract class I have at the top of my DAO hierarchy handles all the boilerplate code for common CRUD methods. So, I definitely want to keep it.
Does anyone have an explanation about that?
Here's an excerpt of code :
public abstract class GenericDaoImpl <T, ID extends Serializable> implements BeanPostProcessor, GenericDao<T, ID>{
#Autowired(required=true)
private SessionFactory sessionFactory;
private Session currentSession;
private Class<T> persistentClass;
...
}
#Repository
public class AdvertisementDaoImpl extends GenericDaoImpl<Advertisement, Long> implements AdvertisementDao {
...
public List<Advertisement> listAdvertisementByType(AdvertisementType advertisementType, Class<? extends Good> type) {
return null;
}
}
#Service
public class AdvertisementServiceImpl implements AdvertisementService{
#Autowired(required=false)
private AdvertisementDao advertisementDao;
public List<Advertisement> listAllAdvertisements() {
return null;
}
}
Here's the most relevant part of the stacktrace (at least, I guess it is):
nested exception is
org.springframework.beans.factory.BeanCreationException: Could not
autowire field: be.glimmo.service.AdvertisementService
be.glimmo.controller.HomeController.advertisementService; nested
exception is java.lang.IllegalArgumentException: Can not set
be.glimmo.service.AdvertisementService field
be.glimmo.controller.HomeController.advertisementService to
be.glimmo.dao.AdvertisementDaoImpl
And here's my Spring configuration (link to pastebin.com) :
I believe you should use proxy-target-class in your transaction management configuration:
<tx:annotation-driven transaction-manager="transactionManagerForHibernate"
proxy-target-class="true" />
The symptoms of the problem you describe match the ones mentioned in Spring Transaction Management (look for Table 10.2) and AOP proxying with Spring:
If the target object to be proxied implements at least one interface
then a JDK dynamic proxy will be used. All of the interfaces
implemented by the target type will be proxied. If the target object
does not implement any interfaces then a CGLIB proxy will be created.
So, when CGLIB is not there by default, you have all the methods coming from implemented interfaces but you will miss proxying of the methods that come from super classes in the hierarchy and that's why you get an exception on this.
After a few more tests, it turns out that the problem was caused by my abstract GenericDaoImpl class implementing BeanPostProcessor interface : for some reason, the methods from this interface were executed not only at this bean instantiation but at every bean intantiation.
Given that, within my BeanPostProcessor hook methods, I was retrieving generics parameterized types, when these methods were executed against classes that are not in my DAO hierarchy, they would end up yielding runtime Exceptions (more specifically, ClassCastException).
So, to solve this issue, I had my GenericDaoImpl class not implement BeanPostProcessor interface anymore and I moved the body of the hook method in the empty contructor.

Resources