javaspringhibernatemaven

Get a NoSuchBeanDefinitionException in my DAO Spring + Hibernate + MySQL code


I have code based on DAO architecture with Maven on IDEA Ultimate 2024.2.2 In it, I use Spring + Hibernate to work with the MySQL database and Project Lombok to work with User entity annotations. It would seem that I set everything up according to the instructions on the Internet, but my Spring doesn’t want to start. I'm trying to execute the following code:

public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
        ApplicationContext context = new AnnotationConfigApplicationContext("applicationContext.xml");

        UserDaoHibernateImpl userService = context.getBean(UserDaoHibernateImpl.class);

        userService.createUsersTable();
    }
}

but this code gives me the following exception: Console:

Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'jm.task.core.jdbc.dao.UserDaoHibernateImpl' available
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:343)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:334)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1261)
    at jm.task.core.jdbc.Application.main(Application.java:17)

and even if I try to execute this code from my class for tests, I get the same exception.

My project tree:


/kata_preproj_with_lombok
│ pom.xml
├── /src
│   ├── /main
│   │   ├── /java
│   │   │   ├── /jm.task.core.jdbc
│   │   │   │   ├── Application.class // this is the Main class I'm trying to run
│   │   │   │   ├── /dao
│   │   │   │   │   ├── Userdao.java
│   │   │   │   │   ├── UserDaoHibernateImpl.java
│   │   │   │   ├── /model
│   │   │   │   │   ├── User.class // entity
│   │   │   │   ├── /service // dao business logic
│   │   │   │   │   ├── UserService.class
│   │   │   │   │   ├── UserServiceImpl.class // contains UserDaoHibernateImpl methods
│   │   │   │   ├── Util // connection directory
│   │   │   │   │   ├── Util.class
│   │   ├── /resources
│   │   │   ├── database.properties  // properties for connecting to the MySQL database (task requirement)
│   │   │   ├── log4j.properties // logger properties
│   │   │   ├── applicationContext.xml // Spring config with beans
├── /test
│   ├── /java
│   │   ├── UserServiceTest.java // contains all tests for project

My UserDaoHibernateImpl:

@Repository
@Transactional
public class UserDaoHibernateImpl implements UserDao {

    private static final Logger logger = LoggerFactory.getLogger(UserDaoHibernateImpl.class);

    private final SessionFactory sessionFactory;

    @Autowired
    public UserDaoHibernateImpl(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    @Override
    @Transactional
    public void createUsersTable() {
        logger.debug("Creating users table");
        try {
            Session session = sessionFactory.getCurrentSession();
            NativeQuery<?> query = session.createNativeQuery("CREATE TABLE IF NOT EXISTS users (" +
                    "id BIGINT PRIMARY KEY AUTO_INCREMENT," +
                    "name VARCHAR(45)," +
                    "lastname VARCHAR(45)," +
                    "age TINYINT)");
            query.executeUpdate();
            logger.info("Users table created successfully.");
        } catch (Exception e) {
            logger.error("Error creating Users table.", e);
            throw new RuntimeException(e);
        }
    }

    @Override
    @Transactional
    public void dropUsersTable() {
        logger.debug("Start dropping users table");
        try {
            Session session = sessionFactory.getCurrentSession();
            NativeQuery<?> query = session.createNativeQuery("DROP TABLE IF EXISTS users");
            query.executeUpdate();
            logger.info("Users table dropped successfully.");
        } catch (Exception e) {
            logger.error("Error dropping Users table.", e);
            throw new RuntimeException(e);
        }
    }

    @Override
    @Transactional
    public void saveUser(String name, String lastName, byte age) {
        logger.debug("Start saving User");
        try {
            Session session = sessionFactory.getCurrentSession();
            User user = new User(null, name, lastName, age);
            session.save(user);
            logger.info("User saved: {}", user);
        } catch (Exception e) {
            logger.error("Error saving User.", e);
            throw new RuntimeException(e);
        }
    }

    @Override
    @Transactional
    public void removeUserById(long id) {
        logger.debug("Start deleting User with id: {}", id);
        try {
            Session session = sessionFactory.getCurrentSession();
            User user = session.get(User.class, id);
            if (user != null) {
                session.delete(user);
                logger.info("User deleted with id: {}", id);
            }
        } catch (Exception e) {
            logger.error("Error deleting User with id: {}", id, e);
            throw new RuntimeException(e);
        }
    }

    @Override
    @Transactional(readOnly = true)
    public List<User> getAllUsers() {
        logger.debug("Start retrieving all Users");
        try {
            Session session = sessionFactory.getCurrentSession();
            List<User> users = session.createQuery("from User", User.class).getResultList();
            logger.info("All users retrieved.");
            return users;
        } catch (Exception e) {
            logger.error("Error retrieving all users.", e);
            throw new RuntimeException(e);
        }
    }

    @Override
    @Transactional
    public void cleanUsersTable() {
        logger.debug("Start cleaning Users table");
        try {
            Session session = sessionFactory.getCurrentSession();
            NativeQuery<?> query = session.createNativeQuery("TRUNCATE TABLE users");
            query.executeUpdate();
            logger.info("Users table cleaned successfully.");
        } catch (Exception e) {
            logger.error("Error cleaning Users table.", e);
            throw new RuntimeException(e);
        }
    }
}

My User entity with Project Lombok annotations:

@Data                   // equals and hashCode; getters and setters; and more
@NoArgsConstructor      // constructor without args for Reflection API that uses in Hibernate
@AllArgsConstructor     // constructor with all args from fields of class
@Builder                // for creating and initializing class
@Entity                 // this class is entity of Hibernate
@Table(name = "users")  // table name of database
public class User implements Serializable {
    @Serial
    private static final long serialVersionUID = 42L;

    @Id     // primary (first) key ---- ID type needs to be Serializable
    @GeneratedValue(strategy = GenerationType.IDENTITY) // auto increment ID value in table
    @Column(name = "id")  // column name in table  (default is property name)
    private Long id;

    @Column(name = "name") // this name needs for great mapping
    private String name;

    @Column(name = "lastname")
    private String lastName;

    @Column(name = "age")
    private Byte age;
}

My UserServiceImpl:

@Service
public class UserServiceImpl implements UserService {
    private final UserDaoHibernateImpl userDao;

    @Autowired
    public UserServiceImpl(UserDaoHibernateImpl userDao) {
        this.userDao = userDao;
    }

    @Override
    @Transactional
    public void createUsersTable() throws SQLException {
        userDao.createUsersTable();
    }

    @Override
    @Transactional
    public void dropUsersTable() throws SQLException {
        userDao.dropUsersTable();
    }

    @Override
    @Transactional
    public void saveUser(String name, String lastName, byte age) throws SQLException {
        userDao.saveUser(name, lastName, age);
    }

    @Override
    @Transactional
    public void removeUserById(long id) throws SQLException {
        userDao.removeUserById(id);
    }

    @Override
    @Transactional
    public List<User> getAllUsers() throws SQLException {
        return userDao.getAllUsers();
    }

    @Override
    @Transactional
    public void cleanUsersTable() throws SQLException {
        userDao.cleanUsersTable();
    }
}

My connection class named Util:

public class Util {
    static ResourceBundle bundle = ResourceBundle.getBundle("database"); // database.properties

    // для JDBC присоединения
    public static Connection getConnection() {
        // переменные хранения данных от значений обращенных ссылок
        String url = bundle.getString("mysql.url");
        String username = bundle.getString("mysql.username");
        String password = bundle.getString("mysql.password");

        // реализация подключения
        try {
            return DriverManager.getConnection(url, username, password);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

My applicationContext.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- scan for components (Services, Repositories, ...) -->
    <context:component-scan base-package="jm.task.core.jdbc"/>
    <!-- copy information from database.properties -->
    <context:property-placeholder location="classpath:database.properties"/>

    <!-- DataSource Bean Declaration -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${mysql.driverClassName}"/>
        <property name="url" value="${mysql.url}"/>
        <property name="username" value="${mysql.username}"/>
        <property name="password" value="${mysql.password}"/>
    </bean>

    <!-- SessionFactory Bean Declaration -->
    <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="packagesToScan" value="jm.task.core.jdbc.model"/>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.hbm2ddl.auto">update</prop>
            </props>
        </property>
    </bean>

    <!-- Transaction Manager Declaration -->
    <bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
</beans>

I tried to transfer annotations between interfaces and classes that initialize them, I added this line: "SpringApplication.run(Application.class, args);" but it didn't help.

Also in the following code: UserDaoHibernateImpl userService = context.getBean(UserDaoHibernateImpl.class); I tried to change the class used in the.getBean() argument to an interface, and also tried to change this class to UserService and its class, but it also did not help.

In the settings, IDEA set the Spring configuration to let IDEA know that it is a configuration

ChatGPT couldn't help me with this question :(


Solution

  • The problem is your main class (in multiple ways).

    You have an XML file which needs to be loaded to parse the configuration for this you should use an XmlApplicationContext not a annotation based one like the AnnotationConfigApplicationContext. The AnnotationConfigApplicationContext is for use with @Configuration classes or component scanning directly.

    Using plain Spring Framework for bootstrapping

    What you should use is the ClassPathXmlApplicationContext instead.

    public class Application {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            UserService userService = context.getBean(UserService.class);
            userService.createUsersTable();
        }
    }
    

    In your main you don't need SpringApplication.run as that is for Spring Boot.

    Using Spring Boot for bootstrapping

    Now while this works the fact that you have SpringApplication.run in there hints add that you are actually using Spring Boot (or at least the dependencies, not Spring Boot itself). So instead of what you should do with ClassPathXmlApplicationContext you can utilize Spring Boot as well.

    @SpringBootApplication
    @ImportResouce("classpath:applicationContext.xml");
    public class Application {
      public static void main(String[] args) {
        var context = SpringApplication.run(Application.class, args);
    
        var userService = context.getBean(UserService.class);
        userService.createUsersTable();
      }
    }
    

    Now all the complexity is handled for you.

    Using Spring Boot with all its features

    In fact if you do this you could even ditch the applicationContext.xml in its totally. Rename your database.properties to application.properties and rename the properties.

    spring.datasource.url=<jdbc url here from mysql.url>
    spring.datasource.username=<value from mysql.username>
    spring.datasource.password=<value from mysql.password>
    

    Now ditch your applicationContext.xml and the @ImportResource from the Application class and re-run. Spring Boot will automatically detect your services, dao, entities and configure itself accordingly.