Why does @NotNull validation on 2nd level nested list fail on top level update?

Viewed 143

I have application that has leagues, players and teams. The app allows clients to create a league with teams and players on those teams.

Later, the client can modify the league by adding a new team with players. It's at that point that the client gets an unexpected validation error from my app. The error says that the list of new players cannot be null even though a client has clearly sent list of new players belonging to the new team.

Here are entities:

@Entity
@Data
public class League {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
    @JoinColumn(name="leagueId", nullable=false)
    @Valid
    @NotNull
    private List<Team> teams;
}

@Entity
@Data
public class Team {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(cascade=CascadeType.ALL, orphanRemoval=true)
    @JoinColumn(name="teamId", nullable=false)
    @Valid
    @NotNull
    private List<Player> players;
}

@Entity
@Data
public class Player {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

    private String name;    
}

Here's my integration test that reproduces the issue:

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class LeagueSaveTest {

    @Autowired
    private LeagueRepository repo;

    @Test
    public void updateLegaue() {

        Player ramsey = new Player();
        ramsey.setId(1L);
        ramsey.setName("Aaron Ramsey");

        Team arsenal = new Team();
        arsenal.setId(1L);
        arsenal.setName("Arsenal");
        arsenal.setPlayers(Arrays.asList(ramsey));

        Player hazard = new Player();
        hazard.setName("Eden Hazard");

        Team chelsea = new Team();
        chelsea.setName("Chelsea");
        chelsea.setPlayers(Arrays.asList(hazard));

        League premier = new League();
        premier.setId(1L);
        premier.setName("Premier");
        premier.setTeams(Arrays.asList(arsenal, chelsea));

        repo.save(premier);
    }   
}

And here's the validation error that I get:

INFO 16136 --- [           main] o.s.t.c.transaction.TransactionContext   : Rolled back transaction for test: [DefaultTestContext@557caf28 testClass = LeagueSaveTest, testInstance = com.example.service.LeagueSaveTest@133d0471, testMethod = updateLegaue@LeagueSaveTest, testException = javax.validation.ConstraintViolationException: Validation failed for classes [com.example.domain.Team] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
    ConstraintViolationImpl{interpolatedMessage='must not be null', propertyPath=players, rootBeanClass=class com.example.domain.Team, messageTemplate='{javax.validation.constraints.NotNull.message}'}
], mergedContextConfiguration = [WebMergedContextConfiguration@408d971b testClass = LeagueSaveTest, locations = '{}', classes = '{class com.example.LeagueApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@371a67ec, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@1a3869f4, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@63440df3, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@569cfc36], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.populatedRequestContextHolder' -> true, 'org.springframework.test.context.web.ServletTestExecutionListener.resetRequestContextHolder' -> true]]

Why I'm getting this validation error even though all teams have at least one player?

1 Answers

In Team class, you declared "id" is auto generation concept. But you assigned the value as constant.Then only it throw error.

@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class LeagueSaveTest {

@Autowired
private LeagueRepository repo;

@Test
public void updateLeague() {

    Player ramsey = new Player();
    ramsey.setName("Aaron Ramsey");

    Team arsenal = new Team();
    arsenal.setName("Arsenal");
    arsenal.setPlayers(Arrays.asList(ramsey));

    Player hazard = new Player();
    hazard.setName("Eden Hazard");

    Team chelsea = new Team();
    chelsea.setName("Chelsea");
    chelsea.setPlayers(Arrays.asList(hazard));

    League premier = new League()
    premier.setName("Premier");
    premier.setTeams(Arrays.asList(arsenal, chelsea));

    repo.save(premier);
}   

}

Related