Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Building A Data Repository With Spring Data

DZone's Guide to

Building A Data Repository With Spring Data

Creating a data repository is quick and simple with Spring Data. With under 100 lines of code, you can have a data repository with CRUD operations.

· Database Zone
Free Resource

Whether you work in SQL Server Management Studio or Visual Studio, Redgate tools integrate with your existing infrastructure, enabling you to align DevOps for your applications with DevOps for your SQL Server databases. Discover true Database DevOps, brought to you in partnership with Redgate.

In 2004, I started to use Spring and Hibernate — open source frameworks — for web application development. With the help of those open source frameworks, I was able to build web applications single-handedly from scratch. Those open source frameworks revolutionized Java application development. Today, the Spring frameworks are still inventing. Spring Data, one of Spring's projects, makes it easy to use data access technologies, relational and non-relational databases, map-reduce frameworks, and cloud-based data services. Under the Spring Data umbrella project, Spring Data JPA is built on top of Hibernate for relational databases. In this article, we are going to build a data repository with Spring Data Rest, an extension of Spring Data.

We need a text editor or IDE, JDK 1.8 or later, Gradle 2.3+ or Maven 3.0+ to run the code in this article.

1. Project Setup

To set up a new project, we go to https://start.spring.io and select either Maven or Gradle project and use the default Spring Boot version, which is the latest release version. We will use Gradle for this sample project in this article. We can name the group what we like and the artifact as spring-data-sample. After switching to the full version, we choose a name for the project like Spring Data Sample. We can leave the default data for the rest of fields in the top section, and we select the following libraries:

  • Lombok in the Core section for omitting getter and setter as well as other Java bean methods.
  • Rest Repository for building a data repository and a client to access the repository.
  • JPA and H2 in the SQL section for a JPA and a relational database.

We will use an in-memory relational database, H2, to simplify the database setup and configuration. Finally, we click Generate Project. A project ZIP file will be in our computer download directory. Extract the ZIP file to the directory we want the project to reside. We can build and run the project at this point by running the following command in the project directory:

.\gradlew build && java -jar build\libs\spring-data-sample-0.0.1-SNAPSHOT.jar


Or simply run:

.\gradlew bootRun


We shall see a message, “BUILD SUCCESSFUL” and something similar to the following log message

2017-01-23 15:47:13.960  INFO 10796 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 26 ms.


The above lines indicate the project has been built successfully and the server is up.

If we want to run this application in an IDE, we need to install a Lombok plugin and possibly other related bits for configuration.

To access the server, we can use a web browser point to http://localhost:8080/, and we should get the following:

{
  "_links" : {
    "profile" : {
      "href" : "http://localhost:8080/profile"
    }
  }
}


2. Develop a Data Repository

With the newly set up project, we can start building an application that uses Spring Data JPA to store and retrieve data in a relational database.

2.1 Develop a Basic Repository

We add the following code to the SpringDataSampleApplication.java file under src/main/java/com/example.

@Entity
@Data
@NoArgsConstructor
class Book {
   @Id
   @GeneratedValue
   private Long id;

   private String title;

   private String description;

   Book(String title, String description) {
      this.title = title;
      this.description = description;
   }
}


Here we have a Book class that is annotated with @Entity, indicating that it is a JPA entity. For lack of a @Table annotation, it is assumed that this entity will be mapped to a table named Book. The Lombok annotation @Data generates all the boilerplate that is normally associated with simple POJOs (Plain Old Java Objects) and beans: getters for all fields, setters for all non-final fields, and appropriate toString, equals and hashCode implementations that involve the fields of the class, and a constructor that initializes all final fields, as well as all non-final fields with no initializer that have been marked with @NonNull, in order to ensure the field is never null. The Lombok annotation @NoArgsConstructor generates a constructor with no parameters. The default constructor only exists for the sake of JPA. We won’t use it directly.

The Book class has three attributes: id, title, and description. The Book’s id property is annotated with @Id so that JPA will recognize it as the object’s ID. The id property is also annotated with @GeneratedValue to indicate that the ID should be generated automatically.

The other two properties, title and description, are left unannotated. It is assumed that they’ll be mapped to columns that share the same name as the properties themselves.

Now, let’s create a repository interface that works with Book entities. Spring Data JPA focuses on using JPA to store data in a relational database. Its most compelling feature is the ability to create repository implementations automatically, at runtime, from a repository interface.

@RepositoryRestResource
interface  BookRepository extends CrudRepository {

}


BookRepository extends the CrudRepository interface. The types of entity and ID that it works with, Book and Long, are specified in the generic parameters on CrudRepository. By extending CrudRepository, BookRepository inherits several methods for working with Book persistence, including methods for saving, deleting, and finding Book entities.

At runtime, Spring Data REST will create an implementation of this interface automatically. Then it will use the @RepositoryRestResource annotation to direct Spring MVC to create RESTful endpoints at /books. In a typical Java application, we would expect to write a class that implements BookRepository. But we don’t have to write an implementation of the repository interface. Spring Data JPA creates an implementation on the fly when you run the application. @RepositoryRestResource is not required for a repository to be exported.

Spring Data REST builds on top of Spring MVC. It creates a collection of Spring MVC controllers, JSON converters, and other beans needed to provide a RESTful front end. These components link up to the Spring Data JPA backend.

With the repository, we can add some book data into the database after the server is up. This can be achieved by adding the following method into the SpringDataSampleApplication class.

@Bean
CommandLineRunner initData(BookRepository bookRepository){
   return args -> {
      bookRepository.save(new Book("Spring Microservices", "Learn how to efficiently build and implement microservices in Spring,\n" +
            "and how to use Docker and Mesos to push the boundaries. Examine a number of real-world use cases and hands-on code examples.\n" +
            "Distribute your microservices in a completely new way"));
      bookRepository.save(new Book("Pro Spring Boot", "A no-nonsense guide containing case studies and best practise for Spring Boot"));
   };
}


At this point, let’s see what RESTful endpoints we have. It is better to have a RESTful API tool such as Postman so that we can submit various RESTful requests easily.

When we send a Get request to http://localhost:8080/ again, we see the following:

{
  "_links": {
    "books": {
      "href": "http://localhost:8080/books"
    },
    "profile": {
      "href": "http://localhost:8080/profile"
    }
  }
}


When we submit a Get request for http://localhost:8080/books, we see the data of those two books.

If we want to create or update a book entity through the RESTful endpoint, we need to set the content-Type entity header as application/json.

Spring Data JPA also allows us to define other query methods by simply declaring their method signature. In the case of BookRepository, we can have a findByTitle() method and a findByTitleContains method, which finds all books with a given title and finds all books which their titles contain certain keywords respectively.

@RepositoryRestResource
interface  BookRepository extends CrudRepository {
  List findByTitle(@Param("title") String title);
List findByTitleContains(@Param("keyword") String keyword);
}


After restarting the server, we see a new entry on http://localhost:8080/books.

"search": {
      "href": "http://localhost:8080/books/search"
}


And we see the followings for http://localhost:8080/books/search:

    "findTitleByTitleContains": {
      "href": "http://localhost:8080/books/search/findByTitleContains{?keyword}",
      "templated": true
    },
    "findByTitle": {
      "href": "http://localhost:8080/books/search/findByTitle{?title}",
      "templated": true
    }


After submitting a Get request to http://localhost:8080/books/search/findByTitleContains?keyword=Spring, we shall have data from both books.

2.2 Data Validation

To create new book data through the REST endpoints, we can send a Post request to "http://localhost:8080/books" with book data in JSON format in the request body. There are some problems with our code, however. We can create a Book entity with a blank title and/or a blank description. Also, a length of the title or a length of the description that exceeds their corresponding data field length constraints can pass the REST layer and cause a database error. We can prevent those problems with validation annotations.

@Data
@Entity
@NoArgsConstructor
class Book {
   @Id
   @GeneratedValue
   private Long id;

   @NotBlank
   @Size(max=255)   
   private String title;

   @NotBlank
   @Size(max=255)
   private String description;

   Book(String title, String description) {
      this.title = title;
      this.description = description;
   }
}


After adding those validation annotations, we will get a 500 Internal Server Error if we submit a Post request with either a blank field and/or a field with over 255 characters.

2.3 Non-String Data Type Attribute — Published Date

Let’s add more attributes with other data types to the Book entity class. First, we add a published date attribute the Book class as follows:

@NotNull private LocalDate publishedDate;

@NotNull indicates the value of the publishedDate field must not be null. We need to update the class constructor to have a published date as a parameter, accordingly. The publishedDate field needs to be in either the Spring format of yyyy-MM-dd or [yyyy, MM, dd] to send a POST or PUT request to create or update a data.

To use the Java 8 new data type LocalData for RES JSON data, we need to add a dependency in our project build file. For a Gradle build file, we need to add the following line to the dependency block.

 compile('com.fasterxml.jackson.datatype:jackson-datatype-jsr310') 


Now, we can add search queries based on this newly added attribute in the repository.

  List findByPublishedDateAfter(@Param("publishedDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate publishedDate);
  List findByTitleContainsAndPublishedDateAfter(@Param("keyword") String keyword, 
@Param("publishedDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate publishedDate);

@DateTimeFormat(iso = DateTimeFormat.ISO.DATE states that it declares that a field should be formatted as a date time.

To retrieve all book data which the titles contain a key work, “Spring”, and were published after June 1, 2016, we can submit the following Restful request:

  http://localhost:8080/books/search/findByTitleContainsAndPublishedDateAfter?keyword=Spring&publishedDate=2016-06-01

2.4 Embedded Data — Price

Also, we need to add a price attribute to the Book class. For the price, there are two attributes: currency and amount. The Currency is an enumeration data type that enables for a variable to be a set of predefined constants — CAD, EUR, and USD in this case. The price data doesn’t have a persistent identity of its own, unlike entity class. Instances of an embeddable class share the identity of the entity that owns it. The Money class exists only as the state of Book entity.

@Embeddable
@Data
@NoArgsConstructor
class Money {
   enum Currency {CAD, EUR, USD }

   @DecimalMin(value="0",inclusive=false)
   @Digits(integer=1000000000,fraction=2)
   private BigDecimal amount;

   private Currency currency;

   Money(BigDecimal amount){
      this(Currency.USD, this.amount);
   }

   Money(Currency currency, BigDecimal amount){
      this.currency = currency;
      this.amount = amount;
   }
}


The Price class is annotated with the javax.persistence.Embeddable annotation instead of @Entity. This embeddable class is used by the Book entity. The following lines are added to the Book class:

@Embedded
@NotNull
private Money price;


And we need to update the Book class constructor to have a price data as a parameter accordingly. Now, we can add more search queries with the price attributes:

  List findByTitleContainsAndPriceCurrencyAndPriceAmountBetweem(@Param("keyword") String keyword, 
                                                                @Param("currency") Money.Currency currency, @Param("low") BigDecimal low, @Param("high") BigDecimal high);


To have books in which the titles contain a keyword, “Spring”, and the prices are between $40 and $80 US dollars, we can send a Get request to the following URL.

http://localhost:8080/books/search/findByTitleContainsAndPriceCurrencyAndPriceAmountBetween?keyword=Spring¤cy=USD&low=45&high=80

3. Summary

We have written a simple application that uses Spring Data JPA to save Book objects to a database and to fetch them — all without writing a concrete repository implementation. All of those functionalities are built with barely over a hundred lines of code. A complete version of this sample application can be found on GitHub. We will explore a more complicated scenario in the next installment.

It’s easier than you think to extend DevOps practices to SQL Server with Redgate tools. Discover how to introduce true Database DevOps, brought to you in partnership with Redgate

Topics:
spring data jpa ,spring data rest ,database ,data repository ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}