DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Visually Designing Views for Java Web Apps
  • Migrating Spring Java Applications to Azure App Service (Part 1: DataSources and Credentials)
  • Getting Started With JPA/Hibernate
  • Aggregating REST APIs Calls Using Apache Camel

Trending

  • Build Your First AI Model in Python: A Beginner's Guide (1 of 3)
  • Grafana Loki Fundamentals and Architecture
  • How to Format Articles for DZone
  • Understanding Java Signals
  1. DZone
  2. Coding
  3. Java
  4. Realistic Test Data Generation for Java Apps

Realistic Test Data Generation for Java Apps

Might as well make it real.

By 
Alejandro Duarte user avatar
Alejandro Duarte
DZone Core CORE ·
Updated Oct. 10, 21 · Tutorial
Likes (6)
Comment
Save
Tweet
Share
21.2K Views

Join the DZone community and get the full member experience.

Join For Free

The closer your test or demo data is to the real world the better you can test the application for UX, improve, and catch corner cases during development. In this article, I'll show you how to use Vaadin's example data generator to create demo data for a simple SQL database. The article shows how to build a complete app combining Spring Boot, JPA, Project Lombok, Vaadin, and MariaDB.

You can also watch a video version of this article:

Setting Up a Project

In this article, I use a Vaadin Flow project generated using an online tool called Vaadin Start. However, you can use Vaadin's example data generator in any Java project with or without Vaadin.

Adding the Required Dependencies

To use the Vaadin example data generator, add the following dependency to the pom.xml file:

XML
 
<dependency>
    <groupId>com.vaadin</groupId>
    <artifactId>exampledata</artifactId>
    <version>4.0.0</version>
</dependency>

If you are following this article from scratch, add the Spring Data, MariaDB JDBC driver, and Project Lombok dependencies as well:

XML
 
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.mariadb.jdbc</groupId>
    <artifactId>mariadb-java-client</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

Implementing a Simple Backend

Let's say we want to implement a view to show a list of books. We need to configure a database connection, create a Java class that represents a book (as a JPA Entity), create a class (or interface) to access the database using JPA, and implement a service class to encapsulate database technology details.

Configuring the Database Connection

With Spring Boot, you can configure the database connection in the application.properties file by adding the following:

Properties files
 
spring.datasource.url=jdbc:mariadb://localhost:3306/book_demo
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=create

The last line is needed only if you want to drop and recreate the database schema every time the application is started. There are other options that could be more convenient during development depending on the stage you are in your project.

Remember to set the database connection parameters to point to your database instance.

Implementing a JPA Entity

Implementing a JPA Entity gets easier with Project Lombok. Here's a possible implementation:

Java
 
package com.example.application.backend;

import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.persistence.*;
import java.time.LocalDate;

@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
public class Book {

    @EqualsAndHashCode.Include
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Lob
    private String imageData;

    private String title;

    private String author;

    private LocalDate publishDate;

    private Integer pages;

}

This class is persistence-ready which means that JPA will be able to map instances of this class to a MariaDB database table (or any other database that provides a JDBC driver). Important here is to notice that we want the id column to be automatically generated for us if we pass a null value. Lombok's @Data annotation adds getters and setters, and @EqualsAndHashCode... I'm sure you can guess what it does. What's important is that we are indicating Lombok to use only the id property for the equals(Object) and hashCode() methods. So, two books are the same if they have the same id values regardless of whether the other properties have different values or not. Having these two methods implemented is needed for the correct functioning of JPA.

Implementing a Repository cClass

We need a way to access the database. We could use the JDBC API to connect to the MariaDB database, run SQL queries, and manually set the returned values in instances of Book. However, JPA mission is to provide this functionality and it is augmented by Spring Data. Accessing the database is so easy, that we only need to declare an interface:

Java
 
package com.example.application.backend;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface BookRepository extends JpaRepository<Book, Integer> {
}

There's no need to implement this interface at all. Spring Data will provide objects that implement the interface when needed. If you inspect the methods available in the interface, you'll find many useful ones to create, read, update, delete Book instances. You can also add methods to the interface without implementing them and Spring Data will use a naming convention to create the implementation for you.

Implementing a Service Class

The BookRepository interface doesn't hide the fact that we are using JPA as a persistence mechanism. To improve code maintainability, we can introduce a new class that uses the repository and provides the methods that the UI needs. This class could also add any additional business logic required by the application:

Java
 
package com.example.application.backend;

import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BookService {

    private final BookRepository repository;

    public BookService(BookRepository repository) {
        this.repository = repository;
    }

    public List<Book> findAll() {
        return repository.findAll();
    }

}

The constructor of this service class accepts an object of type BookRepository. Since the class is also marked with @Service, Spring will create a new instance of the repository and pass it to the constructor when you, in turn, have a constructor in a different class (for example, another service, or the UI when implemented in Java) that accepts a BookService object. Spring creates all the instances for you using a pattern called Inversion of Control so you never use the new Java keyword to create these instances and give Spring the chance to pass objects via the constructors using a pattern called Dependency Injection. There are many online resources to learn more about this.

Using Vaadin's Example Data Generator

A good point to generate demo data is at application startup. To run a Java method when the application starts, we can create a Spring bean of type CommandLineRunner in any configuration class, for example, we can add the following method to the Application class:

Java
 
@Bean
public CommandLineRunner createDemoDataIfNeeded(BookRepository repository) {
    return args -> {
        ... logic here ...
    };
}

Spring will inject the required BookRepository object before executing the method.

Configuring the Generator

Vaadin's example data generator is accessed through the ExampleDataGenerator class. Here's the code we can add inside the lambda expression of the previous code snippet:

Java
 
if (repository.count() == 0) {
    var generator = new ExampleDataGenerator<>(Book.class, LocalDateTime.now());
    generator.setData(Book::setImageData, DataType.BOOK_IMAGE_URL);
    generator.setData(Book::setTitle, DataType.BOOK_TITLE);
    generator.setData(Book::setAuthor, DataType.FULL_NAME);
    generator.setData(Book::setPublishDate, DataType.DATE_LAST_10_YEARS);
    generator.setData(Book::setPages, new ChanceIntegerType("integer", "{min: 20, max: 1000}"));

    List<Book> books = generator.create(100, new Random().nextInt());
}

This checks that there are no books in the database since we don't want to mess up with data if it already exists.

The generator is configured by using the setData(BiConsumer, DataType) method which accepts a method reference to a setter in the Book class and a specific data type. There are many data types available. Make sure to inspect the values in the DataType class to get an idea. You'll find for example data types for creating book titles, people's names, dates, times, cities, countries, phone numbers, addresses, food names, words, sentences, numbers, booleans, and others.

Creating and Saving the Example Data

Call the create(int, int) method to create Book instances:

Java
 
List<Book> books = generator.create(100, new Random().nextInt());

The first parameter is the number of instances to create (100 books in the previous example), and the second is the seed used by the internal random generator. The method returns a list of objects that we can persist using the repository instance:

Java
 
repository.saveAll(books);

Measuring Data Generation and Saving Time

It's useful to show a message in the log when the application is generating data, a process that can take time depending on the kind and amount of data to create. It's also useful to show a message when the data generation process is completed, possibly, showing the time it took. Here's a complete implementation of the createDemoDataIfNeeded(BookRepository) method that does exactly that:

Java
 
@SpringBootApplication
@Theme(value = "demo")
@PWA(name = "Demo", shortName = "Demo", offlineResources = {"images/logo.png"})
@NpmPackage(value = "line-awesome", version = "1.3.0")
@Log4j2
public class Application extends SpringBootServletInitializer implements AppShellConfigurator {

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

    @Bean
    public CommandLineRunner createDemoDataIfNeeded(BookRepository repository) {
        return args -> {
            if (repository.count() == 0) {
                log.info("Generating demo data...");
                var generator = new ExampleDataGenerator<>(Book.class, LocalDateTime.now());
                generator.setData(Book::setImageData, DataType.BOOK_IMAGE_URL);
                generator.setData(Book::setTitle, DataType.BOOK_TITLE);
                generator.setData(Book::setAuthor, DataType.FULL_NAME);
                generator.setData(Book::setPublishDate, DataType.DATE_LAST_10_YEARS);
                generator.setData(Book::setPages, new ChanceIntegerType("integer", "{min: 20, max: 1000}"));

                var stopWatch = new StopWatch();
                stopWatch.start();
                List<Book> books = generator.create(100, new Random().nextInt());
                repository.saveAll(books);
                stopWatch.stop();
                log.info("Demo data generated in " + stopWatch.getTime() + "ms.");
            }
        };
    }

}

This uses Apache Commons (StopWatch) for timing and Lombok (@Log4j2) for logging.

Implementing a Web View in Java

You can check that the data is indeed in the database by connecting to the MariaDB instance and running the following query:

MariaDB SQL
 
select * from book;

However, to make it more interesting we can add a web view using Vaadin and explore the data in the browser:

Java
 
package com.example.application.ui;

import com.example.application.backend.Book;
import com.example.application.backend.BookService;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;

@Route("")
public class BooksView extends VerticalLayout {

    public BooksView(BookService service) {
        var grid = new Grid<Book>();
        grid.setSizeFull();
        grid.addComponentColumn(this::getThumbnail);
        grid.addColumn(Book::getTitle).setHeader("Title");
        grid.addColumn(Book::getAuthor).setHeader("Author");
        grid.addColumn(Book::getPublishDate).setHeader("Publish date");
        grid.addColumn(Book::getPages).setHeader("Pages");

        grid.setItems(service.findAll());

        add(grid);
        setSizeFull();
    }

    private Image getThumbnail(Book book) {
        var image = new Image(book.getImageData(), book.getTitle() + " cover");
        image.setHeight("70px");
        image.addClickListener(event -> showCover(book));
        return image;
    }

    private void showCover(Book book) {
        var image = new Image(book.getImageData(), "Cover");
        image.setSizeFull();

        var dialog = new Dialog(image);
        dialog.setHeight("90%");
        dialog.open();
    }
}

This class uses Vaadin's API to add a view mapped to the context root via @Route(""). The constructor creates a Grid UI component and configures the columns connecting each one to a property in the Book class using the corresponding getters. There's a special column that shows a thumbnail image that, when clicked, opens a dialog to show the book's cover as a larger image.

To start the application, run:

Plain Text
 
mvn spring-boot:run

Alternatively, you can simply run the standard entry point main(String[]) method in the Application class. Once the application is compiled and started (a process that could take longer if it's the first time you are building it), you can access it in the browser at http://localhost:8080. Here's a screenshot:


Test data Database connection Java (programming language) Spring Framework app application Vaadin

Opinions expressed by DZone contributors are their own.

Related

  • Visually Designing Views for Java Web Apps
  • Migrating Spring Java Applications to Azure App Service (Part 1: DataSources and Credentials)
  • Getting Started With JPA/Hibernate
  • Aggregating REST APIs Calls Using Apache Camel

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!