Search code examples
javaspring-boottransactionsspring-data-jpa

Spring Boot - Transaction Management is not working


Expectation - lazy loading should not work out of transaction scope (in rest controller for example), but it works.

The problem is in @Transactional, it is not used by spring application in my configuration. How i could fix it?

...Rest controllers do not have any transactional methods, its use only specifiedServices to load entities. Dependent collection if it was not loaded in service should be empty.

Application starter class:

@SpringBootApplication
@EntityScan("com.vl.pmanager.domain.model")
@EnableJpaRepositories("com.vl.pmanager.domain.repository")
@EnableTransactionManagement
public class ProjectManagerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProjectManagerApplication.class, args);
    }
}

I know that spring boot auto configure repositories and scans entities, but i added in hope...

@EntityScan("com.vl.pmanager.domain.model")
@EnableJpaRepositories("com.vl.pmanager.domain.repository")

Also I tried to add @Transactional to repository interface, but it did not work for me

package com.vl.pmanager.domain.repository;

import com.vl.pmanager.domain.model.Tag;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
@Transactional
public interface TagRepository extends PagingAndSortingRepository<Tag, Long> {
}

So i deleted @Transactional from repository, created other service layer to manage it with the annotation and inject the service to controller:

package com.vl.pmanager.domain.db.service.api;

import com.vl.pmanager.domain.model.Tag;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface ITagManager {
    Page<Tag> findAll(Pageable pageable);
    Tag findOne(Long id);
}
// ======================== TAG SERVICE WRAPPED WITH TRANSACTION ==========================
package com.vl.pmanager.domain.db.service;

import com.vl.pmanager.domain.db.service.api.ITagManager;
import com.vl.pmanager.domain.model.Tag;
import com.vl.pmanager.domain.repository.TagRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class TagManager implements ITagManager {
    @Autowired
    private TagRepository tagRepository;

    @Override
    public Page<Tag> findAll(Pageable pageable) {
        return tagRepository.findAll(pageable);
    }

    @Override
    public Tag findOne(Long id) {
        return tagRepository.findOne(id);
    }
}
// ====================== REST CONTROLLER ============================
package com.vl.pmanager.web;

import com.vl.pmanager.domain.db.service.api.ITagManager;
import com.vl.pmanager.domain.model.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/tags")
public class TagController {
    private static final int DEFAULT_PAGE_SIZE = 50;

    @Autowired
    private ITagManager tagManager;

    @RequestMapping(method = RequestMethod.GET, consumes = MediaType.ALL_VALUE)
    public Page<Tag> getTags(@PageableDefault(size = DEFAULT_PAGE_SIZE) Pageable pageable) {
        return tagManager.findAll(pageable);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET, consumes = MediaType.ALL_VALUE)
    public Tag getTag(@PathVariable("id") Long id) {
        return tagManager.findOne(id);
    }
}

Tag entity has an field Set projectInfo mapped to other entity with @ManyToMany relation with FetchType fetch() default LAZY; so return result can not contain dependent entity, but it is.

I also monitored DB logs:

// make request - load data using db service to controller level
Hibernate: select count(tag0_.id) as col_0_0_ from tag tag0_
Hibernate: select tag0_.id as id1_6_, tag0_.description as descript2_6_, tag0_.name as name3_6_ from tag tag0_ limit ?
// converting data to JSON automatically
Hibernate: select projectinf0_.tags as tags1_6_0_, projectinf0_.project_info as project_2_7_0_, projectinf1_.id as id1_4_1_, projectinf1_.description as descript2_4_1_, projectinf1_.name as name3_4_1_ from tag_project_info projectinf0_ inner join project_info projectinf1_ on projectinf0_.project_info=projectinf1_.id where projectinf0_.tags=?
Hibernate: select projectinf0_.tags as tags1_6_0_, projectinf0_.project_info as project_2_7_0_, projectinf1_.id as id1_4_1_, projectinf1_.description as descript2_4_1_, projectinf1_.name as name3_4_1_ from tag_project_info projectinf0_ inner join project_info projectinf1_ on projectinf0_.project_info=projectinf1_.id where projectinf0_.tags=?
Hibernate: select projectinf0_.tags as tags1_6_0_, projectinf0_.project_info as project_2_7_0_, projectinf1_.id as id1_4_1_, projectinf1_.description as descript2_4_1_, projectinf1_.name as name3_4_1_ from tag_project_info projectinf0_ inner join project_info projectinf1_ on projectinf0_.project_info=projectinf1_.id where projectinf0_.tags=?
Hibernate: select projectinf0_.tags as tags1_6_0_, projectinf0_.project_info as project_2_7_0_, projectinf1_.id as id1_4_1_, projectinf1_.description as descript2_4_1_, projectinf1_.name as name3_4_1_ from tag_project_info projectinf0_ inner join project_info projectinf1_ on projectinf0_.project_info=projectinf1_.id where projectinf0_.tags=?
Hibernate: select projectinf0_.tags as tags1_6_0_, projectinf0_.project_info as project_2_7_0_, projectinf1_.id as id1_4_1_, projectinf1_.description as descript2_4_1_, projectinf1_.name as name3_4_1_ from tag_project_info projectinf0_ inner join project_info projectinf1_ on projectinf0_.project_info=projectinf1_.id where projectinf0_.tags=?

And i know there are only 5 extra requests, because i have only 5 dependent on tag project_info. So as conclusion my transaction level does not manage transaction. ...i have checked datasource and transactionManager injecting beans - ones was created. No errors, no warning in start time or run time...


Solution

  • By JB Nizet in the comments

    It's not a transaction problem. Look for the property spring.jpa.open-in-view in the documentation and set it to false if you don't want it.

    If I turned off jpa open in view transactions was working like I expected.