How to implement repository in Spring data JPA when MappedSuperClass is involved? - spring

I'm implementing the repository structure of an entity which extends a MappedSuperClass in Sping data JPA.
Base.java:
#MappedSuperclass
public abstract class Base {
public abstract Long getId();
public abstract void setId(Long id);
public abstract String getFirstName();
public abstract void setFirstName(String firstName);
}
BaseImpl.java:
#Entity
public class BaseImpl extends Base {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
...
//Default and parameterised constructors
//Getters and setters
//Equals and hashcode
//toString
}
BaseRepository.java:
#NoRepositoryBean
public interface BaseRepository<T extends Base, ID extends Serializable> extends JpaRepository<T, ID> {
}
BaseRepositoryImpl.java:
public interface BaseRepositoryImpl extends BaseRepository<BaseImpl, Long> {
}
When I try to save Base object in the following way:
#Autowired
BaseRepositoryImpl baseRepositoryImpl;
#Test
public void contextLoads() {
Base base = new BaseImpl();
base.setFirstName("Mamatha");
System.out.println(baseRepositoryImpl.save(base));
}
I do see a compile time error in the sysout line saying "The method save(Iterable<S>) in the type JpaRepository<BaseImpl,Long> is not applicable for the arguments (Base)".
In JPA, everything happens through MappedSuperClass only, except the instantiation. Am I going wrong in implementing the repository structure. Please help.

Related

AOP. Hibernate EventListeners

I am trying to use Hibernate event listeners with AOP. My code:
#EntityListeners(MyEntityListener.class)
#NoArgsConstructor
#Getter
#Setter
#ToString
#Entity
#Table(name = "some_table", uniqueConstraints = #UniqueConstraints(columnNames = "code", name = "uc_some_table_code")
public class MyEntity {
#Column(name = "code", nullable = false)
private String code;
#Column(name = "description")
private String description;
}
public class MyEntityListener {
#AnnotationForAudit(name = "EVENT1")
#PostPersist
private postCreate(MyEntity myEntity) {}
#AnnotationForAudit(name= "EVENT2")
#PreUpdate
private void preUpdate(MyEntity myEntity) {}
}
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface AnnotationForAudit {
String name() default "";
}
#AllArgsConstructor
#Service
public class MyEntityServiceImpl implements MyEntityService {
private final MyEntityRepository repository;
private final MyEntityMapper mapper;
#Override
#Transactional
public MyEntity create(MyEntityModel model) {
var entity = mapper.fromModel(model);
return mapper.toModel(repository.save(entity));
}
}
#Aspect
#Component
public class AuditEventAspect {
#Pointcut("#annotation(annotationForAudit)")
public void callAnnotatedmethod(AnnotationForAudit annotation) {}
#before(value = "callAnnotatedmethod(annotation)", argnames="joinPoint,annotation")
public void beforeCallAnnotatedmethod(JoinPoint joinPoint, AnnotationForAudit annotation) {
System.out.println("do something...");
}
}
I want to intercept the Hibernate event when saving or editing an entity, but this configuration does not work. I explicitly declared the listener as a bean in application configuration - and no, it does not work anyway. But if I inject it into the service and call it methods, then the aspect works.
Background: Spring AOP is based on JDK dynamic proxies for interfaces or CGLIB proxies for class types. Proxies rely on sub-classing, and in Java subclasses or other classes other than the declaring class do not have access to private methods. Therefore, you also cannot intercept private methods using proxy-based AOP technologies.
Solution: Make your listener method public, then it works.
Alternative: If your really need to intercept private methods, you have to switch to a more powerful AOP technology like AspectJ. AspectJ does not work proxy-based, so there you can intercept private methods.

Spring Data Repository without specifying Entity

With Spring Data you can make a Repository for a given entity:
#Repository
public interface MyRepo extends CrudRepository<MyEntity, Long> {...}
But what if you have a lot of custom queries not tied to a specific Entity?
None of the below work:
#Repository
public interface MyRepo {...}
#Repository
public interface MyRepo extends CrudRepository {...}
#Component
public interface MyRepo extends Repository {...}
And so on..
Essentially what I want, is to be able to encapsulate some #Querys into an injectable class or interface.
You can use a generic entity superclass instead of a concrete entity. It's very usual to have an abstract superclass to declare the id of the entities or other common stuff.
#MappedSuperclass
public abstract class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Then you can create a repository like this and autowire where you need it:
public interface MyRepo extends JpaRepository<AbstractEntity, Long> {
#Query("...")
myQueryMethod();
}
That said, Spring Data interfaces are designed to work with a root entity. I think that if you want to avoid it, you should use the underlying JPA layer (that is, use the EntityManager to execute queries instead of a Spring Data Repository).

JpaRepository of abstract #MappedSuperclass without #Id

I have an abstract entity annotated with #MappedSuperclass:
#MappedSuperclass
public abstract class BaseEntity {
public abstract T getId();
public abstract void setId(T id);
}
Then I inherit my Entities from it, defining their id in each one:
#Entity
public class EntityA {
#Id
private int id;
// ....
}
#Entity
public class EntityB {
#Id
private long id;
// ....
}
Now I want to create a generic JpaRepository that accepts any class that extends from my Base Entity:
public interface BaseRepository<T extends BaseEntity, ID extends Serializable> extends JpaRepository<T, ID> {
}
But Spring trows an exception saying BaseEntity has no ID:
java.lang.IllegalArgumentException: This class [BaseEntity] does not define an IdClass
Please, check Plog's comments in his answer. I could solve it injecting each repository type in service's contructor
As far as I know you can't make generic repositories like this. You will need to make an individual repository for each of your concrete entity classes. aggregate roots (thanks #JensSchauder).
You can however make a generic base repository that can define some common queries between these two repositories by marking it as a #NoRepositoryBean:
#NoRepositoryBean
public interface BaseRepository<T extends BaseEntity, ID extends Serializable> extends JpaRepository<T, ID> {
//common methods
}
Your concrete class repositories should then extend this instead of JpaRepository:
public interface EntityARepository extends BaseRepository<EntityA, Integer> {
}

#unique constraint with database support in hibernate

I have a spring project and want to enforce uniqueness in the database on a field and get the error message back to the UI.
I have read this SO answer and it makes sense so #Column(unique = true) makes the constraint on the table but doesn't enforce it.
So the question becomes how to create a #Unique annotation that checks with the database and returns a error message into BindingResult on POST handler.
An example would be great.
UPDATE
I tried the following way to make a custom validator:
The objects (note I have added #valid to get the validator messages to navigate up to BindingResult)
Person.java
#Entity
public class Person {
public Person() {}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// other stuff
#UniqueNid
private BigInteger nid;
EpisodePerson.java
#Entity
public class EpisodePerson {
public EpisodePerson(){};
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#Valid
private Person person;
EpisodeViewModel (DTO)
public class EpisodeViewModel {
#Valid
private Episode episode = new Episode();
#Valid
private List<EpisodePerson> persons = new ArrayList<>();
UniqueNid.java
#Documented
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = UniqueNiaValidator.class)
public #interface UniqueNid {
String message() default "{Duplicate ID}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
UniqueNidValidator.java
public class UniqueNidValidator implements ConstraintValidator<UniqueNid, BigInteger> {
public UniqueNidValidator(){};
private PersonRepository personRepository;
#Autowired
public void setPersonRepository(PersonRepository personRepository) {this.personRepository = personRepository;}
public UniqueNidValidator(PersonRepository personRepository) {
this.personRepository = personRepository;
}
#Override
public void initialize(UniqueNid constraint) {
}
#Override
public boolean isValid(BigInteger nid, ConstraintValidatorContext context) {
return nid != null && personRepository.existsByNid(nid);
}
}
PersonRepository.java
...
Boolean existsByNid(BigInteger nid);
...
Application.java
#SpringBootApplication
#EnableAutoConfiguration(exclude = { org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class })
public class Demo3Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(WebApplicationInitializer.class);
}
public static void main(String[] args) {
SpringApplication.run(Demo3Application.class, args);
}
#Bean
public javax.validation.Validator localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
}
When I go to submit a person I get :
Stack Trace (abbreviated)
java.lang.NullPointerException: null
at com.example.validators.UniqueNidValidator.isValid(UniqueNidValidator.java:31) ~[main/:na]
UPDATE 2
I have also tried this configuration
public class UniqueNidValidator implements ConstraintValidator<UniqueNid, BigInteger> {
public UniqueNidValidator(){};
private PersonRepository personRepository;
public UniqueNidValidator(PersonRepository personRepository) {
this.personRepository = personRepository;
}
#Override
public void initialize(UniqueNid constraint) {
}
#Override
public boolean isValid(BigInteger nid, ConstraintValidatorContext context) {
System.out.println("About to check " +nid.toString());
System.out.println("person repo " +personRepository.toString() );
return personRepository.existsByNid(nid);
}
}
which gives:
java.lang.NullPointerException: null
at com.example.validators.UniqueNiaValidator.isValid(UniqueNiaValidator.java:29) ~[main/:na]
When I try to print the repo to console.
You'll need to create a custom validation that checks the database. For the database check you can obviously use the probably already existing Spring Data Repository and it's exists() method.
A custom validation consists of an annotation to mark the fields to be checked and a class implementing the actual check.
On minor challenge is that the class needs a default constructor and doesn't really support injecting dependencies. So anything you need, you have to basically access from some static reference, including e.g. the repository. So you probably have a separate bean which puts the repository into that static reference.
Such a bean that "catches" a repository and makes it available in a static variable might look like this.
#Component
public class RepositoryCatcher{
public static MyRepository;
public RepositoryCatcher(MyRepository r){
repository = r;
}
}
From the exception you mentioned it seems that the only possible NullPointerException is when the personRepository is incorrectly injected to the validator.
Please give a try to the solution below:
Remove the following bean from your Demo3Application and let Spring Boot create the default one instead.
#Bean
public javax.validation.Validator localValidatorFactoryBean() {
return new LocalValidatorFactoryBean();
}
Remove the setter for the repository from the validator but leave the dependency in the constructor as it is right now.
#Autowired
public void setPersonRepository(PersonRepository personRepository {
this.personRepository = personRepository;
}
It's not entirely true that custom validators require a default constructor as mentioned by Jens in his answer. Spring will inject dependencies based on the constructor even though a validator isn't mark as a managed component. The #Autowired annotation is also redundant.
In addition, you probably made a mistake in the condition. You should check if a person doesn't exist (Notice the ! mark in the second part).
return nid != null && !personRepository.existsByNid(nid);
I encourage you to look into a blog post which addresses your issue. Sample code is available in the GitHub repository. You can run, test it, and then compare with your solution.
This is the validator that worked and errors into BindingResult:
UniqueNidValidator.java
public class UniqueNiaValidator implements ConstraintValidator<UniqueNid, BigInteger> {
public UniqueNiaValidator(){};
#Autowired
private PersonRepository personRepository;
public UniqueNiaValidator(PersonRepository personRepository) {
this.personRepository = personRepository;
}
#Override
public void initialize(UniqueNid constraint) {
}
#Override
public boolean isValid(BigInteger nid, ConstraintValidatorContext context) {
return !personRepository.existsByNid(nid);
}
}
Note the !personRepository.existByNid(nid);
Further more the reason that the repo was blank the second time around was because it was getting called twice as outlined here
But checking for RDBMS constraint violations on Beans probably isn't a good idea anyway.

Spring autowire trouble with generic parameter

I try to use generic parameter in autowire but it doesn't work. My goal is to create a generic JSON controller with Spring 4.1.3 and Hibernate
I have a generic abstract class , and I use it to create service via a model objec(the same as my DAO) as generic parameter.
The code of my AbstractService
public interface IGenericService<T extends Serializable> extends IOperations <T>{}
public interface IOperations<T extends Serializable> {
T findOne(final long id);
List<T> findAll();
void create(final T entity);
T update(final T entity);
void delete(final T entity);
void deleteById(final long entityId);
List<T> findByField(String field, String value);
T save(final T entity);
}
//MyAbstractService (generic service)
public abstract class AbstractService<T extends Serializable> implements
IGenericService<T> {
public static final Logger logger = LoggerFactory
.getLogger(AbstractService.class);
public AbstractService(){}
...
#Override
#Transactional
public T update( T entity) {
logger.debug("public T update( T entity)");
return getDao().update(entity);
}
...
}
Now I create a SecuredUserService with this abstract service
#Transactional
#Component //(value = "userService")
#Qualifier("userService")
public class UserService extends AbstractService<SecuredUser> implements
IUserService {
// I override the method upate of the abstract service
#Override
#Transactional
public SecuredUser update(SecuredUser user){
... // password encoding for example
}
}
public interface IUserService extends IGenericService<SecuredUser> {
T findOne(final long id);
...
}
In my JUnit test I made autowire with this code :
#Autowire
IGenericService<SecuredUser> userGenericService;
Or
#Autowire
IUserService userService;
At this point every thing is ok, I use the overrided method of userService and not those of abstractService. I pass my Junit Test. An I create a package.
Now I want to make generic spring mvc controller to handle common Json request GET/PUT/DELETE/POST :
//Generic Controller
public abstract class GenericSecuredController <MODEL extends Serializable> extends CommonSecuredController {
/**
* spring generic service retrieve by MODEL class type
*/
#Autowired
private IGenericService <MODEL> genericService;
/**
* Spring generic URI retrieve by MODEL class type
*/
#Autowired
private IGenericURI<MODEL> genericURI ;
...
}
// interface to manage URI in a generic way
public interface IGenericURI<MODEL extends Serializable> {
// root for the controller
public static String CONTROLLER_MAPPING="" ;
// path to the file system
public static String PATH_MAPPING = "";
// key to retrieve data in path
public static String PATH="{id}";
// Json REST SERVICE MappedUri
public static String JSON_DUMMY = "/dummy";
public static String JSON_GET = "/" + PATH;
public static String JSON_GET_ALL = "";
public static String JSON_CREATE = "";
public static String JSON_DELETE = "/" + PATH;
public static String JSON_UPDATE = "/" + PATH;
public static String HTML_VIEW = "/{view}.view.html";
public String getControllerMapping() ;
public String getPathMapping() ;
}
// The specific URI for the SecuredUser model object
#Component
public class SecuredUserURI implements Serializable, IGenericURI<SecuredUser> {
public static final String CONTROLLER_MAPPING = "/user";
public static final String PATH_MAPPING = "user";
public String getControllerMapping() {
return CONTROLLER_MAPPING;
}
public String getPathMapping() {
return PATH_MAPPING;
}
}
Now I could create a specific controller for SecuredUser like this :
public class UserController extends GenericSecuredController<SecuredUser> {
/**
* creator to set Class type for the GenericSecuredController<MODEL>
*/
public UserController() {
super(SecuredUser.class);
}
}
The problem appear at this point. The autowire of the
IGenericURI<MODEL>
work fine, but the autowiring with
IGenericService <MODEL> genericService;
doesn't use the overrided specific method of the userService but the abstract method with common behaviour!!!
So my question is :
Is it possible to autowire bean with generic parameter like in my example.
Maybe there is to many level for Spring autowiring .
Other information :
As workaround, I try to pass the userService as parameter of the contoller but, the same behaviour: the generic service use the abstract method.
UPDATE : If I autowire IGenericService genericService in the UserController and create a new handler, the specific service is call.
Thanks

Resources