Home > Mobile >  @Transactional in tests does not roll back changes in database
@Transactional in tests does not roll back changes in database

Time:03-30

I have a problem while run test class complexly, but not each method separately. Previously ORM was based on JDBCTemplate, I changed it to Hibernate. So when i run test class all methods make changes in database, but don't rolling them back.

AssertionFailed errors after one completed test

Test class:

package ua.com.foxminded.dao;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
import ua.com.foxminded.config.SpringDaoTestConfig;
import ua.com.foxminded.model.Course;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = SpringDaoTestConfig.class)
@Transactional
@Rollback
class CourseDaoIT {

    private static final int GENERATED_COURSES_COUNT = 3;

    @Autowired
    private CourseDao courseDao;

    private Course expectedCourse;

    @Test
    void create_shouldCreateCourse() {

        assertEquals(GENERATED_COURSES_COUNT, courseDao.getAll().size());

        expectedCourse = new Course(1L, 2021);
        Course actualCourse = courseDao.create(expectedCourse);

        assertEquals(expectedCourse, actualCourse);
    }

    @Test
    void getById_shouldReturnCourse() {

        assertEquals(GENERATED_COURSES_COUNT, courseDao.getAll().size());
        Course course1 = courseDao.create(new Course(2021));
        Course course2 = courseDao.create(new Course(2022));
        Course course3 = courseDao.create(new Course(2023));

        expectedCourse = course2;
        Optional<Course> actualCourse = courseDao.getById(expectedCourse.getId());
        assertTrue(actualCourse.isPresent());
        assertEquals(expectedCourse, actualCourse.get());
    }

    @Test
    void getAll_shouldReturnAllCourses() {

        assertEquals(GENERATED_COURSES_COUNT, courseDao.getAll().size());
        Course course1 = courseDao.create(new Course(2021));
        Course course2 = courseDao.create(new Course(2022));
        Course course3 = courseDao.create(new Course(2023));

        List<Course> expectedCourses = Arrays.asList(course1, course2, course3);
        List<Course> actualCourses = courseDao.getAll().stream().skip(GENERATED_COURSES_COUNT).collect(Collectors.toList());

        assertEquals(expectedCourses, actualCourses);
    }

    @Test
    void delete_shouldDeleteCourse() {

        assertEquals(GENERATED_COURSES_COUNT, courseDao.getAll().size());
        Course course = courseDao.create(new Course(2021));
        assertEquals(GENERATED_COURSES_COUNT   1, courseDao.getAll().size());

        courseDao.delete(course.getId());

        assertEquals(GENERATED_COURSES_COUNT, courseDao.getAll().size());
    }

    @Test
    void update_shouldUpdateCourse() {


        assertEquals(GENERATED_COURSES_COUNT, courseDao.getAll().size());
        Course course = courseDao.create(new Course(2021));
        int randomEstablishYear = course.getEstablishYear()   285;

        assertEquals(GENERATED_COURSES_COUNT   1, courseDao.getAll().size());

        course.setEstablishYear(randomEstablishYear);
        courseDao.update(course);

        Optional<Course> updatedCourse = courseDao.getById(course.getId());

        assertTrue(updatedCourse.isPresent());
        assertEquals(updatedCourse.get(), course);
    }
}

Dao class:

package ua.com.foxminded.dao;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.GenericTypeResolver;
import ua.com.foxminded.exception.ClassNotFoundException;

import javax.persistence.OptimisticLockException;
import java.util.List;
import java.util.Optional;

/**
 * DAO class AbstractDao
 *
 * @param <T> the type of ua.com.foxminded.model package classes
 */
public abstract class AbstractDao<T> {

    /**
     * Property - logger to log important actions
     */
    private static final Logger logger = LoggerFactory.getLogger(AbstractDao.class.getName());

    /**
     * Property - generic type
     */
    private final Class<T> genericType;
    /**
     * Property - session factory
     */
    private final SessionFactory sessionFactory;
    /**
     * Property - simple class name
     */
    private final String simpleClassName;

    /**
     * Constructor autowired by SessionFactory bean.
     *
     * <p> Defines session factory, using Spring
     *
     * <p> Defines child class type, using GenericTypeResolver
     *
     * <p> Defines simple class name, using generic type for logger
     *
     * @param sessionFactory autowired SessionFactory bean
     * @throws ClassNotFoundException if class of described by AbstractDao value is not found
     * @see GenericTypeResolver#resolveTypeArgument(Class, Class)
     */
    @Autowired
    @SuppressWarnings("unchecked")
    protected AbstractDao(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
        Class<?> classType = GenericTypeResolver.resolveTypeArgument(getClass(), AbstractDao.class);
        this.genericType = (Class<T>) classType;
        if (genericType == null) {
            throw new ClassNotFoundException("Generic type in AbstractDao is null");
        }
        this.simpleClassName = this.genericType.getSimpleName();
    }

    /**
     * Returns created object in table mapped by value class type
     *
     * @param value value to create
     * @return non-null value with defined id, described by AbstractDao
     */
    public T create(T value) {

        try (Session session = sessionFactory.openSession()) {

            session.beginTransaction();
            logger.debug("Creating new {} object", simpleClassName);
            value = genericType.cast(session.merge(value));
            logger.debug("{} object has been created", simpleClassName);
            session.getTransaction().commit();
            return value;
        }
    }


    /**
     * If a value with param id is present in table mapped by this class type,
     * returns an Optional describing the value, otherwise throws NullPointerException
     *
     * @param id id of searching in database object
     * @return an Optional describing the value of this Optional,
     * if a value with param id is present in table mapped by this class type, and the value
     * matches the given predicate, otherwise returns null
     * @throws NullPointerException if value with param id is not present in database
     */
    public Optional<T> getById(long id) {

        try (Session session = sessionFactory.openSession()) {

            session.beginTransaction();
            logger.debug("Getting {} object by id = {}", simpleClassName, id);
            T obtainedObject = session.get(genericType, id);
            logger.debug("{} object with id ={} has been obtained", simpleClassName, id);
            session.getTransaction().commit();
            return Optional.of(obtainedObject);
        }
    }

    /**
     * Returns a list of all values of type described this AbstractDao in mapped table of a database
     *
     * @return a list of all values of type described this AbstractDao in mapped table of a database
     */
    public List<T> getAll() {

        try (Session session = sessionFactory.openSession()) {
            session.beginTransaction();
            logger.debug("Getting all {} objects", simpleClassName);
            List<T> obtainedObjects = session.createQuery(
                "SELECT a FROM "   simpleClassName   " a", genericType).getResultList();
            logger.debug("All objects {} have been obtained", simpleClassName);
            session.getTransaction().commit();
            return obtainedObjects;
        }
    }

    /**
     * Deletes object with param id from the table mapped by the class described in AbstractDao,
     * if object with such param id is present in database, otherwise throws EntityNotFoundException
     *
     * @param id id of deleting from database object
     * @throws ua.com.foxminded.exception.EntityNotFoundException if object with such param id is not present in database
     */
    public void delete(long id) {

        try (Session session = sessionFactory.openSession()) {

            session.beginTransaction();
            logger.debug("Deleting {} object with id = {}", simpleClassName, id);
            T value = session.load(genericType, id);
            session.delete(value);
            logger.debug("{} object with id = {} has been deleted", simpleClassName, id);
            session.getTransaction().commit();
        }
    }

    /**
     * Updates object in table mapped by class type if object with id defined in param value is present,
     * otherwise throws OptimisticLockException
     *
     * @param value value to update object in database with such value's id
     * @throws OptimisticLockException if object with id defined in param value
     *                                 is not present in database
     */
    public void update(T value) {

        try (Session session = sessionFactory.openSession()) {

            session.beginTransaction();
            logger.debug("Updating {} object", simpleClassName);
            session.update(value);
            logger.debug("{} object has been updated", simpleClassName);
            session.getTransaction().commit();
        }
    }
}

I was trying to change CRUD methods in DAO. Some don't help. Others break all project. I think it has a smarter solution.

CodePudding user response:

You're using sessionFactory.openSession() and manually starting and committing transactions with Hibernate.

So those transactions are not managed by Spring.

To use Spring-managed transactions with Hibernate, use Spring's LocalSessionFactoryBean in XML config or LocalSessionFactoryBuilder in Java Config.

Then, in your DAO/repository implementation use sessionFactory.getCurrentSession() to get the current Spring-managed Session instead of opening a new Session, and remove all usage of session.beginTransaction() and session.getTransaction().commit().

You'll also need to use Spring's HibernateTransactionManager.

See the Hibernate Transaction Setup and Hibernate sections of the Spring Framework reference manual for details.

CodePudding user response:

I changed my DataConfig file to this ConfigFile and set @Transactional(propagation = Propagation.REQUIRED) in my Repository (AbstractDao)

  • Related