AOP. Hibernate EventListeners - spring

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.

Related

How to #Autwired MessageSource in spring into Entity class correctly?

I have the following entity in spring boot application:
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#Audited
#XmlRootElement
#XmlAccessorType(XmlAccessType.NONE)
#Table(name = "currency", catalog = "currency_db")
public class Currency implements java.io.Serializable {
#Autowired
Messages messages;
As for message, it just a container of spring MessageSource here it is:
#ApplicationScope
#Component
#Slf4j
public class Messages {
#Autowired
private MessageSource messageSource;
private MessageSourceAccessor accessor;
#PostConstruct
private void init() {
accessor = new MessageSourceAccessor(messageSource, Locale.ENGLISH);
log.info("Messages initialized");
}
public String get(String code) {
return accessor.getMessage(code);
}
}
I'm getting the following error when run mvn clean install. Any idea what I'm missing here?
org.hibernate.MappingException: Could not determine type for: com.company.currencyservice.Messages, at table: currency, for columns: [org.hibernate.mapping.Column(messages)]
It's looks like hibernate think it's a column. Thanks.
Entities are not Spring beans and therefor you cannot use dependency injection in entities.
If you want to access a Spring bean from within an entity you can use a helper class like this:
#Service
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public static <T> T bean(Class<T> beanType) {
return context.getBean(beanType);
}
public static Object bean(String name) {
return context.getBean(name);
}
#Override
public void setApplicationContext(#SuppressWarnings("NullableProblems") ApplicationContext ac) {
context = ac;
}
}
Then you can use ApplicationContextProvider.getBean(Messages.class) to get access to the Messages.

#Qualifier & #Autowired object coming as null

I am having following code below.
#Builder(toBuilder = true)
#AllArgsConstructor(access = AccessLevel.PRIVATE)
#NoArgsConstructor(access = AccessLevel.PRIVATE)
#ToString
#EqualsAndHashCode
#Configurable
public class Employee {
#Autowired
#Qualifier("findEmpByDepartment")
private Function<Long, Long> empByDepartment;
private void save() {
this.empByDepartment.getList();
}
}
and FindEmpByDepartment class below.
#Component("findEmpByDepartment")
public class FindEmpByDepartment implements Function<Long, Long> {
public void getList() {
}
....
}
My problem is I am always getting null when invoke
this.empByDepartment.getList();
line. Here this.empByDepartment is coming as null. Any idea why it is like this?
Thanks
May be you would have missed annotating any class in the flow hierarchy .
#Service, #Repository and #Controller are all specializations of #Component, so any class you want to auto-wire needs to be annotated with one of them.
IoC is like the cool kid on the block and if you are using Spring then you need to be using it all the time .
So make sure you do not have any object created with new operator in the entire flow .
#Controller
public class Controller {
#GetMapping("/example")
public String example() {
MyService my = new MyService();
my.doStuff();
}
}
#Service
public class MyService() {
#Autowired
MyRepository repo;
public void doStuff() {
repo.findByName( "steve" );
}
}
#Repository
public interface MyRepository extends CrudRepository<My, Long> {
List<My> findByName( String name );
}
This will throw a NullPointerException in the service class when it tries to access the MyRepository auto-wired Repository, not because there is anything wrong with the wiring of the Repository but because you instantiated MyService() manually with MyService my = new MyService().
For more details , you can check
https://www.moreofless.co.uk/spring-mvc-java-autowired-component-null-repository-service/

Jackson: exclude every lazy collection on #Entity classes from serialization

In my Spring 4 project with Hibernate 5 and Java-based configuration I keep facing exception "could not initialize proxy - no Session " every time Jackson tries to serialize my entity with a lazy collection. It seems Jackson fails to check if collection is lazy and triggers loading which generates the exception.
How do I make Jackson avoid serialization of every lazy-loaded collection on every #Entity-class and thus avoid constant exceptions and fails with "no Session"? The simplest working solution.
I've read many approaches neighter of which really solves this problem for me.
Any help will be appreciated (not for Spring Boot!).
Some code snippet:
#Data
#Entity
#ToString(exclude="questions")
#Table(name = "theme")
public class Theme {
#Id
#GeneratedValue(generator = "increment")
#GenericGenerator(name = "increment", strategy = "increment")
#Column(name = "id")
private Long id;
#Column(name = "title")
private String title;
#JsonInclude(JsonInclude.Include.NON_NULL)
#OneToMany // LAZY by default
#JoinColumn(name = "theme")
private List<Question> questions;// = new ArrayList<>();
}
DAO
public interface ThemeDAO extends CrudRepository<Theme, Long> {
List<Theme> findAll();
}
Exception goes here (in controller):
ObjectMapper objectMapper = new ObjectMapper();
result = objectMapper.writeValueAsString(theme);
jackson-datatype-hibernate add-on really solved the problem.
I just added HibernateAwareObjectMapper as a separate class:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.hibernate5.Hibernate5Module;
public class HibernateAwareObjectMapper extends ObjectMapper {
public HibernateAwareObjectMapper() {
registerModule(new Hibernate5Module());
}
}
And then overrode the method configureMessageConverters in MVC configurer class:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = { "learning_session.controller" })
public class WebContext extends WebMvcConfigurerAdapter implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(new HibernateAwareObjectMapper()));
super.configureMessageConverters(converters);
}
// more beans
}

Spring Inject Collection From Superclass

I have the following scenario:
class Super{
private List<String> someStringsThatWillBeDifferentForEveryInstancePerDerivedType;
}
#Component
class Derived1 extends Super{
#Autowired
private String name;
}
#Component
class Derived2 extends Super{
#Autowired
private Long configId;
}
I have a different List defined as Spring bean in xml for each derived class…call them listForDerived1 and listForDerived2. How can I wire these lists into my derived classes? I attempted constructor injection but I can't seem to find any luck injecting both the collection and the other deps.
You can use constructor injection with #Qualifier.
class Super {
private List<String> someStrings;
public Super(private List<String> someStrings) {
this.someStrings = someStrings;
}
}
#Component
class Derived1 extends Super {
#Autowired
public Derived1(#Qualifier("listForDerived1") List<String> listForDerived1, OtherBean bean) {
super(listForDerived1);
}
}
#Component
class Derived2 extends Super {
#Autowired
public Derived1(#Qualifier("listForDerived2") List<String> listForDerived1, OtherBean bean) {
super(listForDerived2);
}
}
Also see official Spring doc: http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#beans-autowired-annotation-qualifiers

#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