Multiple request parameters which are not required

I’m using Spring Boot with Neo4j. I’m able to specify several @RequestParam parameters in my controller. And, in my repository, I’m able to build @Query cypher statements and return specific custom query results.
I would like to create dynamic queries that handle request parameters which are not required and can be null. Does anyone have an example to show how this might be done.

public List findTestNodes(
@RequestParam(required = true ) String param1,
@RequestParam(required = false ) String param2,
@RequestParam(required = false ) String param3,
@RequestParam(required = false ) String param4)

param2 … 4 can be null. 9 combinations of null parameters.

Hello @richard.skallos Welcome to our community site.

First of all, Neo4j-OGM and Spring Data is about mapping nodes and their relationships to a domain model. You hardly return raw nodes, at list that is really not recommended.

So, I assume an entity like this:

import org.neo4j.ogm.annotation.GeneratedValue;
import org.neo4j.ogm.annotation.Id;
import org.neo4j.ogm.annotation.NodeEntity;

@NodeEntity
public class SomeEntity {

	@Id @GeneratedValue
	Long id;

	private String name;

	public Long getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

The entity can be accessed by a so called repository, which should be declared like this:

import org.springframework.data.neo4j.repository.Neo4jRepository;

public interface SomeRepository extends Neo4jRepository<SomeEntity, Long> {
}

You don't need to provide an implementation of it. That is done via Spring Data for your.

In you're question you have @RequestParam, I guess your dealing with an Web MVC endpoint that may look like this. Here I inject the repository, which will be done through Spring Framework during runtime.

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SomeController {

	private final SomeRepository someRepository;

	public SomeController(SomeRepository someRepository) {
		this.someRepository = someRepository;
	}

	@GetMapping("/findAll")
	public Iterable<SomeEntity> findAll() {
		return someRepository.findAll();
	}
}

The findAll mapping uses a predefined function on the repository. You can easily add more. First of all, derived finder methods. The repo may now look like this:

import java.util.List;

import org.springframework.data.neo4j.repository.Neo4jRepository;

public interface SomeRepository extends Neo4jRepository<SomeEntity, Long> {
	
	List<SomeEntity> findAllByName(String name);
}

Which can be called from the controller accordingly (this time, I'll only add the additional method for the controller):

@GetMapping("/findAllByName")
public Iterable<SomeEntity> findAllByName(@RequestParam String name) {
	return someRepository.findAllByName(name);
}

While you could add many more parameters. that doesn't help you with a query that might return different results when the parameter is null. Spring Data Neo4j supports IS_NULL in a derived finder method (findAllByNameIsNull), but that refers to the attribute / property, not the parameter.

Having said that, it is absolutely okay to have custom queries on the repository. Here's one that shows you 3 different ways of treating parameters. The repository now looks like this:

import java.util.List;

import org.springframework.data.neo4j.annotation.Query;
import org.springframework.data.neo4j.repository.Neo4jRepository;

public interface SomeRepository extends Neo4jRepository<SomeEntity, Long> {

	List<SomeEntity> findAllByName(String name);

	@Query("MATCH (n:SomeEntity) "
		+ " WHERE n.name = $p1 "
		+ "    OR (n.name = $p2 or $p2 is null) "
		+ "    OR n.name = coalesce($p3, 'default') "
		+ "RETURN  n "
	)
	List<SomeEntity> findByVariousThings(String p1, String p2, String p3);
}

p1 needs to be not null for the condition to be able to evaluate to true, both p2 and p3 can be null.
In case of p2, the name must match to the value of p2 or p2 must be null.
In case of p3, when the parameter is null, than the name of the node must be default.

You controller can just call this again as is:

@GetMapping("/findTestNodes")
public List<SomeEntity> findTestNodes(
	@RequestParam String param1,
	@RequestParam(required = false) String param2,
	@RequestParam(required = false) String param3) 
{
	return someRepository.findByVariousThings(param1, param2, param3);
}

Please note that one does not just expose the repositories to the web. Even though things like Spring Data Rest might suggest this or might look like you're doing this, there's a generated controller in between.

Often it is a good idea to have a service class in between. This is the case when you need to orchestrate one or more repository.

I hope that helps. If you generate a project with SDN and Spring Web MVC on start.spring.io, you can just copy above classes into it.

1 Like

I tested this query and it worked. It is similar to the suggested query with different parameters.

@Query( "MATCH (t:Test) " +
" WHERE (t.name = {name} or {name} is null) " +
" AND (t.nodeId = {nodeId} or {nodeId} is null) " +
"RETURN id(t) AS id, t.name AS name, " +
" t.nodeId AS nodeId " )
List findTestNodes2(String name, Long nodeId);

Either or both “name” or “nodeId” can be null. If either or both are not null then the appropriate node data are returned to the custom annotated query result, “TestQueryResult”. If both are null then all test nodes are returned.

1 Like

This is a really succinct overview of how to use OGM and SDN, which should save newcomers a lot of time. Put this response high in the recommendation tree!

1 Like

Aaaw, thank you. That's very kind, taking time and giving feedback. Much appreciated.