Skip to content
This repository has been archived by the owner on Jul 26, 2024. It is now read-only.

Commit

Permalink
Merge pull request #18 from ralf-ueberfuhr-ars/feature/db
Browse files Browse the repository at this point in the history
Introduce Persistence Layer.
  • Loading branch information
ralf-ueberfuhr-ars authored Jun 21, 2024
2 parents 09a3fc9 + 2a93333 commit 5fcbcce
Show file tree
Hide file tree
Showing 17 changed files with 572 additions and 2 deletions.
8 changes: 8 additions & 0 deletions customer-api-provider/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package de.schulung.sample.quarkus.persistence;

import de.schulung.sample.quarkus.domain.Customer;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;

import java.time.LocalDate;
import java.util.UUID;

@Getter
@Setter
@Entity(name = "Customer")
@Table(name = "CUSTOMERS")
public class CustomerEntity {

@Id
@GeneratedValue(strategy = GenerationType.UUID)
private UUID uuid;
@Size(min = 3, max = 100)
@NotNull
private String name;
@Column(name = "DATE_OF_BIRTH")
private LocalDate birthdate;
@NotNull
private Customer.CustomerState state = Customer.CustomerState.ACTIVE;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package de.schulung.sample.quarkus.persistence;

import de.schulung.sample.quarkus.domain.Customer;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;

@Mapper(componentModel = "cdi")
public interface CustomerEntityMapper {

CustomerEntity map(Customer source);

Customer map(CustomerEntity source);

void copy(CustomerEntity source, @MappingTarget Customer target);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package de.schulung.sample.quarkus.persistence;

import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;

import java.util.UUID;

@ApplicationScoped
public class CustomerEntityRepository implements PanacheRepositoryBase<CustomerEntity, UUID> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package de.schulung.sample.quarkus.persistence;

import de.schulung.sample.quarkus.domain.Customer;
import de.schulung.sample.quarkus.domain.Customer.CustomerState;
import de.schulung.sample.quarkus.domain.CustomersSink;
import io.quarkus.arc.properties.IfBuildProperty;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Typed;
import lombok.RequiredArgsConstructor;

import javax.sql.DataSource;
import java.sql.*;
import java.time.LocalDate;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream;

@ApplicationScoped
@Typed(CustomersSink.class)
@RequiredArgsConstructor
@IfBuildProperty(
name = "persistence.sink.implementation",
stringValue = "jdbc"
)
public class CustomersSinkJdbcImpl implements CustomersSink {

private final DataSource ds;

/* ******************************************************* *
* Converter methods - could be converter objects instead *
* ******************************************************* */

private static UUID convertUuid(String uuid) {
return Optional.ofNullable(uuid)
.map(UUID::fromString)
.orElse(null);
}

private static String convertUuid(UUID uuid) {
return Optional.ofNullable(uuid)
.map(UUID::toString)
.orElse(null);
}

private static LocalDate convertDate(Date date) {
return Optional.ofNullable(date)
.map(Date::toLocalDate)
.orElse(null);
}

private static Date convertDate(LocalDate date) {
return Optional.ofNullable(date)
.map(Date::valueOf)
.orElse(null);
}

private static CustomerState convertState(int value) {
return CustomerState.values()[value];
}

private static int convertState(CustomerState value) {
return Optional.ofNullable(value)
.map(CustomerState::ordinal)
.orElse(0);

}

/* ******************************************************* *
* Row Mapping - could be a RowMapper object instead *
* ******************************************************* */

private static Customer readSingle(ResultSet rs) throws SQLException {
return Customer.builder()
.uuid(convertUuid(rs.getString("UUID")))
.birthdate(convertDate(rs.getDate("DATE_OF_BIRTH")))
.name(rs.getString("NAME"))
.state(convertState(rs.getInt("STATE")))
.build();
}

private static Stream<Customer> readAll(ResultSet rs) throws SQLException {
Collection<Customer> result = new LinkedList<>();
while (rs.next()) {
result.add(readSingle(rs));
}
return result.stream();
}

/* ******************************************************* *
* CustomersSink implementation *
* ******************************************************* */

@Override
public Stream<Customer> findAll() {
try (Connection con = ds.getConnection();
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(
"select * from CUSTOMERS"
)) {

return readAll(rs);

} catch (SQLException e) {
throw new RuntimeException(e); // eigene Exception?
}
}

@Override
public Stream<Customer> findByState(CustomerState state) {
try (Connection con = ds.getConnection();
PreparedStatement stmt = con.prepareStatement(
"select * from CUSTOMERS where STATE=?"
)) {

stmt.setInt(1, convertState(state));

try (ResultSet rs = stmt.executeQuery()) {
return readAll(rs);
}

} catch (SQLException e) {
throw new RuntimeException(e); // eigene Exception?
}
}

@Override
public Optional<Customer> findByUuid(UUID uuid) {
try (Connection con = ds.getConnection();
PreparedStatement stmt = con.prepareStatement(
"select * from CUSTOMERS where UUID=?"
)) {

stmt.setString(1, convertUuid(uuid));

try (ResultSet rs = stmt.executeQuery()) {
if (!rs.next()) {
return Optional.empty();
}
return Optional.of(readSingle(rs));
}

} catch (SQLException e) {
throw new RuntimeException(e); // eigene Exception?
}
}

@Override
public void save(Customer customer) {
// TODO only insert, we need an update too, if the UUID is already set
try (Connection con = ds.getConnection();
PreparedStatement stmt = con.prepareStatement(
"insert into CUSTOMERS(UUID,NAME,DATE_OF_BIRTH,STATE) values(?,?,?,?)",
Statement.RETURN_GENERATED_KEYS
)) {

stmt.setString(1, convertUuid(customer.getUuid()));
stmt.setString(2, customer.getName());
stmt.setDate(3, convertDate(customer.getBirthdate()));
stmt.setInt(4, convertState(customer.getState()));
stmt.executeUpdate();

try (ResultSet rs = stmt.getGeneratedKeys()) {
if (!rs.next()) {
throw new RuntimeException("not expected"); // bessere Exception
}
customer.setUuid(convertUuid(rs.getString(1)));
}

} catch (SQLException e) {
throw new RuntimeException(e); // eigene Exception?
}
}

@Override
public boolean delete(UUID uuid) {
try (Connection con = ds.getConnection();
PreparedStatement stmt = con.prepareStatement(
"delete from CUSTOMERS where UUID=?"
)) {

stmt.setString(1, convertUuid(uuid));
return stmt.executeUpdate() > 0;

} catch (SQLException e) {
throw new RuntimeException(e); // eigene Exception?
}
}

@Override
public long count() {
try (Connection con = ds.getConnection();
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery(
"select count(uuid) from CUSTOMERS"
)) {

if (!rs.next()) {
return 0;
}
return rs.getLong(1);

} catch (SQLException e) {
throw new RuntimeException(e); // eigene Exception?
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package de.schulung.sample.quarkus.persistence;

import de.schulung.sample.quarkus.domain.Customer;
import de.schulung.sample.quarkus.domain.CustomersSink;
import io.quarkus.arc.properties.IfBuildProperty;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Typed;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;

import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream;

@ApplicationScoped
@Typed(CustomersSink.class)
@RequiredArgsConstructor
@IfBuildProperty(
name = "persistence.sink.implementation",
stringValue = "jpa"
)
public class CustomersSinkJpaImpl implements CustomersSink {

private final CustomerEntityMapper mapper;
private final EntityManager em;

@Override
public Stream<Customer> findAll() {
return em.createQuery(
"select c from Customer c",
CustomerEntity.class
)
.getResultList()
.stream()
.map(mapper::map);
}

@Override
public Stream<Customer> findByState(Customer.CustomerState state) {
return em.createQuery(
"select c from Customer c where c.state = :state",
CustomerEntity.class
)
.setParameter("state", state)
.getResultList()
.stream()
.map(mapper::map);
}

@Override
public Optional<Customer> findByUuid(UUID uuid) {
return Optional
.ofNullable(em.find(CustomerEntity.class, uuid))
.map(mapper::map);
}

@Override
public void save(Customer customer) {
var entity = this.mapper.map(customer);
em.persist(entity);
//customer.setUuid(entity.getUuid());
mapper.copy(entity, customer);
}

@Override
public boolean delete(UUID uuid) {
var found = em.find(CustomerEntity.class, uuid);
if(found == null) {
return false;
}
em.remove(found);
return true;
}

@Override
public boolean exists(UUID uuid) {
var found = em.find(CustomerEntity.class, uuid);
return found != null;
}

@Override
public long count() {
return 0; // TODO ??
}
}
Loading

0 comments on commit 5fcbcce

Please sign in to comment.