SpringBoot 2.1.1升级到2.7.5(Java17)触发NPE问题求助
错误信息
Caused by: java.lang.NullPointerException: Cannot invoke "String.contains(java.lang.CharSequence)" because "variable" is null
at org.springframework.data.jpa.repository.query.QueryUtils.createCountQueryFor(QueryUtils.java:607)
at org.springframework.data.jpa.repository.query.DefaultQueryEnhancer.createCountQueryFor(DefaultQueryEnhancer.java:49)
at org.springframework.data.jpa.repository.query.StringQuery.deriveCountQuery(StringQuery.java:119)
at org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery.(AbstractStringBasedJpaQuery.java:72)
at org.springframework.data.jpa.repository.query.NativeJpaQuery.(NativeJpaQuery.java:53)
at org.springframework.data.jpa.repository.query.JpaQueryFactory.fromMethodWithQueryString(JpaQueryFactory.java:51)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$DeclaredQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:169)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:253)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:93)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:103)
... 60 common frames omitted
当前依赖配置
implementation 'org.springframework.boot:spring-boot:2.7.5' implementation('org.springframework.boot:spring-boot-starter-data-jpa') implementation('org.springframework.boot:spring-boot-starter-actuator') implementation('org.springframework.boot:spring-boot-starter-web') implementation('org.springframework.boot:spring-boot-starter-parent:2.7.5') implementation('org.springframework:spring-jdbc') implementation('org.springframework:spring-orm') implementation('org.springframework:spring-core') implementation('org.springframework:spring-beans') implementation('org.springframework:spring-webmvc') implementation('org.springframework:spring-web') implementation('org.springframework:spring-context') testImplementation 'org.springframework:spring-test:5.3.25' compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.5' runtime("org.springframework.boot:spring-boot-properties-migrator") compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1' implementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '9.0.56' implementation group: 'org.hibernate', name: 'hibernate-core', version: '5.6.5.Final' implementation 'org.springframework.cloud:spring-cloud-context:3.1.0' implementation group: 'org.springframework.security', name: 'spring-security-core', version: '5.6.9' implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.3.1' implementation group: 'org.hibernate', name: 'hibernate-entitymanager', version: '5.4.0.Final' implementation group: 'org.hibernate.javax.persistence', name: 'hibernate-jpa-2.1-api', version: '1.0.2.Final' implementation("net.bytebuddy:byte-buddy:1.14.0") compileOnly 'org.projectlombok:lombok:1.18.26' annotationProcessor 'org.projectlombok:lombok:1.18.26'
触发错误的查询代码
@Modifying @Transactional @Query(value = "INSERT INTO W (CREATED_DATE, PRODUCT_CODE, TENANT_ID, UPDATED_DATE, UUID, STORE_ID) SELECT TO_TIMESTAMP(:createdDate, :format), :productCode, :tenantId, TO_TIMESTAMP(:updatedDate, :format), :uuid, :storeId FROM W WHERE ROWNUM <= 1 AND NOT EXISTS (select * from W w2 WHERE UUID = :uuid AND PRODUCT_CODE =:productCode)" ,nativeQuery = true) int insertIntoW(@Param("format") String format, @Param("createdDate")String createdDate, @Param("productCode")String productCode, @Param("tenantId")String tenantId, @Param("updatedDate") String updatedDate, @Param("uuid")String uuid, @Param("storeId") String storeId);
解决方案
1. 修复JPA查询注解
Spring Data JPA 2.7版本会尝试为带@Modifying的原生查询自动生成count查询,但INSERT语句无查询结果,导致解析时触发NPE。只需在@Query注解中显式指定空的countQuery,阻止自动生成逻辑:
@Modifying @Transactional @Query(value = "INSERT INTO W (CREATED_DATE, PRODUCT_CODE, TENANT_ID, UPDATED_DATE, UUID, STORE_ID) SELECT TO_TIMESTAMP(:createdDate, :format), :productCode, :tenantId, TO_TIMESTAMP(:updatedDate, :format), :uuid, :storeId FROM W WHERE ROWNUM <= 1 AND NOT EXISTS (select * from W w2 WHERE UUID = :uuid AND PRODUCT_CODE =:productCode)", nativeQuery = true, countQuery = "") int insertIntoW(@Param("format") String format, @Param("createdDate")String createdDate, @Param("productCode")String productCode, @Param("tenantId")String tenantId, @Param("updatedDate") String updatedDate, @Param("uuid")String uuid, @Param("storeId") String storeId);
2. 清理冗余依赖
当前依赖存在重复、冲突和不必要的单独引入,清理后可避免版本兼容性问题:
- 移除
implementation('org.springframework.boot:spring-boot-starter-parent:2.7.5'):parent应在构建脚本的parent区块配置,而非implementation依赖 - 移除单独引入的
spring-core、spring-beans、spring-webmvc等Spring核心依赖:这些已被spring-boot-starter-web、spring-boot-starter-data-jpa自动包含 - 移除
compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.5':保留javax.servlet-api:4.0.1即可,避免版本冲突 - 移除
hibernate-entitymanager、hibernate-jpa-2.1-api:hibernate-core:5.6.5.Final已包含这些功能,且Spring Boot starter已引入正确的JPA API
清理后的依赖示例:
plugins { id 'org.springframework.boot' version '2.7.5' id 'io.spring.dependency-management' version '1.0.15.RELEASE' id 'java' } group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '17' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.cloud:spring-cloud-context:3.1.0' implementation 'org.springframework.security:spring-security-core:5.6.9' implementation 'javax.xml.bind:jaxb-api:2.3.1' implementation 'net.bytebuddy:byte-buddy:1.14.0' compileOnly 'org.projectlombok:lombok:1.18.26' annotationProcessor 'org.projectlombok:lombok:1.18.26' runtime("org.springframework.boot:spring-boot-properties-migrator") testImplementation 'org.springframework.boot:spring-boot-starter-test' } test { useJUnitPlatform() }
内容的提问来源于stack exchange,提问作者Zahid Hussain

