14_Database
Lernmaterialien
Id
PizzaOrder.java
private static final AtomicLong sequence = new AtomicLong(1000);
public void setOrderId() {
this.orderId = sequence.getAndIncrement();
}+------------+ +------------+ +------------+ +------------+
| Client 1 | | Client 2 | | Client 3 | | Client 4 |
| sequence | | sequence | | sequence | | sequence |
+------+-----+ +------+-----+ +------+-----+ +------+-----+
| | | | HTTP request
+---------------+------+--------+---------------+
|
+-------+-------+
| Server |
| Spring/Vaadin |
| sequence |
+--------+------+
|
+--------+------+
| Database |
| PostgreSQL |
| sequence |
+---------------+
private static final AtomicLong sequence = new AtomicLong(1000);
public void setOrderId() { this.orderId = sequence.getAndIncrement(); }
setOrderId();
ALTER SEQUENCE pizza_order_order_id_seq RESTART WITH 1000;Postgres
docker run --name postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres
docker start postgres
pom.xml
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>Create database and user
psql "postgresql://postgres:postgres@localhost:5432/"
CREATE USER pizzauser WITH PASSWORD 'secret';
CREATE DATABASE pizzadb OWNER pizzauser;
quit;
Check the connection
psql -U pizzauser -d pizzadb
application.properties
# PostgreSQL configuration.
spring.datasource.url=jdbc:postgresql://localhost:5432/pizzadb
spring.datasource.username=pizzauser
spring.datasource.password=secret
spring.jpa.hibernate.ddl-auto = update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=trueCode
PizzaOrder.java
@Entity
public class PizzaOrder implements Cloneable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long orderId;Repository
PizzaOrderRepository.java
public interface PizzaOrderRepository
extends JpaRepository<PizzaOrder, Long> {
}public interface PizzaOrderRepository
extends JpaRepository<PizzaOrder, Long> {
}This defines a repository interface for
PizzaOrder.It extends
JpaRepository, so Spring automatically provides database methods.PizzaOrder= entity typeLong= type of the ID (orderId)
You automatically get methods like:
findAll()
findById(Long id)
save(PizzaOrder order)
deleteById(Long id)👉 Interface for database access — no implementation needed, Spring generates it.
Service
Repository
PizzaOrderService.java
@Service
public class PizzaOrderService {
private ArrayList<PizzaOrder> orders;private ArrayList<PizzaOrder> orders;
private final PizzaOrderRepository repository;
Main difference:
ArrayList version |
PostgreSQL version |
|---|---|
| stores data in RAM | stores data in PostgreSQL |
| data lost when app stops | data stays in database |
uses orders.add() |
uses repository.save() |
uses orders.clear() |
uses repository.deleteAll() |
| searches with loops | uses repository.findById() |
| manually sets IDs | database creates IDs |
PizzaOrderService.java
@Service
public class PizzaOrderService {
private final PizzaOrderRepository repository;
public PizzaOrderService(PizzaOrderRepository repository) {
this.repository = repository;
if (repository.count() == 0) {
fillTestData(500);
}
}public PizzaOrderService(PizzaOrderRepository repository) {This is the constructor of
PizzaOrderService.
Spring sees that the constructor needs a
PizzaOrderRepository.
this.repository = repository;The given repository object is stored in the service field:
private final PizzaOrderRepository repository;So the service can use it later.
if (repository.count() == 0) {
fillTestData(500);
}This checks the database table.
count() == 0→ table is emptythen
fillTestData(500)creates 500 test orders
Where is repository generated?
You write only the interface:
public interface PizzaOrderRepository
extends JpaRepository<PizzaOrder, Long> {
}Spring Data JPA automatically creates the real implementation at runtime.
So you do not write:
new PizzaOrderRepository()Spring does it for you and injects it into the constructor.
In short:
PizzaOrderRepository interface
|
v
Spring Data JPA creates implementation
|
v
Spring injects it into PizzaOrderService constructor
OneMore
public void oneMore(Long orderId) {
if (orderId == null)
throw new PizzaOrderException("No Order ID!");
Optional<PizzaOrder> order = repository.findById(orderId);
if (order.isEmpty())
throw new PizzaOrderException("Order not found!");
else
order.get().setQuantity(order.get().getQuantity()+1);
}Now we have to change the implementation from a collection to the database.
Optional<PizzaOrder> order = repository.findById(orderId);
This searches the database for a PizzaOrder with the
given orderId.
findById(orderId) returns an Optional
because the order may or may not exist.
order.isPresent()order was found.order.isEmpty()order was not found
order.get().setQuantity(order.get().getQuantity()+1);
orderis anOptional<PizzaOrder>order.get()→ gets the actualPizzaOrderobject (must exist!)getQuantity()→ reads current quantity+ 1→ increases it by onesetQuantity(...)→ writes the new value back
increase the order’s quantity by 1
public PizzaOrder add(PizzaOrder order) {
return repository.save(order);
}repository.save(order)→ saves thePizzaOrderto the database- if it is new → insert
- if it already exists → update
- It returns the saved object (including generated
values like
orderId)
store the order in the database and return it
OrdersView.java
if (existingOrder != null)
Button saveButton = new Button(OK", event -> {
if (binder.validate().isOk()) {
pizzaOrderService.add(order);Because with JPA this works for both cases:
pizzaOrderService.add(order);
save() means:
orderId == null→ create new database roworderId exists→ update existing database ro
JPA save() handles insert and update
automatically.
public void removePizzaOrder(Long orderId) {
if (orderId == null) {
throw new PizzaOrderException("No Order ID!");
}
if (!repository.existsById(orderId)) {
throw new PizzaOrderException(
"Order with the ID " + orderId + " not found!"
);
}
repository.deleteById(orderId);
}repository.existsById(orderId)
true→ order existsfalse→ order does not exist
repository.deleteById(orderId);
If an order with this ID exists → it is removed.
If not → depending on setup, it may throw an exception.
public void removeAll() {
repository.deleteAll();
}repository.deleteAll();
Deletes all PizzaOrder records from the
database.
public void oneMore(Long orderId) {
if (orderId == null)
throw new PizzaOrderException("No Order ID!");
Optional<PizzaOrder> order = repository.findById(orderId);
if (order.isEmpty()) {
throw new PizzaOrderException("Order not found!");
}
else {
PizzaOrder o = order.get();
o.setQuantity(o.getQuantity() + 1);
repository.save(o);
}
}Do not forget to call repository.save(o);. Otherwise it
is not written back to the database!
public void addWrongOrder() {
PizzaOrder order;
// -20 Euro!!!
order = new PizzaOrder(LocalDate.now(), "Margherita", "Large", -20.0, 1, true);
repository.save(order);
}This method is useless.
public void fillTestData(int anz) {
PizzaOrder p;
Faker faker;
String[] PIZZAS = {"Margherita", "Salami", "Tonno", "Hawaii", "Funghi", "Diavolo"};
String[] SIZES = { "Small", "Medium", "Large", "Family" };
faker = new Faker();
//orders.clear();
for (int i = 0; i < anz; i++) {
p = new PizzaOrder();
p.setOrderDate(LocalDate.now().minusDays(faker.number().numberBetween(0, 30)));
p.setPizza(PIZZAS[faker.number().numberBetween(0, PIZZAS.length)]);
p.setSize(SIZES[faker.number().numberBetween(0, SIZES.length)]);
p.setPrice(faker.number().randomDouble(2, 8, 30));
p.setQuantity(faker.number().numberBetween(1, 6));
p.setGarlic(faker.bool().bool());
repository.save(p);
}
}Do not forget to call repository.save(o);.
@Override
public String toString() {
return repository.findAll()
.stream()
.map(PizzaOrder::toString)
.reduce("", (a, b) -> a + b + "\n");
}Same.