# Store and load products

In this step you implement the two simplest data-access methods on `KeyValueServiceNewClient`. The `storeProduct` method writes a `Product` business object to a set, and `getProduct` reads one back by its key. Before you implement them, you walk through the constructor that wires up the SDK’s `ClusterDefinition`, `Behavior`, and `Session`.

## Open the file

1.  Stop the Spring Boot application from the previous step with `Ctrl+C`. Leave Voyager open and connected.
    
2.  Open `spring-server/src/main/java/com/aerospikeworkshop/service/KeyValueServiceNewClient.java` in your IDE.
    
    Every code change in this section, and in the next section, is made in this single file.
    

## Walk through the SDK connection code

The SDK connection code is already written for you, in the constructor of `KeyValueServiceNewClient`. Find it under the `// TODO: STEP 1: VALIDATE THE CONNECTION` header near the top of the file:

```java
public KeyValueServiceNewClient(ClientConfiguration config) {

    // =================================================================================

    // TODO: STEP 1: VALIDATE THE CONNECTION

    // =================================================================================

    ClusterDefinition clusterDef = new ClusterDefinition(config.getHostname(), config.getPort());

    if (config.getUserName() != null && config.getPassword() != null) {

        clusterDef = clusterDef.withNativeCredentials(config.getUserName(), config.getPassword());

    }

    aerospikeCluster = clusterDef.connect();

    session = aerospikeCluster.createSession(Behavior.DEFAULT);

}
```

You do not change this code in this tutorial, but reading the constructor top to bottom helps you understand the SDK pieces you use later:

-   `ClusterDefinition` describes how to find and authenticate against your cluster. It accepts the seed hostname and port, plus optional credentials, TLS settings, and other connection options. The hostname and port come from `application.yaml` through standard Spring property binding, so for a default Docker install the values are already correct.
-   `connect()` opens the cluster connection and returns a `Cluster` object that owns the underlying network resources.
-   A `Cluster` cannot run data operations on its own. The SDK requires a `Behavior`, which centralizes timeouts, retry policy, replica selection, and other per-call settings in one place. If you have used the legacy Aerospike client, `Behavior` replaces the per-method `Policy` objects. Behaviors are hierarchical and can be loaded from a YAML file by name.
-   `Cluster.createSession(Behavior.DEFAULT)` returns a `Session` that you use for inserts, queries, and updates. This tutorial uses `Behavior.DEFAULT` to keep the focus on the data API.

You use the `session` field for every database call from this point on.

## Implement storeProduct

The auto-loader calls `storeProduct(product)` once per record in the `data/styles` folder. The method body is a placeholder comment that describes what to implement.

1.  Find the `storeProduct` method.
    
    ```java
    public void storeProduct(Product product) {
    
        // =================================================================================
    
        // TODO: STEP 2: STORE A PRODUCT OBJECT
    
        // =================================================================================
    
        // Implement the logic to store a `Product` object in the database.
    
        //
    
        // This task tests the client's object mapping capabilities. Your goal is to:
    
        //  - Use the `session` object to `insertInto` the `productDataSet`.
    
        //  - Pass the `product` object to the operation.
    
        //  - Specify the `productMapper` to handle the conversion.
    
        //  - Execute the operation.
    
        //  - Throw an exception if it's already there.
    
        // =================================================================================
    
    }
    ```
    
2.  Replace the comment with the SDK call.
    
    ```java
    session.insert(productDataSet)
    
        .object(product)
    
        .using(productMapper)
    
        .execute();
    ```
    
    This is the SDK’s _fluent_ style: each method call adds one piece of the operation to a builder, and the chain reads top to bottom:
    
    -   `insert` writes the record and fails if a record with the same key already exists. The other write verbs are `update` (fails if the record does not exist) and `upsert` (writes the record whether or not it exists). The corresponding read verb, `query`, is what you use in `getProduct` later in this step.
    -   `productDataSet` is the destination. A `DataSet` is a `(namespace, set)` pair. Because this method uses object mapping, `productDataSet` is declared as a `TypeSafeDataSet<Product>`, which lets the SDK enforce that you only write `Product` objects to it.
    -   `object(product)` is the value to write. The `product` parameter is a plain Java object.
    -   `using(productMapper)` selects the `RecordMapper<Product>` that converts the object to and from Aerospike bins. The sample application provides `productMapper` as a field. You can also use the standalone [Java Object Mapper](https://github.com/aerospike/java-object-mapper) instead of writing your own `RecordMapper`.
    -   `execute()` is the terminal call that ships the operation to the cluster. Every fluent chain in the SDK ends with a terminal call (most often `execute()`). If you forget it, the chain only describes the operation and no request reaches the cluster.

### Confirm storeProduct works

1.  Stop the Spring Boot application if it is still running.
    
    In the terminal where the application is running, press `Ctrl+C` and wait for the shell prompt to return.
    
    If the previous Spring Boot process is no longer running but port `8080` is still bound, find the holding process with `lsof -nP -iTCP:8080 -sTCP:LISTEN` and stop it.
    
2.  Change into the `spring-server` directory if you are not already there.
    
    Terminal window
    
    ```shell
    cd spring-server
    ```
    
    Every Maven command in this tutorial runs from `aerospike-client-sdk-workshop/spring-server`, where the project’s `pom.xml` lives. If you open a new terminal between steps, run this `cd` first.
    
3.  Rebuild the project, then run it with the `new-client` profile.
    
    Terminal window
    
    ```shell
    mvn clean package -DskipTests
    
    mvn spring-boot:run -Dspring-boot.run.profiles=new-client
    ```
    
    Run the two commands one at a time and wait for the first to print `BUILD SUCCESS` before you start the second. The auto-load line in the logs still reports `success=200`.
    
    Build error: no POM, or no Spring Boot plugin
    
    If `mvn` reports `there is no POM in this directory` or `No plugin found for prefix 'spring-boot'`, you are running Maven from the workshop root instead of from `spring-server`. Run `cd spring-server` and try the commands again.
    
4.  In Voyager, select **Refresh** at the top of the cluster details panel.
    
    The `test` namespace now reports 200 records.
    
     ![Voyager showing the test namespace with 200 records after storeProduct is implemented](https://aerospike.com/docs/_astro/voyager-200-records.C3RW7EAm_pdx6.png)
5.  Drill into the `products` set to confirm that the records contain product data.
    
     ![Voyager showing rows in the products set with product bins populated](https://aerospike.com/docs/_astro/voyager-products-set.Ds576vMF_1K2zi4.png)
    
    You may need to refresh the set view as well as the namespace view to see the rows.
    

The fluent verb / `execute()` pattern reads like a sentence, so AI coding assistants in your IDE can often suggest the rest of a chain after you type the first call. Type each chain yourself the first time to understand what every part does, then accept the assistant’s suggestions on later methods if you find them helpful.

 ![IDE inline AI suggestion proposing the same SDK call shown in the tutorial](https://aerospike.com/docs/_astro/ide-ai-suggestion.CfOYXzKW_Z12wpsM.png)

## Implement getProduct

The `getProduct(productId)` method returns an `Optional<Product>`: the matching product if one exists, or `Optional.empty()` if not.

1.  Find the `getProduct` method. The body is a single line, `return Optional.empty();`.
    
2.  Delete the `return Optional.empty();` line, then paste the following chain in its place. If both `return` statements remain, the compiler reports an `unreachable statement` error.
    
    ```java
    return session.query(productDataSet.id(productId))
    
        .execute()
    
        .getFirst(productMapper);
    ```
    
    The chain has three parts:
    
    -   `session.query(productDataSet.id(productId))` builds a query with one key. The SDK detects the query type for you. With one key this is a point read; with several keys it is a batch read; with a data set being passed, it is either an index scan or a full scan. You do not have to specify which.
    -   `execute()` ships the operation and returns a result stream.
    -   `getFirst(productMapper)` reads at most one record from the stream and maps it to a `Product`. Because point reads return zero or one record, the result is naturally an `Optional<Product>`.

### Confirm getProduct works

1.  Stop the Spring Boot application if it is still running, then rebuild and rerun it from the `spring-server` directory.
    
    Terminal window
    
    ```shell
    cd spring-server
    
    mvn clean package -DskipTests
    
    mvn spring-boot:run -Dspring-boot.run.profiles=new-client
    ```
    
    If you are already in `spring-server`, you can skip the `cd`. Run the two `mvn` commands one at a time.
    
2.  In your browser, reload [http://localhost:8080](http://localhost:8080).
    
    The home page is still empty because you implement the listing query in the next step, but you can confirm that point reads work by visiting a product detail page directly.
    
3.  In Voyager, open the `products` set and copy any value from the **userKey** column at the start of a row. This is the record key you query against.
    
4.  In your browser, visit the product detail URL, replacing `PRODUCT_ID` with the key you copied:
    
    ```plaintext
    http://localhost:8080/product/PRODUCT_ID
    ```
    
    The page renders with the product’s details. If you visit a URL with a key that does not exist (for example, append `xyz` to a real key), the page renders an empty or “not found” state instead of throwing.
    

## Outcomes

You now have:

-   Two hundred product records in the `test.products` set.
-   The ability to look up any product by key from the running application.
-   A working understanding of `ClusterDefinition`, `Behavior`, `Session`, and the SDK’s fluent verb / `execute()` pattern.

You **cannot** yet:

-   See products listed on the home page or in category dropdowns.
-   Filter products by category, article type, usage, or brand.

In the next step you implement the secondary-index query that powers product listings, then build a multi-condition query for the advanced search filters.

::: undefined
-   I understand how the application connects to Aerospike with ClusterDefinition, Behavior, and Session.
-   I’ve implemented storeProduct and confirmed all 200 records are written.
-   I’ve implemented getProduct and confirmed product detail pages load.
:::

[Previous  
Start the database, Voyager, and the application](https://aerospike.com/docs/database/get-started-with-aerospike-java-sdk-and-voyager/step/1/part/1/start-the-demo) [Next  
Query by secondary index](https://aerospike.com/docs/database/get-started-with-aerospike-java-sdk-and-voyager/step/2/part/1/secondary-and-advanced-queries)