Spring Boot @PrePersist监听器报错:this.partido为null
@PrePersist监听器NullPointerException问题排查与修复
问题现象
使用@PrePersist监听器对Partido实体做前置验证时,抛出以下异常:
java.lang.NullPointerException: Cannot invoke "es.mde.repositorios.Partido.getCampo()" because "this.partido" is null
现有代码
监听器类
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.persistence.PrePersist; import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; @Component public class PartidoListener { @Autowired private PartidoDAO partidoDAO; @PrePersist public void validacionPartido(Partido partido) { System.err.println("punto de fallo: 1"); Long partidoCampoId = partido.getCampo().getId(); LocalDateTime partidoCuando = partido.getCuando(); System.err.println("datos de partido: " + partidoCampoId + ", " + partidoCuando ); System.err.println("punto de fallo: 2"); try { List<Partido> partidosCoincidentes = partidoDAO.findAll() .stream() .filter(p -> p.getCuando().equals(partidoCuando)) .filter(p -> p.getCampo().getId().equals(partidoCampoId)) .collect(Collectors.toList()); System.err.println("punto de fallo: 3"); boolean valido = partidosCoincidentes.isEmpty(); System.err.println("punto de fallo: 4"); System.err.println("partido es valido: " + valido); System.err.println("punto de fallo: 5"); } catch (Exception e) { System.err.println(e); } } }
Partido实体类
@Entity @Table(name = "PARTIDOS") @EntityListeners(PartidoListener.class) public class Partido { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(unique = true) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "CAMPO") private Campo campo; private LocalDateTime cuando; @OneToMany(cascade = CascadeType.ALL, targetEntity = Puntuacion.class, mappedBy = "partido") private Collection<Puntuacion> puntuaciones = new ArrayList<>(); // getter、setter省略 }
Campo实体类
@Entity @Table(name = "CAMPOS") public class Campo { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(unique = true) private Long id; private String nombre, provincia; private float valorCampo, valorSlope; @OneToMany(cascade = CascadeType.ALL, targetEntity = Jugador.class, mappedBy = "campo") private Collection<Jugador> jugadores = new ArrayList<>(); @OneToMany(cascade = CascadeType.ALL, targetEntity = Partido.class, mappedBy = "campo") private Collection<Partido> partidos = new ArrayList<>(); // getter、setter省略 }
错误原因分析
从异常信息来看,核心问题是在调用Partido.getCampo()时,代码中引用了this.partido且该变量为null。结合代码排查,可能的原因有:
- Partido类的
getCampo()方法写错:比如误将返回this.campo写成this.partido.getCampo(),导致引用了不存在的partido成员变量。 - 保存的Partido实例为null:调用
partidoRepository.save(partido)时传入了null实例,导致监听器方法参数为null。 - Spring与JPA监听器集成问题:监听器未被Spring正确管理,导致JPA自行实例化监听器时出现异常(但此情况更多导致依赖注入失败,而非实体参数null)。
修复步骤
1. 检查并修正Partido类的getCampo()方法
确保方法返回正确的成员变量:
public Campo getCampo() { return this.campo; // 确认此处是campo而非partido }
2. 确保保存的Partido实例有效
检查业务代码中调用save的逻辑,确保传入的Partido实例不为null,且campo和cuando字段已正确初始化。
3. 完善监听器的空值检查
在监听器方法开头添加空值校验,提前抛出明确异常:
@PrePersist public void validacionPartido(Partido partido) { if (partido == null) { throw new IllegalArgumentException("待保存的Partido实例不能为null"); } if (partido.getCampo() == null) { throw new IllegalArgumentException("Partido关联的Campo不能为null"); } if (partido.getCuando() == null) { throw new IllegalArgumentException("Partido的时间字段cuando不能为null"); } // 后续验证逻辑 }
4. 确保Spring正确管理监听器
使用构造注入替代字段注入,提升可靠性:
@Component public class PartidoListener { private final PartidoDAO partidoDAO; @Autowired public PartidoListener(PartidoDAO partidoDAO) { this.partidoDAO = partidoDAO; } // 监听器方法... }
优化建议:替换全量查询的低效验证
当前使用findAll()全量查询后过滤的方式效率极低,推荐以下两种优化方案:
方案1:数据库层面添加联合唯一约束(最优)
直接在Partido实体上配置联合唯一约束,由数据库保证唯一性:
@Entity @Table(name = "PARTIDOS", uniqueConstraints = @UniqueConstraint(columnNames = {"CAMPO", "cuando"})) @EntityListeners(PartidoListener.class) public class Partido { // ... 其他代码 }
当存在冲突时,数据库会抛出DataIntegrityViolationException,业务代码中捕获该异常即可处理重复提交的情况。
方案2:自定义DAO查询验证
在PartidoDAO中添加针对性查询方法,仅查询必要数据:
public interface PartidoDAO extends JpaRepository<Partido, Long> { boolean existsByCampoAndCuando(Campo campo, LocalDateTime cuando); }
然后在监听器中调用该方法:
@PrePersist public void validacionPartido(Partido partido) { // 空值检查... boolean exists = partidoDAO.existsByCampoAndCuando(partido.getCampo(), partido.getCuando()); if (exists) { throw new IllegalStateException("该场地在指定时间已有安排的比赛"); } }
内容的提问来源于stack exchange,提问作者Xavier Guerrero
相关产品推荐
相关产品推荐

