home shape

Part 2 - CRUD operations & query by example

Following the previous part, in our Spring Data demo, where we built our setup, we will now take a look at CRUD operations and the query by example API.

CRUD operations

Create a repository

Now that we have our data model we want to store data. For this, we create a repository interface which extends ArangoRepository. This gives us access to CRUD operations, pagein and query by example mechanics.

package com.arangodb.spring.demo.repository;
 
import com.arangodb.spring.demo.entity.Character;
import com.arangodb.springframework.repository.ArangoRepository;
 
public interface CharacterRepository extends ArangoRepository {
 
}
background img reverse min

Create a CommandLineRunner

To run our demo with Spring Boot we have to create a class implementing CommandLineRunner. In this class we can use the @Authowired annotation to inject our CharacterRepository – we created one step earlier – and also ArangoOperations which offers a central support for interactions with the database over a rich feature set. It mostly offers the features from the ArangoDB Java driver with additional exception translation.

To get the injection successfully running we have to add @ComponentScan to our runner to define where Spring can find our configuration class DemoConfiguration.

package com.arangodb.spring.demo.runner;
 
import com.arangodb.spring.demo.repository.CharacterRepository;
import com.arangodb.springframework.core.ArangoOperations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.ComponentScan;
 
@ComponentScan("com.arangodb.spring.demo")
public class CrudRunner implements CommandLineRunner {
 
    @Autowired
    private ArangoOperations operations;
    @Autowired
    private CharacterRepository repository;
 
    @Override
    public void run(final String... args) throws Exception {
 
    }
}

Save and read an entity

It’s time to save our first entity in the database. Both the database and the collection don’t have to be created manually. This happens automatically as soon as we execute a database request with the components involved. We don’t have to leave the Java world to manage our database.

After we saved a character in the database the id in the original entity is updated with the one generated from the database. We can then use this id to find our persisted entity.

package com.arangodb.spring.demo.runner;
 
import com.arangodb.spring.demo.entity.Character;
import com.arangodb.spring.demo.repository.CharacterRepository;
import com.arangodb.springframework.core.ArangoOperations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.ComponentScan;
 
import java.util.Optional;
 
@ComponentScan("com.arangodb.spring.demo")
public class CrudRunner implements CommandLineRunner {
 
    @Autowired
    private ArangoOperations operations;
    @Autowired
    private CharacterRepository repository;
 
    @Override
    public void run(String... args) throws Exception {
        // first drop the database so that we can run this multiple times with the same dataset
        operations.dropDatabase();
 
        // save a single entity in the database
        // there is no need of creating the collection first. This happen automatically
        final Character nedStark = new Character("Ned", "Stark", true, 41);
        repository.save(nedStark);
        // the generated id from the database is set in the original entity
        System.out.println(String.format("Ned Stark saved in the database with id: '%s'", nedStark.getId()));
 
        // lets take a look whether we can find Ned Stark in the database
        final Optional foundNed = repository.findById(nedStark.getId());
        assert foundNed.isPresent();
        System.out.println(String.format("Found %s", foundNed.get()));
    }
}
background img

Run the demo

The last thing we have to do before we can successfully run our demo application is to add our command line runner CrudRunner in the list of runners in our main class DemoApplication.

Class[]runner = new Class[]{CrudRunner.class};

After executing the demo application we should see the following lines within our console output. The id will of course deviate.

Ned Stark saved in the database with id: '346'
Found Character [id=346, name=Ned, surname=Stark, alive=true, age=41]

Update an entity

As everyone probably knows, Ned Stark died in the first season of Game of Thrones. So we have to update his ‘alive’ flag. Thanks to our id field in the class Character, we can use the method save() from our repository to perform an upsert with our variable nedStark in which id is already set.

Let’s add the following lines of code to the end of our run() method in CrudRunner.

nedStark.setAlive(false);
repository.save(nedStark);
final Optional deadNed = repository.findById(nedStark.getId());
assert deadNed.isPresent();
System.out.println(String.format("The 'alive' flag of the persisted Ned Stark is now '%s'",deadNed.get().isAlive()));

If we run our demo a second time the console output should look like this:

Ned Stark saved in the database with id: '508'
Found Character [id=508, name=Ned, surname=Stark, alive=true, age=41]
The 'alive' flag of the persisted Ned Stark is now 'false'
background img reverse min

Save and read multiple entities

What we can do with a single entity, we can also do with multiple entities. It’s not just a single method call for convenience purpose, it also requires only one database request.

Let’s save a bunch of characters. Only main casts of Game of Thrones – but that’s already a lot. After that we fetch all of them from our collection and count them.

Extend the run() method with these lines of code.

Collection createCharacters = createCharacters();
System.out.println(String.format("Save %s additional chracters",createCharacters.size()));
repository.saveAll(createCharacters);
 
Iterable all = repository.findAll();
long count=StreamSupport.stream(Spliterators.spliteratorUnknownSize(all.iterator(),0),false).count();
System.out.println(String.format("A total of %s characters are persisted in the database",count));

We also need the method createCharacters() which looks as follow:

public static Collection createCharacters(){
        return Arrays.asList(new Character("Robert","Baratheon",false),
        new Character("Jaime","Lannister",true,36),new Character("Catelyn","Stark",false,40),
        new Character("Cersei","Lannister",true,36),new Character("Daenerys","Targaryen",true,16),
        new Character("Jorah","Mormont",false),new Character("Petyr","Baelish",false),
        new Character("Viserys","Targaryen",false),new Character("Jon","Snow",true,16),
        new Character("Sansa","Stark",true,13),new Character("Arya","Stark",true,11),
        new Character("Robb","Stark",false),new Character("Theon","Greyjoy",true,16),
        new Character("Bran","Stark",true,10),new Character("Joffrey","Baratheon",false,19),
        new Character("Sandor","Clegane",true),new Character("Tyrion","Lannister",true,32),
        new Character("Khal","Drogo",false),new Character("Tywin","Lannister",false),
        new Character("Davos","Seaworth",true,49),new Character("Samwell","Tarly",true,17),
        new Character("Stannis","Baratheon",false),new Character("Melisandre",null,true),
        new Character("Margaery","Tyrell",false),new Character("Jeor","Mormont",false),
        new Character("Bronn",null,true),new Character("Varys",null,true),new Character("Shae",null,false),
        new Character("Talisa","Maegyr",false),new Character("Gendry",null,false),
        new Character("Ygritte",null,false),new Character("Tormund","Giantsbane",true),
        new Character("Gilly",null,true),new Character("Brienne","Tarth",true,32),
        new Character("Ramsay","Bolton",true),new Character("Ellaria","Sand",true),
        new Character("Daario","Naharis",true),new Character("Missandei",null,true),
        new Character("Tommen","Baratheon",true),new Character("Jaqen","H'ghar",true),
        new Character("Roose","Bolton",true),new Character("The High Sparrow",null,true));
        }

After executing the demo again the console should print the following additional lines:

Save 42 additional chracters
A total of 43 characters are persisted in the database

Counting is just an example of what you can do with the returned entities and it’s also not a perfect one. Fetching every entity from a collection only to count them is quite inefficient. As an alternative we can use the method count() from ArangoRepository or we use ArangoOperations for it.

// count with ArangoRepository
long count = repository.count();
// count with ArangoOperations
long count = operations.collection(Character.class).count();
right blob min

Read with sorting and paging

Next to the normal findAll()ArangoRepository also offers the ability to sort the fetched entities by a given field name. Adding the following source code at the end of your run() method gives you all characters sorted by field name.

System.out.println("## Return all characters sorted by name");
Iterable allSorted = repository.findAll(Sort.by(Sort.Direction.ASC, "name"));
allSorted.forEach(System.out::println);

Furthermore, it’s possible to use pagination combined with sorting. With the following code you get the first 5 characters sorted by name.

System.out.println("## Return the first 5 characters sorted by name");
Page first5Sorted = repository.findAll(PageRequest.of(0, 5, Sort.by(Sort.Direction.ASC, "name")));
<first5Sorted.forEach(System.out::println);<

Your console output should include Arya Stark, Bran Stark, Brienne Tarth, Bronn and Catelyn Stark.

## Return the first 5 characters sorted by name
Character [id=1898, name=Arya, surname=Stark, alive=true, age=11]
Character [id=1901, name=Bran, surname=Stark, alive=true, age=10]
Character [id=1921, name=Brienne, surname=Tarth, alive=true, age=32]
Character [id=1913, name=Bronn, surname=null, alive=true, age=null]
Character [id=1890, name=Catelyn, surname=Stark, alive=false, age=40]
right blob long

Query by example

Since version 1.12 Spring Data provides the interface QueryByExampleExecutor which is also supported by ArangoDB Spring Data. It allows execution of queries by Example instances.

Lets create a new CommandLineRunner for this

package com.arangodb.spring.demo.runner;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
 
import com.arangodb.spring.demo.entity.Character;
import com.arangodb.spring.demo.repository.CharacterRepository;
 
@ComponentScan("com.arangodb.spring.demo")
public class ByExampleRunner implements CommandLineRunner {
 
    @Autowired
    private CharacterRepository repository;
 
    @Override
    public void run(final String... args) throws Exception {
        System.out.println("# Query by example");
    }
 
}

and add it to our DemoApplication.

Class[]runner = new Class[]{
        CrudRunner.class,
        ByExampleRunner.class
};

Single entity

First we want to find Ned Stark again. But this time we don’t know the id of the persisted entity. We start with creating a Character with the same property values as the searched one. Then create an Example instance of it with Example.of(T) and search for it with findOne(Example) from our CharacterRepository.

final Character nedStark=new Character("Ned", "Stark", false, 41);
System.out.println(String.format("## Find character which exactly match %s",nedStark));
Optional foundNedStark = repository.findOne(Example.of(nedStark));
assert foundNedStark.isPresent();
System.out.println(String.format("Found %s", foundNedStark.get()));

If we did everything right the console output should be:

# Query by example
## Find character which exactly match Character [id=null, name=Ned, surname=Stark, alive=false, age=41]
Found Character [id=1880, name=Ned, surname=Stark, alive=false, age=41]
small left bkgd img

Multiple entities

Now we want to find more than one entity. During the time when I watched Game of Thrones I saw a lot of Starks dying, so lets take look who is already dead. For this we create a new instance of Character with surname ‘Stark’ and alive false. Because we only need these two fields in our entity, we have to ignore the other fields in our ExampleMatcher.

System.out.println("## Find all dead Starks");
Iterable allDeadStarks = repository
        .findAll(Example.of(new Character(null, "Stark", false),ExampleMatcher.matchingAll()
        .withMatcher("surname", GenericPropertyMatcher::exact).withIgnorePaths("name", "age")));
allDeadStarks.forEach(System.out::println);

After executing the application the console output should be:

## Find all dead Starks
Character [id=1887, name=Ned, surname=Stark, alive=false, age=41]
Character [id=1890, name=Catelyn, surname=Stark, alive=false, age=40]
Character [id=1899, name=Robb, surname=Stark, alive=false, age=null]

In addition to search for specific values from our example entity, we can search for dynamically depending values. In the next example we search for a Stark who is 30 years younger than Ned Stark. Instead of changing the age for Ned Stark in the previously fetched entity, we use a transformer within the ExampleMatcher and subtract 30 from the age of Ned Stark.

System.out.println("## Find all Starks which are 30 years younger than Ned Stark");
Iterable allYoungerStarks = repository.findAll(
    Example.of(foundNedStark.get(), ExampleMatcher.matchingAll()
        .withMatcher("surname", GenericPropertyMatcher::exact)
        .withIgnorePaths("id", "name", "alive")
        .withTransformer("age", age -> age.map(it -> (int) it - 30))));
        allYoungerStarks.forEach(System.out::println);

Because we are using the entity foundNedStark – fetched from the database – we have to ignore the field id which isn’t null in this case.

The console output should only include Arya Stark.

## Find all Starks which are 30 years younger than Ned Stark
Character [id=1898, name=Arya, surname=Stark, alive=true, age=11]

Aside from searching for exact and transformed values we can – in case of type String also search for other expressions. In this last case we search for every character whose surname ends with ‘ark’. The console output should include every Stark.

System.out.println("## Find all character which surname ends with 'ark' (ignore case)");
Iterable ark = repository.findAll(Example.of(new Character(null, "ark", false),
ExampleMatcher.matchingAll().withMatcher("surname", GenericPropertyMatcher::endsWith)
        .withIgnoreCase()
        .withIgnorePaths("name", "alive", "age")));
ark.forEach(System.out::println);

What's next

In the next part of our Spring Data demo we’re going to take a look how we can use custom methods in our repository interfaces to perform AQL queries without the need of writing them on our own.