Java Spring Hibernate - @Transactional between different transactions

Felipe S. :

I'm creating a test and basically doing different transactions inside a @Transactional method.

I add a Project, then add a Task to it, and last will fetch the project again from DB to test it has the task saved.

Please note the case I'm showing is a unit test but I'm interested in fixing the transactional methods and not the test itself as I already had this in the past in "production code".

Model Classes:

    @Entity
    @Table(name = "Task")
    public class Task {

        @Id
        @SequenceGenerator(name = "TaskSeq", sequenceName = "TaskSeq", initialValue = 100)
        @GeneratedValue(generator = "TaskSeq")
        private Long id;

        @Column(nullable = false)
        private String name;

        private String description;

        private LocalDateTime inZ;
        private LocalDateTime outZ;
        private boolean completed;

        @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
        @JoinColumn(name = "projectId")
        private Project project;
    }

    @Entity
    @Table(name = "Project")
    public class Project {

        @Id
        @SequenceGenerator(name = "ProjectSeq", sequenceName = "ProjectSeq", initialValue = 100)
        @GeneratedValue(generator = "ProjectSeq")
        private Long id;

        @Column(nullable = false)
        private String name;

        @OneToMany(mappedBy = "project", cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
        private List<Task> tasks;
    }

Service Classes:

    @Service
    public class ProjectServiceImpl implements ProjectService {
        private final ProjectRepository projectRepository;

        @Autowired
        public ProjectServiceImpl(ProjectRepository projectRepository) {
            this.projectRepository = projectRepository;
        }

        @Override
        public Project save(Project project) {
            return projectRepository.save(project);
        }

        @Override
        public List<Project> findAll() {
            return projectRepository.findAll();
        }
    }


    @Service
    public class TaskServiceImpl implements TaskService {
        private TaskRepository taskRepository;
        private ProjectRepository projectRepository;

        @Autowired
        public TaskServiceImpl(TaskRepository taskRepository, ProjectRepository projectRepository) {
            this.taskRepository = taskRepository;
            this.projectRepository = projectRepository;
        }

        @Override
        @Transactional
        public Task addTaskToProject(Long id, Task task) {
            Project project = projectRepository.findById(id).orElseThrow(() -> new RuntimeException());
            task.setProject(project);

            return taskRepository.save(task);
        }
    }

The class I'm trying to use the transactional method:

    public class TaskServiceTest extends JavaExampleApplicationTests {

        @Autowired
        private ProjectService projectService;

        @Autowired
        private TaskService taskService;

        @Test
    //    @Transactional
        public void canInsertTaskToProject() {
            Project project = new Project();
            project.setName("create company");
            project = projectService.save(project);
            Task task = new Task();
            task.setName("Check how many people we need to hire");
            task = taskService.addTaskToProject(project.getId(), task);

            assertTrue(task.getId() > 0);
            List<Project> projects = projectService.findAll();

            assertEquals(1, projects.size());
            assertEquals(1, projects.get(0).getTasks().size());
            assertEquals(task.getId(), projects.get(0).getTasks().get(0).getId());

        }

    }

If I add a @Transactional(REQUIRES_NEW) to the methods in the service it will work, but I don't want it as if this method is called inside a real transaction I want it to be rolled back accordingly. Also I'd like to avoid using too many REQUIRES_NEW to avoid future problems

If I remove the @Transactional from the test method, it won't work when I test the size of the task list on last two lines as they are lazy.

If I add the @Transactional on the test method, it will throw me NullPointerException on the two last lines as when I do a projectService.findAll() the tasks are not commited yet

What is the best way to make it work ? I thought that inside a @Transactional when I used another command from db it would get the latest updates that were not committed yet..

Thanks in advance!

Update: added the reason I removed the @Transactional from test

Kayaman :

In its roots this is a design issue. In the test code you're making changes and then verifying that those changes are made. This brings us to the problem of @Transactional or not.

With @Transactional, you end up in the situation where the changes are made, but they're not flushed or committed yet, so you can't see the changes in the same transaction. In this case you would either need to flush the results, and/or reconsider your transaction boundaries.

Without @Transactional, the individual transactions work fine, but since you're not inside a transaction, you can't initialize the lazy-loaded entities. For this your option is to either load the values in a way that eagerly initializes those, or load the values in a way that doesn't require initialization. Both of those would probably involve custom queries in your repository.

Without seeing the actual codebase, it's impossible to say what would be the optimal way to go. Using saveAndFlush() will probably solve the problem in this case, but it's not much of a general solution.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=153509&siteId=1