测试期间出现 ManyToMany nullpointerexception - MergedContextConfiguration ... SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]

ManyToMany nullpointerexception during test - MergedContextConfiguration ... SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]

我确信我犯了一些愚蠢的错误,但我想不通。

我想我的单元测试的这一部分不知何故搞砸了:

...
    User u1 = new User();
    u1.setEmail("email1");
    u1.setFirstName("firstName1");
    u1.setLastName("lastName1");
    u1.setMobile("mobile1");
      
    Role r1 = new Role();
    r1.setDescription("description 1");
    r1.setName("name1");

    User userSaved = userRepository.save(u1);

    r1.getUsers().add(userSaved); 

我的意思是,我一定是做错了什么,首先保存实体而不填充 ManyToMany 字段,然后尝试添加它。但是我不知道我在这里做错了什么。

如果我 post 来自 PostMan,该项目将完美运行,因此超出测试目标,它已正确建模,我相信它。但是我在测试期间遇到了这个问题

2020-07-30 17:23:13.610  INFO 15464 --- [           main] com.notyfyd.JPAUnitTest                  : Starting JPAUnitTest on SPANOT149 with PID 15464 (started by Cast in C:\_d\Testes\Samples\jpa-demo-many-to-many-bidirectional-getters)
2020-07-30 17:23:13.613  INFO 15464 --- [           main] com.notyfyd.JPAUnitTest                  : No active profile set, falling back to default profiles: default
2020-07-30 17:23:13.988  INFO 15464 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in LAZY mode.
2020-07-30 17:23:14.053  INFO 15464 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 58ms. Found 2 JPA repository interfaces.
2020-07-30 17:23:14.588  INFO 15464 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2020-07-30 17:23:14.648  INFO 15464 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.18.Final
2020-07-30 17:23:14.793  INFO 15464 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.0.Final}
2020-07-30 17:23:15.848  INFO 15464 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-07-30 17:23:15.937  INFO 15464 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2020-07-30 17:23:15.968  INFO 15464 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.PostgreSQL10Dialect
Hibernate: alter table if exists t_user_roles drop constraint if exists FKj47yp3hhtsoajht9793tbdrp4
Hibernate: alter table if exists t_user_roles drop constraint if exists FKp67oqi40xgs7j7ad5xqaxx857
Hibernate: drop table if exists t_role cascade
Hibernate: drop table if exists t_user cascade
Hibernate: drop table if exists t_user_roles cascade
Hibernate: create table t_role (id int8 generated by default as identity, description varchar(255), name varchar(255), primary key (id))
Hibernate: create table t_user (id int8 generated by default as identity, email varchar(255), first_name varchar(255), last_name varchar(255), mobile varchar(255), primary key (id))
Hibernate: create table t_user_roles (users_id int8 not null, roles_id int8 not null)
Hibernate: alter table if exists t_user add constraint UK_i6qjjoe560mee5ajdg7v1o6mi unique (email)
Hibernate: alter table if exists t_user_roles add constraint FKj47yp3hhtsoajht9793tbdrp4 foreign key (roles_id) references t_role
Hibernate: alter table if exists t_user_roles add constraint FKp67oqi40xgs7j7ad5xqaxx857 foreign key (users_id) references t_user
2020-07-30 17:23:16.668  INFO 15464 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-07-30 17:23:16.675  INFO 15464 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-07-30 17:23:16.813  INFO 15464 --- [           main] com.notyfyd.JPAUnitTest                  : Started JPAUnitTest in 3.597 seconds (JVM running for 4.482)
2020-07-30 17:23:16.927  INFO 15464 --- [           main] o.s.t.c.transaction.TransactionContext   : Began transaction (1) for test context [DefaultTestContext@19e4fcac testClass = JPAUnitTest, testInstance = com.notyfyd.JPAUnitTest@68b32e3e, testMethod = should_save_an_user@JPAUnitTest, testException = [null], mergedContextConfiguration = [MergedContextConfiguration@52c3cb31 testClass = JPAUnitTest, locations = '{}', classes = '{class com.notyfyd.JpaDemoApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@4b79ac84 key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@4681c175, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6fa51cd4, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@6be968ce, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@b6cf2445, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@3de8f619, org.springframework.boot.test.context.SpringBootTestArgs@1], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]; transaction manager [org.springframework.orm.jpa.JpaTransactionManager@11cadb32]; rollback [true]
Hibernate: insert into t_user (email, first_name, last_name, mobile) values (?, ?, ?, ?)
2020-07-30 17:23:17.345  INFO 15464 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@19e4fcac testClass = JPAUnitTest, testInstance = com.notyfyd.JPAUnitTest@68b32e3e, testMethod = should_save_an_user@JPAUnitTest, testException = java.lang.NullPointerException, mergedContextConfiguration = [MergedContextConfiguration@52c3cb31 testClass = JPAUnitTest, locations = '{}', classes = '{class com.notyfyd.JpaDemoApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTestContextBootstrapper=true}', contextCustomizers = set[[ImportsContextCustomizer@4b79ac84 key = [org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration, org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration, org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration, org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration, org.springframework.boot.test.autoconfigure.jdbc.TestDatabaseAutoConfiguration, org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManagerAutoConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@4681c175, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@6fa51cd4, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.autoconfigure.OverrideAutoConfigurationContextCustomizerFactory$DisableAutoConfigurationContextCustomizer@6be968ce, org.springframework.boot.test.autoconfigure.filter.TypeExcludeFiltersContextCustomizer@351584c0, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@b6cf2445, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@3de8f619, org.springframework.boot.test.context.SpringBootTestArgs@1], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map[[empty]]]
2020-07-30 17:23:17.355  INFO 15464 --- [extShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2020-07-30 17:23:17.358  INFO 15464 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2020-07-30 17:23:17.373  INFO 15464 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

JunitTest

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertNotNull;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit4.SpringRunner;

import com.notyfyd.repository.*;
import com.notyfyd.model.*;
import com.notyfyd.entity.*;

@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) 
public class JPAUnitTest {

  @Autowired
  private TestEntityManager entityManager;

  @Autowired
  RoleRepository roleRepository;
  
  @Autowired
  UserRepository userRepository;

  @Test
  public void should_save_an_user() {
    
    User u1 = new User();
    u1.setEmail("email1");
    u1.setFirstName("firstName1");
    u1.setLastName("lastName1");
    u1.setMobile("mobile1");
      
    Role r1 = new Role();
    r1.setDescription("description 1");
    r1.setName("name1");

    User userSaved = userRepository.save(u1);

    r1.getUsers().add(userSaved); //here raises the exception
    
    Role roleSaved = roleRepository.save(r1);
    
    u1.getRoles().add(roleSaved);
    
    userSaved = userRepository.save(u1);
  
    assertNotNull(userSaved);

  }
}

实体:

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

import javax.persistence.*;
import java.util.List;



@Entity
@Table(name = "t_role")
public class Role  {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    @ManyToMany(targetEntity = User.class, mappedBy = "roles", cascade = {CascadeType.PERSIST, CascadeType.DETACH,CascadeType.MERGE,CascadeType.REFRESH})
    private List<User> users;

    public Long getId() {
        return this.id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return this.name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return this.description;
    }
    public void setDescription(String description) {
        this.description = description;
    }

    public List<User> getUsers() {
        return users;
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }
}

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

import javax.persistence.*;
import java.util.List;

@Entity
@Table(name = "t_user")

public class User  {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String firstName;
    private String lastName;
    private String mobile;
    @Column(unique = true)
    private String email;

    @ManyToMany(targetEntity = Role.class, cascade = {CascadeType.PERSIST, CascadeType.DETACH,CascadeType.MERGE,CascadeType.REFRESH} )
    private List<Role> roles;


    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public String getMobile() {
        return mobile;
    }
    public void setMobile(String mobile) {
        this.mobile = mobile;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
}

Pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <!-- version>2.2.5.RELEASE</version-->
        <version>2.3.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.notyfyd</groupId>
    <artifactId>jpa-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>jpa-demo</name>
    <description>Demo project for JPA</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <!-- exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions-->
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

型号:

import java.util.List;

public class RoleModel {
    private String name;
    private String description;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
}

import com.notyfyd.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.List;

public class UserModel {
    private String firstName;
    private String lastName;
    private String mobile;
    private String email;
    private List<RoleModel> roles;
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public String getMobile() {
        return mobile;
    }
    public void setMobile(String mobile) {
        this.mobile = mobile;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public List<RoleModel> getRoles() {
        return roles;
    }
    public void setRoles(List<RoleModel> roles) {
        this.roles = roles;
    }
}
  • 您的 role 实体 r1 未通过休眠获取,并且还不是 managed 实体,因此 r1.getUsers()null。当您调用 r1.getUsers() 时,Hibernate 对 r1 一无所知。因此,为了防止这些问题,请像下面这样修改 Role 实体并使用 new ArrayList<>();
  • 对其进行初始化
    @Entity
    @Table(name = "t_role")
    public class Role  {
      @Id
      @GeneratedValue(strategy = GenerationType.IDENTITY)
      private Long id;
      
      private String name;

      private String description;

      @ManyToMany(...
      private List<User> users = new ArrayList<>();
      ...

    }