27

I'm developing a Spring Data JPA application, and I've created an AttributeConverter class in order to save an ArrayList of objects as JSON in a database column. Inside this class I need to make use of a class I have defined as a Spring Bean.

As the AttributeConverter class is managed by Hibernate, it seems to be instantiated before any Spring beans are created, and therefore DI does not seem to work (the Spring Bean in the AttributeConverter class is null, and I'm getting a NullPointer exception thrown). So at the moment I'm creating another instance of the said bean to be able to use it in the AttributeConverter class (which defeats the purpose of DI).

I've also tried creating a Util class (annotated with @Component) which implements ApplicationContextAware, which provides a method giving the SpringBean (cxt.getBean(BeanClass.class)). But this also is instantiated after the AttributeConverter.

Is there any idea of how this can be solved?

Thank you.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224
Effie Makri
  • 399
  • 1
  • 4
  • 12
  • duplicate to https://stackoverflow.com/a/3813725/3285997 – tom01 Nov 10 '17 at 09:33
  • 1
    @tom01 No it is not. The link you provided is about objects that one controls when they are created, in case of AttributeConverter it is not possible. – Krzysztof Krasoń Dec 28 '17 at 17:32
  • The link shows how to autowire an instance that was not initialized by the Spring container, what is different in this scenario? Surely just get reference to the AttributeConverter instance created by Hibernate and autowire using AutowireCapableBeanFactory at the appropriate app lifecycle point. – tom01 Dec 30 '17 at 13:59

3 Answers3

26

With JPA 2.2, Spring 5.1( SPR-16305) and Hibernate 5.3.0 (HHH-12135) you no longer need to use the mutable static property hack and can use dependency injection just like you would on a regular spring managed bean (note that annotations are no longer necessary):

public class MyAttributeConverter implements AttributeConverter<X,Y> {

    private final MySpringBean bean;

    public MyAttributeConverter(MySpringBean bean) {
        this.bean = bean;
    }

    public Y convertToDatabaseColumn(X attribute) {
      ...
    }

    public X convertToEntityAttribute(Y dbData) {
      ...
    }
}
Lovro Pandžić
  • 5,920
  • 4
  • 43
  • 51
  • That worked, thank you! Here's what I had to do: Upgrade to Spring Boot 2.1 (since that uses Spring 5.1), together with spring cloud version Greenwich.RELEASE (if you want to omit the parent pom). Add spring.jpa.database-platform to my application.properties, add the timezone to the DB connection URL, and remove the annotations from the AttibuteConverter (not sure if that is strictly needed). – NSV Feb 14 '19 at 17:07
  • 1
    It works with Spring Boot 2.1 unless i add `@Converter` annotation to the`MyAttributeConverter` class – Munish Chandel Mar 31 '19 at 10:09
  • You have to annotate the field with `@Converter`. I mean, it doesn't work with `@Converter(autoApply = false)` (at least not in my tests, it didn't...) – acdcjunior May 23 '19 at 01:03
  • 1
    Didn't work for me, I updated to spring-boot-starter-parent 2.5.5. My converter looks exactly the same. But application fails to start with error message `NoSuchMethodException` - complains that there is no default constructor – Oleg K. Oct 06 '21 at 09:21
  • 1
    I just get `org.hibernate.InstantiationException: Could not instantiate managed bean directly : com.my.AttributeConverter Caused by java.lang.NoSuchMethodException: com.my.AttributeConverter.()`. Is there a link to where this feature is documented properly and in detail? Seems that there's more to it than this answer gives away. Thanks! – Frans Mar 24 '22 at 14:04
  • 1
    OK, so if you've customized your JPA configuration (i.e. defining your own `LocalContainerEntityManagerFactoryBean`), this will NOT work out of the box, you need to configure the JPA property that connects the `LocalContainerEntityManagerFactoryBean` to Spring's `BeanFactory` yourself as per [Jürgen Höller's comment](https://github.com/spring-projects/spring-framework/issues/20852#issuecomment-453465206). – Frans Mar 25 '22 at 09:13
  • Closest thing to documentation about this that I can find, then, is https://docs.jboss.org/hibernate/stable/orm/javadocs/org/hibernate/cfg/AvailableSettings.html#BEAN_CONTAINER and https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/orm/hibernate5/SpringBeanContainer.html . – Frans Mar 25 '22 at 09:14
22

You can inject a bean(@Component, @Service, @Repository) inside an AttributeConverter using static properties

Setps:

  1. Set in your AttributeConverter with the following annotations: @Component, @Converter and @Configurable
  2. Define the field that you want to be autowired with the static modifier access
  3. Create an init method in order to autowire the repository
  4. Implement the methods defined in the interface AttributeConverter

Basically, the code should look like this...

// Step 1
@Component
@Converter
@Configurable
public class MyAttributeConverter implements AttributeConverter<X,Y> {
    // Where: X = the type of the entity attribute and Y = the type of the database column
    
    // Step 2
    private static MyRepository myRepository;
    
    //Step 3
    @Autowired
    public void initMyRepository(MyRepository myRepository){
        MyAttributeConverter.myRepository = myRepository;
    }
    
    // Step 4
    Y convertToDatabaseColumn(X attribute){//TODO implement method}
    X convertToEntityAttribute(Y dbData){//TODO implement method}
}

I hope it can help!!!

skwisgaar
  • 880
  • 2
  • 13
  • 31
Gustavo Ponce
  • 417
  • 6
  • 12
7

Generally Ipandzic's answer is the right hint I guess. However it did not work for me how he described it. I argument-constructor also looks a little strange in a Spring-environment. I played around a bit and was able to use an AttributeConverter of the following form (indeed you do not need the @Converter-annotation or any other on the AttributeConverter-class itself):

import javax.persistence.AttributeConverter;
import org.springframework.beans.factory.annotation.Autowired;

public class MyConverter implements AttributeConverter<String, String> {
    @Autowired
    private MyBean mybean;

    public String convertToDatabaseColumn(String value) {
        return myBean.changeValue(value);
    }

    public String convertToEntityAttribute(String dbValue) {
        return myBean.undoChange(dbValue);
    }
}

But creating this class and upgrading to Spring-Boot 2.1 (including Spring 5.1, Hibernate 5.3 and JPA 2.2) did not do the trick for me. The thing was, that I used LocalContainerEntityManagerFactoryBean to configure my persistent storage which will not enable Dependency injection for AttributeConverters. Reading the first link Ipandzic posted suggests you have to bring LocalSessionFactoryBuilder into play somehow. That's why I ended up with the following configuration:

//...

@Bean
public LocalSessionFactoryBean entityManagerFactory(DataSource dataSource, Environment env) {
    LocalSessionFactoryBean factory = new LocalSessionFactoryBean();
    factory.setDataSource(dataSource);

    // somehow tell the factory where you entitiy-definitions are, this is just
    // one possibility of doing so:
    String entityPackage = JpaMarkerModel.class.getPackage().getName();
    log.info("EntityManager will scan for entities in package [{}].", entityPackage);
    factory.setPackagesToScan(entityPackage);

    return factory;
}

@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(entityManagerFactory);
    return transactionManager;
}

//...

This "answer" is merely an addition to Ipandzi's, but maybe it helps some people to solve their problem quicker than I did.

Fencer
  • 1,026
  • 11
  • 27
  • Prefer using `@EnableEntityScan` annotation instead. – Dragas Nov 08 '19 at 13:50
  • Does that have something to do with the problem at hand? Why should I prefer using that annotation? – Fencer Nov 18 '19 at 09:23
  • 1
    There is a way to continue using a `LocalContainerEntityManagerFactoryBean` by setting a bean factory for Hibernate. https://github.com/spring-projects/spring-framework/issues/23968#issuecomment-570131791 – pdubs Sep 09 '20 at 19:56
  • 3
    I had a similar issue, upgraded all the dependencies but still no autowiring. The mention of `LocalContainerEntityManagerFactoryBean` was a clue - we were explicitly creating it in our application context/config and so a non-Spring BeanContainer was being used. The link to the Github issue provided the answer for me: `LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean(); emfb.getJpaPropertyMap().put(AvailableSettings.BEAN_CONTAINER, new SpringBeanContainer(beanFactory));` – Matthew Wise Sep 10 '20 at 16:38
  • what a solution what should I do explain it clearly – ZootHii Feb 09 '22 at 16:42