Search code examples
javahibernatespring-booth2querydsl

Spring Query DSL Giving Null Pointer Exception


I'm using Spring Boot and a Hibernate file database for this prototype.

here's a snippet out of application.properties (user/pass omitted. obviously) NOTE I have set fave as a String rather than a boolean so I can use a String in the Query DSL Bindings:

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

here's the model w/ City class omitted as it's quite simple:

@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
}

here's the repository which extends JPARepository as is necessary:

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);
    }

}

my SearchCriteria class:

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.
}

my MatchBuilderPredicates class and my MatchPredicates class:

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;
    }   
}

so this integration test throws a 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));
    }
}

i have the contents of builder.build() printing out & it's:

containsIc(FilterMatch.displayName,Emma)

here's the stack trace cut down a bit. :

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)

I have a couple of questions:

1) I have my MatchPredicatesBuilder returning a BooleanExpression as the tutorial I'm following says I should do so but I don't know why the compiler doesn't choke on this. I've been in the java docs & it's still a mystery.

2) Why is it throwing a NPE? builder.build() isn't null. It's hard to tell from the stack trace.


Solution

  • The answer ended up being that naming the entity table to any other name than the name of the class itself didn't work out. this happens when you let hibernate create the db and schema automatically.

    I changed the line in the MatchPredicate class from:

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

    to:

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

    and that caused other exceptions. so now it's back to a lower-case "filtermatch" which also now is the same as the Table annotation name on the entity class:

    @Table(name="filtermatch")
    

    I also changed:

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

    to:

    @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")
            })
    

    which got rid of some deprecated errors in the start-up log.