Mr.Robot
Mr.Robot

Reputation: 416

Spring-JPA @Transactional causing error. Why adding @EnableAspectJAutoProxy(proxyTargetClass = true) fixes it?

Problem: I am setting up Spring + JPA + MYSQL database. However, once I've set it up, I always get the following error:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'lt.robot.dao.UserJPADaoImpl' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:353)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:340)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1090)
at lt.robot.main.App.main(App.java:15)

This problem was solved here. Because my config file is in Java, I've added annotation (below) and now it works.

@EnableAspectJAutoProxy(proxyTargetClass = true)

Question: Why do I need it? I've looked at "Spring in Action 4th edition", "Spring Data", numerous online tutorial, but none of them annotates Config class with AspectJProxy. How does my application setup differ from those found online (even Spring's github examples).

My Setup

Package structure:

lt.robot.main
         App.class
         AppConfig.class
lt.robot.entity
         User.class
lt.robot.dao
         UserJPADao.interface
         UserJPADaoImpl.class

pom.xml dependencies

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>io.spring.platform</groupId>
            <artifactId>platform-bom</artifactId>
            <version>Brussels-SR4</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>


<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
        <version>1.5.0.RELEASE</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
    </dependency>

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
    </dependency>
</dependencies>

App.class

import lt.robot.dao.UserJPADaoImpl;
import lt.robot.entity.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App 
{
    public static void main( String[] args )
    {
        ApplicationContext ctx = 
            new AnnotationConfigApplicationContext(AppConfig.class);

        UserJPADaoImpl userDaoImpl = ctx.getBean(UserJPADaoImpl.class);

        System.out.println(userDaoImpl.get(2));

        User userNew = new User();
        userNew.setUser_name("NewUser");
        userDaoImpl.add(userNew);
   }
}

AppConfig.class

@Configuration
@EnableTransactionManagement
@ComponentScan("lt.robot")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {

    @Bean
    public DataSource getDataSource() {

        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring1");
        dataSource.setUsername("root");
        dataSource.setPassword("pass");
        return dataSource;
    }


    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(){

        HibernateJpaVendorAdapter vendorAdapter = new 
            HibernateJpaVendorAdapter();
        vendorAdapter.setDatabase(Database.MYSQL);
        vendorAdapter.setGenerateDdl(true);

        LocalContainerEntityManagerFactoryBean factory = 
                new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan("lt.robot.entity");
        factory.setDataSource(getDataSource());
        return factory;
    }

    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager(){
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return txManager;
    }
}

User.class

@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private int user_id;

    @Column(name = "user_name")
    private String user_name;
    //Getters & Setters
}

UserJPADaoImpl.class

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import lt.robot.dao.UserJPADao;
import lt.robot.entity.User;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
@Transactional(transactionManager = "transactionManager")
public class UserJPADaoImpl implements UserJPADao {

    @PersistenceContext
    private EntityManager em;

    public void add(User t) {
        em.persist(t);
    }

    public User get(int id) {
        return em.find(User.class, id);
    }
}

Upvotes: 2

Views: 1163

Answers (2)

Ammar
Ammar

Reputation: 4024

Your answer lies here and here (bean proxy mechanism used by Spring)

Since UserJPADaoImpl class is marked @Transactional default JDK interface based proxy is created for it as it implements interface UserJPADao.

The statement ctx.getBean(UserJPADaoImpl.class) signals Spring to specifically look for the bean of type UserJPADaoImpl only and not of UserJPADao; which is what created by default as explained above.

Thus Spring application context could not find any bean of type UserJPADaoImpl and the below exception is encountered.

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'lt.robot.dao.UserJPADaoImpl' available

Thus to signal Spring to look for specific bean type you can have either of below

  • Instead of bean lookup for implementation class via ctx.getBean(UserJPADaoImpl.class) look up bean of it's interface type i.e. UserJPADao via ctx.getBean(UserJPADao.class)
  • Tell Spring to use CGLIB class based proxies instead of default JDK interface based proxies (if bean lookup is required by the implementing class only) - which is what you did via annotation @EnableAspectJAutoProxy(proxyTargetClass = true)

Mostly in tutorials the first approach is used because of which you don't see any mention of @EnableAspectJAutoProxy(proxyTargetClass = true) there as they rely on default JDK interface based proxies and bean lookup via interface instead of its implementation.

Let know in comments if you need any further information.

Upvotes: 2

Ajit Soman
Ajit Soman

Reputation: 4084

Since you are using Spring-Data-JPA , I would suggest you to create repository using JPARepository inteface like this:

@Repository
public interface UserRepository extends JpaRepository<User, Serializable>{
    User findByUser_id(int id); 
}

And in your database configuration use @EnableJpaRepositories

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "com.example.repository")
public class AppConfig {
    ..
    ..
    ..
}

Create a service and then autowire UserRepository like this and use its method .

@Service
public class UserService {
    @Autowired
    UserRepository userRepository;

    public User saveUser(User user){
        return userRepository.save(user);
    }

    public List<User> getAllUsers(){
        return userRepository.getAll();
    }

    public User getUserById(int id){
        return userRepository.findByUser_id(id);
    }

}

Create a controller and autowire UserService and use its method.

Upvotes: 0

Related Questions