[java, spring, jpa, repository]


You'll have a better experience reading in DEV

Click here to continue reading this post there >>

However, if you want to know more about the project to mirror my posts from DEV here (and why), go ahead and read more.

You can continue to read here too, it's up to you... =]


Well, nothing is simple nowadays. I'm a fan of complexity, so here's not the place you'll find quick answers. Nevertheless, I do seek to organize things in a way that's easy to refer to later, after reading and understand these blog posts.

With that in mind, in this second post of the series Querying your Spring Data JPA Repository I'll create a basic setup where everything that lacks are repository queries. The following sections explain the app architecture.

If you don't want to invest time creating the basic setup, here's the code/app.

Main considerations

I'll use the current version of Spring Boot 2.2.2 with the starters:

  • spring-boot-starter-web: adds Spring MVC with Tomcat as the application server.
  • spring-boot-starter-data-jpa: adds Spring Data JPA, providing persistence of objects.
  • spring-boot-starter-thymeleaf: adds Thymeleaf, the most popular templating engine to build pages dynamically on the server for the client (browser) to download. "Oh, you're not using Angular or React, you fool" you may say. Yeah, deal with with. Thymeleaf is actually a great solution if you don't want a "decoupled" frontend with Angular or other frontend framework.
  • h2: provides H2 in-memory database. Adding this dependency is enough to start coding without any database configured. I'll make everything compatible with MySQL so later one can only add the MySQL driver and configure the connection on application.properties.

So my pom.xml starts with the following basic dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>
Enter fullscreen mode Exit fullscreen mode

And the following complementary dependencies to help in the development process:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
Enter fullscreen mode Exit fullscreen mode

App Layers (packages)

  • Web web.*: controllers for pages or endpoints go here.
  • Business domain.*: models (for now), e.g. Restaurant and Cuisine definitions.
  • Infrastructure infrastructure.*: repositories go here.

The code

I encourage you to build your own project following the scarce instructions I'm providing. The reason is that only through hands on experience you really learn. Of course I provide the complete code if you don't want the trouble.

Web layer

The web layer is composed of one index controller:

@Controller
public class IndexPage {

    private final RestaurantRepository restaurantRepository;

    public IndexPage(RestaurantRepository restaurantRepository) {
        this.restaurantRepository = restaurantRepository;
    }

    @RequestMapping
    public String index(Model model) {
        model.addAttribute("restaurants", restaurantRepository.findAll());
        return "index";
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice the restaurantRepository.findAll() that I'll explain in a bit.

Business layer

In the business layer I included the domain, i.e., the objects that'll be persisted.

Restaurant:

@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = "name"))
public class Restaurant {

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

    private String name;

    private BigDecimal deliveryFee;

    @ManyToOne
    @JoinColumn(name = "cuisine_id", nullable = false)
    private Cuisine cuisine;

    private String address;

    @CreationTimestamp
    @Column(columnDefinition = "datetime", updatable = false)
    private LocalDateTime createdDate;

    @UpdateTimestamp
    @Column(columnDefinition = "datetime")
    private LocalDateTime updatedDate;

}
Enter fullscreen mode Exit fullscreen mode

And Cuisine:

@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = "name"))
public class Cuisine {

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

    @OneToMany(mappedBy = "cuisine")
    List<Restaurant> restaurants;

    private String name;

}
Enter fullscreen mode Exit fullscreen mode

Notice they have a relationship between then. That's it for the Business layer. Notice also that no Service is provided: for brevity I'll query the Repositories from the Controllers.

Infrastructure layer

An interface that extends an interface from Spring Data JPA project. This provides the findAll() method that you saw earlier.

public interface RestaurantRepository extends JpaRepository<Restaurant, Long> {
}

Enter fullscreen mode Exit fullscreen mode

Initial Data

To provide initial data, there's a file import.sql into resources folder. This is JPA stuff and runs when starting the application.

insert into cuisine (name) values ('Italian');
insert into cuisine (name) values ('Brazilian');
insert into cuisine (name) values ('American');
insert into cuisine (name) values ('French');

insert into restaurant (name, address, delivery_fee, cuisine_id, created_date, updated_date) values ('Mamma Mia', 'Mamma Street, 14', 10.0, 1, current_timestamp, current_timestamp);
insert into restaurant (name, address, delivery_fee, cuisine_id, created_date, updated_date) values ('Churrascaria', 'Disorder Avenue, 5000', 8.0, 2, current_timestamp, current_timestamp);
insert into restaurant (name, address, delivery_fee, cuisine_id, created_date, updated_date) values ('Burguer Place', 'Clueless Alley, 10', 5.0, 3, current_timestamp, current_timestamp);
insert into restaurant (name, address, delivery_fee, cuisine_id, created_date, updated_date) values ('La Maison du Croissant ', 'Rue Paris, 7', 15.0, 4, current_timestamp, current_timestamp);

insert into restaurant (name, address, delivery_fee, cuisine_id, created_date, updated_date) values ('Pasta Buona', 'Pasta Street, 4', 2.0, 1, current_timestamp, current_timestamp);
insert into restaurant (name, address, delivery_fee, cuisine_id, created_date, updated_date) values ('Marcante Pizzaria', 'Bricks Street, 21', 9.0, 2, current_timestamp, current_timestamp);
Enter fullscreen mode Exit fullscreen mode

More to come... this series isn't over.