Spring Data to the rescue!

Creating repositories to use the Java persistence API can be a complicated process. It takes not only a lot of time, but also lots of boilerplate code.
So when I met Spring Data for the first time, it felt like a blessing from heaven.
We read in Spring Data home page project:"Spring Data's mission is to provide a familiar and consistent, Spring-based programming model for data access." 

Spring Data is an umbrella project that contains many subprojects that are specific to a given database (ie. Spring Data JPA, Spring Data MongoDB, Spring Data REST, etc.). It makes it easy to use data access technologies, relational and non-relational databases, map-reduce frameworks and cloud-based data services.

In this example I will use Spring Data JPA and will show you how easy is to map your relational objects to your DB using Spring Data's powerful repository and custom object-mapping abstractions.

We will be creating an application with Spring Boot, H2 as the in-memory database and Maven.
If this is your first time with Spring Boot this post will explain to you how to create your application.

You will need JDK 1.8+ and Maven 3.0+.
This is the POM file you will need to build and deploy the app.



The first thing worth mentioning is the spring-boot-starter-data-jpa  dependency: it provides a quick way to get started and it includes Hibernate 5.0 by default (one of the most popular JPA implementations).
As I mentioned before, we will be using H2 as the in-memory database, meaning that we can only access the data while the application is running and it will disappear at shutdown.

Here is the project structure:

└── src
    └── main
        └── java
            └── com.example
                -+ Application.java
                └── controller
                    -+ MusicianController
                └── entity
                    -+ Instrument
                    -+ Musician
                └── repository
                    -+ InstrumentRepository
                    -+ MusicianRepository
                
Spring recommends to locate your main application class in a root package above all other classes (in our case is  Application ).

These are our domain classes. First the   Musician  class, annotated as a JPA entity.


package com.example.entity;

import lombok.Data;

import javax.persistence.*;


@Data
@Entity
public class Musician {


    protected Musician(){}

    public Musician(String firstName, String lastName){
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String firstName;

    private String lastName;

    @ManyToOne(cascade = CascadeType.ALL)
    private Instrument instrument;

    @Override
    public String toString(){
        return "Id: " + id + ", firstName: " + firstName + ", lastName: " + lastName;
    }


}

The  Musician class has 3 attributes: the  id , the  firstName  and the  lastName . There are also 2 constructors in the class: the first one,  the default one,  is needed by JPA. The second will be used to persist the data in the database.
The  @Data  annotation is from Lombok, a library that can help you save lots of boilerplate code, you can check the details in this post.
Our entity has an  @Entity   annotation, which means it is a JPA entity. Since we don't have a   @Table  annotation, it is assumed this entity will be mapped to a table on your database named  Musician  .
The  @Id  annotation will help JPA recognize it as this object's id. We also tell JPA this id will be generated automatically with the @GeneratedValue  annotation.
We also have the  firstName  and  lastName  attributes: because they don't have any annotation on them, it is assumed they'll be mapped to columns with the same name.
The last property is another entity class: Instrument . With the @ManyToOne annotation we let JPA knows the  many-to-one association between the two entities, meaning many musicians can play the same instrument. The cascade type option added is just for the sake of the example, it will let us propagate the persisting of our main entity to the related entities.

Here is the Instrument  entity with the two attributes I added for the example:   id  and  type .

package com.example.entity;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@Entity
public class Instrument {

    protected Instrument(){}

    public Instrument(String type){
        this.type = type;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String type;
}

The central interface in Spring Data abstraction is  Repository . It takes the domain class as well as the id type of the domain class as type arguments. This interface acts primarily as a marker interface to capture the types to work with and to help you discover interfaces that extend this one. The   CrudRepository  provides sophisticated CRUD functionality for the entity class that is being managed. (Spring Data reference documentation).
It gives us the following methods just by extending it:
  •    save (S entity);   - it saves the given entity.
  •   T findOne (ID primaryKey);   - returns the entity identified by the given id.
  •   Iterable<T> findAll ();   - returns all entities.
  •   void delete (T entity);   - deletes the given entity.
  •   boolean exists (ID primaryKey);   - tells us if the entity with the given id exists.
Spring Data also provides persistence technology-specific abstractions like  JpaRepository  or  MongoRepository. Those interfaces extend the  CrudRepository and expose the capabilities of the underlying persistence technology.

Having this in mind we will use the repository functionality (including the underlying queries on the datastore) by creating an interface that extends the  JpaRepository  .
Here are our two repositories for our domain classes:  MusicianRepository   &  InstrumentRepository  .


package com.example.repository;

import com.example.entity.Musician;
import org.springframework.data.jpa.repository.JpaRepository;


import java.util.List;


public interface MusicianRepository extends JpaRepository<Musician, Long> {

    public Musician findByLastName(String lastName);

    public List findByInstrumentType(String type);

    public List findAllByOrderByLastName();
}

package com.example.repository;

import com.example.entity.Instrument;
import org.springframework.data.jpa.repository.JpaRepository;

public interface InstrumentRepository extends JpaRepository<Instrument, Long> {
}


Now let's try our repositories capabilities. We will use, for the sake of the example, a controller, and we will create methods to save the information, to query the data and delete an entity.


@RestController
@RequestMapping("/musicianApp")
public class MusicianController {

    private final static Logger log = LoggerFactory.getLogger(MusicianController.class);

    @Autowired
    MusicianRepository musicianRepository;
    @Autowired
    InstrumentRepository instrumentRepository;

    @RequestMapping("/save")
    public void save(@RequestParam(value="firstName") String firstName,
                     @RequestParam(value="lastName") String lastName,
                     @RequestParam(value = "instrument") String instrument){

     Musician musician = new Musician(firstName, lastName);
     musician.setInstrument(new Instrument(instrument));
        musicianRepository.save(musician);

    }

    @RequestMapping("/findAll")
    public List findAll(){
        List musicians =  new ArrayList();
        musicianRepository.findAll().forEach(musicians::add);
        return musicians;
    }

    @RequestMapping("/findByLastName/{lastName}")
    public Musician findByLastName(@PathVariable("lastName") String lastname){
        return musicianRepository.findByLastName(lastname);
    }

    @RequestMapping("/findOne/{id}")
    public Musician findOne(@PathVariable("id") String id)
    {
        return musicianRepository.findOne(Long.parseLong(id));
    }

    @RequestMapping("/findByInstrumentType/{type}")
    public List findByInstrumentType(@PathVariable("type") String type){
        return musicianRepository.findByInstrumentType(type);
    }

    @RequestMapping("/findAllOrderByLastName")
    public List findAllOrderByLastName(){
        List musicians =  new ArrayList();
        musicianRepository.findAllByOrderByLastName().forEach(musicians::add);
        return musicians;
    }

    @RequestMapping("/delete/{id}")
    public void deleteMusician(@PathVariable("id") Long id){
        musicianRepository.delete(id);
    }
}


In order to use the repositories we need to inject them in the controller, we do this with the
 @Autowired   annotation.
The  save method receives three String parameters - musician last name, first name and the instrument name, to persist the entities in the database, using the  save  method from the repository.
Can you spot how easy is to create these entities ?

We also have the  findAll method, where we call the method of the same name in the repository of the correspondant entity. You don't need to write any SQL or HQL (Hibernate query language).
The guys from Pivotal (the company that brings us Spring) are determined to improve our (developer) lives.

The Spring Data repository infrastructure comes with a query builder mechanism built in and is very useful for building constraining queries over entities of the repository. This mechanism takes off the prefixes find…By, read…Byquery…By , count…By and get…By and starts parsing the rest of it.

For example the findByLastName method will look for the lastName  attribute of the Musician  entity using the parameter to search in the database.
You can also concatenate entity properties: in our findByInstrumentType method the repository mechanism will look for the  instrument  property of the  Musician  entity, and since this attribute maps to another entity, it will look for the attribute  type  of that  Instrument , again, using the parameter as an input.
At a very basic level you can define conditions on entity properties and concatenate them with And and Or.

Enough chit-chat ! Let's try our work of art.

You can now start your Spring Boot application, running this maven command on the command line, in the directory where your pom file resides:
$ mvn spring-boot:run

Then we can call the first endpoint declared in our controller to persist our entities:
http://localhost:8080/musicianApp/save?firstName=David&lastName=Lebon&instrument=Guitar
http://localhost:8080/musicianApp/save?firstName=Charly&lastName=Garcia&instrument=Piano
http://localhost:8080/musicianApp/save?firstName=Indio&lastName=Solari&instrument=Voice
http://localhost:8080/musicianApp/save?firstName=Billy-Joe&lastName=Armstrong&instrument=Vocals

Let's see what's in the database:
http://localhost:8080/musicianApp/findAll


[
  • {
    • id1,
    • firstName"David",
    • lastName"Lebon",
    • instrument
      {
      • id1,
      • type"Guitar"
      }
    },
  • {
    • id2,
    • firstName"Charly",
    • lastName"Garcia",
    • instrument
      {
      • id2,
      • type"Piano"
      }
    },
  • {
    • id3,
    • firstName"Indio",
    • lastName"Solari",
    • instrument
      {
      • id3,
      • type"Voice"
      }
    },
  • {
    • id4,
    • firstName"Billy-Joe",
    • lastName"Armstrong",
    • instrument
      {
      • id4,
      • type"Vocals"
      }
    }
]

Now, let's look for one id:
http://localhost:8080/musicianApp/findOne/2

{
  • id2,
  • firstName"Charly",
  • lastName"Garcia",
  • instrument:
    {
    • id2,
    • type"Piano"
    }
}

And if we want to find the musicians that plays the guitar
http://localhost:8080/musicianApp/findByInstrumentType/Guitar
[
  • {
    • id1,
    • firstName"David",
    • lastName"Lebon",
    • instrument
      {
      • id1,
      • type"Guitar"
      }
    }
]

That's it for today! As I always say this is just the tip of the iceberg, there's a whole lot more functionality on the Spring Data reference documentation.

Enjoy!

Comments

Popular Posts