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

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
}

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.

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.

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.

JPA #EntityListener does not work as expected

I am integrating Spring4 and Hibernate5, but there is a problem that I can't resolve.
I use #EntityListener annotation on the BaseEntity class that is a super class for other business model.
Also I use #MappedSuperclass on the BaseEntity.
But it don't work!
Use Spring base annotation and run application successfully.
Also I inserted a record to db.
So I think my configuration of project is current.
Any body let me know why?
Thanks very much.
This is BaseEntity class.
#MappedSuperclass
#EntityListeners(EntityListener.class)
public class BaseEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, updatable = false)
private Date createDate;
#Column(nullable = false)
private Date modifyDate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Date getModifyDate() {
return modifyDate;
}
public void setModifyDate(Date modifyDate) {
this.modifyDate = modifyDate;
}
}
This is EntityListener class.
public class EntityListener {
#PrePersist
public void prePersist(BaseEntity entity) {
entity.setCreateDate(new Date());
entity.setModifyDate(new Date());
}
#PreUpdate
public void preUpdate(BaseEntity entity) {
entity.setModifyDate(new Date());
}
}
The following is my project configuration base on Spring annotation.
#Configuration
#EnableWebMvc
//#ImportResource({ "classpath:xxxxx.xml" })
#PropertySources({
#PropertySource("classpath:application.properties")
})
#ComponentScan({"com.yeager.admin.persistence","com.yeager.admin.web","com.yeager.admin.service","com.yeager.admin.common"})
#EnableAspectJAutoProxy
//#EnableRetry
public class AppConfig {
#Bean(name = "multipartResolver")
public CommonsMultipartResolver getResolver() throws IOException {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
return resolver;
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Bean
public static SpringContext springContext() {
return new SpringContext();
}
}
The main configuration about DAL like this,
#Configuration
#EnableTransactionManagement
#PropertySource({"classpath:persistence-mysql.properties"})
public class PersistenceConfig {
#Autowired
private Environment env;
public PersistenceConfig() {
super();
}
#Bean
public LocalSessionFactoryBean sessionFactory() {
final LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan("com.yeager.admin.persistence.entity");
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Bean
public DataSource dataSource() {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
try {
comboPooledDataSource.setDriverClass(env.getProperty("jdbc.driver"));
} catch (PropertyVetoException e) {
e.printStackTrace();
}
comboPooledDataSource.setJdbcUrl(env.getProperty("jdbc.url"));
comboPooledDataSource.setUser(env.getProperty("jdbc.username"));
comboPooledDataSource.setPassword(env.getProperty("jdbc.password"));
comboPooledDataSource.setInitialPoolSize(Integer.valueOf(env.getProperty("datasource.pool.initialPoolSize")));
return comboPooledDataSource;
}
#Bean
public PlatformTransactionManager transactionManager() {
final HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(sessionFactory().getObject());
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
private final Properties hibernateProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
hibernateProperties.setProperty("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
hibernateProperties.setProperty("hibernate.generate_statistics",env.getProperty("hibernate.generate_statistics"));
hibernateProperties.setProperty("hibernate.jdbc.fetch_size", env.getProperty("hibernate.jdbc.fetch_size"));
hibernateProperties.setProperty("hibernate.jdbc.batch_size", env.getProperty("hibernate.jdbc.batch_size"));
hibernateProperties.setProperty("hibernate.max_fetch_depth", env.getProperty("hibernate.max_fetch_depth"));
hibernateProperties.setProperty("hibernate.cache.use_second_level_cache",env.getProperty("hibernate.cache.use_second_level_cache"));
hibernateProperties.setProperty("hibernate.cache.use_query_cache",env.getProperty("hibernate.cache.use_query_cache"));
// hibernateProperties.setProperty("hibernate.cache.provider_class",env.getProperty("hibernate.cache.provider_class"));
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "update");
return hibernateProperties;
}
}
I use LocalSessionFactoryBean class of Hibernate rather than EntityManager class of JPA. I wonder if this cause ?
--------------- 6.19 --------------
I am wrong. I don't should use #EntityListener annotation base on Spring LocalSessionFactoryBean class.
For hibernate5, there is a special configuration way.
http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#annotations-jpa-entitylisteners
Now, I modify my code as following,
#Component
public class EntityEventListener {
#Autowired
private SessionFactory sessionFactory;
#PostConstruct
public void registerListeners(){
EventListenerRegistry eventListenerRegistry = ((SessionFactoryImplementor) sessionFactory).getServiceRegistry().getService(EventListenerRegistry.class);
eventListenerRegistry.prependListeners(EventType.PRE_INSERT, PreInsertEntityListener.class);
}
}
PreInsertEntityListener
public class PreInsertEntityListener implements PreInsertEventListener {
#Override
public boolean onPreInsert(PreInsertEvent event) {
// if (event.getEntity() instanceof AdminUser){
// ((AdminUser) event.getEntity()).setCreateDate(new Date());
// ((AdminUser) event.getEntity()).setModifyDate(new Date());
// }
BaseEntity baseEntity = (BaseEntity) event.getEntity();
baseEntity.setCreateDate(new Date());
baseEntity.setModifyDate(new Date());
return false;
}
}
But, I have a other problem.
I read hibernate doc and search many information about this. My code don't work already when I insert entity data.
Please help me, thanks!
Although you did neither post the concrete / derived entity nor the business code to persist it, the code you posted seems correct.
For giving it a small test I added a generated UID to the super class and created a concrete entity:
import javax.persistence.Entity;
#Entity
public class DerivedEntity extends BaseEntity {
private static final long serialVersionUID = -6441043639437893962L;
}
And since you mentioned Spring, here is a Spring Data JPA repository to save it:
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface DerivedEntityRepository extends CrudRepository<DerivedEntity, Long> {
}
This small test should show that the (#PrePersist) listener works:
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
#RunWith(SpringRunner.class)
#Transactional
#SpringBootTest
public class DerivedEntityRepositoryTests {
#Autowired
private DerivedEntityRepository derivedEntityRepository;
#Test
public void insertDerivedEntity() {
DerivedEntity entity = new DerivedEntity();
entity = derivedEntityRepository.save(entity);
assertThat(entity.getCreateDate()).isNotNull();
}
}
And just to mention it, if you don't want to enhance your custom listener in future, the existing Spring Data JPA AuditingEntityListener does exactly what you are doing at the moment (and even more). In this case you could just enhance a #Configuration class with #EnableJpaAuditing and modify your BaseEntity as following:
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public class BaseEntity implements Serializable {
// ...
#CreatedDate
#Column(nullable = false, updatable = false)
private Date createDate;
#LastModifiedDate
#Column(nullable = false)
private Date modifyDate;
// ...
}
That would make your custom EntityListener dispensable.
Just take a look Spring JPA Auditing for more information. If you want to enhance auditing with Hibernate, try Hibernate Envers.
I ran into this same issue and in my case the listener defined with #EntityListeners was referring to class (not in the same classloader) in another package and it wasn't being scanned. After adding the class to my persistence context it began working as expected.
So always be sure that any classes related to the persistence are added to the persistence context.
Thanks very much for everyone. I have resolved this problem.
I will share my solution, hope it's helpful for you if you are doing same things.
First, my starting point is wrong. Because I use JPA before, so I use acquiescently #EntityListener annotation when I integrate Spring4 and Hibernate5.
Then, I read Hibernate doc and many relevant article and found there is a new way to implement entity listener. See hibernate doc
Finally, my solution is following.
This is my BaseEntity class.
#MappedSuperclass
public class BaseEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(nullable = false, updatable = false)
private Date createDate;
#Column(nullable = false)
private Date modifyDate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Date getModifyDate() {
return modifyDate;
}
public void setModifyDate(Date modifyDate) {
this.modifyDate = modifyDate;
}
}
First of all, you need to define EntityListener class.
public class EntityListener implements PreInsertEventListener, PreUpdateEventListener {
private static final String CREATE_DATE_PROPERTY = "createDate";
private static final String MODIFY_DATE_PROPERTY = "modifyDate";
#Override
public boolean onPreInsert(PreInsertEvent event) {
if (event.getEntity() instanceof BaseEntity){
//property name of entity
String[] propertyNames = event.getPersister().getEntityMetamodel().getPropertyNames();
//property value of entity
Object[] state = event.getState();
for (int i = 0; i < propertyNames.length ; i ++) {
if (CREATE_DATE_PROPERTY.equals(propertyNames[i]) || MODIFY_DATE_PROPERTY.equals(propertyNames[i])){
state[i] = new Date();
}
}
}
return false;
}
#Override
public boolean onPreUpdate(PreUpdateEvent event) {
if (event.getEntity() instanceof BaseEntity){
//property name of entity
String[] propertyNames = event.getPersister().getEntityMetamodel().getPropertyNames();
//property value of entity
Object[] state = event.getState();
for (int i = 0; i < propertyNames.length ; i ++) {
if (MODIFY_DATE_PROPERTY.equals(propertyNames[i])){
state[i] = new Date();
}
}
}
return false;
}
}
Last, you should register entity event listener.
#SuppressWarnings("unchecked")
#Component
public class EntityEventListenerRegistry {
#Autowired
private SessionFactory sessionFactory;
/**
* EventListenerRegistry:http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#annotations-jpa-entitylisteners
*/
#PostConstruct
public void registerListeners(){
EventListenerRegistry eventListenerRegistry = ((SessionFactoryImplementor) sessionFactory).getServiceRegistry().getService(EventListenerRegistry.class);
eventListenerRegistry.prependListeners(EventType.PRE_INSERT, EntityListener.class);
eventListenerRegistry.prependListeners(EventType.PRE_UPDATE, EntityListener.class);
}
}

Accessing to Spring beans in Hibernate managed entity method marked by #PostLoad

I would like to enrich entity with additional data on load from DB inside #PostLoad.
How can I access to Spring managed beans inside #PostLoad method?
I use ugly solution with static accessor:
#Service
public class StaticApplicationContext implements ApplicationContextAware {
private static ApplicationContext ctx = null;
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ctx = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return ctx;
}
}
#Entity
public class Car {
#Id
private Long id;
...
#Transient
private List<XType> details;
#PostLoad
private void onLoad() {
XTypeRepository repo = StaticApplicationContext.getCtx()
.getBean(XTypeRepository.class) ;
this.details = repo.findByCarId(this.id);
}
}
Corresponding idea for static access described in Accessing spring beans in static method
Is there more idiomatic solution / framework sugar?
#OneToMany
#JoinFormula(value = "SELECT * FROM xTypeTable xt WHERE xt.carId = id")
private List<XType> pastArticles;
Can't you just use JoinFormula and load the data with standard tools? Just write SQL which retrieves data for the XType. In the example above replace id with column name of id property (if it's different)

Resources