在ControllerTest

时间:2018-05-19 21:40:37

标签: java spring spring-boot spring-data-jpa spring-transactions

我使用的是以下型号:

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    private String username;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "USER_ROLE", joinColumns = {@JoinColumn(name = "USER_ID", referencedColumnName = "ID")}, inverseJoinColumns = {@JoinColumn(name = "ROLE_ID", referencedColumnName = "ID")})
    private List<Role> roles;

   (...)
}

@Entity
public class Role {

    private String name;

    @ManyToMany(mappedBy = "roles", fetch = FetchType.LAZY)
    private List<User> users;

    (...)
}

导致以下存储库:

public interface UserRepository extends CrudRepository<User, Long> {

    User findByUsername(String username);
}

然后是一个控制器,如:

@RestController
@RequestMapping("/test")
public class Controller {

    @Autowired
    private UserRepository userRepository;

    @GetMapping
    public void test(@RequestParam final String u) {
        if(!this.userRepository.findByUsername(u).getRoles().get(0).getUsers().get(0).getRoles().get(0).getName().equals("Role1")) throw new RuntimeException();
    }

}

应用程序是vanilla,不使用任何与事务相关的注释(但main使用@SpringBootApplication注释)。

@SpringBootApplication
public class Application {
    public static void main(final String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

我只在application.properties中使用了一个属性:spring.jpa.open-in-view=false

测试结果如下:

@RunWith(SpringRunner.class)
@WebAppConfiguration @ContextConfiguration(classes = Application.class)
@AutoConfigureMockMvc
public class ControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void test() throws Exception {
        this.mockMvc.perform(get("/test").param("u", "user1")).andExpect(status().isOk());
    }
}

然后我的问题是,当调试看起来所有的实体图都被完全急切地获取,并且测试返回成功,因为控制器没有失败。

我知道JPA存储库方法使用事务,但据我所知,只要我们不将它们包装在更高级别的事务上,它就应该在方法结束后完成。在尝试访问roler或user时,我怎么可能没有得到LazyInitializatioExceptions - &gt;角色 - &gt;用户??这是预期的行为吗?

这是Eclipse调试器在用户角色中显示用户时所显示的内容,值得:

enter image description here

Hibernate日志记录显示了在找到用户时如何急切地获取关系:

12:47:24.890 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Loading collection:  ..domain.User.roles
12:47:24.937 [main] DEBUG org.hibernate.loader.plan.exec.process.internal.CollectionReferenceInitializerImpl - Found row of collection: [User.roles#1002]
12:47:24.953 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Resolving associations for [...domain.Role#5]
12:47:24.953 [main] DEBUG org.hibernate.engine.internal.TwoPhaseLoad - Done materializing entity [...domain.Role#5]
12:47:24.953 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections were found in result set for role: ...domain.User.roles
12:47:24.968 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - Collection fully initialized: [...domain.User.roles#1002]
12:47:24.968 [main] DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext - 1 collections initialized for role: ....domain.User.roles
12:47:24.968 [main] DEBUG org.hibernate.resource.jdbc.internal.ResourceRegistryStandardImpl - HHH000387: ResultSet's statement was not registered
12:47:24.968 [main] DEBUG org.hibernate.loader.collection.plan.AbstractLoadPlanBasedCollectionInitializer - Done loading collection

编辑:我发现如果我通过现代@WebAppConfiguration更改ControllerTest上的@SpringBootTest行,则会获得LazyInitializationException。为什么@WebAppConfiguration忽略了懒惰的提取?

1 个答案:

答案 0 :(得分:1)

Spring Boot默认注册OpenEntityManagerInViewInterceptor以在View模式中应用Open EntityManager,以允许在Web视图中延迟加载。如果您不想要此行为,则应在spring.jpa.open-in-view中将application.properties设置为false。

编辑:编辑问题后

确实你应该有一个 LazyInitializationException 。我的猜测是,在你的测试中,Session仍然是开放的?

如果您使用@SpringBootTest进行测试,则会获得LazyInitializationException

@RunWith(SpringRunner.class)
@SpringBootTest
public class LazyLoadingExceptionTest {

   @Autowired UserRepository userRepository;

   @Test(expected=LazyInitializationException.class)
   public void showRolesTest() {
     User whimusical =  userRepository.findByUsername("Whimusical");
     System.err.println(whimusical.getRoles());
   }

}

但如果您使用@DataJpaTest进行测试,则不会出现例外

 @RunWith(SpringRunner.class)
 @DataJpaTest
 public class NoLazyLoadingExceptionTest {

   @Autowired UserRepository userRepository;

   @Test
   public void showRolesTest() {
      User whimusical =  userRepository.findByUsername("Whimusical");;
      System.err.println(whimusical.getRoles());
   }
}

另一种触发 LazyInitializationException

的方法

覆盖toString()

User
@Override
public String toString() {
  return username + " with roles: " + roles;
} 

并使用此Component

启动应用程序
@Component
public class DataSetup implements CommandLineRunner{

  @Override
  public void run(String... args) throws Exception {
    List<Role> roles = new ArrayList<>();

    Role role = new Role();
    role.setName("user");
    roles.add(roleRepository.save(role));

    User whimusical = new User();
    whimusical.setUsername("Whimusical");
    whimusical = userRepository.save(whimusical);

    whimusical.setRoles(roles);
    whimusical = userRepository.save(whimusical);

    userRepository.findAll().forEach(System.err::println);  
  }
}