database

djmil 2023-07-20 12:33:27 +02:00
parent 997c67f20a
commit d35729d123
6 changed files with 459 additions and 223 deletions

@ -25,26 +25,26 @@
"state": {
"type": "markdown",
"state": {
"file": "IntegrationTests.md",
"file": "Database.md",
"mode": "source",
"source": false
}
}
},
{
"id": "ec5879550528c04c",
"id": "af9b076d6f1ac377",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "GET.md",
"file": "Database.md",
"mode": "source",
"source": false
}
}
}
],
"currentTab": 2
"currentTab": 1
}
],
"direction": "vertical"
@ -110,7 +110,7 @@
"state": {
"type": "backlink",
"state": {
"file": "GET.md",
"file": "Database.md",
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
@ -127,7 +127,7 @@
"state": {
"type": "outgoing-link",
"state": {
"file": "GET.md",
"file": "Database.md",
"linksCollapsed": false,
"unlinkedCollapsed": true
}
@ -150,7 +150,7 @@
"state": {
"type": "outline",
"state": {
"file": "GET.md"
"file": "Database.md"
}
}
}
@ -171,12 +171,14 @@
"command-palette:Open command palette": false
}
},
"active": "ec5879550528c04c",
"active": "7e5ec70badaa86f3",
"lastOpenFiles": [
"IntegrationTests.md",
"Home.md",
"GET.md",
"UnitTests.md",
"Database.md",
"Home.md",
"assets/Pasted image 20230719152007.png",
"GET.md",
"Pasted image 20230719102301.png",
"assets/Pasted image 20230719102322.png",
"assets",

164
Database.md Normal file

@ -0,0 +1,164 @@
# Spring Data and a database dependencies
This project was originally created using the [Spring Initializr](https://start.spring.io/), which allowed us to automatically add dependencies to our project. However, now we must manually add dependencies to our project.
In `build.gradle` file
```groovy
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// Add the two dependencies below
implementation 'org.springframework.data:spring-data-jdbc'
testImplementation 'com.h2database:h2'
}
```
The two dependencies we added are related, but different.
## Spring Data JDBC
```groovy
implementation 'org.springframework.data:spring-data-jdbc'
```
Spring Data - has many implementations for a variety of relational and non-relational database technologies. Spring Data also has several abstractions on top of those technologies. These are commonly called an Object-Relational Mapping framework, or ORM.
Here we'll elect to use [Spring Data JDBC](https://spring.io/projects/spring-data-jdbc).
> Spring Data JDBC aims at being conceptually easy...This makes Spring Data JDBC a simple, limited, opinionated ORM.
## The actual database
```groovy
testImplementation 'com.h2database:h2'
```
Database management frameworks only work if they have a linked database. H2 is a "very fast, open source, JDBC API" SQL database implemented in Java. It works seamlessly with Spring Data JDBC.
- Note `testImplementation`
This tells Spring Boot to make the H2 database available only when running tests. Eventually we'll need a database outside of a testing context, but not yet.
# Create the `CashCardRepository`
Create `src/main/java/djmil/cashcard/CashCardRepository.java` and have it `extend CrudRepository`.
```java
package djmil.cashcard;
import org.springframework.data.repository.CrudRepository;
public interface CashCardRepository extends CrudRepository<CashCard, Long> {
}
```
We have to indicated which data object the `CashCardRepository` should manage. For our application, the "domain type" of this repository will be the `CashCard`. When we configure the repository as `CrudRepository<CashCard, Long>` we indicate that the `CashCard`'s ID is `Long`. However, we still need to tell Spring Data which field is the ID.
Edit the `CashCard` class to configure the `id` as the `@Id` for the `CashCardRepository`.
Don't forget to add the new import.
```java
package djmil.cashcard;
// Add this import
import org.springframework.data.annotation.Id;
public record CashCard(@Id Long id, Double amount) {
}
```
# Inject the `CashCardRepository` into `CashCardController`
Although we've configured our `CashCard` and `CashCardRepository` classes, we haven't utilized the new `CashCardRepository` to manage our `CashCard` data. Let's do that now.
```java
@RestController
@RequestMapping("/cashcards")
public class CashCardController {
private CashCardRepository cashCardRepository;
public CashCardController(CashCardRepository cashCardRepository) {
this.cashCardRepository = cashCardRepository;
}
...
```
The tests will now pass, despite no other changes to the codebase utilizing the new, _required_ constructor `CashCardController(CashCardRepository cashCardRepository)`!
Spring's Auto Configuration is utilizing its dependency injection (DI) framework, specifically _constructor injection_, to supply `CashCardController` with the correct implementation of `CashCardRepository` at runtime.
## Learn to read compiler error
Temporarily change the `CashCardRepository` to remove the implementation of `CrudRepository`
```java
public interface CashCardRepository {
}
```
Compile the project and note the failure.
```shell
[~/exercises] $ ./gradlew build
## shouldNotReturnACashCardWithAnUnknownId()
java.lang.IllegalStateException: Failed to load ApplicationContext for
...
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'cashCardController' defined in file [/Users/oxbee/code/hqlxa/FamilyCashCard/build/classes/java/main/djmil/cashcard/CashCardController.class]: Unsatisfied dependency expressed through constructor parameter 0: No qualifying bean of type 'djmil.cashcard.CashCardRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
...
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'djmil.cashcard.CashCardRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
```
Clues such as `NoSuchBeanDefinitionException`, `No qualifying bean`, and `expected at least 1 bean which qualifies as autowire candidate` tell us that Spring is trying to find a properly configured class to provide during the dependency injection phase of Auto Configuration, but none qualify. We can satisfy this DI requirement by implementing the `CrudRepository`.
# Use the `CashCardRepository` for data management
The `CrudRepository` interface provides [many helpful methods](https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html#findById-ID-), including `findById(ID id)`. Update the `CashCardController` to utilize this method on the `CashCardRepository` and update the logic; be sure to `import java.util.Optional;`
```java
import java.util.Optional;
...
@GetMapping("/{requestedId}")
public ResponseEntity<CashCard> findById(@PathVariable Long requestedId) {
Optional<CashCard> cashCardOptional = cashCardRepository.findById(requestedId);
if (cashCardOptional.isPresent()) {
return ResponseEntity.ok(cashCardOptional.get());
} else {
return ResponseEntity.notFound().build();
}
}
```
We're calling `CrudRepository.findById` which returns an `Optional`. This smart object _might or might not_ contain the `CashCard` for which we're searching. Learn more about `Optional` [here](https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html).
# Configure an in-memory Database
Spring Data and H2 can automatically create and populate the in-memory database we need for our test. The tests would expect the API to find and return a `CashCard` with `id` of `99`. If you'd to run test not - you'd get a `500 INTERNAL_SERVER_ERROR`, meaning that our application has no database to connect to. So let's create one!
## schema.sql - create database/tables
Spring Data will automatically configure a database for tests if we provide `src/test/resources/schema.sql`
```sql
CREATE TABLE cash_card
(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
AMOUNT NUMBER NOT NULL DEFAULT 0
);
```
A database schema is a "blueprint" for how data is stored in a database. Our database schema reflects the `CashCard` object that we understand, which contains an `id` and an `amount`.
Running tests at this moment will produce `404 NOT_FOUND`, meaning that we have to populate freshly created DB with some test data.
## data.sql - load data into DB
`src/test/resources/data.sql`
```sql
INSERT INTO CASH_CARD(ID, AMOUNT) VALUES (99, 123.45);
```

65
Home.md

@ -17,28 +17,29 @@ Essentially, this will generate a default minimalistic jet functional SpringBoot
# TDD
Software development teams love to move fast. So how do you go fast forever? By continuously improving and simplifying your code this is called **refactoring**. Refactoring is the act of altering the implementation of a software system without altering its inputs, outputs, or behavior. One of the only ways you can safely refactor is when you have a trustworthy test suite. Which brings us to the **TDD Cycle**:
1. **Red:** Write a failing test for the desired functionality.
2. **Green:** Implement the simplest thing that can work to make the test pass.
3. **Refactor:** Look for opportunities to simplify, reduce duplication, or otherwise improve the code without changing any behavior - to _refactor._
4. Repeat!
Different tests can be written at different levels of the system. At each level, there is a balance between the speed of execution, the “cost” to maintain the test, and the confidence it brings to system correctness. This hierarchy is often represented as a “testing pyramid”.
![Testing pyramid](https://blog.missiondata.com/wp-content/uploads/MD_TestingPyramid2x-1560x1045.png "Testing pyramid")
## Unit Tests
[[UnitTests]] exercises a small “unit” of the system that is isolated from the rest of the system. They should be simple and speedy. You want a high ratio of Unit Tests in your testing pyramid as theyre key to designing highly cohesive, loosely coupled software.
## Integration Tests
[[IntegrationTests]] exercise a subset of the system and may exercise groups of units in one test. They are more complicated to write and maintain, and run slower than unit tests.
## End-to-End Tests
An End-to-End Test exercises the system using the same interface that a user would, such as a web browser. While extremely thorough, End-to-End Tests can be very slow and fragile because they use simulated user interactions in potentially complicated UIs. Implement the smallest number of these tests.
## TDD Cycle
Software development teams love to move fast. So how do you go fast forever? By continuously improving and simplifying your code this is called **refactoring**. One of the only ways you can safely refactor is when you have a trustworthy test suite. Thus, the best time to refactor the code you're currently focusing on is during the TDD cycle. This is called the Red, Green, Refactor development loop:
1. **Red:** Write a failing test for the desired functionality.
2. **Green:** Implement the simplest thing that can work to make the test pass.
3. **Refactor:** Look for opportunities to simplify, reduce duplication, or otherwise improve the code without changing any behavior—to _refactor._
4. Repeat!
# RESTful API
In a RESTful system, data objects are called Resource Representations. The purpose of a RESTful API is to manage the state of these Resources. The chart below shows details about RESTful CRUD operations of an application.
@ -56,7 +57,7 @@ Another common concept associated with REST is the Hypertext Transfer Protocol.
One of the main things Spring does is to configure and instantiate objects. These objects are called *Spring Beans*, and are usually created by Spring (as opposed to using the Java `new` keyword). You can direct Spring to create Beans in several ways.
> We will annotate a class with a Spring Annotation, which directs Spring to create an instance of the class during Springs *Component Scan* phase. This happens at application startup. The Bean is stored in Springs `IoC Container`. From here, the bean can be injected into any code that requests it.
> We will annotate a class with a `@RestController` Spring Annotation, which directs Spring to create an instance of the class during Springs *Component Scan* phase. This happens at application startup. The Bean is stored in Springs `IoC Container`. From here, the bean can be injected into any code that requests it.
![[Pasted image 20230719102322.png]]
@ -95,3 +96,47 @@ Response:
}
```
# Database
The [**Separation of Concerns**](https://en.wikipedia.org/wiki/Separation_of_concerns) principle states that well-designed software should be modular, with each module having distinct and separate concerns from any other module.
Up until now, our codebase only returns a hard-coded response from the Controller. This setup violates the Separation of Concerns principle by mixing the concerns of a Controller, which is an abstraction of a web interface, with the concerns of reading and writing data to a data store, such as a database. In order to solve this, well use a common software architecture pattern to enforce data management separation via the **[Repository](https://www.baeldung.com/java-dao-vs-repository)** pattern.
A common architectural framework that divides these layers, typically by function or value, such as business, data, and presentation layers, is called **Layered Architecture**. Here we can think of our Repository and Controller as two layers in a Layered Architecture. The Controller is in a layer near the Client (as it receives and responds to web requests) while the Repository is in a layer near the data store (as it reads from and writes to the data store). There may be intermediate layers as well, as dictated by business needs.
![[Pasted image 20230719152007.png]]
The Repository is the interface between the application and the database, and provides a **common abstraction** for any database, making it easier to switch to a different database when needed.
[Spring Data](https://spring.io/projects/spring-data) works with Spring Boot to make database integration simple.
## Choosing a Database
For our database selection, well use an **embedded, in-memory** database. “Embedded” simply means that its a Java library, so it can be added to the project just like any other dependency. “In-memory” means that it stores data in memory only, as opposed to persisting data permanent, durable storage.
The specific in-memory database well use is [H2](https://www.h2database.com/html/main.html). Fortunately, H2 is highly compatible with other relational databases, so dev-prod parity *(application might behave differently when running the in-memory database than when running in production)* wont be a big issue. Well use H2 for **convenience for local development**, but want to recognize the tradeoffs.
## Auto Configuration
Simply by adding [[Database#Spring Data and a database dependencies]] we are getting full database functionality. This wonderfully showcases one of the most powerful features of Spring Boot: **Auto Configuration**. Without Spring Boot, wed have to configure Spring Data to speak to H2. However, because weve included the Spring Data dependency (and a specific data provider, H2), Spring Boot will automatically configure your application to communicate with H2.
## Spring Datas CrudRepository
For our Repository selection, well use a specific type of Repository: Spring Datas `CrudRepository`. At first glance, its slightly magical, but lets unpack that magic.
The following is a complete implementation of all CRUD operations by extending `CrudRepository`:
```java
public interface CashCardRepository extends CrudRepository<CashCard, Long> {
}
```
With just the above code, a caller can call any number of predefined `CrudRepository` methods, such as `findById`:
```java
cashCard = cashCardRepository.findById(99);
```
You might immediately wonder: where is the implementation of the `CashCardRepository.findById()` method? `CrudRepository` and everything it inherits from is an Interface with no actual code! Well, based on the specific Spring Data framework used, which for us will be Spring Data JDBC, Spring Data takes care of this implementation for us during the IoC container startup time. The Spring runtime will then expose the repository as yet another bean that you can reference wherever needed in your application.
As weve learned, there are typically trade-offs. For example the `CrudRepository` generates SQL statements to read and write your data, which is useful for many cases, but sometimes you need to write your own custom SQL statements for specific use cases. But for now, were happy to take advantage of its convenient, out-of-the-box methods,

@ -94,3 +94,28 @@ assertThat(id).isNotNull();
```
We expect that when we request a Cash Card with `id` of `99` a JSON object will be returned with _something_ in the `id` field. For now assert that the `id` is not `null`.
# Complex test debugging
To make tests output more verbose, add this section to the `build.gradle` file:
```yaml
// This section causes useful test output to go to the terminal.
test {
testLogging {
events "passed", "skipped", "failed" //, "standardOut", "standardError"
showExceptions true
exceptionFormat "full"
showCauses true
showStackTraces true
// Change to `true` for more verbose test output
showStandardStreams = false
}
}
```
# Additional resources
- [Integration Testing in Spring](https://www.baeldung.com/integration-testing-in-spring) written by baeldung.

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB