Wiring entities loaded via repository - spring

Inside my entities managed by spring data I need some services that should be autowired by spring.
public class MyEntity {
#Autowired
private SomeService service;
#Id
private String id;
...
}
Is it possible to tell spring to autowire the given service when loading that entity?
I know I could do something like this:
public class Worker {
#Autowired
private AutowireCapableBeanFactory autowireBeanFactory;
#Autowired
private MyEntityRepository repo;
public void doSomething() {
MyEntity entity = repo.findOne("1");
autowireBeanFactory.autowireBean(entity);
entity.useService();
}
}
Can I automate that autowiring?

Finally I had to overwrite the MappingMongoConverter. How that can be done is described under Set MongoDb converter programatically

Related

Object not initialized with autowire

I use spring boot 3
I created a object manually, FormGenerator, because everytime I use is in my advance search, some field need to be reset.
So I think the scope prototype is ok for that
#Repository
public class SchoolRepositoryCustomImpl extends SimpleJpaRepository<School, Long> implements SchoolRepositoryCustom {
#Override
public List<School> advanceSearch(SchoolSearch search) {
FormGenerator qg = new FormGenerator();
}
...
}
#Scope("prototype")
public class FormGenerator {
private int fieldCounter=0;
#Autowired
private EntityManager entityManager;
...
}
When I run application, entityManager is null?
It is null because you created the object manually by calling the constructor. You need to obtain it from the ApplicationContext. Something like this:
#Repository
public class SchoolRepositoryCustomImpl extends SimpleJpaRepository<School, Long> implements SchoolRepositoryCustom {
#Autowired
private ApplicationContext applicationContext;
#Override
public List<School> advanceSearch(SchoolSearch search) {
FormGenerator qg = applicationContext.getBean(FormGenerator.class);
}
...
}

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.

What will happen if i remove #Autowired from field/constructor injection but injecting that bean in another class

Suppose i have a class as,
#Repository
public class StudentServiceDao{
private final StudentClient client;
private final StudentValidator validator;
#Autowired <----
public StudentServiceDao(StudentClient studentClient){
client = studentClient;
validator = new StudentValidator(studentClient.getIdentifier());
}
public List<Student> getStudent(Request request){
StudentRS studentRS= client.getStudentList(request);
validator.validate(studentRS);
return StudentMapper.map(studentRS);
}
}
Now i have another class as,
#Component
public class StudentServiceDaoImpl{
#Autowired
private StudentServiceDao studentServiceDao;
public list<Student> retrieveStudent (Request request){
return studentServiceDao.getStudent(request);
}
}
Now if i remove #Autowired from StudentServiceDao what will happen and why ?
Autowiring can happen multiple ways.
For a few years now (currently 2020) all of these are valid ways to autowire dependencies:
Explicit constructor autowire annotation:
#Repository
public class StudentServiceDao {
private final StudentClient client;
private final StudentValidator validator;
#Autowired
public StudentServiceDao(StudentClient studentClient){
client = studentClient;
validator = new StudentValidator(studentClient.getIdentifier());
}
}
Implicit constructor autowire:
#Repository
public class StudentServiceDao {
private final StudentClient client;
private final StudentValidator validator;
public StudentServiceDao(StudentClient studentClient){
client = studentClient;
validator = new StudentValidator(studentClient.getIdentifier());
}
}
Explicit field autowire:
#Repository
public class StudentServiceDao {
#Autowired
private final StudentClient client;
#Autowired
private final StudentValidator validator;
}
Pick which ever one makes the most sense for you. I personally like implicit constructor. I think it makes instantiating the bean for testing easier with mocks. All types are valid.
5 or 6 years ago, before java config took over, there were other requirements like getters/setters needing to be present, xml files needing to specify all the beans, etc. But those are mostly gone and if you are working on a modern spring app you won't encounter them.
As to why, I have no idea, this is just how it is.

How to solve too many #Value in Spring Boot service

I build Spring Boot service and have BookService with almost 15 different #Value fields. They all are constants and can be shared into other places into application. Some of them relate to Amazon access, some to Redis keys.
Is there any way to reduce amount of values consumed from application.properties? Is it normal industry practice to have anything like this? Can I use any other approach for this?
BookService.java snippet:
#Service
public class BookService {
#Autowired
private StringRedisTemplate redisTemplate;
#Value("${redis.orders.timestampKey}")
private String redisOrdersTimestampKey;
#Value("${redis.orders.retrunsKey}")
private String redisOrdersReturnsKey;
#Value("${redis.orders.quantityKey}")
private String redisOrdersQuantityKey;
...
}
My main concern that design I built is far away from industry best-practices and may be hard to work in the future.
Just use some kind of spring's ConfigurationProperties. Here you can find more.
this should work for you:
#ConfigurationProperties(prefix = "redis")
public class RedisProperties {
#NestedConfigurationProperty
private Order orders;
// ..getters and setters
public static class Order{
private String timestampKey;
private String retrunsKey;
private String quantityKey;
// ..getters and setters
}
}
then add this dependency to your pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
and create some configuration like this:
#Configuration
#EnableConfigurationProperties(RedisProperties.class)
public class SomeConfiguration {
}
you can then use RedisProperties like standard spring bean and inject it where you need.
Check out the documentation: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-typesafe-configuration-properties
You create a properties class and autowire it in your service. You can also validate your properties. Your class could look as below (note: this is with Lombok):
#Configuration
#ConfigurationProperties(prefix = "redis.orders")
#NoArgsConstructor
#Getter
#Setter
#Validated
public class RedisOrderProperties {
#NotEmpty
private String redisOrdersTimestampKey;
#NotEmpty
private String redisOrdersReturnsKey;
#NotEmpty
private String redisOrdersQuantityKey;
}
Autowire it in your service as follows (note: constructor injection should be favoured over field injection):
#Service
public class BookService {
private final RedisOrderProperties redisOrderProperties;
private final StringRedisTemplate redisTemplate;
#Autowired
public BookService(RedisOrderProperties redisOrderProperties, StringRedisTemplate redisTemplate) {
this.redisOrderProperties = redisOrderProperties;
this.redisTemplate = redisTemplate;
}
}

#Value returning null

I have a couple #Value annotations within my Entity Class. I am not sure why but they are both returning null. I also have "ShaPasswordEncoder" Object Autowired, which too is throwing a NullPointer Exception. I have no idea why. Please advise.
#Repository
#Configurable
#Entity
#Table(name="user")
#NamedQueries({...})
public class User implements Serializable{
#Transient private static final Logger logger = Logger.getLogger(User.class);
#Transient private static final AppUtil appUtil = new AppUtil();
#Transient #Value("some value") private String extDir;
#Transient #Value("100x100") private String imageSize;
#Transient private static byte[] salt = "FBar".getBytes();
#Transient #Autowired private ShaPasswordEncoder passwordEncoder;
....
//Default Constructor
public User(){
logger.info("TEST IMAGE => "+imageSize);
}
public String passwordEncoder(String password) {
return passwordEncoder.encodePassword(password,salt);
}
Making a JPA entity as a Spring bean is a bad design.
You should keep your entity simple: only getters and setters.
// Only JPA annotations
#Entity
#Table(name="user")
#NamedQueries({...})
public class User {
// Getters & Setters
}
Then you should delegate the business logic to service classes:
#Service
public class UserService {
#Autowired
private ShaPasswordEncoder passwordEncoder;
#Value("${conf.extDir}")
private String dir;
// Some operations ...
public void createUser(User user) {
// ...
}
public void updateUser(User user) {
// ...
}
}
Are you passing valid value expressions? For properties placeholder you can use something like:
#Value("${directory.extDirectory}")
You can also use Spring EL and get all the goodness from it using the #{value} check the docs here
Is also possible to assign a default value in case the property is not found
#Value("${directory.extDirectory:defaultValue}")
Using Spring annotations on a POJO means you are delegating the creation and the configuration of this bean to the Spring IoC Container !!!
The Spring container will supply all the required dependencies.
For example:
#Component
public class MyBean {
#Value("${data}")
private String data;
#Autowired
private MyService service;
// ...
}
When you try to instantiate a bean using the new operator, you will get null values.
MyBean bean = new MyBean();
In order to have a fully-configured bean you MUST get it from the applicationContext. The Spring container will provide all the requested dependencies.
MyBean bean = (MyBean) applicationContext.getBean(MyBean.class);
Or
#Component
public class AnotherBean {
// You are sure that Spring will create and inject the bean for you.
#Autowired
private MyBean bean;
}
Although the bean is managed by Spring it is also possible that you make a new bean yourself instead of getting it from spring container.
So below code will make a new user but it is not get from Spring context.
User user = new User()
If you use above code the #value is not applied to your bean.
If you want you must get the User from Spring by #Autowired
public class SampleService{
#Autowired
private User user;
public void Sample(){
user.getExtDir(); //here user.extDir is not null
}
}

Resources