JpaRepository同名书籍去重查询及异类型注册校验实现方案
实现方案
原有代码存在两个核心问题:
- 自定义JPQL未做结果数量限制,同名书籍有多条记录时会直接抛出
NonUniqueResultException,无法稳定返回单条结果 - 仅取第一条记录的
tipo_registro做比对逻辑不严谨,无法覆盖数据库中已经存在多个不同注册类型同名书籍的冲突场景
1. Repository层修正
两种实现方式都可以保证多结果场景下稳定返回单条匹配记录,优先用Spring Data JPA派生查询,无需手写JPQL:
import org.springframework.data.jpa.repository.JpaRepository; import java.util.Optional; public interface LivroRepository extends JpaRepository<LivroEntity, Long> { // 派生查询:按书籍名称查询第一条记录的注册类型,自动追加LIMIT 1,不会触发多结果异常 Optional<String> findFirstTipoRegistroByNome(String nome); // 辅助查询:统计同名下不同注册类型的数量,用于覆盖存量冲突场景 long countDistinctTipoRegistroByNome(String nome); }
如果需要保留自定义@Query写法,必须显式加分页参数限制返回1条结果:
@Query("select l.tipo_registro from LivroEntity l where l.nome = :nome") Optional<String> findFirstTipoRegistroByNome(String nome, Pageable pageable); // 调用时传入分页参数即可: // livroRepository.findFirstTipoRegistroByNome(nome, PageRequest.of(0, 1))
2. 业务校验逻辑修正
校验分两层执行,覆盖所有冲突场景:
- 先判断同名下是否已存在多个不同注册类型的记录,若存在直接拦截
- 若同名下仅存在一种注册类型,再和当前待注册书籍的类型做比对,不一致则拦截
String nomeLivro = livro.getNome(); String tipoRegistroAtual = livro.getTipo_registro(); // 校验存量是否已存在多注册类型冲突 long distinctTipoCount = livroRepository.countDistinctTipoRegistroByNome(nomeLivro); if (distinctTipoCount > 1) { throw new CadastroException(String.format( "书籍[%s]已存在多个不同注册类型的存量记录,禁止新增", nomeLivro )); } // 校验存量注册类型和当前传入类型是否一致 Optional<String> tipoRegistroExistenteOpt = livroRepository.findFirstTipoRegistroByNome(nomeLivro); if (tipoRegistroExistenteOpt.isPresent()) { String tipoRegistroExistente = tipoRegistroExistenteOpt.get(); if (!tipoRegistroExistente.equals(tipoRegistroAtual)) { throw new CadastroException(String.format( "同名书籍[%s]已绑定注册类型[%s],与当前传入类型[%s]不一致,禁止注册", nomeLivro, tipoRegistroExistente, tipoRegistroAtual )); } } // 所有校验通过后执行保存 return livroRepository.save(livro);
关键注意事项
- 不要用空字符串作为查询结果的默认判空值,使用
Optional包装返回值可以有效避免空指针异常 - 禁止在未加结果限制的JPQL中查询单条字段,多结果场景下JPA会直接抛出非唯一结果异常
- 仅取第一条记录做比对的逻辑存在漏洞,必须先做去重计数校验,否则存量数据已经存在冲突时校验规则会失效
内容的提问来源于stack exchange,提问作者Pitter
相关产品推荐
相关产品推荐

