Spring Query DSL给出空指针异常

时间:2018-01-26 13:27:21

标签: java hibernate spring-boot h2 querydsl

我正在使用Spring Boot和Hibernate文件数据库来创建这个原型。

这里是application.properties的一个片段(显然是user / pass。)注意我已经将fave设置为String而不是布尔值,所以我可以在查询DSL绑定中使用String:

spring.jpa.generate-ddl=true
spring.datasource.url=jdbc:h2:file:~/db
spring.datasource.driver-class-name=org.h2.Driver
hibernate.hbm2ddl.auto=update
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.show_sql=true

这里是省略了City类的模型,因为它非常简单:

@Data
@AllArgsConstructor
@Entity
@Table(name = "matches")
@EqualsAndHashCode(of = "id")
public class FilterMatch {

    @SequenceGenerator(name = "MATCHES_SEQ_GENERATOR", sequenceName = "MATCHES_SEQ",
                       initialValue = 1, allocationSize = 1)

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "MATCHES_SEQ_GENERATOR")
    @Column
    private Long id;

    @Column
    private String fave;

    @Column
    private int age;

    @Column
    private int cmHeight;

    @Column
    private int contactsExchanged;

    @Column
    private float compatScore;

    @Column
    private String displayName;

    @Column
    private String jobTitle;

    @Column
    private String mainPhoto;

    @Column
    private String religion;

    @OneToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
    private City city;

    public FilterMatch() {} 

    // getters/setters omitted
}

这里是必要的扩展JPARepository的存储库:

public interface FilterMatchRepository extends JpaRepository<FilterMatch, Long>,
    QueryDslPredicateExecutor<FilterMatch>,QuerydslBinderCustomizer<QFilterMatch> {


    @Override
    default public void customize(QuerydslBindings bindings, QFilterMatch root) {
        bindings.bind(String.class)
                .first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
        bindings.excluding(root.religion,root.jobTitle);
    }

}

我的SearchCriteria类:

public class SearchCriteria {

    private static final Logger logger = LogManager.getLogger(SearchCriteria.class);

    private String key;
    private String operation;
    private Object value;

    public SearchCriteria(String key, String operation, Object value)  {
        this.key = key;
        this.operation = operation;
        this.value = value;
    }
    //getters & setters omitted.
}

我的MatchBuilderPredicates类和我的MatchPredicates类:

public class MatchPredicatesBuilder {

    private static final Logger logger = LoggerFactory.getLogger(MatchPredicatesBuilder.class);

    private List<SearchCriteria> parameters;

    public MatchPredicatesBuilder() {
        parameters = new ArrayList<>();
    }

    public MatchPredicatesBuilder with(String key, String operation, Object value) {

        parameters.add(new SearchCriteria(key, operation, value));
        return this;
    }

    public BooleanExpression build() {

        if (parameters.size() == 0) {
            return null;
        }

        List<BooleanExpression> predicates = new ArrayList<>();
        MatchPredicate predicate;

        for (SearchCriteria param : parameters) {

            predicate = new MatchPredicate(param);
            BooleanExpression expression = predicate.getPredicate();

            if (Objects.nonNull(expression)) {
                predicates.add(expression);
            }
        }

        BooleanExpression result = predicates.get(0);

        for (int i = 1; i < predicates.size(); i++) {

            result = result.and(predicates.get(i));
        }
        return result;
    }   

}

public class MatchPredicate {

    private static final long serialVersionUID = 8621295752447527269L;
    private static final Logger logger = LoggerFactory.getLogger(MatchPredicate.class);

    private SearchCriteria criteria;

    public MatchPredicate(SearchCriteria criteria) {
        this.criteria = criteria;
    }

    public BooleanExpression getPredicate() {

        PathBuilder<FilterMatch> entityPath = new PathBuilder<>(FilterMatch.class, "FilterMatch");

        if (FilterUtils.isNumeric(criteria.getValue().toString())) {

            NumberPath<Double> path = entityPath.getNumber(criteria.getKey(), Double.class);
            double value = Double.parseDouble(criteria.getValue().toString());

            switch (criteria.getOperation()) {
                case ":":
                    return path.eq(value);
                case ">":
                    return path.goe(value);
                case "<":
                    return path.loe(value);
            }
        } else {

            StringPath path = entityPath.getString(criteria.getKey());

            if (criteria.getOperation().equalsIgnoreCase(":")) {
                return path.containsIgnoreCase(criteria.getValue().toString());
            }
        }
        return null;
    }   
}

所以这个集成测试会抛出一个NPE:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class JpaQueryDslIntegrationTest {

    private static final Logger logger = LoggerFactory.getLogger(JpaQueryDslIntegrationTest.class);

    @Autowired
    private FilterMatchRepository matchRepository;

    private FilterMatch match1;
    private FilterMatch match2;

    /**
     * @throws java.lang.Exception
     */
    @Before
    public void setUp() throws Exception {

        match1 = new FilterMatch();
        match1.setDisplayName("Emma");
        match1.setAge(40);
        match1.setJobTitle("Banker");
        match1.setCmHeight(150);

        City leeds = new City();
        leeds.setName("Leeds");
        leeds.setLon(-1.548567);
        leeds.setLat(53.801277);

        match1.setCity(leeds);
        match1.setMainPhoto("http://thecatapi.com/api/images/get?format=src&type=gif");
        match1.setCompatScore(0.73f);
        match1.setContactsExchanged(0);
        match1.setFave(Constants.FALSE.getValue());
        match1.setReligion("Christian");
        matchRepository.save(match1);

        match2 = new FilterMatch();
        match2.setDisplayName("Diana");
        match2.setAge(44);
        match2.setJobTitle("Consultant");
        match2.setCmHeight(153);

        City london = new City();
        london.setName("London");
        london.setLat(51.509865);
        london.setLon(-0.118092);

        match2.setCity(london);
        match2.setMainPhoto("http://thecatapi.com/api/images/get?format=src&type=gif");
        match2.setCompatScore(0.50f);
        match2.setContactsExchanged(0);
        match2.setFave(Constants.TRUE.getValue());
        match2.setReligion("Atheist");
        matchRepository.save(match2);
    }

    @Test
    public final void testMultipleMatch() {

        MatchPredicatesBuilder builder = new MatchPredicatesBuilder().with("displayName", ":", "Emma");

        Iterable<FilterMatch> results = matchRepository.findAll();
        results = matchRepository.findAll(builder.build()); // This is the line that throws the NPE. i've tested and builder.build() isn't null  
        assertThat(results, Matchers.containsInAnyOrder(match1, match2));
    }
}

我将builder.build()的内容打印出来&amp;它是:

containsIc(FilterMatch.displayName,Emma)

这里的堆栈跟踪减少了一点。 :

java.lang.NullPointerException
    at java.lang.String$CaseInsensitiveComparator.compare(String.java:1192)
    at java.lang.String$CaseInsensitiveComparator.compare(String.java:1186)
    at java.util.TreeMap.getEntryUsingComparator(TreeMap.java:376)
    at java.util.TreeMap.getEntry(TreeMap.java:345)
    at java.util.TreeMap.get(TreeMap.java:278)
    at org.hibernate.dialect.function.SQLFunctionRegistry.findSQLFunction(SQLFunctionRegistry.java:45)
    at org.hibernate.hql.internal.ast.util.SessionFactoryHelper.findSQLFunction(SessionFactoryHelper.java:369)
    at org.hibernate.hql.internal.ast.tree.IdentNode.getDataType(IdentNode.java:374)
    at org.hibernate.hql.internal.ast.HqlSqlWalker.lookupProperty(HqlSqlWalker.java:654)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.addrExpr(HqlSqlBaseWalker.java:5003)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.expr(HqlSqlBaseWalker.java:1286)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.exprOrSubquery(HqlSqlBaseWalker.java:4707)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.functionCall(HqlSqlBaseWalker.java:2733)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.expr(HqlSqlBaseWalker.java:1365)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.exprOrSubquery(HqlSqlBaseWalker.java:4707)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.comparisonExpr(HqlSqlBaseWalker.java:4319)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.logicalExpr(HqlSqlBaseWalker.java:2138)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.whereClause(HqlSqlBaseWalker.java:815)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:609)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:313)
    at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:261)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:266)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:189)
    at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:141)
    at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:115)
    at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:77)
    at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:153)
    at org.hibernate.internal.AbstractSharedSessionContract.getQueryPlan(AbstractSharedSessionContract.java:553)
    at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:662)
    at org.hibernate.internal.AbstractSessionImpl.createQuery(AbstractSessionImpl.java:23)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:298)
    at com.sun.proxy.$Proxy58.createQuery(Unknown Source)
    at com.querydsl.jpa.impl.AbstractJPAQuery.createQuery(AbstractJPAQuery.java:101)
    at com.querydsl.jpa.impl.AbstractJPAQuery.createQuery(AbstractJPAQuery.java:94)
    at com.querydsl.jpa.impl.AbstractJPAQuery.fetch(AbstractJPAQuery.java:201)
    at org.springframework.data.jpa.repository.support.QueryDslJpaRepository.findAll(QueryDslJpaRepository.java:105)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:520)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:505)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:477)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:56)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:282)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)

我有几个问题:

1)我让我的MatchPredicatesBuilder返回一个BooleanExpression作为我正在遵循的教程说我应该这样做但我不知道为什么编译器不会扼杀这个。我一直在java docs&amp;它仍然是一个谜。

2)为什么要投掷NPE? builder.build()不为null。从堆栈跟踪中很难分辨出来。

1 个答案:

答案 0 :(得分:1)

答案最终是将实体表命名为除了类本身名称之外的任何其他名称。当你让hibernate自动创建数据库和模式时会发生这种情况。

我更改了MatchPredicate类中的行:

    PathBuilder<FilterMatch> entityPath = new PathBuilder<>(FilterMatch.class, "FilterMatch");

为:

    PathBuilder<FilterMatch> entityPath = new PathBuilder<>(FilterMatch.class, "matches");

这引起了其他例外。所以现在它又回到了一个小写的过滤器匹配&#34;现在也与实体类上的表注释名称相同:

@Table(name="filtermatch")

我也改变了:

@SequenceGenerator(name = "MATCHES_SEQ_GENERATOR", sequenceName = "MATCHES_SEQ",
                   initialValue = 1, allocationSize = 1)

到:

@GenericGenerator(
        name = "MATCH_SEQ_GENERATOR",
        strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
        parameters = {
                @Parameter(name = "sequence_name", value = "MATCH_SEQ"),
                @Parameter(name = "initial_value", value = "1"),
                @Parameter(name = "increment_size", value = "1")
        })

删除了启动日志中的一些弃用错误。