A typical application works with data and has a database to store it. Let’s imagine such an application. What can it do with the data? First of all, this data has to be stored (or created) in the database. Then, of course, we want this application to read the data. After reading, we might want to update it or delete it from the database. All these operations are known as CRUD, which stands for Create, Read, Update, and Delete.

In this topic, you will learn how to use CRUD operations to interact with a database from a Spring application. This topic is intentionally written as database-agnostic. It means that it should work with any kind of database supported by Spring Data.

Repositories in Spring

You are already familiar with the entity concept. In Java/Kotlin, we manipulate database data using an entity. For example, we decide to open a fitness center and want to save all information about our fitness equipment: so far, we have a Treadmill. That’s not a lot of equipment, right? But we’ve just started our business, give us a bit more time. We have to implement all CRUD operations for our entity. A little bit annoying, but it is just one entity. And then it turns out there will be not one but a few dozen different entities in the gym! Oops!

Fortunately, Spring data has the Repository concept. But what is it and how can it help us?

In Spring Data, a repository is an abstraction that helps us reduce the amount of boilerplate code. It provides several repository interfaces. All these interfaces are database-agnostic. It means that you can use all these abstractions with any database: it could be a relational database or a NoSQL database.

The base interface is Repository:

@Indexed
public interface Repository<T, ID> {
}

Did you notice the @Indexed annotation here? It indicates that all descendants of this interface should be treated as candidates for repository beans. Also, you can see that the Repository interface doesn’t have any declared methods. That’s because its purpose is to be a marker interface.

The Repository interface is generic. Generic type T represents an entity type, and generic type ID represents the entity’s unique id type.

For CRUD operations, there is another interface, CrudRepository:

@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
    // CREATE/UPDATE methods
    <S extends T> S save(S entity);
    <S extends T> Iterable<S> saveAll(Iterable<S> entities);

    // READ methods
    Optional<T> findById(ID id);
    boolean existsById(ID id);
    Iterable<T> findAll();
    Iterable<T> findAllById(Iterable<ID> ids);
    long count();

    // DELETE methods
    void deleteById(ID id);
    void delete(T entity);
    void deleteAllById(Iterable<? extends ID> ids);
    void deleteAll(Iterable<? extends T> entities);
    void deleteAll();
}

CrudRepository contains operations for each CRUD action. As you can see, there are more than four methods: the interface provides an opportunity to pass both an entity object and an entity id to its method. Also, you can choose whether you want to work with a single entity or with multiple entities at once.

You may have noticed that the CrudRepository interface has the same declared methods for the Create and Update operations. Spring data under the hood checks if a given entity is new or old and, depending on that, creates or updates the entity.

Don’t worry if you can’t remember them all. The source code is always available and easy to find. The important part is to understand the main ideas behind CrudRepository.

Declaring a repository

Now we’re fully equipped to create our own repository.

Let’s start from the Treadmill data class:

// an annotation goes here
public class Treadmill {
    private String code;
    private String model;

    // constructor, getters, setters

}

You may wonder why we didn’t mark the Treadmill class with any annotation. The reason is that the entity declaration can vary depending on the actual database you are using. Depending on your target database, you have to choose @Entity@Document@KeySpace, or some other annotation.

To each entity its own… Wait, what is the thing on which an entity is mapped? It depends on the database of your choice. It could be a table (good old relational databases), a document (modern NoSQL document-oriented databases, e.g., MongoDB), a node and an edge (a NoSQL graph database Neo4J is an example for that), or some other type of user database.

To create our repository, we need to extend CrudRepository and specify the entity type (Treadmill) and the entity’s id type (String) as follows:

public interface TreadmillRepository extends CrudRepository<Treadmill, String> {

}

That’s all. Here in the topic, we are using the property String code as an id. But you can use any type you want. In real-world applications, the Long type is commonly used, especially if you’re going to use a relational database and sequential numbers as an id. Spring creates required implementations for all CRUD methods presented in the CrudRepository class.

Note that in our examples, we use a Treadmill entity and TreadmillRepository. You will need to enrich Treadmill entity with annotations for the database you’re using.

Application Runner

Here is the template of the Application class used in our project:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }

    @Component
    public class Runner implements CommandLineRunner {
        private final TreadmillRepository repository;

        public Runner(TreadmillRepository repository) {
            this.repository = repository;
        }

        @Override
        public void run(String... args) {
            // work with the repository here
        }
    }
}

You need to place all your work with the repository inside the run method.

Create

We use the count method to check if the database has any data in it. The method name is rather self-explanatory. It will return the number of objects we have in the database.

private void doWeHaveSomethingInDb() {
    long count = repository.count();
    if (count > 0) {
        System.out.printf("Db has %d treadmills", count);
    } else {
        System.out.println("Db is empty");
    }
}

To create an entity in the database, you need to create a new object and pass it to the save method. We can check our database before and after calling the save method in the following way:

System.out.println("Before save:");
doWeHaveSomethingInDb();
            
System.out.println("Saving...");
repository.save(new Treadmill("aaa", "Yamaguchi runway"));

System.out.println("After save:");
doWeHaveSomethingInDb();
Before save:
Db is empty
Saving...
After save:
Db has 1 treadmills

The output proves that the entity didn’t exist before calling the save method. After calling the save method, we can observe some data in the database. You may wonder what exactly that data represents. To answer this question, we read the data from the database.

Read

The CrudRepository has five methods to read data from the database. We’ve covered the count method in the previous section. Now let’s discuss two more methods: findById and findAll. The existsById and findAllById methods are similar to the findById method. The only difference is that the existsById method returns a boolean flag that indicates whether the entity with the requested id is present in the database. The findAllById method allows requesting multiple entities by their id at once.

Let’s add some more data to our database: Treadmill("bbb", "Yamaguchi runway pro-x")Treadmill("ccc", "Yamaguchi max"). You’ve already learned how to do it in the previous section.

The findById method returns an Optional object that requires you to perform additional actions to access a real object. But if there is a probability of getting a null object, you have to handle it anyway, either with Optional or with the manual ‘if not null’ check.

Also, we need a method to represent an entity as a string:

private String createTreadmillView(Treadmill treadmill) {
    return "Treadmill(code: %s, model: %s)"
            .formatted(treadmill.getCode(), treadmill.getModel());
}

In Java, alternatively, you can declare the toString() method inside the Treadmill entity. Note that the formatted method is a feature of Java 15. If you have a lower version of Java, you can use String.format.

In Kotlin, alternatively, you can declare the toString() method inside the Treadmill entity or mark entity class as data class.

As usual, we can print the details to see how it works:

System.out.println("Looking for the treadmill with code='bbb'... ");
Optional<Treadmill> treadmill = repository.findById("bbb");
String result = treadmill.map(this::createTreadmillView).orElse("Not found");
System.out.println(result);
Looking for the treadmill with code='bbb'... 
Treadmill(code: bbb, model: Yamaguchi runway pro-x)

No surprises here. We knew that there was such an object because we’d just created it ourselves.
What if there is no object with the requested id? Here is how it would go:

System.out.println("Looking for the treadmill with code='not-existed-code'... ");
Optional<Treadmill> treadmill = repository.findById("not-existed-code");
String result = treadmill.map(this::createTreadmillView).orElse("Not found");
System.out.println(result);

As expected, we get:

Looking for the treadmill with code='not-existed-code'... 
Not found

If you want to get all entities from the database, the CrudRepository interface provides you with the findAll method. It’s even simpler than findById:

Iterable<Treadmill> treadmills = repository.findAll();
for (Treadmill treadmill : treadmills) {
    System.out.println(createTreadmillView(treadmill));
}
Treadmill(code: aaa, model: Yamaguchi runway)
Treadmill(code: bbb, model: Yamaguchi runway pro-x)
Treadmill(code: ccc, model: Yamaguchi max)

The output shows all our entities. If we don’t have any entities in the database, the findAll method returns an empty Iterable object, and nothing will be printed. You can find it out for yourself.

If the database has many entities, the findAll method can lead to performance degradation or even out-of-memory error. Use this method wisely.

Update

When we print all our treadmills to the output, we realize that the treadmill with code="aaa" has the wrong model. It should be "Yamaguchi runway-x" instead of "Yamaguchi runway". Here is how we can fix this:

Optional<Treadmill> existedTreadmill = repository.findById("aaa");

String existed = existedTreadmill
        .map(this::createTreadmillView)
        .orElse("Not found");

System.out.println("Before update: " + existed);
System.out.println("Updating...");

existedTreadmill.ifPresent(treadmill -> {
    treadmill.setModel("Yamaguchi runway-x");
    repository.save(treadmill);
});

Optional<Treadmill> updatedTreadmill = repository.findById("aaa");
String updated = updatedTreadmill
        .map(this::createTreadmillView)
        .orElse("Not found");

System.out.println("After update: " + updated);

Just a reminder: if the database has an entity with the same id, save methods act as update methods.

Before update: Treadmill(code: aaa, model: Yamaguchi runway)
Updating...
After update: Treadmill(code: aaa, model: Yamaguchi runway-x)

The output shows that everything works as expected, and our treadmill model has been updated.

Delete

For the delete action, CrudRepository provides five methods. We are going to cover the deleteById and delete(entity) methods. These methods work similarly just for a set of entities at once. The deleteAll method cleans up all your entities.

To show how it works, we introduce a new method:

private void printAllTreadmills() {
    Iterable<Treadmill> treadmills = repository.findAll();
    for (Treadmill treadmill : treadmills) {
        System.out.println(createTreadmillView(treadmill));
    }
}

Three types of treadmills are a lot. We’ve decided to delete the "Yamaguchi max" treadmill from our list. It has code="ccc":

System.out.println("Before delete: ");
printAllTreadmills();

System.out.println("Deleting...");
repository.deleteById("ccc");

System.out.println("After delete: ");
printAllTreadmills();
Before delete: 
Treadmill(code: aaa, model: yamaguchi runway-x)
Treadmill(code: bbb, model: Yamaguchi runway pro-x)
Treadmill(code: ccc, model: Yamaguchi max)
Deleting...
After delete: 
Treadmill(code: aaa, model: yamaguchi runway-x)
Treadmill(code: bbb, model: Yamaguchi runway pro-x)

Now there are only two treadmills in the database.

A fitness center is not a new idea. We’ve got a better one: now we will open a coworking center equipped with height-adjustable desks and compact treadmills "Yamaguchi runway-x". So we need to delete "Yamaguchi runway pro-x":

System.out.println("Before delete: ");
printAllTreadmills();

System.out.println("Deleting...");
Optional<Treadmill> proXTreadmill = repository.findById("bbb");
proXTreadmill.ifPresent(
        treadmill -> {
            repository.delete(treadmill);
        }
);

System.out.println("After delete: ");
printAllTreadmills();
Before delete: 
Treadmill(code: aaa, model: yamaguchi runway-x)
Treadmill(code: bbb, model: Yamaguchi runway pro-x)
Deleting...
After delete: 
Treadmill(code: aaa, model: yamaguchi runway-x)

Now we have only one treadmill and can start a new coworking center business.

Conclusion

Modern applications often include interactions with a database. CRUD is the acronym for the operations you can do with data stored in a database and used in your application: create, read, update, and delete. The Spring Data CrudRepository allows us to perform all these operations. Without it, we would have to implement these operations over and over again for our entities. Another important thing is that CrudRepository is database-agnostic.

In this topic, we’ve covered predefined operations from CrudRepository. Soon you will learn how to create your own operations in your repository. Stay tuned!

Leave a Reply

Your email address will not be published.