I'am using #EntityListeners(AuditingEntityListener.class) in Spring Boot JPA application and having some issue with inserting entity. I created #RestController for REST API, #Service for extra business logic and spring's JpaRepository interfaces to access DB.
So the issue is when I want to update some entity and create some another entity in #Service (BL layer). When creating new entity from service I get exception that attribute annotated with #CreatedBy could not be null. I see that AuditorAware is empty. But if comment out creating of new entity and make update of first entity only then AuditorAware is not null and works fine.
It's also working if I create this problematic entity form #Controller.
Does anyone have similar problem and know how to solve this.
I suppose the proxyMode of AuditorAware #Bean is wrong but I don't know how to fixed it.
I changed the implementation of AuditorAware from:
#Bean
public AuditorAware<String> auditorProvider() {
return () -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return Optional.ofNullable(Objects.isNull(authentication) ? null : authentication.getName());
};
}
to:
#Bean
public AuditorAware<String> auditorProvider() {
return () -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (Objects.isNull(authentication) || !authentication.isAuthenticated() || !(authentication.getPrincipal() instanceof Principal)) {
return Optional.of("anonymous");
}
return Optional.of(authentication.getName());
};
}
which also populate created_by in test phase where there is no security context.
Related
The question seems to be duplicate but none of the answers from existing questions worked.
I am creating a custom annotation as below,
#Target({ ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface Translate {
}
I have created an Aspect as,
#Aspect
#Configuration
public class TranslateAspect {
#Around("#annotation(translate)")
public Object translate(ProceedingJoinPoint joinPoint, Translate translate) throws Throwable {
Object result = joinPoint.proceed();
System.out.println(result); //Other logic
return result;
}
}
I tried providing the complete class name with the package also. The entity is getting passed to RestController,
#Entity
#Getter
#Setter
public class Pojo {
#Translate
private String label;
}
But the translate method is not getting invoked whenever the new request is served.
Highly appreciate any help around this.
From the reference documentation : 5.2. Spring AOP Capabilities and Goals
Spring AOP currently supports only method execution join points (advising the execution of methods on Spring beans). Field interception is not implemented, although support for field interception could be added without breaking the core Spring AOP APIs. If you need to advise field access and update join points, consider a language such as AspectJ.
Spring AOP works with spring container managed beans and advicing method executions. The code shared here annotates a field and not the corresponding setter method. PCD defined is for the execution any Spring container managed bean method annotated with #Translate.
A class annotated with #Entity will not register its instance as a Spring managed bean. Do go through this StackOverflow Q&A
Pojo instance is not a Spring managed bean ( also pointed out by João Dias in his answer ).
Try this:
#Aspect
#Configuration
public class TranslateAspect {
#Pointcut("#annotation(com.full.packagename.to.annotation.Translate)")
public void anyTranslatableMethod() {
}
#Around("anyTranslatableMethod()")
public Object translate(ProceedingJoinPoint joinPoint) throws Throwable {
// ...
}
}
A working example here: https://github.com/asgarov1/springTricks/blob/main/aop/src/main/java/com/asgarov/aop/config/LoggingConfiguration.java
Try the following:
#Aspect
#Configuration
public class TranslateAspect {
#Around("#annotation(Translate)")
public Object translate(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
System.out.println(result); //Other logic
return result;
}
}
Mind the uppercase "T" in #Around("#annotation(Translate)").
UPDATE
Just noticed you are expecting the aspect to be applied to a class annotated with #Entity. These are Entities that are JPA entities but they are not Spring-managed Beans. Spring AOP only handles Spring-managed Beans so this is simply not possible.
Although I found some similar problems (here and here for example) it did not completely answer my problem, as I used spring Boot 2.4.4, or simply don't work. My problem is that repository becomes null during second (JPA) validation. Here is the detail :
I use Spring Boot, and Bean Validation, via the spring-boot-starter-validation dependency.
I have a custom validator to check if an email exists in database. First I declare the #EmailExisting annotation :
#Documented
#Constraint(validatedBy = EmailValidator.class)
#Target( { ElementType.METHOD, ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
public #interface EmailExisting {
String message() default "Email already exists";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Then the custom validator, with the repository injected to check Database :
public class EmailValidator implements
ConstraintValidator<EmailExisting, String> {
#Autowired
UserRepository userRepository;
#Override
public boolean isValid(String email,
ConstraintValidatorContext cxt) {
List<User> users = userRepository.findByEmail(email);
if (!users.isEmpty()) {
return false;
}
return true;
}
}
Finally the SPRING MVC part, with the #Valid annotation to trigger validation :
#Autowired
UserRepository userRepository;
#PostMapping(value = "/users")
public ResponseEntity addUSer(#Valid #RequestBody User user) {
user.setEmail(user.getEmail());
user.setLastName(StringUtils.capitalize(user.getLastName()));
user.setFirstName(StringUtils.capitalize(user.getFirstName()));
userRepository.save(user);
return new ResponseEntity(user, HttpStatus.CREATED);
}
Problem:
When I test the endpoint, I have a NullPointerException.
At first, during MVC validation the repository is correctly injected. But after, when we call save() method to save the entity in the controller, the control triggers again, and the repository becomes null, hence the NullPointerException.
I found a workaround, to deactivate control on JPA side, with the following configuration :
spring.jpa.properties.javax.persistence.validation.mode=none
It works well, but I think it should be possible to have the repository injected in the 2 cases?
All code is in my repository
Thank you in advance!
Spring Boot version - 2.4.4,
mongodb version - 4.4.4
In my project, I want to do entry in 2 different document of mongodb, but if one fails than it should do rollback. mongodb supports transaction after version 4.0 but only if you have at least one replica set.
In my case I don't have replica set and also cannot create it according to my project structure. I can't use transaction support of mongodb because no replica-set. So, I am using Spring Transaction.
According to spring docs, to use transaction in Spring Boot, you only need to use #transactional annotation and everything will work(i.e. rollback or commit).
I tried many things from many sources but it is not rollbacking transaction if one fail.
Demo code is here,
This is demo code, not actual project.
This is my service class.
#Service
public class UserService {
#Autowired
UserRepository userRepository;
#Autowired
UserDetailRepository userDetailRepository;
#Transactional(rollbackFor = Exception.class)
public ResponseEntity<JsonNode> createUser(SaveUserDetailRequest saveUserDetailRequest) {
try {
User _user = userRepository.save(new User(saveUserDetailRequest.getId(), saveUserDetailRequest.getFirstName(), saveUserDetailRequest.getLastName()));
UserDetail _user_detail = userDetailRepository.save(new UserDetail(saveUserDetailRequest.getPhone(), saveUserDetailRequest.getAddress()));
} catch (Exception m) {
System.out.print("Mongo Exception");
}
return new ResponseEntity<>(HttpStatus.OK);
}
}
Also tried below code but still not working,
#EnableTransactionManagement
#Configuration
#EnableMongoRepositories({ "com.test.transaction.repository" })
#ComponentScan({"com.test.transaction.service"})
public class Config extends AbstractMongoClientConfiguration{
private com.mongodb.MongoClient mongoClient;
#Bean
MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
#Bean
public com.mongodb.MongoClient mongodbClient() {
mongoClient = new com.mongodb.MongoClient("mongodb://localhost:27017");
return mongoClient;
}
#Override
protected String getDatabaseName() {
return "test";
}
}
The transaction support in Spring is only there to make things easier, it doesn't replace the transaction support for the underlying datastore being used.
In this case, it will simply delegate the starting/committing of a transaction to MongoDB. WHen using a database it will eventually delegate to the database etc.
As this is the case, the pre-requisites for MongoDB still need to be honoured and you will still need a replica.
I will try to ignore other details and make it short:
#Entity
public class User
#UniqueEmail
#Column(unique = true)
private String email;
}
#Component
public class UniqueEmailValidatior implements ConstraintValidator<UniqueEmail,String>, InitializingBean {
#Autowired private UserService userService;
#Override
public void initialize(UniqueEmail constraintAnnotation) {
}
#Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if(userService == null) throw new IllegalStateException();
if(value == null) return false;
return !userService.isEmailExisted(value);
}
}
This will work when the validation is made in Spring (Spring MVC #Valid or inject the Validator using #Autowire), everything will be fine.
But as soon as I save the entity using Spring Data JPA:
User save = userRepository.save(newUser);
Hibernate will try to instantiate a new UniqueEmailValidatior without inject the UserService bean.
So how can I make Hibernate to use my UniqueEmailValidatior component without it instantiate a new one.
I could disable hibernate validation using spring.jpa.properties.javax.persistence.validation.mode=none but I hope there is another way
Update: Here is my UserService:
#Autowired private Validator validator;
#Transactional
public SimpleUserDTO newUser(UserRegisterDTO user) {
validator.validate(user);
System.out.println("This passes");
User newUser = new User(user.getUsername(),
passwordEncoder.encode(user.getPassword()),user.getEmail(),
"USER",
user.getAvatar());
User save = userRepository.save(newUser);
System.out.println("This won't pass");
return ....
}
I would expect that Spring Boot would wire the existing validator to the EntityManager apparently it doesn't.
You can use a HibernatePropertiesCustomizer and add properties to the existing EntityManagerFactoryBuilder and register the Validator.
NOTE: I'm assuming here that you are using Spring Boot 2.0
#Component
public class ValidatorAddingCustomizer implements HibernatePropertiesCustomizer {
private final ObjectProvider<javax.validation.Validator> provider;
public ValidatorAddingCustomizer(ObjectProvider<javax.validation.Validator> provider) {
this.provider=provider;
}
public void customize(Map<String, Object> hibernateProperties) {
Validator validator = provider.getIfUnique();
if (validator != null) {
hibernateProperties.put("javax.persistence.validation.factory", validator);
}
}
}
Something like this should wire the existing validator with hibernate and with that it will make use of auto wiring.
NOTE: You don't need to use #Component on the validator the autowiring is build into the validator factory before returning the instance of the Validator.
To have the Spring beans injected into your ConstraintValidator, you need a specific ConstraintValidatorFactory which should be passed at the initialization of the ValidatorFactory.
Something along the lines of:
ValidatorFactory validatorFactory = Validation.byDefaultProvider()
.configure()
.constraintValidatorFactory( new MySpringAwareConstraintValidatorFactory( mySpringContext ) )
.build();
with MySpringAwareConstraintValidatorFactory being a ConstraintValidatorFactory that injects the beans inside your ConstraintValidator.
I suspect the ValidatorFactory used by Spring Data does not inject the validators when creating them, which is unfortunate.
I suppose you should be able to override it. Or better, you should open an issue against Spring Boot/Spring Data so that they properly inject the ConstraintValidators as it the second time in a row we have this question on SO.
The answer is quite big to post here. Please check for this article in S.O to help you with. This should help you get started.
Test Custom Validator with Autowired spring Service
The problem is hibernate will no way know spring definition. However you can make Entities to be aware of any type of javax.validation types. Hope this helps.
I'm using spring-data-jpa, and I'm trying to apply spring cache abstraction.
findByEmail() method caches well, however, user variable on retrieve() method at controller which spring-data-jpa provide DomainClassConverter always looks up DB.
In documentation, it calles findOne() to look up the resource, but the #Cacheable trigger won't work.
It seems like implementation class as SimpleJpaRepository just invoke CrudRepository instead of UserRepository which I created and put #Cacheable annotation.
Is there any way to apply #Cacheable to findOne() except for custom DomainClassConverter class ?
UserController.class
#RequestMapping(method = RequestMethod.GET, value = "/users/{user}")
public ResponseEntity retrieve(#PathVariable User user) {
logger.info("Retrieve: " + user);
return new ResponseEntity(user.toUserResponse(), HttpStatus.OK);
}
UserService.class
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
#Cacheable("Users")
User findOne(Long id);
#Cacheable("Users")
User findByEmail(String email);
User findByEmailAndPassword(String email, String password);
Long countByEmail(String email);
}
I've filed and fixed DATACMNS-620 for you.
This issue shouldn't actually occur with Spring Data Commons 1.10.0 M1 (Fowler) as we moved to the Spring Data REST RepositoryInvokerAPI which explicitly checked for custom overrides. I created a fix for the 1.9.x bugfix branch so that we fix this for users of the current release train Evans.
If you want to check this upgrade to Spring Data Commons 1.9.0.BUILD-SNAPSHOT or 1.10.0.BUILD-SNAPSHOT. If you're using Boot, you can simply set a property spring-data-releasetrain.version and set it to either Evans-BUILD-SNAPSHOT or Fowler-BUILD-SNAPSHOT.