Quantcast
Channel: Baeldung
Viewing all 4469 articles
Browse latest View live

Switching Between Frames Using Selenium WebDriver in Java

$
0
0

1. Introduction

Managing frames and iframes is a crucial skill for test automation engineers. Selenium WebDriver allows us to work with both frames and iframes in the same way.

In this tutorial, we’ll explore a few distinct methods to switch between frames with Selenium WebDriver. These methods include using a WebElement, a name or ID, and an index.

By the end, we’ll be well-equipped to tackle iframe interactions confidently, enhancing the scope and effectiveness of our automation tests.

2. Difference Between Frame and Iframe

The terms frames and iframes are often encountered in web development. Each serves a distinct purpose in structuring and enhancing web content.

Frames, an older HTML feature, partition a web page into separate sections where each section has its own dedicated HTML document. Although frames are deprecated, they are still encountered on the web.

Iframes (inline frames) embed a separate HTML document within a single frame on a web page. They are widely used in web pages for various purposes, such as incorporating external content like maps, social media widgets, advertisements, or interactive forms seamlessly.

3. Switch to Frame Using a WebElement

Switching using a WebElement is the most flexible option. We can find the frame using any selector, like ID, name, CSS selector, or XPath, to find the specific iframe we want:

WebElement iframeElement = driver.findElement(By.cssSelector("#frame_selector"));
driver.switchTo().frame(iframeElement);

For a more reliable approach, it’s better to use explicit waits, such as ExpectedConditions.frameToBeAvailableAndSwitchToIt():

WebElement iframeElement = driver.findElement(By.cssSelector("#frame_selector"));
new WebDriverWait(driver, Duration.ofSeconds(10))
  .until(ExpectedConditions.frameToBeAvailableAndSwitchToIt(iframeElement))

This helps ensure that the iframe is fully loaded and ready for interaction, reducing potential timing issues and making our automation scripts more robust when working with iframes.

4. Switch to Frame Using a Name or ID

Another method to navigate into a frame is by leveraging its name or ID attribute. This approach is straightforward and particularly useful when these attributes are unique:

driver.switchTo().frame("frame_name_or_id");

Using explicit wait ensures that the frame is fully loaded and prepared for interaction:

new WebDriverWait(driver, Duration.ofSeconds(10))
  .until(ExpectedConditions.frameToBeAvailableAndSwitchToIt("frame_name_or_id"));

5. Switch to Frame Using an Index

Selenium allows us to switch to a frame using a simple numerical index. The first frame has an index of 0, the second has an index of 1, and so on. Switching to frames using an index offers a flexible and convenient approach, especially when an iframe lacks a distinct name or ID.

By specifying the index of the frame, we can seamlessly navigate through the frames within a web page:

driver.switchTo().frame(0);

Explicit wait makes code more robust:

new WebDriverWait(driver, Duration.ofSeconds(10))
  .until(ExpectedConditions.frameToBeAvailableAndSwitchToIt(0));

However, it’s important to use frame indexes with caution because the order of frames can change on a web page. If a frame is added or removed, it can disrupt the index order, leading to potential failures in our automated tests.

6. Switching to a Nested Frame

When frames are nested, it means that one or more frames are embedded within other frames, forming a parent-child relationship. This hierarchy can continue to multiple levels, resulting in complex nested frame structures:

<!DOCTYPE html>
<html>
<head>
    <title>Frames Example</title>
</head>
<body>
    <h1>Main Content</h1>
    <p>This is the main content of the web page.</p>
    <iframe id="outer_frame" width="400" height="300">
        <h2>Outer Frame</h2>
        <p>This is the content of the outer frame.</p>
        <iframe id="inner_frame" width="300" height="200">
            <h3>Inner Frame</h3>
            <p>This is the content of the inner frame.</p>
        </iframe>
    </iframe>
    <p>More content in the main page.</p>
</body>
</html>

Selenium provides a straightforward method for handling them. To access an inner frame within a nested frame structure, we should switch from the outermost to the inner one sequentially. This allows us to access the elements within each frame as we go deeper into the hierarchy:

driver.switchTo().frame("outer_frame");
driver.switchTo().frame("inner_frame");

7. Switching Back From Frame or Nested Frame

Selenium provides a mechanism to switch back from frames and nested frames with distinct methods. For returning to the main content, we can use the method defaultContent():

driver.switchTo().defaultContent()

It essentially exits all frames and ensures that our subsequent interactions take place in the main context of the web page. This is particularly useful when we’ve completed tasks within frames and need to continue our actions in the main content.

For moving to the parent frame, we can use the parentFrame() method:

driver.switchTo().parentFrame()

This method allows us to transition from a child frame back to its immediate parent frame. It’s particularly valuable when we’re working with nested frames, each embedded within another, and we need to move between them.

8. Conclusion

In this article, we’ve explored frames and how to work with them using Selenium WebDriver. We’ve learned different methods to switch between them using WebElements, names or IDs, and numerical indices. These methods offer flexibility and precision.

By using explicit waits, we’ve ensured reliable interactions with frames, reducing potential issues and making our automation scripts more robust.

We’ve learned how to handle nested frames by sequentially switching from the outermost frame to the inner ones, allowing us to access elements within complex nested frame structures. We also learned how to switch back to the main content as well as move to the parent frame.

In conclusion, mastering frame and iframe handling with Selenium WebDriver is vital for test automation engineers. With the knowledge and techniques, we’re well-prepared to confidently deal with frames.

As always, the code presented in this article is available over on GitHub.

       

Why Is sun.misc.Unsafe.park Actually Unsafe?

$
0
0

1. Overview

Java provides certain APIs for internal use and discourages unnecessary use in other cases. The JVM developers gave the packages and classes names such as Unsafe, which should warn developers.  However, often, it doesn’t stop developers from using these classes.

In this tutorial, we’ll learn why Unsafe.park() is actually unsafe. The goal isn’t to scare but to educate and provide a better insight into the interworking of the park() and unpark(Thread) methods.

2. Unsafe

The Unsafe class contains a low-level API that aims to be used only with internal libraries. However, sun.misc.Unsafe is still accessible even after the introduction of JPMS. This was done to maintain backward compatibility and support all the libraries and frameworks that might use this API. In more detail, the reasons are explained in JEP 260,

In this article, we won’t use Unsafe directly but rather the LockSupport class from the java.util.concurrent.locks package that wraps calls to Unsafe:

public static void park() {
    UNSAFE.park(false, 0L);
}
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

3. park() vs. wait()

The park() and unpark(Thread) functionality are similar to wait() and notify(). Let’s review their differences and understand the danger of using the first instead of the second.

3.1. Lack of Monitors

Unlike wait() and notify(), park() and unpark(Thread) don’t require a monitor. Any code that can get a reference to the parked thread can unpark it. This might be useful in low-level code but can introduce additional complexity and hard-to-debug problems. 

Monitors are designed in Java so that a thread cannot use it if it hasn’t acquired it in the first place. This is done to prevent race conditions and simplify the synchronization process. Let’s try to notify a thread without acquiring it’s monitor:

@Test
@Timeout(3)
void giveThreadWhenNotifyWithoutAcquiringMonitorThrowsException() {
    Thread thread = new Thread() {
        @Override
        public void run() {
            synchronized (this) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    // The thread was interrupted
                }
            }
        }
    };
    assertThrows(IllegalMonitorStateException.class, () -> {
        thread.start();
        Thread.sleep(TimeUnit.SECONDS.toMillis(1));
        thread.notify();
        thread.join();
    });
}

Trying to notify a thread without acquiring a monitor results in IllegalMonitorStateException. This mechanism enforces better coding standards and prevents possible hard-to-debug problems.

Now, let’s check the behavior of park() and unpark(Thread):

@Test
@Timeout(3)
void giveThreadWhenUnparkWithoutAcquiringMonitor() {
    Thread thread = new Thread(LockSupport::park);
    assertTimeoutPreemptively(Duration.of(2, ChronoUnit.SECONDS), () -> {
        thread.start();
        LockSupport.unpark(thread);
    });
}

We can control threads with little work. The only thing required is the reference to the thread. This provides us with more power over locking, but at the same time, it exposes us to many more problems.

It’s clear why park() and unpark(Thread) might be helpful for low-level code, but we should avoid this in our usual application code because it might introduce too much complexity and unclear code.

3.2. Information About the Context

The fact that no monitors are involved also might reduce the information about the context. In other words, the thread is parked, and it’s unclear why, when, and if other threads are parked for the same reason. Let’s run two threads:

public class ThreadMonitorInfo {
    private static final Object MONITOR = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread waitingThread = new Thread(() -> {
            try {
                synchronized (MONITOR) {
                    MONITOR.wait();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "Waiting Thread");
        Thread parkedThread = new Thread(LockSupport::park, "Parked Thread");
        waitingThread.start();
        parkedThread.start();
        waitingThread.join();
        parkedThread.join();
    }
}

Let’s check the thread dump using jstack:

"Parked Thread" #12 prio=5 os_prio=31 tid=0x000000013b9c5000 nid=0x5803 waiting on condition [0x000000016e2ee000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
        at com.baeldung.park.ThreadMonitorInfo$$Lambda$2/284720968.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:750)
"Waiting Thread" #11 prio=5 os_prio=31 tid=0x000000013b9c4000 nid=0xa903 in Object.wait() [0x000000016e0e2000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000007401811d8> (a java.lang.Object)
        at java.lang.Object.wait(Object.java:502)
        at com.baeldung.park.ThreadMonitorInfo.lambda$main$0(ThreadMonitorInfo.java:12)
        - locked <0x00000007401811d8> (a java.lang.Object)
        at com.baeldung.park.ThreadMonitorInfo$$Lambda$1/1595428806.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:750)

While analyzing the thread dump, it’s clear that the parked thread contains less information. Thus, it might create a situation when a certain thread problem, even with a thread dump, would be hard to debug.

An additional benefit of using specific concurrent structures or specific locks would provide even more context in the thread dumps, giving more information about the application state. Many JVM concurrent mechanisms are using park() internally. However, if a thread dump explains that the thread is waiting, for example, on a CyclicBarrier, it’s waiting for other threads.

3.3. Interrupted Flag

Another interesting thing is the difference in handling interrupts. Let’s review the behavior of a waiting thread:

@Test
@Timeout(3)
void givenWaitingThreadWhenNotInterruptedShouldNotHaveInterruptedFlag() throws InterruptedException {
    Thread thread = new Thread() {
        @Override
        public void run() {
            synchronized (this) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    // The thread was interrupted
                }
            }
        }
    };
    thread.start();
    Thread.sleep(TimeUnit.SECONDS.toMillis(1));
    thread.interrupt();
    thread.join();
    assertFalse(thread.isInterrupted(), "The thread shouldn't have the interrupted flag");
}

If we’re interrupting a thread from its waiting state, the wait() method would immediately throw an InterruptedException and clear the interrupted flag. That’s why the best practice is to use while loops checking the waiting conditions instead of the interrupted flag.

In contrast, a parked thread isn’t interrupted immediately and rather does it on its terms. Also, the interrupt doesn’t cause an exception, and the thread just returns from the park() method. Subsequently, the interrupted flag isn’t reset, as happens while interrupting a waiting thread:

@Test
@Timeout(3)
void givenParkedThreadWhenInterruptedShouldNotResetInterruptedFlag() throws InterruptedException {
    Thread thread = new Thread(LockSupport::park);
    thread.start();
    thread.interrupt();
    assertTrue(thread.isInterrupted(), "The thread should have the interrupted flag");
    thread.join();
}

Not accounting for this behavior may cause problems while handling the interruption. For example, if we don’t reset the flag after the interrupt on a parked thread, it may cause subtle bugs.

3.4. Preemptive Permits

Parking and unparking work on the idea of a binary semaphore. Thus, we can provide a thread with a preemptive permit. For example, we can unpark a thread, which would give it a permit, and the subsequent park won’t suspend it but would take the permit and proceed:

private final Thread parkedThread = new Thread() {
    @Override
    public void run() {
        LockSupport.unpark(this);
        LockSupport.park();
    }
};
@Test
void givenThreadWhenPreemptivePermitShouldNotPark()  {
    assertTimeoutPreemptively(Duration.of(1, ChronoUnit.SECONDS), () -> {
        parkedThread.start();
        parkedThread.join();
    });
}

This technique can be used in some complex synchronization scenarios. As the parking uses a binary semaphore, we cannot add up permits, and two unpark calls wouldn’t produce two permits:

private final Thread parkedThread = new Thread() {
    @Override
    public void run() {
        LockSupport.unpark(this);
        LockSupport.unpark(this);
        LockSupport.park();
        LockSupport.park();
    }
};
@Test
void givenThreadWhenRepeatedPreemptivePermitShouldPark()  {
    Callable<Boolean> callable = () -> {
        parkedThread.start();
        parkedThread.join();
        return true;
    };
    boolean result = false;
    Future<Boolean> future = Executors.newSingleThreadExecutor().submit(callable);
    try {
        result = future.get(1, TimeUnit.SECONDS);
    } catch (InterruptedException | ExecutionException | TimeoutException e) {
        // Expected the thread to be parked
    }
    assertFalse(result, "The thread should be parked");
}

In this case, the thread would have only one permit, and the second call to the park() method would park the thread. This might produce some undesired behavior if not appropriately handled.

4. Conclusion

In this article, we learned why the park() method is considered unsafe. JVM developers hide or suggest not to use internal APIs for specific reasons. This is not only because it might be dangerous and produce unexpected results at the moment but also because these APIs might be subject to change in the future, and their support isn’t guaranteed.

Additionally, these APIs require extensive learning about underlying systems and techniques, which may differ from platform to platform. Not following this might result in fragile code and hard-to-debug problems.

As always, the code in this article is available over on GitHub.

       

HashSet toArray() Method in Java

$
0
0

1. Introduction

HashSet is one of the common data structures that we can utilize in Java Collеctions.

In this tutorial, we’ll dive into the toArray() method of the HashSet class, illustrating how to convert a HashSet to an array.

2. Convеrting HashSеt to Array

Let’s look at a set of examples that illustrate how to apply the toArray() method to convert a HashSet into an array.

2.1. HashSet to an Array of Strings

In the following method, we are seeking to convert a HashSet of strings into an array of strings:

@Test
public void givenStringHashSet_whenConvertedToArray_thenArrayContainsStringElements() {
    HashSet<String> stringSet = new HashSet<>();
    stringSet.add("Apple");
    stringSet.add("Banana");
    stringSet.add("Cherry");
    // Convert the HashSet of Strings to an array of Strings
    String[] stringArray = stringSet.toArray(new String[0]);
    // Test that the array is of the correct length
    assertEquals(3, stringArray.length);
    for (String str : stringArray) {
        assertTrue(stringSet.contains(str));
    }
}

Here, a HashSet named stringSet is initialized with three String elements: (“Apple” “Banana” and “Cherry“). To be specific, the test method ensures that the resulting array has a length of 3, matching the number of elements in the HashSet.

Then, it iterates through the stringArray and checks if each element is contained within the original stringSet, asserting that the array indeed contains the String elements, confirming the successful conversion of the HashSet to a String array. 

2.2. HashSet to an Array of Integers

Additionally, we can utilize the toArray() method to convert an Integer HashSet into an array of Integers as follows:

@Test
public void givenIntegerHashSet_whenConvertedToArray_thenArrayContainsIntegerElements() {
    HashSet<Integer> integerSet = new HashSet<>();
    integerSet.add(5);
    integerSet.add(10);
    integerSet.add(15);
    // Convert the HashSet of Integers to an array of Integers
    Integer[] integerArray = integerSet.toArray(new Integer[0]);
    // Test that the array is of the correct length
    assertEquals(3, integerArray.length);
    for (Integer num : integerArray) {
        assertTrue(integerSet.contains(num));
    }
    assertTrue(integerSet.contains(5));
    assertTrue(integerSet.contains(10));
    assertTrue(integerSet.contains(15));
}

Here, we create a HashSet named integerSet with three Integer elements: (5, 10, and 15). The test method is responsible for verifying the conversion of this Integer HashSet into an array of Integers, referred to as integerArray.

Moreover, it confirms that the resulting array has length = 3, corresponding to the number of elements in the original HashSet. Subsequently, the method iterates through integerArray, ensuring each element is contained within the original integerSet.

3. Conclusion

In conclusion, it is easy to convert a HashSet into an array using the toArray() method of the HashSet class. This can also be useful while handling array-based data structures or some other components in our Java apps.

As always, the complete code samples for this article can be found over on GitHub.

       

Convert ResultSet Into Map

$
0
0

1. Introduction

Java applications widely use the Java Database Connectivity (JDBC) API to connect and execute queries on a database. ResultSet is a tabular representation of the data extracted by these queries.

In this tutorial, we’ll learn how to convert the data of a JDBC ResultSet into a Map.

2. Setup

We’ll write a few test cases to achieve our goal. Our data source will be an H2 database. H2 is a fast, open-source, in-memory database that supports the JDBC API. Let’s add the relevant Maven dependency:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>

Once the database connection is ready, we’ll write a method to do the initial data setup for our test cases. To achieve this, we first create a JDBC Statement, and subsequently create a database table named employee using the same. The employee table consists of columns named empId, empName, and empCity that will hold information about the ID, name, and city of the employee. We now can insert sample data in the table using the Statement.execute() method:

void initialDataSetup() throws SQLException {
    Statement statement = connection.createStatement();
    String sql = "CREATE TABLE employee ( " +
      "empId INTEGER not null, " +
      "empName VARCHAR(50), " +
      "empCity VARCHAR(50), " +
      "PRIMARY KEY (empId))";
    statement.execute(sql);
    List<String> sqlQueryList = Arrays.asList(
      "INSERT INTO employee VALUES (1, 'Steve','London')", 
      "INSERT INTO employee VALUES (2, 'John','London')", 
      "INSERT INTO employee VALUES (3, 'David', 'Sydney')",
      "INSERT INTO employee VALUES (4, 'Kevin','London')", 
      "INSERT INTO employee VALUES (5, 'Jade', 'Sydney')");
    
    for (String query: sqlQueryList) {
        statement.execute(query);
    }
}

3. ResultSet to Map

Now that the sample data is present in the database, we can query it for extraction. Querying the database gives the output in the form of a ResultSet. Our goal is to transform the data from this ResultSet into a Map where the key is the city name, and the value is the list of employee names in that city.

3.1. Using Java 7

We’ll first create a PreparedStatement from the database connection and provide an SQL query to it. Then, we can use the PreparedStatement.executeQuery() method to get the ResultSet.

We can now iterate over the ResultSet data and fetch the column data individually. In order to do this, we can use the ResultSet.getString() method by passing the column name of the employee table into it. After that, we can use the Map.containsKey() method to check if the map already contains an entry for that city name. If there’s no key found for that city, we’ll add an entry with the city name as the key and an empty ArrayList as the value. Then, we add the employee’s name to the list of employee names for that city:

@Test
void whenUsingContainsKey_thenConvertResultSetToMap() throws SQLException {
    ResultSet resultSet = connection.prepareStatement(
        "SELECT * FROM employee").executeQuery();
    Map<String, List<String>> valueMap = new HashMap<>();
    while (resultSet.next()) {
        String empCity = resultSet.getString("empCity");
        String empName = resultSet.getString("empName");
        if (!valueMap.containsKey(empCity)) {
            valueMap.put(empCity, new ArrayList<>());
        }
        valueMap.get(empCity).add(empName);
    }
    assertEquals(3, valueMap.get("London").size());
}

3.2. Using Java 8

Java 8 introduced the concept of lambda expressions and default methods. We can leverage them in our implementation to simplify the entry of new keys in the output map. We can use the method named computeIfAbsent() of the Map class, which takes two parameters: a key and a mapping function. If the key is found, then it returns the relevant value; otherwise, it will use the mapping function to create the default value and store it in the map as a new key-value pair. We can add the employee’s name to the list afterward.

Here’s the modified version of the previous test case using Java 8:

@Test
void whenUsingComputeIfAbsent_thenConvertResultSetToMap() throws SQLException {
    ResultSet resultSet = connection.prepareStatement(
        "SELECT * FROM employee").executeQuery();
    Map<String, List<String>> valueMap = new HashMap<>();
    while (resultSet.next()) {
        String empCity = resultSet.getString("empCity");
        String empName = resultSet.getString("empName");
        valueMap.computeIfAbsent(empCity, data -> new ArrayList<>()).add(empName);
    }
    assertEquals(3, valueMap.get("London").size());
}

3.3. Using Apache Commons DbUtils

Apache Commons DbUtils is a third-party library that provides additional and simplified functionalities for JDBC operations. It provides an interesting interface named ResultSetHandler that consumes JDBC ResultSet as input and allows us to transform it into the desired object that the application expects. Moreover, this library uses the QueryRunner class to run SQL queries on the database table. The QueryRunner.query() method takes the database connection, SQL query, and ResultSetHandler as input and directly returns the expected format.

Let’s look at an example of how to create a Map from a ResultSet using ResultSetHandler:

@Test
void whenUsingDbUtils_thenConvertResultSetToMap() throws SQLException {
    ResultSetHandler <Map<String, List<String>>> handler = new ResultSetHandler <Map <String, List<String>>>() {
        public Map<String, List<String>> handle(ResultSet resultSet) throws SQLException {
            Map<String, List<String>> result = new HashMap<>();
            while (resultSet.next()) {
                String empCity = resultSet.getString("empCity");
                String empName = resultSet.getString("empName");
                result.computeIfAbsent(empCity, data -> new ArrayList<>()).add(empName);
            }
            return result;
        }
    };
    QueryRunner run = new QueryRunner();
    Map<String, List<String>> valueMap = run.query(connection, "SELECT * FROM employee", handler);
    assertEquals(3, valueMap.get("London").size());
}

4. Conclusion

To summarize, we took a look at several ways we can aggregate data from ResultSet and convert it into a Map using Java 7, Java 8, and the Apache DbUtils library.

As always, the full code for this article can be found over on GitHub.

       

MongoDB Atlas Search Using the Java Driver and Spring Data

$
0
0

1. Introduction

In this tutorial, we’ll learn how to use Atlas Search functionalities using the Java MongoDB driver API. By the end, we’ll have a grasp on creating queries, paginating results, and retrieving meta-information. Also, we’ll cover refining results with filters, adjusting result scores, and selecting specific fields to be displayed.

2. Scenario and Setup

MongoDB Atlas has a free forever cluster that we can use to test all features. To showcase Atlas Search functionalities, we’ll only need a service class. We’ll connect to our collection using MongoTemplate.

2.1. Dependencies

First, to connect to MongoDB, we’ll need spring-boot-starter-data-mongodb:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
    <version>3.1.2</version>
</dependency>

2.2. Sample Dataset

Throughout this tutorial, we’ll use the movies collection from MongoDB Atlas’s sample_mflix sample dataset to simplify examples. It contains data about movies since the 1900s, which will help us showcase the filtering capabilities of Atlas Search.

2.3. Creating an Index With Dynamic Mapping

For Atlas Search to work, we need indexes. These can be static or dynamic. A static index is helpful for fine-tuning, while a dynamic one is an excellent general-purpose solution. So, let’s start with a dynamic index.

There are a few ways to create search indexes (including programmatically); we’ll use the Atlas UI. There, we can do this by accessing Search from the menu, selecting our cluster, then clicking Go to Atlas Search:

Creating an index

 

After clicking on Create Search Index, we’ll choose the JSON Editor to create our index, then click Next:

JSON editor

Finally, on the next screen, we choose our target collection, a name for our index, and input our index definition:

{
    "mappings": {
        "dynamic": true
    }
}

We’ll use the name idx-queries for this index throughout this tutorial. Note that if we name our index default, we don’t need to specify its name when creating queries. Most importantly, dynamic mappings are a simple choice for more flexible, frequently changing schemas.

By setting mappings.dynamic to true, Atlas Search automatically indexes all dynamically indexable and supported field types in a document. While dynamic mappings provide convenience, especially when the schema is unknown, they tend to consume more disk space and might be less efficient compared to static ones.

2.4. Our Movie Search Service

We’ll base our examples on a service class containing some search queries for our movies, extracting interesting information from them. We’ll slowly build them up to more complex queries:

@Service
public class MovieAtlasSearchService {
    private final MongoCollection<Document> collection;
    public MovieAtlasSearchService(MongoTemplate mongoTemplate) {
        MongoDatabase database = mongoTemplate.getDb();
        this.collection = database.getCollection("movies");
    }
    // ...
}

All we need is a reference to our collection for future methods.

3. Constructing a Query

Atlas Search queries are created via pipeline stages, represented by a List<Bson>. The most essential stage is Aggregates.search(), which receives a SearchOperator and, optionally, a SearchOptions object. Since we called our index idx-queries instead of default, we must include its name with SearchOptions.searchOptions().index(). Otherwise, we’ll get no errors and no results.

Many search operators are available to define how we want to conduct our query. In this example, we’ll find movies by tags using SearchOperator.text(), which performs a full-text search. We’ll use it to search the contents of the fullplot field with SearchPath.fieldPath(). We’ll omit static imports for readability:

public Collection<Document> moviesByKeywords(String keywords) {
    List<Bson> pipeline = Arrays.asList(
        search(
          text(
            fieldPath("fullplot"), keywords
          ),
          searchOptions()
            .index("idx-queries")
        ),
        project(fields(
          excludeId(),
          include("title", "year", "fullplot", "imdb.rating")
        ))
    );
    return collection.aggregate(pipeline)
      .into(new ArrayList<>());
}

Also, the second stage in our pipeline is Aggregates.project(), which represents a projection. If not specified, our query results will include all the fields in our documents. But we can set it and choose which fields we want (or don’t want) to appear in our results. Note that specifying a field for inclusion implicitly excludes all other fields except the _id field. So, in this case, we’re excluding the _id field and passing a list of the fields we want. Note we can also specify nested fields, like imdb.rating.

To execute the pipeline, we call aggregate() on our collection. This returns an object we can use to iterate on results. Finally, for simplicity, we call into() to iterate over results and add them to a collection, which we return. Note that a big enough collection can exhaust the memory in our JVM. We’ll see how to eliminate this concern by paginating our results later on.

Most importantly, pipeline stage order matters. We’ll get an error if we put the project() stage before search().

Let’s take a look at the first two results of calling moviesByKeywords(“space cowboy”) on our service:

[
    {
        "title": "Battle Beyond the Stars",
        "fullplot": "Shad, a young farmer, assembles a band of diverse mercenaries in outer space to defend his peaceful planet from the evil tyrant Sador and his armada of aggressors. Among the mercenaries are Space Cowboy, a spacegoing truck driver from Earth; Gelt, a wealthy but experienced assassin looking for a place to hide; and Saint-Exmin, a Valkyrie warrior looking to prove herself in battle.",
        "year": 1980,
        "imdb": {
            "rating": 5.4
        }
    },
    {
        "title": "The Nickel Ride",
        "fullplot": "Small-time criminal Cooper manages several warehouses in Los Angeles that the mob use to stash their stolen goods. Known as \"the key man\" for the key chain he always keeps on his person that can unlock all the warehouses. Cooper is assigned by the local syndicate to negotiate a deal for a new warehouse because the mob has run out of storage space. However, Cooper's superior Carl gets nervous and decides to have cocky cowboy button man Turner keep an eye on Cooper.",
        "year": 1974,
        "imdb": {
            "rating": 6.7
        }
    },
    (...)
]

3.1. Combining Search Operators

It’s possible to combine search operators using SearchOperator.compound(). In this example, we’ll use it to include must and should clauses. A must clause contains one or more conditions for matching documents. On the other hand, a should clause contains one or more conditions that we’d prefer our results to include.

This alters the score so the documents that meet these conditions appear first:

public Collection<Document> late90sMovies(String keywords) {
    List<Bson> pipeline = asList(
        search(
          compound()
            .must(asList(
              numberRange(
                fieldPath("year"))
                .gteLt(1995, 2000)
            ))
            .should(asList(
              text(
                fieldPath("fullplot"), keywords
              )
            )),
          searchOptions()
            .index("idx-queries")
        ),
        project(fields(
          excludeId(),
          include("title", "year", "fullplot", "imdb.rating")
        ))
    );
    return collection.aggregate(pipeline)
      .into(new ArrayList<>());
}

We kept the same searchOptions() and projected fields from our first query. But, this time, we moved text() to a should clause because we want the keywords to represent a preference, not a requirement.

Then, we created a must clause, including SearchOperator.numberRange(), to only show movies from 1995 to 2000 (exclusive) by restricting the values on the year field. This way, we only return movies from that era.

Let’s see the first two results for hacker assassin:

[
    {
        "title": "Assassins",
        "fullplot": "Robert Rath is a seasoned hitman who just wants out of the business with no back talk. But, as things go, it ain't so easy. A younger, peppier assassin named Bain is having a field day trying to kill said older assassin. Rath teams up with a computer hacker named Electra to defeat the obsessed Bain.",
        "year": 1995,
        "imdb": {
            "rating": 6.3
        }
    },
    {
        "fullplot": "Thomas A. Anderson is a man living two lives. By day he is an average computer programmer and by night a hacker known as Neo. Neo has always questioned his reality, but the truth is far beyond his imagination. Neo finds himself targeted by the police when he is contacted by Morpheus, a legendary computer hacker branded a terrorist by the government. Morpheus awakens Neo to the real world, a ravaged wasteland where most of humanity have been captured by a race of machines that live off of the humans' body heat and electrochemical energy and who imprison their minds within an artificial reality known as the Matrix. As a rebel against the machines, Neo must return to the Matrix and confront the agents: super-powerful computer programs devoted to snuffing out Neo and the entire human rebellion.",
        "imdb": {
            "rating": 8.7
        },
        "year": 1999,
        "title": "The Matrix"
    },
    (...)
]

4. Scoring the Result Set

When we query documents with search(), the results appear in order of relevance. This relevance is based on the calculated score, from highest to lowest. This time, we’ll modify late90sMovies() to receive a SearchScore modifier to boost the relevance of the plot keywords in our should clause:

public Collection<Document> late90sMovies(String keywords, SearchScore modifier) {
    List<Bson> pipeline = asList(
        search(
          compound()
            .must(asList(
              numberRange(
                fieldPath("year"))
                .gteLt(1995, 2000)
            ))
            .should(asList(
              text(
                fieldPath("fullplot"), keywords
              )
              .score(modifier)
            )),
          searchOptions()
            .index("idx-queries")
        ),
        project(fields(
          excludeId(),
          include("title", "year", "fullplot", "imdb.rating"),
          metaSearchScore("score")
        ))
    );
    return collection.aggregate(pipeline)
      .into(new ArrayList<>());
}

Also, we include metaSearchScore(“score”) in our fields list to see the score for each document in our results. For example, we can now multiply the relevance of our “should” clause by the value of the imdb.votes field like this:

late90sMovies(
  "hacker assassin", 
  SearchScore.boost(fieldPath("imdb.votes"))
)

And this time, we can see that The Matrix comes first, thanks to the boost:

[
    {
        "fullplot": "Thomas A. Anderson is a man living two lives (...)",
        "imdb": {
            "rating": 8.7
        },
        "year": 1999,
        "title": "The Matrix",
        "score": 3967210.0
    },
    {
        "fullplot": "(...) Bond also squares off against Xenia Onatopp, an assassin who uses pleasure as her ultimate weapon.",
        "imdb": {
            "rating": 7.2
        },
        "year": 1995,
        "title": "GoldenEye",
        "score": 462604.46875
    },
    (...)
]

4.1. Using a Score Function

We can achieve greater control by using a function to alter the score of our results. Let’s pass a function to our method that adds the value of the year field to the natural score. This way, newer movies end up with a higher score:

late90sMovies(keywords, function(
  addExpression(asList(
    pathExpression(
      fieldPath("year"))
      .undefined(1), 
    relevanceExpression()
  ))
));

That code starts with a SearchScore.function(), which is a SearchScoreExpression.addExpression() since we want an add operation. Then, since we want to add a value from a field, we use a SearchScoreExpression.pathExpression() and specify the field we want: year. Also, we call undefined() to determine a fallback value for year in case it’s missing. In the end, we call relevanceExpression() to return the document’s relevance score, which is added to the value of year.

When we execute that, we’ll see “The Matrix” now appears first, along with its new score:

[
    {
        "fullplot": "Thomas A. Anderson is a man living two lives (...)",
        "imdb": {
            "rating": 8.7
        },
        "year": 1999,
        "title": "The Matrix",
        "score": 2003.67138671875
    },
    {
        "title": "Assassins",
        "fullplot": "Robert Rath is a seasoned hitman (...)",
        "year": 1995,
        "imdb": {
            "rating": 6.3
        },
        "score": 2003.476806640625
    },
    (...)
]

That’s useful for defining what should have greater weight when scoring our results.

5. Getting Total Rows Count From Metadata

If we need to get the total number of results in a query, we can use Aggregates.searchMeta() instead of search() to retrieve metadata information only. With this method, no documents are returned. So, we’ll use it to count the number of movies from the late 90s that also contain our keywords.

For meaningful filtering, we’ll also include the keywords in our must clause:

public Document countLate90sMovies(String keywords) {
    List<Bson> pipeline = asList(
        searchMeta(
          compound()
            .must(asList(
              numberRange(
                fieldPath("year"))
                .gteLt(1995, 2000),
              text(
                fieldPath("fullplot"), keywords
              )
            )),
          searchOptions()
            .index("idx-queries")
            .count(total())
        )
    );
    return collection.aggregate(pipeline)
      .first();
}

This time, searchOptions() includes a call to SearchOptions.count(SearchCount.total()), which ensures we get an exact total count (instead of a lower bound, which is faster depending on the collection size). Also, since we expect a single object in the results, we call first() on aggregate().

Finally, let’s see what is returned for countLate90sMovies(“hacker assassin”):

{
    "count": {
        "total": 14
    }
}

This is useful for getting information about our collection without including documents in our results.

6. Faceting on Results

In MongoDB Atlas Search, a facet query is a feature that allows retrieving aggregated and categorized information about our search results. It helps us analyze and summarize data based on different criteria, providing insights into the distribution of search results.

Also, it enables grouping search results into different categories or buckets and retrieving counts or additional information about each category. This helps answer questions like “How many documents match a specific category?” or “What are the most common values for a certain field within the results?”

6.1. Creating a Static Index

In our first example, we’ll create a facet query to give us information about genres from movies since the 1900s and how these relate. We’ll need an index with facet types, which we can’t have when using dynamic indexes.

So, let’s start by creating a new search index in our collection, which we’ll call idx-facets. Note that we’ll keep dynamic as true so we can still query the fields that are not explicitly defined:

{
  "mappings": {
    "dynamic": true,
    "fields": {
      "genres": [
        {
          "type": "stringFacet"
        },
        {
          "type": "string"
        }
      ],
      "year": [
        {
          "type": "numberFacet"
        },
        {
          "type": "number"
        }
      ]
    }
  }
}

We started by specifying that our mappings aren’t dynamic. Then, we selected the fields we were interested in for indexing faceted information. Since we also want to use filters in our query, for each field, we specify an index of a standard type (like string) and one of a faceted type (like stringFacet).

6.2. Running a Facet Query

Creating a facet query involves using searchMeta() and starting a SearchCollector.facet() method to include our facets and an operator for filtering results. When defining the facets, we have to choose a name and use a SearchFacet method that corresponds to the type of index we created. In our case, we define a stringFacet() and a numberFacet():

public Document genresThroughTheDecades(String genre) {
    List pipeline = asList(
      searchMeta(
        facet(
          text(
            fieldPath("genres"), genre
          ), 
          asList(
            stringFacet("genresFacet", 
              fieldPath("genres")
            ).numBuckets(5),
            numberFacet("yearFacet", 
              fieldPath("year"), 
              asList(1900, 1930, 1960, 1990, 2020)
            )
          )
        ),
        searchOptions()
          .index("idx-facets")
      )
    );
    return collection.aggregate(pipeline)
      .first();
}

We filter movies with a specific genre with the text() operator. Since films generally contain multiple genres, the stringFacet() will also show five (specified by numBuckets()) related genres ranked by frequency. For the numberFacet(), we must set the boundaries separating our aggregated results. We need at least two, with the last one being exclusive.

Finally, we return only the first result. Let’s see what it looks like if we filter by the “horror” genre:

{
    "count": {
        "lowerBound": 1703
    },
    "facet": {
        "genresFacet": {
            "buckets": [
                {
                    "_id": "Horror",
                    "count": 1703
                },
                {
                    "_id": "Thriller",
                    "count": 595
                },
                {
                    "_id": "Drama",
                    "count": 395
                },
                {
                    "_id": "Mystery",
                    "count": 315
                },
                {
                    "_id": "Comedy",
                    "count": 274
                }
            ]
        },
        "yearFacet": {
            "buckets": [
                {
                    "_id": 1900,
                    "count": 5
                },
                {
                    "_id": 1930,
                    "count": 47
                },
                {
                    "_id": 1960,
                    "count": 409
                },
                {
                    "_id": 1990,
                    "count": 1242
                }
            ]
        }
    }
}

Since we didn’t specify a total count, we get a lower bound count, followed by our facet names and their respective buckets.

6.3. Including a Facet Stage to Paginate Results

Let’s return to our late90sMovies() method and include a $facet stage in our pipeline. We’ll use it for pagination and a total rows count. The search() and project() stages will remain unmodified:

public Document late90sMovies(int skip, int limit, String keywords) {
    List<Bson> pipeline = asList(
        search(
          // ...
        ),
        project(fields(
          // ...
        )),
        facet(
          new Facet("rows",
            skip(skip),
            limit(limit)
          ),
          new Facet("totalRows",
            replaceWith("$$SEARCH_META"),
            limit(1)
          )
        )
    );
    return collection.aggregate(pipeline)
      .first();
}

We start by calling Aggregates.facet(), which receives one or more facets. Then, we instantiate a Facet to include skip() and limit() from the Aggregates class. While skip() defines our offset, limit() will restrict the number of documents retrieved. Note that we can name our facets anything we like.

Also, we call replaceWith(“$$SEARCH_META“) to get metadata info in this field. Most importantly, so that our metadata information is not repeated for each result, we include a limit(1). Finally, when our query has metadata, the result becomes a single document instead of an array, so we only return the first result.

7. Conclusion

In this article, we saw how MongoDB Atlas Search provides developers with a versatile and potent toolset. Integrating it with the Java MongoDB driver API can enhance search functionalities, data aggregation, and result customization. Our hands-on examples have aimed to provide a practical understanding of its capabilities. Whether implementing a simple search or seeking intricate data analytics, Atlas Search is an invaluable tool in the MongoDB ecosystem.

Remember to leverage the power of indexes, facets, and dynamic mappings to make our data work for us. As always, the source code is available over on GitHub.

       

Sharing Memory Between JVMs

$
0
0

1. Introduction

In this tutorial, we’ll show how to share memory between two or more JVMs running on the same machine. This capability enables very fast inter-process communication since we can move data blocks around without any I/O operation.

2. How Shared Memory Works?

A process running in any modern operating system gets what’s called a virtual memory space. We call it virtual because, although it looks like a large, continuous, and private addressable memory space, in fact, it’s made of pages spread all over the physical RAM. Here, page is just OS slang for a block of contiguous memory, whose size range depends on the particular CPU architecture in use. For x86-84, a page can be as small as 4KB or as large as 1 GB.

At a given time, only a fraction of this virtual space is actually mapped to physical pages. As time passes and the process starts to consume more memory for its tasks, the OS starts to allocate more physical pages and map them to the virtual space. When the demand for memory exceeds what’s physically available, the OS will start to swap out pages that are not being used at that moment to secondary storage to make room for the request.

A shared memory block behaves just like regular memory, but, in contrast with regular memory, it is not private to a single process. When a process changes the contents of any byte within this block, any other process with access to this same shared memory “sees” this change instantly.

This is a list of common uses for shared memory:

  • Debuggers (ever wondered how a debugger can inspect variables in another process?)
  • Inter-process communication
  • Read-only content sharing between processes (ex: dynamic library code)
  • Hacks of all sorts ;^)

3. Shared Memory and Memory-Mapped Files

A memory-mapped file, as the name suggests, is a regular file whose contents are directly mapped to a contiguous area in the virtual memory of a process. This means that we can read and/or change its contents without explicit use of I/O operations. The OS will detect any writes to the mapped area and will schedule a background I/O operation to persist the modified data.

Since there are no guarantees on when this background operation will happen, the OS also offers a system call to flush any pending changes. This is important for use cases like database redo logs, but not needed for our inter-process communication (IPC, for short) scenario.

Memory-mapped files are commonly used by database servers to achieve high throughput I/O operations, but we can also use them to bootstrap a shared-memory-based IPC mechanism. The basic idea is that all processes that need to share data map the same file and, voilà, they now have a shared memory area.

4. Creating Memory-Mapped Files in Java

In Java, we use the FileChannel‘s map() method to map a region of a file into memory, which returns a MappedByteBuffer that allows us to access its contents:

MappedByteBuffer createSharedMemory(String path, long size) {
    try (FileChannel fc = (FileChannel)Files.newByteChannel(new File(path).toPath(),
      EnumSet.of(
        StandardOpenOption.CREATE,
        StandardOpenOption.SPARSE,
        StandardOpenOption.WRITE,
        StandardOpenOption.READ))) {
        return fc.map(FileChannel.MapMode.READ_WRITE, 0, size);
    }
    catch( IOException ioe) {
        throw new RuntimeException(ioe);
    }
}

The use of the SPARSE option here is quite relevant. As long the underlying OS and file system supports it, we can map sizable memory area without actually consuming disk space.

Now, let’s create a simple demo application. The Producer application will allocate a shared memory large enough to hold 64KB of data plus a SHA1 hash (20 bytes). Next, it will start a loop where it will fill the buffer with random data, followed by its SHA1 hash. We’ll repeat this operation continuously for 30 seconds and then exit:

// ... SHA1 digest initialization omitted
MappedByteBuffer shm = createSharedMemory("some_path.dat", 64*1024 + 20);
Random rnd = new Random();
long start = System.currentTimeMillis();
long iterations = 0;
int capacity = shm.capacity();
System.out.println("Starting producer iterations...");
while(System.currentTimeMillis() - start < 30000) {
    for (int i = 0; i < capacity - hashLen; i++) {
        byte value = (byte) (rnd.nextInt(256) & 0x00ff);
        digest.update(value);
        shm.put(i, value);
    }
    // Write hash at the end
    byte[] hash = digest.digest();
    shm.put(capacity - hashLen, hash);
    iterations++;
}
System.out.printf("%d iterations run\n", iterations);

To test that we indeed can share memory, we’ll also create a Consumer app that will read the buffer’s content, compute its hash, and compare it with the Producer-generated one. We’ll repeat this process for 30 seconds. At each iteration, will also compute the buffer content’s hash and compare it with the one present at the buffer’s end:

// ... digest initialization omitted
MappedByteBuffer shm = createSharedMemory("some_path.dat", 64*1024 + 20);
long start = System.currentTimeMillis();
long iterations = 0;
int capacity = shm.capacity();
System.out.println("Starting consumer iterations...");
long matchCount = 0;
long mismatchCount = 0;
byte[] expectedHash = new byte[hashLen];
while (System.currentTimeMillis() - start < 30000) {
    for (int i = 0; i < capacity - 20; i++) {
        byte value = shm.get(i);
        digest.update(value);
    }
    byte[] hash = digest.digest();
    shm.get(capacity - hashLen, expectedHash);
    if (Arrays.equals(hash, expectedHash)) {
        matchCount++;
    } else {
        mismatchCount++;
    }
    iterations++;
}
System.out.printf("%d iterations run. matches=%d, mismatches=%d\n", iterations, matchCount, mismatchCount);

To test our memory-sharing scheme, let’s start both programs at the same time. This is their output when running on a 3Ghz, quad-core Intel I7 machine:

# Producer output
Starting producer iterations...
11722 iterations run
# Consumer output
Starting consumer iterations...
18893 iterations run. matches=11714, mismatches=7179

We can see that, in many cases, the consumer detects that the expected computed values are different. Welcome to the wonderful world of concurrency issues!

5. Synchronizing Shared Memory Access

The root cause for the issue we’ve seen is that we need to synchronize access to the shared memory buffer. The Consumer must wait for the Producer to finish writing the hash before it starts reading the data. On the other hand, the Producer also must wait for the Consumer to finish consuming the data before writing to it again.

For a regular multithreaded application, solving this issue is no big deal. The standard library offers several synchronization primitives that allow us to control who can write to the shared memory at a given time.

However, ours is a multi-JVM scenario, so none of those standard methods apply. So, what should we do? Well, the short answer is that we’ll have to cheat. We could resort to OS-specific mechanisms like semaphores, but this would hinder our application’s portability. Also, this implies using JNI or JNA, which also complicates things.

Enter Unsafe. Despite its somewhat scary name, this standard library class offers exactly what we need to implement a simple lock mechanism: the compareAndSwapInt() method.

This method implements an atomic test-and-set primitive that takes four arguments. Although not clearly stated in the documentation, it can target not only Java objects but also a raw memory address. For the latter, we pass null in the first argument, which makes it treat the offset argument as a virtual memory address.

When we call this method, it will first check the value at the target address and compare it with the expected value. If they’re equal, then it will modify the location’s content to the new value and return true indicating success. If the value at the location is different from expected, nothing happens, and the method returns false.

More importantly, this atomic operation is guaranteed to work even in multicore architectures, which is a critical feature for synchronizing multiple executing threads.

Let’s create a SpinLock class that takes advantage of this method to implement a (very!) simple lock mechanism:

//... package and imports omitted
public class SpinLock {
    private static final Unsafe unsafe;
    // ... unsafe initialization omitted
    private final long addr;
    public SpinLock(long addr) {
        this.addr = addr;
    }
    public boolean tryLock(long maxWait) {
        long deadline = System.currentTimeMillis() + maxWait;
        while (System.currentTimeMillis() < deadline ) {
            if (unsafe.compareAndSwapInt(null, addr, 0, 1)) {
                return true;
            }
        }
        return false;
    }
    public void unlock() {
        unsafe.putInt(addr, 0);
    }
}

This implementation lacks key features, like checking whether it owns the lock before releasing it, but it will suffice for our purpose.

Okay, so how do we get the memory address that we’ll use to store the lock status? This must be an address within the shared memory buffer so both processes can use it, but the MappedByteBuffer class does not expose the actual memory address.

Inspecting the object that map() returns, we can see that it is a DirectByteBuffer. This class has a public method called address() that returns exactly what we want. Unfortunately, this class is package-private so we can’t use a simple cast to access this method.

To bypass this limitation, we’ll cheat a little again and use reflection to invoke this method:

private static long getBufferAddress(MappedByteBuffer shm) {
    try {
        Class<?> cls = shm.getClass();
        Method maddr = cls.getMethod("address");
        maddr.setAccessible(true);
        Long addr = (Long) maddr.invoke(shm);
        if (addr == null) {
            throw new RuntimeException("Unable to retrieve buffer's address");
        }
        return addr;
    } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ex) {
        throw new RuntimeException(ex);
    }
}

Here, we’re using setAccessible() to make the address() method callable through the Method handle. However, be aware that, from Java 17 onwards, this technique won’t work unless we explicitly use the runtime –add-opens flag.

6. Adding Synchronization to Producer and Consumer

Now that we have a lock mechanism, let’s apply it to the Producer first. For the purposes of this demo, we’ll assume that the Producer will always start before the Consumer. We need this so we can initialize the buffer, clearing its content including the area we’ll use with the SpinLock:

public static void main(String[] args) throws Exception {
    // ... digest initialization omitted
    MappedByteBuffer shm = createSharedMemory("some_path.dat", 64*1024 + 20);
    // Cleanup lock area 
    shm.putInt(0, 0);
    long addr = getBufferAddress(shm);
    System.out.println("Starting producer iterations...");
    long start = System.currentTimeMillis();
    long iterations = 0;
    Random rnd = new Random();
    int capacity = shm.capacity();
    SpinLock lock = new SpinLock(addr);
    while(System.currentTimeMillis() - start < 30000) {
        if (!lock.tryLock(5000)) {
            throw new RuntimeException("Unable to acquire lock");
        }
        try {
            // Skip the first 4 bytes, as they're used by the lock
            for (int i = 4; i < capacity - hashLen; i++) {
                byte value = (byte) (rnd.nextInt(256) & 0x00ff);
                digest.update(value);
                shm.put(i, value);
            }
            // Write hash at the end
            byte[] hash = digest.digest();
            shm.put(capacity - hashLen, hash);
            iterations++;
        }
        finally {
            lock.unlock();
        }
    }
    System.out.printf("%d iterations run\n", iterations);
}

Compared to the unsynchronized version, there are just minor changes:

  • Retrieve the memory address associated with the MappedByteBufer
  • Create a SpinLock instance using this address. The lock uses an int, so it will take the four initial bytes of the buffer
  • Use the SpinLock instance to protect the code that fills the buffer with random data and its hash

Now, let’s apply similar changes to the Consumer side:

private static void main(String args[]) throws Exception {
    // ... digest initialization omitted
    MappedByteBuffer shm = createSharedMemory("some_path.dat", 64*1024 + 20);
    long addr = getBufferAddress(shm);
    System.out.println("Starting consumer iterations...");
    Random rnd = new Random();
    long start = System.currentTimeMillis();
    long iterations = 0;
    int capacity = shm.capacity();
    long matchCount = 0;
    long mismatchCount = 0;
    byte[] expectedHash = new byte[hashLen];
    SpinLock lock = new SpinLock(addr);
    while (System.currentTimeMillis() - start < 30000) {
        if (!lock.tryLock(5000)) {
            throw new RuntimeException("Unable to acquire lock");
        }
        try {
            for (int i = 4; i < capacity - hashLen; i++) {
                byte value = shm.get(i);
                digest.update(value);
            }
            byte[] hash = digest.digest();
            shm.get(capacity - hashLen, expectedHash);
            if (Arrays.equals(hash, expectedHash)) {
                matchCount++;
            } else {
                mismatchCount++;
            }
            iterations++;
        } finally {
            lock.unlock();
        }
    }
    System.out.printf("%d iterations run. matches=%d, mismatches=%d\n", iterations, matchCount, mismatchCount);
}

With those changes, we can now run both sides and compare them with the previous result:

# Producer output
Starting producer iterations...
8543 iterations run
# Consumer output
Starting consumer iterations...
8607 iterations run. matches=8607, mismatches=0

As expected, the reported iteration count will be lower compared to the non-synchronized version. The main reason is that we spend most part of the time within the critical section of the code holding the lock. Whichever program holding the lock prevents the other side from doing anything.

If we compare the average iteration count reported from the first case, it will be approximately the same as the sum of iterations we got this time. This shows that the overhead added by the lock mechanism itself is minimal.

6. Conclusion

In this tutorial, we’ve explored how to use share a memory area between two JVMs running on the same machine. We can use the technique presented here as the foundation for high-throughput, low-latency inter-process communication libraries.

As usual, all code is available over on GitHub.

       

Java Weekly, Issue 516

$
0
0

1. Spring and Java

>> Table partitioning with Spring and Hibernate [vladmihalcea.com]

Splitting a large table into multiple smaller partition tables using Spring and Hibernate — allowing a more efficient seek/scan.

>> Pattern Matching for switch – Sip of Java [inside.java]

And, a crash course on Pattern Matching in Java 21: type patterns, guard conditions, null handling, exhaustiveness, sealed hierarchies, and records.

Also worth reading:

Webinars and presentations:

Time to upgrade:

2. Technical & Musings

>> Exploring the OpenTelemetry Collector [blog.frankel.ch]

Exploring the OpenTelemetry collector: setup, intermediary data processing, receivers, exporters, log manipulation, and more!

Also worth reading:

3. Pick of the Week

Our only “sale” of the year is now live – Black Friday.

If you’re planning to go deep into Spring and Spring Boot, this is a good time to explore:

>> All Baeldung Courses

       

Check If a Java StringBuilder Object Contains a Character

$
0
0

1. Introduction

Thе StringBuildеr class in Java provides a flеxiblе and еfficiеnt way to manipulatе strings. In some cases, we nееd to chеck whеthеr a StringBuildеr objеct contains a specific character.

In this tutorial, wе’ll еxplorе several ways to achiеvе this task.

2. StringBuildеr: An Overview

Thе StringBuildеr class in Java is part of thе java.lang packagе and is usеd to crеatе mutablе sеquеncеs of charactеrs.

Unlikе thе String class, which is immutablе, StringBuildеr allows for еfficiеnt modifications to thе sеquеncе of charactеrs without crеating a nеw objеct еach timе:

StringBuilder stringBuilder = new StringBuilder("Welcome to Baeldung Java Tutorial!");
stringBuilder.append(" We hope you enjoy your learning experience.");
stringBuilder.insert(29, "awesome ");
stringBuilder.replace(11, 18, "Baeldung's");
stringBuilder.delete(42, 56);

In thе abovе codе, wе dеmonstratе various opеrations on a StringBuildеr. Moreover, these opеrations includе appеnding a nеw string to thе еnd of thе StringBuildеr, insеrting thе word “awеsomе” at position 29, rеplacing thе substring “Java Tutorial” with “Baеldung’s“, and dеlеting thе portion from indеx 42 to 55.

3. Using indеxOf() Method

Thе indеxOf() mеthod in thе StringBuildеr class can bе utilizеd to chеck if a spеcific charactеr is prеsеnt within thе sеquеncе. It rеturns thе indеx of thе first occurrеncе of thе spеcifiеd charactеr or -1 if thе charactеr is not found.

Let’s see the following code example:

StringBuilder stringBuilder = new StringBuilder("Welcome to Baeldung Java Tutorial!");
char targetChar = 'o';
@Test
public void givenStringBuilder_whenUsingIndexOfMethod_thenCheckIfSCharExists() {
    int index = stringBuilder.indexOf(String.valueOf(targetChar));
    assertTrue(index != -1);
}

Here, wе еmploy thе indеxOf() mеthod to chеck if thе charactеr ‘o’ еxists within thе stringBuilder sеquеncе, еnsuring thе indеx is not -1 to affirm its prеsеncе.

4. Using contains() Method

Besides, another approach that can accomplish this task by utilizing the contains() method. Let’s see the following code example:

@Test
public void givenStringBuilder_whenUsingContainsMethod_thenCheckIfSCharExists() {
    boolean containsChar = stringBuilder.toString().contains(String.valueOf(targetChar));
    assertTrue(containsChar);
}

Here, we first convеrt thе stringBuildеr to a String using toString(), and thеn usе thе contains() mеthod to ascеrtain whеthеr thе charactеr ‘o’ еxists in thе rеsulting string:

5. Using Java Strеams

With Java 8 and latеr vеrsions, you can lеvеragе thе Strеam API to pеrform thе chеck morе concisеly.

Now, let’s see the following code example:

@Test
public void givenStringBuilder_whenUsingJavaStream_thenCheckIfSCharExists() {
    boolean charFound = stringBuilder.chars().anyMatch(c -> c == targetChar);
    assertTrue(charFound);
}

We first convert the stringBuildеr into characters and then wе utilizе thе Strеam API’s anyMatch() mеthod to dеtеrminе if any charactеr in thе stringBuildеr sеquеncе matchеs thе spеcifiеd charactеr ‘o’.

6. Itеrating Through Charactеrs

A morе manual approach involvеs itеrating through thе charactеrs of thе StringBuildеr using a loop and chеcking for thе dеsirеd charactеr.

Here’s how this approach works:

@Test
public void givenStringBuilder_whenUsingIterations_thenCheckIfSCharExists() {
    boolean charFound = false;
    for (int i = 0; i < stringBuilder.length(); i++) {
        if (stringBuilder.charAt(i) == targetChar) {
            charFound = true;
            break;
        }
    }
    assertTrue(charFound);
}

In this еxamplе, wе manually itеratе through thе charactеrs of thе stringBuildеr using a loop. Furthermore, we chеck if еach charactеr is еqual to thе spеcifiеd charactеr ‘o’.

7. Conclusion

In conclusion, wе can utilize several approaches to chеck if a Java StringBuildеr objеct contains a specific character. Moreover, thе choicе of mеthod depends on factors such as codе rеadability, pеrformancе, and pеrsonal prеfеrеncе.

As always, the complete code samples for this article can be found over on GitHub.

       

@Query Definitions With SpEL Support in Spring Data JPA

$
0
0

1. Overview

SpEL stands for Spring Expression Language and is a powerful tool that can significantly enhance our interaction with Spring and provide an additional abstraction over configuration, property settings, and query manipulation.

In this tutorial, we’ll learn how to use this tool to make our custom queries more dynamic and hide database-specific actions in the repository layers. We’ll be working with @Query annotation, which allows us to use JPQL or native SQL to customize the interaction with a database.

2. Accessing Parameters

Let’s first check how we can work with SpEL regarding the method parameters.

2.1. Accessing by an Index

Accessing parameters by an index isn’t optimal, as it might introduce hard-to-debug problems to the code. Especially when the arguments have the same types.

At the same time, it provides us with more flexibility, especially at the development stage when the names of the parameters change often. IDEs might not handle updates in the code and the queries correctly.

JDBC provided us with the ? placeholder we can use to identify the parameter’s position in the query. Spring supports this convention and allows writing the following:

@Modifying
@Transactional
@Query(value = "INSERT INTO articles (id, title, content, language) "
  + "VALUES (?1, ?2, ?3, ?4)",
  nativeQuery = true)
void saveWithPositionalArguments(Long id, String title, String content, String language);

So far, nothing interesting is happening. We’re using the same approach we used previously with the JDBC application. Note that @Modifying and @Transactional annotations are required for any queries that make changes in the database, and INSERT is one of them. All the examples for INSERT will use native queries because JPQL doesn’t support them.

We can rewrite the query above using SpEL:

@Modifying
@Transactional
@Query(value = "INSERT INTO articles (id, title, content, language) "
  + "VALUES (?#{[0]}, ?#{[1]}, ?#{[2]}, ?#{[3]})",
  nativeQuery = true)
void saveWithPositionalSpELArguments(long id, String title, String content, String language);

The result is similar but looks more cluttered than the previous one. However, as it’s SpEL, it provides us with all the rich functionality. For example, we can use conditional logic in the query:

@Modifying
@Transactional
@Query(value = "INSERT INTO articles (id, title, content, language) "
  + "VALUES (?#{[0]}, ?#{[1]}, ?#{[2] ?: 'Empty Article'}, ?#{[3]})",
  nativeQuery = true)
void saveWithPositionalSpELArgumentsWithEmptyCheck(long id, String title, String content, String isoCode);

We used the Elvis operator in this query to check if the content was provided. Although we can write even more complex logic in our queries, it should be used sparingly as it might introduce problems with debugging and verifying the code.

2.2. Accessing by a Name

Another way we can access parameters is by using a named placeholder, which usually matches the parameter name, but it’s not a strict requirement. This is yet another convention from JDBC; the named parameter is marked with the :name placeholder. We can use it directly:

@Modifying
@Transactional
@Query(value = "INSERT INTO articles (id, title, content, language) "
  + "VALUES (:id, :title, :content, :language)",
  nativeQuery = true)
void saveWithNamedArguments(@Param("id") long id, @Param("title") String title,
  @Param("content") String content, @Param("isoCode") String language);

The only additional thing required is to ensure that Spring will know the names of the parameters. We can do it either in a more implicit way and compile the code using a -parameters flag or do it explicitly with the @Param annotation.

The explicit way is always better, as it provides more control over the names, and we won’t get problems because of incorrect compilation.

However, let’s rewrite the same query using SpEL:

@Modifying
@Transactional
@Query(value = "INSERT INTO articles (id, title, content, language) "
  + "VALUES (:#{#id}, :#{#title}, :#{#content}, :#{#language})",
  nativeQuery = true)
void saveWithNamedSpELArguments(@Param("id") long id, @Param("title") String title,
  @Param("content") String content, @Param("language") String language);

Here, we have standard SpEL syntax, but additionally, we need to use to distinguish the parameter name from an application bean. If we omit it, Spring will try to look for beans in the context with the names id, title, content, and language.

Overall, this version is quite similar to a simple approach without SpEL. However, as discussed in the previous section, SpEL provides more capabilities and functionalities. For example, we can call the functions available on the passed objects:

@Modifying
@Transactional
@Query(value = "INSERT INTO articles (id, title, content, language) "
  + "VALUES (:#{#id}, :#{#title}, :#{#content}, :#{#language.toLowerCase()})",
  nativeQuery = true)
void saveWithNamedSpELArgumentsAndLowerCaseLanguage(@Param("id") long id, @Param("title") String title,
  @Param("content") String content, @Param("language") String language);

We can use the toLowerCase() method on a String object. We can do conditional logic, method invocation, concatenation of Strings, etc. At the same time, having too much logic inside @Query might obscure it and make it tempting to leak business logic into infrastructure code.

2.3. Accessing Object’s Fields

While previous approaches were more or less mirroring the capabilities of JDBC and prepared queries, this one allows us to use native queries in a more object-oriented way. As we saw previously, we can use simple logic and call the objects’ methods in SpEL. Also, we can access the objects’ fields:

@Modifying
@Transactional
@Query(value = "INSERT INTO articles (id, title, content, language) "
  + "VALUES (:#{#article.id}, :#{#article.title}, :#{#article.content}, :#{#article.language})",
  nativeQuery = true)
void saveWithSingleObjectSpELArgument(@Param("article") Article article);

We can use the public API of an object to get its internals. This is quite a useful technique as it allows us to keep the signatures of our repositories tidy and don’t expose too much. It allows us to even reach to nested objects. Let’s say we have an article wrapper:

public class ArticleWrapper {
    private final Article article;
    public ArticleWrapper(Article article) {
        this.article = article;
    }
    public Article getArticle() {
        return article;
    }
}

And we can use it in our example:

@Modifying
@Transactional
@Query(value = "INSERT INTO articles (id, title, content, language) "
  + "VALUES (:#{#wrapper.article.id}, :#{#wrapper.article.title}, " 
  + ":#{#wrapper.article.content}, :#{#wrapper.article.language})",
  nativeQuery = true)
void saveWithSingleWrappedObjectSpELArgument(@Param("wrapper") ArticleWrapper articleWrapper);

Thus, we can treat the arguments as Java objects inside SpEL and use any available fields or methods. We can add logic and method invocation to this query as well.

Additionally, we can use this technique with Pageable to get the information from the object, for example, offset or the page size, and add it to our native query. Although Sort is also an object, it has a more complex structure and would be harder to use.

3. Referencing an Entity

Reducing duplicated code is a good practice. However, custom queries might make it challenging. Even if we have similar logic to extract to a base repository, the tables’ names are different, making it hard to reuse them.

SpEL provides a placeholder for an entity name, which it infers from the repository parametrization. Let’s create such a base repository:

@NoRepositoryBean
public interface BaseNewsApplicationRepository<T, ID> extends JpaRepository<T, ID> {
    @Query(value = "select e from #{#entityName} e")
    List<Article> findAllEntitiesUsingEntityPlaceholder();
    @Query(value = "SELECT * FROM #{#entityName}", nativeQuery = true)
    List<Article> findAllEntitiesUsingEntityPlaceholderWithNativeQuery();
}

We’ll have to use a couple of additional annotations to make it work. The first one is @NoRepositoryBean. We need this to exclude this base repository from instantiation. As it doesn’t have specific parametrization, the attempt to create such a repository will fail the context. Thus, we need to exclude it.

The query with JPQL is quite straightforward and will use the entity name of a given repository:

@Query(value = "select e from #{#entityName} e")
List<Article> findAllEntitiesUsingEntityPlaceholder();

However, the case with a native query isn’t so simple. Without any additional changes and configurations, it will try to use the entity name, in our case, Article, to find the table:

@Query(value = "SELECT * FROM #{#entityName}", nativeQuery = true)
List<Article> findAllEntitiesUsingEntityPlaceholderWithNativeQuery();

However, we don’t have such a table in the database. In the entity definition, we explicitly stated the name of the table:

@Entity
@Table(name = "articles")
public class Article {
// ...
}

To handle this problem, we need to provide the entity with the matching name to our table:

@Entity(name = "articles")
@Table(name = "articles")
public class Article {
// ...
}

In this case, both JPQL and the native query will infer a correct entity name, and we’ll be able to reuse the same base queries across all entities in our application.

4. Adding a SpEL Context

As pointed out, while referencing arguments or placeholders, we must provide an additional before their names. This is done to distinguish the bean names from the argument names.

However, we cannot use beans from the Spring context directly in the queries. IDEs usually provide hints about beans from the context, but the context would fail. This happens because @Value and similar annotations and @Query are handled differently. We can refer to the beans from the context of the former but not the latter.

At the same time, we can use EvaluationContextExtension to register beans in the SpEL context, and this way, we can use them in @Query. Let’s imagine the following situation – we would like to find all the articles from our database but filter them based on the locale settings of a user:

@Query(value = "SELECT * FROM articles WHERE language = :#{locale.language}", nativeQuery = true)
List<Article> findAllArticlesUsingLocaleWithNativeQuery();

This query would fail because we cannot access the locale by default. We need to provide our custom EvaluationContextExtension that would hold the information about the user’s locale:

@Component
public class LocaleContextHolderExtension implements EvaluationContextExtension {
    @Override
    public String getExtensionId() {
        return "locale";
    }
    @Override
    public Locale getRootObject() {
        return LocaleContextHolder.getLocale();
    }
}

We can use LocaleContextHolder to access the current locale anywhere in the application. The only thing to note is that it’s tied to the user’s request and inaccessible outside this scope. We need to provide our root object and the name. Optionally, we can also add properties and functions, but we’ll work only with a root object for this example.

Another step we need to take before we’ll be able to use locale inside @Query is to register locale interceptor:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
        localeChangeInterceptor.setParamName("locale");
        registry.addInterceptor(localeChangeInterceptor);
    }
}

Here, we can add information about the parameter we’ll be tracking, so whenever a request contains a locale parameter, the locale in the context will be updated. It’s possible to check the logic by providing the locale in the request:

@ParameterizedTest
@CsvSource({"eng,2","fr,2", "esp,2", "deu, 2","jp,0"})
void whenAskForNewsGetAllNewsInSpecificLanguageBasedOnLocale(String language, int expectedResultSize) {
    webTestClient.get().uri("/articles?locale=" + language)
      .exchange()
      .expectStatus().isOk()
      .expectBodyList(Article.class)
      .hasSize(expectedResultSize);
}

EvaluationContextExtension can be used to dramatically increase the power of SpEL, especially while using @Query annotations. The ways to use this can range from security and role restrictions to feature flagging and interaction between schemas.

5. Conclusion

SpEL is a powerful tool, and as with all powerful tools, people tend to overuse them and attempt to solve all the problems using only it. It’s better to use complex expressions reasonably and only in cases when necessary.

Although IDEs provide SpEL support and highlighting, complex logic might hide the bugs that would be hard to debug and verify. Thus, use SpEL sparingly and avoid “smart code” that might be better expressed in Java rather than hidden inside SpEL.

As usual, all the code used in the tutorial is available over on GitHub.

       

Java Weekly, Issue 522

$
0
0

1. Spring and Java

>> Hibernate StatelessSession Upsert [vladmihalcea.com]

A portable way of performing an upsert using Hibernate StatelessSession’s Upsert method. Interesting.

>> This Year in Spring – 2023 [spring.io]

Always a good way to take a step back and see just how fast we’re moving 🙂

Also worth reading:

Webinars and presentations:

Time to upgrade:

2. Technical & Musings

>> Five Apache Projects You Probably Haven’t Heard Of (Yet) [foojay.io]

Some lesser-known Apache projects for use cases like real-time data warehouse, API gateway, sharding sphere, and more. A good scan.

Also worth reading:

3. Pick of the Week

>> Why do programmers need private offices with doors [blobstreaming.org]

       

Determine the Class of a Generic Type in Java

$
0
0

1. Overview

Generics, which was released in Java 5, allowed developers to create classes, interfaces, and methods with typed parameters, enabling the writing of type-safe code. Extracting this type of information at runtime allows developers to write more flexible code.

In this tutorial, we’ll learn how to get the class of a generic type.

2. Generics and Type Erasure

Generics were introduced in Java with the major goal of providing compile-time type-safety checks along with flexibility and reusability of code. The introduction of Generics made the Collections framework undergo significant enhancements and improvements. Before generics, Java collections used raw type, which was kind of error-prone, and developers often faced typecast exceptions.

To demonstrate this, let’s consider a simple example where we create a List and add data to it:

void withoutGenerics(){
    List container = new ArrayList();
    container.add(1);
    container.add("2");
    container.add("string");
    for (int i = 0; i < container.size(); i++) {
        int val = (int) container.get(i); //For "string", we get java.lang.ClassCastException: class String cannot be cast to class Integer 
    } 
}

In the above example, the List contains raw data. Hence, we’re able to add Integers and Strings. When we read the list using get(), we’re type-casting it to Integer, but for the String type, we get a type-casting exception.

With generics, we define a type parameter for a collection. If we try to add any other data type than the defined type parameter, then the compiler complains about it.

For example, let’s create a generic List with an Integer type and try adding different types of data to it:

void withGenerics(){
    List<Integer> container = new ArrayList();
    container.add(1);
    container.add("2"); // compiler won't allow this since we cannot add string to list of integer container.
    container.add("string"); // compiler won't allow this since we cannot add string to list of integer container.
    for (int i = 0; i < container.size(); i++) {
        int val = container.get(i); // not casting required since we defined type for List container.
    }
}

In the above code, when we’re trying to add a String data type to the List of integers, the compiler complains about it.

In Generics, the type of information is only available at compile-time. Java compiler erases type information during compilation and it’s not available at runtime. This is called type erasure. 

Due to type erasure, all the type parameter information is replaced with the bound (if the upper bound is defined) or the object type (if the upper bound isn’t defined).

We can confirm this by using the javap utility, which inspects the .class file and helps to examine the bytecode. Let’s compile the code containing the withGenerics() method above and inspect it with the javap utility:

javac CollectionWithAndWithoutGenerics.java // compiling java file
javap -v CollectionWithAndWithoutGenerics // read bytecode using javap tool
// bytecode mnemonics
public static void withGenerics();
    descriptor: ()V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=0
         0: new           #12                 // class java/util/ArrayList
         3: dup
         4: invokespecial #14                 // Method java/util/ArrayList."<init>":()V
         7: astore_0
         8: aload_0
         9: iconst_1
        10: invokestatic  #15                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        13: invokeinterface #21,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object; 

As we can see in bytecode mnemonics, line #13,  List.add method was passed with Object instead of Integer type.

Type erasure was a design choice made by Java designers to support backward compatibility.

3. Getting Class Information

The unavailability of type information at runtime makes it challenging to capture type information at runtime. However, there are certain workarounds to get the type information at runtime.

3.1. Using Class<T> Parameter

In this approach, we explicitly pass the class of generic type T at runtime, and this information is retained so that we can access it at runtime. In the below example, we’re passing Class<T> at runtime to the constructor, which assigns it to the clazz variable. Then, we can access class information using the getClazz() method:

public class ContainerTypeFromTypeParameter<T> {
    private Class<T> clazz;
    public ContainerTypeFromTypeParameter(Class<T> clazz) {
        this.clazz = clazz;
    }
    public Class<T> getClazz() {
        return this.clazz;
    }
}

Our test verifies that we’re successfully storing and retrieving the class information at runtime:

@Test
public void givenContainerClassWithGenericType_whenTypeParameterUsed_thenReturnsClassType(){
    var stringContainer = new ContainerTypeFromTypeParameter<>(String.class);
    Class<String> containerClass = stringContainer.getClazz();
    assertEquals(String.class, containerClass);
}

3.2. Using Reflection

Using a non-generic field with reflection is another workaround that allows us to get generic information at runtime.

Basically, we use reflection to obtain the runtime class of a generic type. In the below example, we use content.getClass(), which gets class information of content at runtime using reflection:

public class ContainerTypeFromReflection<T> {
    private T content;
    public ContainerTypeFromReflection(T content) {
        this.content = content;
    }
    public Class<?> getClazz() {
        return this.content.getClass();
    }
}

Our test verifies that it works for the ContainerTypeFromReflection class and gets the type information:

@Test
public void givenContainerClassWithGenericType_whenReflectionUsed_thenReturnsClassType() {
    var stringContainer = new ContainerTypeFromReflection<>("Hello Java");
    Class<?> stringClazz = stringContainer.getClazz();
    assertEquals(String.class, stringClazz);
    var integerContainer = new ContainerTypeFromReflection<>(1);
    Class<?> integerClazz = integerContainer.getClazz();
    assertEquals(Integer.class, integerClazz);
}

3.3. Using TypeToken

Type tokens are a popular way to capture generic type information at runtime. It was made popular by Joshua Bloch in his book “Effective Java”.

In this approach, we first create an abstract class called TypeToken, where we pass the type information from the client code. Inside the abstract class, we then use the getGenericSuperClass() method to retrieve the passed type argument at runtime:

public abstract class TypeToken<T> {
    private Type type;
    protected TypeToken(){
        Type superClass = getClass().getGenericSuperclass();
        this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
    }
    public Type getType() {
        return type;
    }
}

As we can see in the above example, Inside our TokenType abstract class, we are capturing Type info at runtime using getGenericSupperClass(), which we are returning using the getType() method.

Our test verifies that it works for the sample class that extends the abstract TypeToken with String as the type parameter:

@Test
public void giveContainerClassWithGenericType_whenTypeTokenUsed_thenReturnsClassType(){
    class ContainerTypeFromTypeToken extends TypeToken<List<String>> {}
    var container = new ContainerTypeFromTypeToken();
    ParameterizedType type = (ParameterizedType) container.getType();
    Type actualTypeArgument = type.getActualTypeArguments()[0];
    assertEquals(String.class, actualTypeArgument);
}

4. Conclusion

In this article, we discuss generics and type erasure, along with its benefits and limitations. We also explored various workarounds for getting a class of generic type information at runtime, along with code examples.

As always, the example code is available over on GitHub.

       

Java System.currentTimeMillis() Vs. System.nanoTime()

$
0
0

1. Introduction

Two commonly used mеthods for timе mеasurеmеnt in Java arе Systеm.currеntTimеMillis() and Systеm.nanoTimе(). Whilе both mеthods providе a way to mеasurе timе, thеy sеrvе diffеrеnt purposеs and havе distinct characteristics.

In this tutorial, wе’ll еxplorе thе diffеrеncеs bеtwееn those two methods and undеrstand whеn to usе еach.

2. The Systеm.currеntTimеMillis() Method

The currеntTimеMillis() method rеturns thе currеnt timе in millisеconds sincе thе date January 1, 1970, 00:00:00 UTC. Moreover, it is basеd on thе systеm clock and is suitablе for mеasuring absolutе timе, such as thе currеnt datе and timе.

If we nееd absolutе timе information, such as for logging or displaying timеstamps, currеntTimеMillis() is appropriate.

Let’s take a simple code example:

@Test
public void givenTaskInProgress_whenMeasuringTimeDuration_thenDurationShouldBeNonNegative() {
    long startTime = System.currentTimeMillis();
    performTask();
    long endTime = System.currentTimeMillis();
    long duration = endTime - startTime;
    logger.info("Task duration: " + duration + " milliseconds");
    assertTrue(duration >= 0);
}

This codе dеmonstratеs how to usе thе currеntTimеMillis() mеthod to mеasurе thе duration of a task. Thе test mеthod capturеs thе start timе bеforе pеrforming a task, capturеs thе еnd timе aftеr thе task is complеtеd, and thеn calculatеs and rеturns thе duration of the task in milliseconds.

Noting that the pеrformTask() mеthod is a placеholdеr for thе actual task, we want to mеasurе. We can replace it with thе spеcific codе rеprеsеnting thе task we want to mеasurе.

3. The Systеm.nanoTimе() Method

Unlikе the currеntTimеMillis(), thе nanoTimе() mеthod rеturns thе currеnt valuе of thе most prеcisе availablе systеm timеr, typically with nanosеcond prеcision. This mеthod is dеsignеd for mеasuring еlapsеd timе with high prеcision and is oftеn usеd in pеrformancе profiling and bеnchmarking.

Let’s take an example:

@Test
public void givenShortTaskInProgress_whenMeasuringShortDuration_thenDurationShouldBeNonNegative() {
    long startNanoTime = System.nanoTime();
    performShortTask();
    long endNanoTime = System.nanoTime();
    long duration = endNanoTime - startNanoTime;
    logger.info("Short task duration: " + duration + " nanoseconds");
    assertTrue(duration >= 0);
}

In this еxamplе, thе test mеthod usеs nanoTimе() to capturе thе start and еnd timеs of a short task, providing high prеcision in nanosеconds.

It’s important to note that the prеcision of nanoTimе() may vary across different platforms. Whilе it is gеnеrally morе prеcisе than currеntTimеMillis(), we should be cautious when rеlying on еxtrеmеly high prеcision.

4. Differences and Similarities

To providе a concisе ovеrviеw of thе distinctions bеtwееn Systеm.currеntTimеMillis() and Systеm.nanoTimе(), lеt’s dеlvе into a comparativе analysis of thеir kеy charactеristics, highlighting both diffеrеncеs and similaritiеs:

Characteristic System.currentTimeMillis() System.nanoTime()
Precision Millisecond precision Nanosecond precision
Use Case Absolute time (logging, timestamps) Elapsed time, performance profiling
Base System clock-based System timer-based
Platform Dependency Less platform-dependent May vary in precision across platforms

5. Conclusion

In conclusion, understanding thе diffеrеncеs bеtwееn currеntTimеMillis() and nanoTimе() is crucial for making informеd dеcisions whеn mеasuring timе in Java applications. Whеthеr we prioritizе absolutе timе or high prеcision, choosing thе right mеthod for our specific usе casе will contribute to morе accuratе and еfficiеnt timе mеasurеmеnt in our Java programs.

As always, the complete code samples for this article can be found over on GitHub.

       

Dead Letter Queue for Kafka With Spring

$
0
0

1. Introduction

In this tutorial, we’ll learn how to configure a Dead Letter Queue mechanism for Apache Kafka using Spring.

2. Dead Letter Queues

A Dead Letter Queue (DLQ) is used to store messages that cannot be correctly processed due to various reasons, for example intermittent system failures, invalid message schema, or corrupted content. These messages can be later removed from the DLQ for analysis or reprocessing.
The following figure presents a simplified flow of the DLQ mechanism:
Dead Letter Queue
Using a DLQ is generally a good idea, but there are scenarios when it should be avoided. For example, it’s not recommended to use a DLQ for a queue where the exact order of messages is important, as reprocessing a DLQ message breaks the order of the messages on arrival.

3. Dead Letter Queues in Spring Kafka

The equivalent of the DLQ concept in Spring Kafka is the Dead Letter Topic (DLT). In the following sections, we’ll see how the DLT mechanism works for a simple payment system.

3.1. Model Class

Let’s start with the model class:

public class Payment {
    private String reference;
    private BigDecimal amount;
    private Currency currency;
    // standard getters and setters
}
Let’s also implement a utility method for creating events:
static Payment createPayment(String reference) {
    Payment payment = new Payment();
    payment.setAmount(BigDecimal.valueOf(71));
    payment.setCurrency(Currency.getInstance("GBP"));
    payment.setReference(reference);
    return payment;
}

3.2. Setup

Next, let’s add the required spring-kafka and jackson-databind dependencies:

<dependency>
    <groupId>org.springframework.kafka</groupId>
    <artifactId>spring-kafka</artifactId>
    <version>2.9.13</version> </dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.14.3</version>
</dependency>

We can now create the ConsumerFactory and ConcurrentKafkaListenerContainerFactory beans:

@Bean
public ConsumerFactory<String, Payment> consumerFactory() {
    Map<String, Object> config = new HashMap<>();
    config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
    return new DefaultKafkaConsumerFactory<>(
      config, new StringDeserializer(), new JsonDeserializer<>(Payment.class));
}
@Bean
public ConcurrentKafkaListenerContainerFactory<String, Payment> containerFactory() {
    ConcurrentKafkaListenerContainerFactory<String, Payment> factory = 
      new ConcurrentKafkaListenerContainerFactory<>();
    factory.setConsumerFactory(consumerFactory());
    return factory;
}

Finally, let’s implement the consumer for the main topic:

@KafkaListener(topics = { "payments" }, groupId = "payments")
public void handlePayment(
  Payment payment, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
    log.info("Event on main topic={}, payload={}", topic, payment);
}

Before moving on to the DLT examples, we’ll discuss the retry configuration.

3.3. Turning Off Retries

In real-life projects, it’s common to retry processing an event in case of errors before sending it to DLT. This can be easily achieved using the non-blocking retries mechanism provided by Spring Kafka.

In this article, however, we’ll turn off the retries to highlight the DLT mechanism. An event will be published directly to the DLT when the consumer for the main topic fails to process it.

First, we need to define the producerFactory and the retryableTopicKafkaTemplate beans:

@Bean
public ProducerFactory<String, Payment> producerFactory() {
    Map<String, Object> config = new HashMap<>();
    config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
    return new DefaultKafkaProducerFactory<>(
      config, new StringSerializer(), new JsonSerializer<>());
}
@Bean
public KafkaTemplate<String, Payment> retryableTopicKafkaTemplate() {
    return new KafkaTemplate<>(producerFactory());
}

Now we can define the consumer for the main topic without additional retries, as described earlier:

@RetryableTopic(attempts = "1", kafkaTemplate = "retryableTopicKafkaTemplate")
@KafkaListener(topics = { "payments"}, groupId = "payments")
public void handlePayment(
  Payment payment, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
    log.info("Event on main topic={}, payload={}", topic, payment);
}

The attempts property in the @RetryableTopic annotation represents the number of attempts tried before sending the message to the DLT.

4. Configuring Dead Letter Topic

We’re now ready to implement the DLT consumer:
@DltHandler
public void handleDltPayment(
  Payment payment, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
    log.info("Event on dlt topic={}, payload={}", topic, payment);
}
The method annotated with the @DltHandler annotation must be placed in the same class as the @KafkaListener annotated method.
In the following sections, we’ll explore the three DLT configurations available in Spring Kafka. We’ll use a dedicated topic and consumer for each strategy to make each example easy to follow individually.

4.1. DLT With Fail on Error

Using the FAIL_ON_ERROR strategy we can configure the DLT consumer to end the execution without retrying if the DLT processing fails:
@RetryableTopic(
  attempts = "1", 
  kafkaTemplate = "retryableTopicKafkaTemplate", 
  dltStrategy = DltStrategy.FAIL_ON_ERROR)
@KafkaListener(topics = { "payments-fail-on-error-dlt"}, groupId = "payments")
public void handlePayment(
  Payment payment, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
    log.info("Event on main topic={}, payload={}", topic, payment);
}
@DltHandler
public void handleDltPayment(
  Payment payment, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
    log.info("Event on dlt topic={}, payload={}", topic, payment);
}
Notably, the @KafkaListener consumer reads messages from the payments-fail-on-error-dlt topic.
Let’s verify that the event isn’t published to the DLT when the main consumer is successful:
@Test
public void whenMainConsumerSucceeds_thenNoDltMessage() throws Exception {
    CountDownLatch mainTopicCountDownLatch = new CountDownLatch(1);
    doAnswer(invocation -> {
        mainTopicCountDownLatch.countDown();
        return null;
    }).when(paymentsConsumer)
        .handlePayment(any(), any());
    kafkaProducer.send(TOPIC, createPayment("dlt-fail-main"));
    assertThat(mainTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
    verify(paymentsConsumer, never()).handleDltPayment(any(), any());
}
Let’s see what happens when both the main and the DLT consumers fail to process the event:
@Test
public void whenDltConsumerFails_thenDltProcessingStops() throws Exception {
    CountDownLatch mainTopicCountDownLatch = new CountDownLatch(1);
    CountDownLatch dlTTopicCountDownLatch = new CountDownLatch(2);
    doAnswer(invocation -> {
        mainTopicCountDownLatch.countDown();
        throw new Exception("Simulating error in main consumer");
    }).when(paymentsConsumer)
        .handlePayment(any(), any());
    doAnswer(invocation -> {
        dlTTopicCountDownLatch.countDown();
        throw new Exception("Simulating error in dlt consumer");
    }).when(paymentsConsumer)
        .handleDltPayment(any(), any());
    kafkaProducer.send(TOPIC, createPayment("dlt-fail"));
    assertThat(mainTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
    assertThat(dlTTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isFalse();
    assertThat(dlTTopicCountDownLatch.getCount()).isEqualTo(1);
}

In the test above, the event was processed once by the main consumer and only once by the DLT consumer.

4.2. DLT Retry

We can configure the DLT consumer to attempt to reprocess the event when the DLT processing fails using the ALWAYS_RETRY_ON_ERROR strategy. This is the strategy used as default:
@RetryableTopic(
  attempts = "1", 
  kafkaTemplate = "retryableTopicKafkaTemplate", 
  dltStrategy = DltStrategy.ALWAYS_RETRY_ON_ERROR)
@KafkaListener(topics = { "payments-retry-on-error-dlt"}, groupId = "payments")
public void handlePayment(
  Payment payment, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
    log.info("Event on main topic={}, payload={}", topic, payment);
}
@DltHandler
public void handleDltPayment(
  Payment payment, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
    log.info("Event on dlt topic={}, payload={}", topic, payment);
}
Notably, the @KafkaListener consumer reads messages from the payments-retry-on-error-dlt topic.
Next, let’s test what happens when the main and the DLT consumers fail to process the event:
@Test
public void whenDltConsumerFails_thenDltConsumerRetriesMessage() throws Exception {
    CountDownLatch mainTopicCountDownLatch = new CountDownLatch(1);
    CountDownLatch dlTTopicCountDownLatch = new CountDownLatch(3);
    doAnswer(invocation -> {
        mainTopicCountDownLatch.countDown();
        throw new Exception("Simulating error in main consumer");
    }).when(paymentsConsumer)
        .handlePayment(any(), any());
    doAnswer(invocation -> {
        dlTTopicCountDownLatch.countDown();
        throw new Exception("Simulating error in dlt consumer");
    }).when(paymentsConsumer)
        .handleDltPayment(any(), any());
    kafkaProducer.send(TOPIC, createPayment("dlt-retry"));
    assertThat(mainTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
    assertThat(dlTTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
    assertThat(dlTTopicCountDownLatch.getCount()).isEqualTo(0);
}

As expected, the DLT consumer tries to reprocess the event.

4.3. Disabling DLT

The DLT mechanism can also be turned off using the NO_DLT strategy:
@RetryableTopic(
  attempts = "1", 
  kafkaTemplate = "retryableTopicKafkaTemplate", 
  dltStrategy = DltStrategy.NO_DLT)
@KafkaListener(topics = { "payments-no-dlt" }, groupId = "payments")
public void handlePayment(
  Payment payment, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
    log.info("Event on main topic={}, payload={}", topic, payment);
}
@DltHandler
public void handleDltPayment(
  Payment payment, @Header(KafkaHeaders.RECEIVED_TOPIC) String topic) {
    log.info("Event on dlt topic={}, payload={}", topic, payment);
}
Notably, the @KafkaListener consumer reads messages from the payments-no-dlt topic.
Let’s check that an event isn’t forwarded to the DLT when the consumer on the main topic fails to process it:
@Test
public void whenMainConsumerFails_thenDltConsumerDoesNotReceiveMessage() throws Exception {
    CountDownLatch mainTopicCountDownLatch = new CountDownLatch(1);
    CountDownLatch dlTTopicCountDownLatch = new CountDownLatch(1);
    doAnswer(invocation -> {
        mainTopicCountDownLatch.countDown();
        throw new Exception("Simulating error in main consumer");
    }).when(paymentsConsumer)
        .handlePayment(any(), any());
    doAnswer(invocation -> {
        dlTTopicCountDownLatch.countDown();
        return null;
    }).when(paymentsConsumer)
        .handleDltPayment(any(), any());
    kafkaProducer.send(TOPIC, createPayment("no-dlt"));
    assertThat(mainTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
    assertThat(dlTTopicCountDownLatch.await(5, TimeUnit.SECONDS)).isFalse();
    assertThat(dlTTopicCountDownLatch.getCount()).isEqualTo(1);
}
As expected, the event isn’t forwarded to the DLT, although we’ve implemented a consumer annotated with @DltHandler

5. Conclusion

In this article, we learned three different DLT strategies. The first one is the FAIL_ON_ERROR strategy, when the DLT consumer won’t try to reprocess an event in case of failure. In contrast, the ALWAYS_RETRY_ON_ERROR strategy ensures that the DLT consumer tries to reprocess the event in case of failure. This is the value used as default when no other strategy is explicitly set. The last one is the NO_DLT strategy, which turns off the DLT mechanism altogether.

As always, the complete code can be found over on GitHub.

       

Using XML in @RequestBody in Spring REST

$
0
0

1. Overview

While JSON is a de-facto standard for RESTful services, in some cases, we might want to work with XML. We can fall back to XML for different reasons: legacy applications, using a more verbose format, standardized schemas, etc.

Spring provides us with a simple way to support XML endpoints with no work from our side. In this tutorial, we’ll learn how to leverage Jackson XML to approach this problem.

2. Dependencies

The first step is to add the dependency to allow XML mapping. Even if we’re using spring-boot-starter-web, it doesn’t contain the libraries for XML support by default:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.16.0</version>
</dependency>

We can leverage the Spring Boot version management system by omitting the version, ensuring the correct Jackson library versions are used across all dependencies.

Alternatively, we can use JAXB to do the same thing, but overall, it’s more verbose, and Jackson generally provides us with a nicer API. However, if we’re using Java 8, JAXB libraries are located in the javax package with the implementation, and we won’t need to add any other dependencies to our application.

On Java versions starting from 9, the javax package was moved and renamed to jakarta, so JAXB requires an additional dependency:

<dependency>
    <groupId>jakarta.xml.bind</groupId>
    <artifactId>jakarta.xml.bind-api</artifactId>
    <version>4.0.0</version>
</dependency>

Also, it needs a runtime implementation for XML mappers, which might create too much confusion and subtle issues.

3. Endpoints

Since JSON is a default format for Spring REST controllers, we need to explicitly identify the endpoints that consume and produce XML. Let’s consider this simple echo controller:

@RestController
@RequestMapping("/users")
public class UserEchoController {
    @ResponseStatus(HttpStatus.CREATED)
    @PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public User echoJsonUser(@RequestBody User user) {
        return user;
    }
    @ResponseStatus(HttpStatus.CREATED)
    @PostMapping(consumes = MediaType.APPLICATION_XML_VALUE, produces = MediaType.APPLICATION_XML_VALUE)
    public User echoXmlUser(@RequestBody User user) {
        return user;
    }
}

The only purpose of the controller is to receive a User and send it back. The only difference between these endpoints is that the first works with JSON format. We specify it explicitly in @PostMapping, but for JSON, we can omit the consumes and produces attributes.

The second endpoint works with XML. We must identify it explicitly by providing the correct types to the consumes and produces values. This is the only thing we need to do to configure the endpoint.

4. Mappings

We’ll be working with the following User class:

public class User {
    private Long id;
    private String firstName;
    private String secondName;
    public User() {
    }
    // getters, setters, equals, hashCode
}

Technically, we don’t need anything else, and the endpoint should support the following XML straight away:

<User>
    <id>1</id>
    <firstName>John</firstName>
    <secondName>Doe</secondName>
</User>

However, if we want to provide other names or translate legacy conventions to ones we use in our application, we might want to use special annotations. @JacksonXmlRootElement and @JacksonXmlProperty are the most common annotations to use for this.

If we opt to use JAXB, it is also possible to configure our mappings with annotations only, and there’s a different set of annotations, for example, @XmlRootElement and @XmlAttribute. In general, the process is quite similar. However, note that JAXB might require explicit mapping.

5. Conclusion

Spring REST provides us with a convenient way to create RESTful services. However, they aren’t constrained to JSON only. We can use them with other formats, for example, XML. Overall, the transition is transparent, and the entire setup is made with several strategically placed annotations. 

As usual, the code from the tutorial is available over on GitHub.

       

Return Map instead of List in Spring Data JPA

$
0
0

1. Overview

Spring JPA provides a very flexible and convenient API for interaction with databases. However, sometimes, we need to customize it or add more functionality to the returned collections.

Using Map as a return type from JPA repository methods might help to create more straightforward interactions between services and databases. Unfortunately, Spring doesn’t allow this conversion to happen automatically. In this tutorial, we’ll check how to overcome this and learn some interesting techniques to make our repositories more functional.

2. Manual Implementation

The most apparent approach to the problem when a framework doesn’t provide something, is to implement it ourselves. In this case, JPA allows us to implement the repositories from scratch, skip the entire generation process, or use default methods to get the best of both worlds.

2.1. Using List

We can implement a method to map the resulting list into the map. Stream API helps greatly with this task, allowing almost one-liner implementation:

default Map<Long, User> findAllAsMapUsingCollection() {
    return findAll().stream()
      .collect(Collectors.toMap(User::getId, Function.identity()));
}

2.2. Using Stream

We can do a similar thing but use Stream directly. To do so, we can identify a custom method that will return a stream of users. Luckily, Spring JPA supports such return types, and we can benefit from autogeneration:

@Query("select u from User u")
Stream<User> findAllAsStream();

After that, we can implement a custom method that would map the results into the data structure we need:

@Transactional
default Map<Long, User> findAllAsMapUsingStream() {
    return findAllAsStream()
      .collect(Collectors.toMap(User::getId, Function.identity()));
}

The repository methods that return Stream should be called inside a transaction. In this case, we directly added a @Transactional annotation to the default method.

2.3. Using Streamable

This is a similar approach to the one discussed previously. The only change is that we’ll be using Streamable. We need to create a custom method to return it first:

@Query("select u from User u")
Streamable<User> findAllAsStreamable();

Then, we can map the result appropriately:

default Map<Long, User> findAllAsMapUsingStreamable() {
    return findAllAsStreamable().stream()
      .collect(Collectors.toMap(User::getId, Function.identity()));
}

3. Custom Streamable Wrapper

Previous examples showed us quite simple solutions to the problem. However, suppose we have several different operations or data structures to which we want to map our results. In that case, we can end up with unwieldy mappers scattered around our code or multiple repository methods that do similar things.

A better approach might be to create a dedicated class representing a collection of entities and place all the methods connected to the operations on the collection inside. To do so, we’ll be using Streamable.

As was shown previously, Spring JPA understands Streamable and can map the result to it. Interestingly, we can extend Streamable and provide it with convenient methods. Let’s create a Users class that would represent a collection of User objects:

public class Users implements Streamable<User> {
    private final Streamable<User> userStreamable;
    public Users(Streamable<User> userStreamable) {
        this.userStreamable = userStreamable;
    }
    @Override
    public Iterator<User> iterator() {
        return userStreamable.iterator();
    }
    // custom methods
}

To make it work with JPA, we should follow a simple convention. First, we should implement Streamable, and secondly, provide the way Spring will be able to initialize it. The initialization part can be addressed either by a public constructor that takes Streamable or static factories with names of(Streamable<T>) or valueOf(Streamable<T>).

After that, we can use Users as a return type of JPA repository methods:

@Query("select u from User u")
Users findAllUsers();

Now, we can place the method we kept in the repository directly in the Users class:

public Map<Long, User> getUserIdToUserMap() {
    return stream().collect(Collectors.toMap(User::getId, Function.identity()));
}

The best part is that we can use all the methods connected to the processing or mapping of the User entities. Let’s say we want to filter out users by some criteria:

@Test
void fetchUsersInMapUsingStreamableWrapperWithFilterThenAllOfThemPresent() {
    Users users = repository.findAllUsers();
    int maxNameLength = 4;
    List<User> actual = users.getAllUsersWithShortNames(maxNameLength);
    User[] expected = {
        new User(9L, "Moe", "Oddy"),
        new User(25L, "Lane", "Endricci"),
        new User(26L, "Doro", "Kinforth"),
        new User(34L, "Otho", "Rowan"),
        new User(39L, "Mel", "Moffet")
    };
    assertThat(actual).containsExactly(expected);
}

Also, we can group them in some way:

@Test
void fetchUsersInMapUsingStreamableWrapperAndGroupingThenAllOfThemPresent() {
    Users users = repository.findAllUsers();
    Map<Character, List<User>> alphabeticalGrouping = users.groupUsersAlphabetically();
    List<User> actual = alphabeticalGrouping.get('A');
    User[] expected = {
        new User(2L, "Auroora", "Oats"),
        new User(4L, "Alika", "Capin"),
        new User(20L, "Artus", "Rickards"),
        new User(27L, "Antonina", "Vivian")};
    assertThat(actual).containsExactly(expected);
}

This way, we can hide the implementation of such methods, remove clutter from our services, and unload the repositories.

4. Conclusion

Spring JPA allows customization, but sometimes it’s pretty straightforward to achieve this. Building an application around the types restricted by a framework might affect the quality of the code and even the design of an application.

Using custom collections as return types might make the design more straightforward and less cluttered with mapping and filtering logic. Using dedicated wrappers for the collections of entities can improve the code even further.

As usual, all the code used in this tutorial is available over on GitHub.

       

Compress and Uncompress Byte Array Using Deflater/Inflater

$
0
0

1. Overview

Data compression is a crucial aspect of software development that enables efficient storage and transmission of information. In Java, the Deflater and Inflater classes from the java.util.zip package provide a straightforward way to compress and decompress byte arrays.

In this short tutorial, we’ll explore how to use these classes with a simple example.

2. Compressing

The Deflater class uses the ZLIB compression library to compress data. Let’s see it in action:

public static byte[] compress(byte[] input) {
    Deflater deflater = new Deflater();
    deflater.setInput(input);
    deflater.finish();
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    while (!deflater.finished()) {
        int compressedSize = deflater.deflate(buffer);
        outputStream.write(buffer, 0, compressedSize);
    }
    return outputStream.toByteArray();
}

In the above code, we’ve used several methods of the Deflater class to compress the input data:

  • setInput(): set input data for compression
  • finish(): indicate that compression should end with the current contents of the input
  • deflate(): compress the data and fill to a specified buffer, then return the actual number of bytes of compressed data
  • finished(): check if the end of the compressed data output stream has been reached

Additionally, we can use the setLevel() method to get better compression results. We can pass values from 0 to 9, corresponding to the range from no compression to best compression:

Deflater deflater = new Deflater();
deflater.setInput(input);
deflater.setLevel(5);

3. Uncompressing

Next, let’s decompress a byte array with the Inflater class:

public static byte[] decompress(byte[] input) throws DataFormatException {
    Inflater inflater = new Inflater();
    inflater.setInput(input);
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    while (!inflater.finished()) {
        int decompressedSize = inflater.inflate(buffer);
        outputStream.write(buffer, 0, decompressedSize);
    }
    return outputStream.toByteArray();
}

This time, we used three methods of the Inflater class:

  • setInput(): set input data for decompression
  • finished(): check if the end of the compressed data stream has been reached
  • inflate(): decompress bytes into the specified buffer and return the actual number of bytes uncompressed

4. Example

Let’s try out our methods with this simple example:

String inputString = "Baeldung helps developers explore the Java ecosystem and simply be better engineers. "
  + "We publish to-the-point guides and courses, with a strong focus on building web applications, Spring, "
  + "Spring Security, and RESTful APIs";
byte[] input = inputString.getBytes();
byte[] compressedData = compress(input);
byte[] decompressedData = decompress(compressedData);
System.out.println("Original: " + input.length + " bytes");
System.out.println("Compressed: " + compressedData.length + " bytes");
System.out.println("Decompressed: " + decompressedData.length + " bytes");
assertEquals(input.length, decompressedData.length);

The result will look like this:

Original: 220 bytes
Compressed: 168 bytes
Decompressed: 220 bytes

5. Conclusion

In this article, we’ve learned how to compress and uncompress a Java byte array using the Deflater and Inflater classes, respectively.

The example code from this article can be found over on GitHub.

       

Difference Between 1L and (long) 1

$
0
0

1. Overview

In this tutorial, we’ll explore the differences between the literal representation of a long type and the conversion of an int value to a long value using the cast operator.

2. Literal Representation

By default, Java treats all integral numeric values as of the int type. Similarly, for the floating-point values, the default type is double.

An integral numeric value is considered to be of the long type only if the value contains the letter “L” or “l” as a suffix:

long x = 1L;

However, the lowercase “l” looks like the number 1. To avoid confusion, we should consider using the uppercase version of the letter.

As we know, each primitive data type has its corresponding wrapper class, which comes with different methods we can use and, additionally, allows us to use primitive values within generics.

Thanks to the Java autoboxing functionality, we can assign the literal value to its wrapper class reference directly:

Long x = 1L;

Here, Java converts the long value to the Long object automatically.

Furthermore, when we define a long value using the literal representation, it always evaluates as a long data type.

3. Explicit Type Casting

Moving forward, let’s examine the cases when we’d need to use explicit type casting. As a quick refresher, the cast operator converts a value of one type into the type defined within parentheses.

3.1. Primitive Data Types

To convert a value from one primitive type to another, we need to perform either widening or narrowing of the primitive data type.

Furthermore, to transform the value of the type that supports a bigger range of numbers, we don’t need to perform any additional action:

int x = 1;
long y = x;

However, to fit the value into the type with a smaller numeric range, we need to use explicit type casting:

int x = 1;
byte y = (byte) x;

One thing to keep in mind here is the possible data overflow that happens if a number is too big to fit into the new type.

3.2. Wrapper Class

One way to convert the primitive data type into a wrapper class that doesn’t correlate with the primitive type is by using the cast operator and embracing the Java autoboxing feature:

int x = 1;
Long y = (long) x;

Here, we first converted the int value to a long value and then let Java convert the long type to Long using autoboxing.

4. Constant Expression

Now that we discussed the basics, let’s dig a little deeper and see how Java treats literal values and casts of primitive data types.

We typically use the term constant to describe values that don’t change after compilation. On a class level, we define them using the static and final keywords.

However, besides the class constants, Java recognizes expressions that can be computed at compile-time. These expressions are referred to as constant expressions.

According to the Java Language Specification (JLS), literal primitive values and casts of primitive data types are both considered constant expressions.

Therefore, Java treats both the literal representation of a long value and the explicit cast of an int data type to a long the same way.

5. Comparison Between 1L and (long) 1

Finally, let’s see how the literal representation of a long type differs from casting an int value to a long.

Firstly, performance-wise, there’s no difference between those two expressions. They’re both considered constants and are evaluated at the compile-time.

Moreover, we can compile both statements and compare their bytecode results using, for instance, the javap tool. We’ll notice they’re the same. They both use lconst_1 to push a long constant into the stack.

Secondly, using the literal representation can increase the readability of the code.

Lastly, when we define a constant expression using the literal, the value will always be of a long type. However, when we’re using the cast operator, the number we’re casting is of the int type.

Therefore, the following code won’t compile:

Long x = (long) 123_456_789_101;

Even though we’re storing the value in the Long data type, the number 123_456_789_101 is of the int type. The code is invalid since the number is outside the integer range.

On the other hand, the literal representation compiles successfully:

Long x = 123_456_789_101L;

6. Conclusion

In this article, we learned the differences between defining a long value using literal representation and casting an int value to a long.

To sum up, from Java’s point of view, they’re both constant expressions. To put it differently, in both cases, the actual value can be determined at the compile-time. However, using the literal representation of a number comes with some benefits, such as increased readability and preventing possible data overflow.

       

When to Use the getReferenceById() and findById() Methods in Spring Data JPA

$
0
0

1. Overview

JpaRepository provides us with basic methods for CRUD operations. However, some of them are not so straightforward, and sometimes, it’s hard to identify which method would be the best for a given situation.

getReferenceById(ID) and findById(ID) are the methods that often create such confusion. These methods are new API names for getOne(ID), findOne(ID), and getById(ID).

In this tutorial, we’ll learn the difference between them and find the situation when each might be more suitable.

2. findById()

Let’s start with the simplest one out of these two methods. This method does what it says, and usually, developers don’t have any issues with it. It simply finds an entity in a repository given a specific ID:

@Override
Optional<T> findById(ID id);

The method returns an Optional. Thus, assuming it would be empty if we passed a non-existent ID is correct.

The method uses eager loading under the hood, so we’ll send a request to our database whenever we call this method. Let’s check an example:

public User findUser(long id) {
    log.info("Before requesting a user in a findUser method");
    Optional<User> optionalUser = repository.findById(id);
    log.info("After requesting a user in a findUser method");
    User user = optionalUser.orElse(null);
    log.info("After unwrapping an optional in a findUser method");
    return user;
}

This method will generate the following logs:

[2023-12-27 12:56:32,506]-[main] INFO  com.baeldung.spring.data.persistence.findvsget.service.SimpleUserService - Before requesting a user in a findUser method
[2023-12-27 12:56:32,508]-[main] DEBUG org.hibernate.SQL - 
    select
        user0_."id" as id1_0_0_,
        user0_."first_name" as first_na2_0_0_,
        user0_."second_name" as second_n3_0_0_ 
    from
        "users" user0_ 
    where
        user0_."id"=?
[2023-12-27 12:56:32,508]-[main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - [1]
[2023-12-27 12:56:32,510]-[main] INFO  com.baeldung.spring.data.persistence.findvsget.service.SimpleUserService - After requesting a user in a findUser method
[2023-12-27 12:56:32,510]-[main] INFO  com.baeldung.spring.data.persistence.findvsget.service.SimpleUserService - After unwrapping an optional in a findUser method

Spring might batch requests in a transaction but will always execute them. Overall, findById(ID) doesn’t try to surprise us and does what we expect from it. However, the confusion arises because it has a counterpart that does something similar.

3. getReferenceById()

This method has a similar signature to findById(ID):

@Override
T getReferenceById(ID id);

Judging by the signature alone, we can assume that this method would throw an exception if the entity doesn’t exist. It’s true, but it’s not the only difference we have. The main difference between these methods is that getReferenceById(ID) is lazy. Spring won’t send a database request until we explicitly try to use the entity within a transaction.

3.1. Transactions

Each transaction has a dedicated persistence context it works with. Sometimes, we can expand the persistence context outside the transaction scope, but it’s not common and useful only for specific scenarios. Let’s check how the persistence context behaves regarding the transactions:

Within a transaction, all the entities inside the persistence context have a direct representation in the database. This is a managed state. Thus, all the changes to the entity will be reflected in the database. Outside the transaction, the entity moved to a detached state, and changes won’t be reflected until the entity is moved back to the managed state.

Lazy-loaded entities behave slightly differently. Spring won’t load them until we explicitly use them in the persistence context:

Spring will allocate an empty proxy placeholder to fetch the entity from the database lazily. However, if we don’t do this, the entity will remain an empty proxy outside the transaction, and any call to it will result in a LazyInitializationException. However, if we do call or interact with the entity in the way it will require the internal information, the actual request to the database will be made:

3.2. Non-transactional Services

Knowing the behavior of transactions and the persistence context, let’s check the following non-transactional service, which calls the repository. The findUserReference doesn’t have a persistence context connected to it, and getReferenceById will be executed in a separate transaction:

public User findUserReference(long id) {
    log.info("Before requesting a user");
    User user = repository.getReferenceById(id);
    log.info("After requesting a user");
    return user;
}

This code will generate the following log output:

[2023-12-27 13:21:27,590]-[main] INFO  com.baeldung.spring.data.persistence.findvsget.service.TransactionalUserReferenceService - Before requesting a user
[2023-12-27 13:21:27,590]-[main] INFO  com.baeldung.spring.data.persistence.findvsget.service.TransactionalUserReferenceService - After requesting a user

As we can see, there’s no database request. After understanding the lazy loading, Spring assumes that we might not need it if we don’t use the entity within. Technically, we cannot use it because our only transaction is one inside the getReferenceById method. Thus, the user we returned will be an empty proxy, which will result in an exception if we access its internals:

public User findAndUseUserReference(long id) {
    User user = repository.getReferenceById(id);
    log.info("Before accessing a username");
    String firstName = user.getFirstName();
    log.info("This message shouldn't be displayed because of the thrown exception: {}", firstName);
    return user;
}

3.3. Transactional Service

Let’s check the behavior if we’re using a @Transactional service:

@Transactional
public User findUserReference(long id) {
    log.info("Before requesting a user");
    User user = repository.getReferenceById(id);
    log.info("After requesting a user");
    return user;
}

This will give us a similar result for the same reason as in the previous example, as we don’t use the entity inside our transaction:

[2023-12-27 13:32:44,486]-[main] INFO  com.baeldung.spring.data.persistence.findvsget.service.TransactionalUserReferenceService - Before requesting a user
[2023-12-27 13:32:44,486]-[main] INFO  com.baeldung.spring.data.persistence.findvsget.service.TransactionalUserReferenceService - After requesting a user

Also, any attempts to interact with this user outside of this transactional service method would cause an exception:

@Test
void whenFindUserReferenceUsingOutsideServiceThenThrowsException() {
    User user = transactionalService.findUserReference(EXISTING_ID);
    assertThatExceptionOfType(LazyInitializationException.class)
      .isThrownBy(user::getFirstName);
}

However, now, the findUserReference method defines the scope of our transaction. This means that we can try to access the user in our service method, and it should cause the call to the database:

@Transactional
public User findAndUseUserReference(long id) {
    User user = repository.getReferenceById(id);
    log.info("Before accessing a username");
    String firstName = user.getFirstName();
    log.info("After accessing a username: {}", firstName);
    return user;
}

The code above would output the messages in the following order:

[2023-12-27 13:32:44,331]-[main] INFO  com.baeldung.spring.data.persistence.findvsget.service.TransactionalUserReferenceService - Before accessing a username
[2023-12-27 13:32:44,331]-[main] DEBUG org.hibernate.SQL - 
    select
        user0_."id" as id1_0_0_,
        user0_."first_name" as first_na2_0_0_,
        user0_."second_name" as second_n3_0_0_ 
    from
        "users" user0_ 
    where
        user0_."id"=?
[2023-12-27 13:32:44,331]-[main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - [1]
[2023-12-27 13:32:44,331]-[main] INFO  com.baeldung.spring.data.persistence.findvsget.service.TransactionalUserReferenceService - After accessing a username: Saundra

The request to the database wasn’t made when we called getReferenceById(), but when we called user.getFirstName(). 

3.3. Transactional Service With a New Repository Transaction

Let’s check a bit more complex example. Imagine we have a repository method that creates a separate transaction whenever we call it:

@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
User getReferenceById(Long id);

Propagation.REQUIRES_NEW means the outer transaction won’t propagate, and the repository method will create its persistence context. In this case, even if we use a transactional service, Spring will create two separate persistence contexts that won’t interact, and any attempts to use the user will cause an exception:

@Test
void whenFindUserReferenceUsingInsideServiceThenThrowsExceptionDueToSeparateTransactions() {
    assertThatExceptionOfType(LazyInitializationException.class)
      .isThrownBy(() -> transactionalServiceWithNewTransactionRepository.findAndUseUserReference(EXISTING_ID));
}

We can use a couple of different propagation configurations to create more complex interactions between transactions, and they can yield different results.

4. Conclusion

The main difference between findById() and getReferenceById() is when they load the entities into the persistence context. Understanding this might help to implement optimizations and avoid unnecessary database lookups. This process is tightly connected to the transactions and their propagation. That’s why the relationships between transactions should be observed.

As usual, all the code used in this tutorial is available over on GitHub.

       

Bind Case Insensitive @Value to Enum in Spring Boot

$
0
0

1. Overview

Spring provides us with autoconfiguration features that we can use to bind components, configure beans, and set values from a property source.

@Value annotation is useful when we don’t want to cannot hardcode the values and prefer to provide them using property files or the system environment.

In this tutorial, we’ll learn how to leverage Spring autoconfiguration to map these values to Enum instances.

2. Converters<F,T>

Spring uses converters to map the String values from @Value to the required type. A dedicated BeanPostPorcessor goes through all the components and checks if they require additional configuration or, in our case, injection. After that, a suitable converter is found, and the data from the source converter is sent to the specified target. Spring provides a String to Enum converter out of the box, so let’s review it.

2.1. LenientToEnumConverter

As the name suggests, this converter is quite free to interpret the data during conversion. Initially, it assumes that the values are provided correctly:

@Override
public E convert(T source) {
    String value = source.toString().trim();
    if (value.isEmpty()) {
        return null;
    }
    try {
        return (E) Enum.valueOf(this.enumType, value);
    }
    catch (Exception ex) {
        return findEnum(value);
    }
}

However, it tries a different approach if it cannot map the source to an Enum. It gets the canonical names for both Enum and the value:

private E findEnum(String value) {
    String name = getCanonicalName(value);
    List<String> aliases = ALIASES.getOrDefault(name, Collections.emptyList());
    for (E candidate : (Set<E>) EnumSet.allOf(this.enumType)) {
        String candidateName = getCanonicalName(candidate.name());
        if (name.equals(candidateName) || aliases.contains(candidateName)) {
            return candidate;
        }
    }
    throw new IllegalArgumentException("No enum constant " + this.enumType.getCanonicalName() + "." + value);
}

The getCanonicalName(String) filters out all special characters and converts the string to lowercase:

private String getCanonicalName(String name) {
    StringBuilder canonicalName = new StringBuilder(name.length());
    name.chars()
      .filter(Character::isLetterOrDigit)
      .map(Character::toLowerCase)
      .forEach((c) -> canonicalName.append((char) c));
    return canonicalName.toString();
}

This process makes the converter quite adaptive, so it might introduce some problems if not considered. At the same time, it provides excellent support for case-insensitive matching for Enum for free, without any additional configuration required.

2.2. Lenient Conversion

Let’s take a simple Enum class as an example:

public enum SimpleWeekDays {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

We’ll inject all these constants into a dedicated class-holder using @Value annotation:

@Component
public class WeekDaysHolder {
    @Value("${monday}")
    private WeekDays monday;
    @Value("${tuesday}")
    private WeekDays tuesday;
    @Value("${wednesday}")
    private WeekDays wednesday;
    @Value("${thursday}")
    private WeekDays thursday;
    @Value("${friday}")
    private WeekDays friday;
    @Value("${saturday}")
    private WeekDays saturday;
    @Value("${sunday}")
    private WeekDays sunday;
    // getters and setters
}

Using lenient conversion, we can not only pass the values using a different case, but as was shown previously, we can add special characters around and inside these values, and the converter will still map them:

@SpringBootTest(properties = {
    "monday=Mon-Day!",
    "tuesday=TuesDAY#",
    "wednesday=Wednes@day",
    "thursday=THURSday^",
    "friday=Fri:Day_%",
    "saturday=Satur_DAY*",
    "sunday=Sun+Day",
}, classes = WeekDaysHolder.class)
class LenientStringToEnumConverterUnitTest {
    @Autowired
    private WeekDaysHolder propertyHolder;
    @ParameterizedTest
    @ArgumentsSource(WeekDayHolderArgumentsProvider.class)
    void givenPropertiesWhenInjectEnumThenValueIsPresent(
        Function<WeekDaysHolder, WeekDays> methodReference, WeekDays expected) {
        WeekDays actual = methodReference.apply(propertyHolder);
        assertThat(actual).isEqualTo(expected);
    }
}

It’s not necessarily a good thing to do, especially if it’s hidden from developers. Incorrect assumptions can create subtle problems that are hard to identify.

2.3. Extremely Lenient Conversion

At the same time, this type of conversion works for both sides and won’t fail even if we break all the naming conventions and use something like this:

public enum NonConventionalWeekDays {
    Mon$Day, Tues$DAY_, Wednes$day, THURS$day_, Fri$Day$_$, Satur$DAY_, Sun$Day
}

The issue with this case is that it might yield the correct result and map all the values to their dedicated enums:

@SpringBootTest(properties = {
    "monday=Mon-Day!",
    "tuesday=TuesDAY#",
    "wednesday=Wednes@day",
    "thursday=THURSday^",
    "friday=Fri:Day_%",
    "saturday=Satur_DAY*",
    "sunday=Sun+Day",
}, classes = NonConventionalWeekDaysHolder.class)
class NonConventionalStringToEnumLenientConverterUnitTest {
    @Autowired
    private NonConventionalWeekDaysHolder holder;
    @ParameterizedTest
    @ArgumentsSource(NonConventionalWeekDayHolderArgumentsProvider.class)
    void givenPropertiesWhenInjectEnumThenValueIsPresent(
        Function<NonConventionalWeekDaysHolder, NonConventionalWeekDays> methodReference, NonConventionalWeekDays expected) {
        NonConventionalWeekDays actual = methodReference.apply(holder);
        assertThat(actual).isEqualTo(expected);
    }
}

Mapping “Mon-Day!” to “Mon$Day” without failing might hide issues and suggest developers skip the established conventions. Although it works with case-insensitive mapping, the assumptions are too frivolous.

3. Custom Converters

The best way to address specific rules during mappings is to create our implementation of a Converter. After witnessing what LenientToEnumConverter is capable of, let’s take a few steps back and create something more restrictive.

3.1. StrictNullableWeekDayConverter

Imagine that we decided to map values to the enums only if the properties correctly identify their names. This might cause some initial problems with not respecting the uppercase convention, but overall, this is a bulletproof solution:

public class StrictNullableWeekDayConverter implements Converter<String, WeekDays> {
    @Override
    public WeekDays convert(String source) {
        try {
            return WeekDays.valueOf(source.trim());
        } catch (IllegalArgumentException e) {
            return null;
        }
    }
}

This converter will make minor adjustments to the source string. Here, the only thing we do is trim whitespace around the values. Also, note that returning null isn’t the best design decision, as it would allow the creation of a context in an incorrect state. However, we’re using nulls here to simplify testing:

@SpringBootTest(properties = {
    "monday=monday",
    "tuesday=tuesday",
    "wednesday=wednesday",
    "thursday=thursday",
    "friday=friday",
    "saturday=saturday",
    "sunday=sunday",
}, classes = {WeekDaysHolder.class, WeekDayConverterConfiguration.class})
class StrictStringToEnumConverterNegativeUnitTest {
    public static class WeekDayConverterConfiguration {
        // configuration
    }
    @Autowired
    private WeekDaysHolder holder;
    @ParameterizedTest
    @ArgumentsSource(WeekDayHolderArgumentsProvider.class)
    void givenPropertiesWhenInjectEnumThenValueIsNull(
        Function<WeekDaysHolder, WeekDays> methodReference, WeekDays ignored) {
        WeekDays actual = methodReference.apply(holder);
        assertThat(actual).isNull();
    }
}

At the same time, if we provide the values in uppercase, the correct values would be injected. To use this converter, we need to tell Spring about it:

public static class WeekDayConverterConfiguration {
    @Bean
    public ConversionService conversionService() {
        DefaultConversionService defaultConversionService = new DefaultConversionService();
        defaultConversionService.addConverter(new StrictNullableWeekDayConverter());
        return defaultConversionService;
    }
}

In some Spring Boot versions or configurations, a similar converter may be a default one, which makes more sense than LenientToEnumConverter.

3.2. CaseInsensitiveWeekDayConverter

Let’s find a happy middle ground where we’ll be able to use case-insensitive matching but at the same time won’t allow any other differences:

public class CaseInsensitiveWeekDayConverter implements Converter<String, WeekDays> {
    @Override
    public WeekDays convert(String source) {
        try {
            return WeekDays.valueOf(source.trim());
        } catch (IllegalArgumentException exception) {
            return WeekDays.valueOf(source.trim().toUpperCase());
        }
    }
}

We’re not considering the situation when Enum names aren’t in uppercase or using mixed case. However, this would be a solvable situation and would require only an additional couple of lines and try-catch blocks. We could create a lookup map for the Enum and cache it, but let’s do it.

The tests would look similar and would correctly map the values. For simplicity, let’s check only the properties that would be correctly mapped using this converter:

@SpringBootTest(properties = {
    "monday=monday",
    "tuesday=tuesday",
    "wednesday=wednesday",
    "thursday=THURSDAY",
    "friday=Friday",
    "saturday=saturDAY",
    "sunday=sUndAy",
}, classes = {WeekDaysHolder.class, WeekDayConverterConfiguration.class})
class CaseInsensitiveStringToEnumConverterUnitTest {
    // ...
}

Using custom converters, we can adjust the mapping process based on our needs or conventions we want to follow.

4. SpEL

SpEL is a powerful tool that can do almost anything. In the context of our problem, we’ll try to adjust the values we receive from a property file before we try to map Enum. To achieve this, we can explicitly change the provided values to upper-case:

@Component
public class SpELWeekDaysHolder {
    @Value("#{'${monday}'.toUpperCase()}")
    private WeekDays monday;
    @Value("#{'${tuesday}'.toUpperCase()}")
    private WeekDays tuesday;
    @Value("#{'${wednesday}'.toUpperCase()}")
    private WeekDays wednesday;
    @Value("#{'${thursday}'.toUpperCase()}")
    private WeekDays thursday;
    @Value("#{'${friday}'.toUpperCase()}")
    private WeekDays friday;
    @Value("#{'${saturday}'.toUpperCase()}")
    private WeekDays saturday;
    @Value("#{'${sunday}'.toUpperCase()}")
    private WeekDays sunday;
    // getters and setters
}

To check that the values are mapped correctly, we can use the StrictNullableWeekDayConverter we created before:

@SpringBootTest(properties = {
    "monday=monday",
    "tuesday=tuesday",
    "wednesday=wednesday",
    "thursday=THURSDAY",
    "friday=Friday",
    "saturday=saturDAY",
    "sunday=sUndAy",
}, classes = {SpELWeekDaysHolder.class, WeekDayConverterConfiguration.class})
class SpELCaseInsensitiveStringToEnumConverterUnitTest {
    public static class WeekDayConverterConfiguration {
        @Bean
        public ConversionService conversionService() {
            DefaultConversionService defaultConversionService = new DefaultConversionService();
            defaultConversionService.addConverter(new StrictNullableWeekDayConverter());
            return defaultConversionService;
        }
    }
    @Autowired
    private SpELWeekDaysHolder holder;
    @ParameterizedTest
    @ArgumentsSource(SpELWeekDayHolderArgumentsProvider.class)
    void givenPropertiesWhenInjectEnumThenValueIsNull(
        Function<SpELWeekDaysHolder, WeekDays> methodReference, WeekDays expected) {
        WeekDays actual = methodReference.apply(holder);
        assertThat(actual).isEqualTo(expected);
    }
}

Although the converter understands only upper-case values, by using SpEL, we convert the properties to the correct format. This technique might be helpful for simple translations and mappings, as it’s present directly in the @Value annotation and is relatively straightforward to use. However, avoid putting a lot of complex logic into SpEL.

5. Conclusion

@Value annotation is powerful and flexible, supporting SpEL and property injection. Custom converters might make it even more powerful, allowing us to use it with custom types or implement specific conventions.

As usual, all the code in this tutorial is available over on GitHub.

       

Fixing Hibernate QueryException: Named Parameter Not Bound

$
0
0

1. Overview

In this short tutorial, we’ll shed light on how to solve the Hibernate QueryException: “named parameter not bound”.

First, we’ll elucidate the main cause behind the exception. Then, we’ll demonstrate how to reproduce it, and finally, we’ll learn how to fix it.

2. Understanding the Exception

Before jumping to the solution, let’s try to understand what the exception and its stack trace mean.

In short, Hibernate throws QueryException to signal an error when translating a Hibernate query to SQL due to an invalid syntax. Therefore, the stack trace “named parameter not bound” indicates that Hibernate fails to bind a named parameter specified in a particular query.

Typically, a named parameter is prefixed with a colon (:) and followed by a placeholder for the actual value that needs to be set before executing the query:

SELECT p FROM Person p WHERE p.firstName = :firstName;

One of the most common causes of our exception is forgetting to assign a value for a named parameter in a Hibernate query.

3. Reproducing the Exception

Now that we know what is the leading cause of the exception, let’s go down the rabbit hole and see how to reproduce it using a practical example.

First, let’s consider the Person entity class:

@Entity
public class Person {
    @Id
    private int id;
    private String firstName;
    private String lastName;
   // standard getters and setters
}

Here, the @Entity annotation denotes that our class is an entity that maps a table in the database. Furthermore, @Id indicates that the id property represents the primary key.

Now, let’s create a Hibernate query with a named parameter and pretend to forget to set a value for our parameter:

@Test
void whenNotSettingValueToNamedParameter_thenThrowQueryException() {
    Exception exception = assertThrows(QueryException.class, () -> {
        Query<Person> query = session.createQuery("FROM Person p WHERE p.firstName = :firstName", Person.class);
        query.list();
    });
    String expectedMessage = "Named parameter not bound";
    String actualMessage = exception.getMessage();
    assertTrue(actualMessage.contains(expectedMessage));
}

As we can see, the test case fails with QueryException: “named parameter not bound”. The reason here is that Hibernate doesn’t know anything about the named parameter firstName and expects this parameter to be set before executing the query.

4. Fixing the Exception

The solution to avoid QueryException in this case is to assign a value to the named parameter before executing the query. To do so, we can use the setParameter() method:

@Test
void whenSettingValueToNamedParameter_thenDoNotThrowQueryException() {
    Query<Person> query = session.createQuery("FROM Person p WHERE p.firstName = :firstName", Person.class);
    query.setParameter("firstName", "Azhrioun");
    assertNotNull(query.list());
}

As shown above, the test case passes with success. The setParameter() call tells Hibernate which value to use for our named parameter when executing the query.

5. Conclusion

In this short article, we explained what causes Hibernate to throw QueryException: “named parameter not bound”. Then, we illustrated using a practical example how to reproduce the exception and how to fix it.

As always, the full source code of the examples is available over on GitHub.

       
Viewing all 4469 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>