Querying your Spring Data JPA Repository - Basic Setup
[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 onapplication.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>
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>
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";
}
}
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;
}
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;
}
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> {
}
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);
More to come... this series isn't over.
…