From 8105baa83df113ba4321240d96ea031cfcc966fc Mon Sep 17 00:00:00 2001 From: tobias Date: Mon, 24 Feb 2025 22:45:20 +0100 Subject: [PATCH] Erster REST-Service mit SpringBoot --- .settings/org.eclipse.core.resources.prefs | 3 + .settings/org.eclipse.jdt.core.prefs | 1 + pom.xml | 16 +++- .../java/info/peper/vz/rest/Aggregate.java | 72 +++++++++++++++++ .../peper/vz/rest/AggregateRepository.java | 7 ++ .../java/info/peper/vz/rest/LoadDatabase.java | 79 +++++++++++++++++++ .../java/info/peper/vz/rest/ReadDbMain.java | 52 ++++++++---- .../info/peper/vz/rest/VzRestApplication.java | 12 +++ .../info/peper/vz/rest/VzRestController.java | 29 +++++++ 9 files changed, 255 insertions(+), 16 deletions(-) create mode 100644 .settings/org.eclipse.core.resources.prefs create mode 100644 src/main/java/info/peper/vz/rest/Aggregate.java create mode 100644 src/main/java/info/peper/vz/rest/AggregateRepository.java create mode 100644 src/main/java/info/peper/vz/rest/LoadDatabase.java create mode 100644 src/main/java/info/peper/vz/rest/VzRestApplication.java create mode 100644 src/main/java/info/peper/vz/rest/VzRestController.java diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..e9441bb --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index eeac0e7..5e4ec05 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,4 +1,5 @@ eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.methodParameters=generate org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 org.eclipse.jdt.core.compiler.compliance=17 org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled diff --git a/pom.xml b/pom.xml index 62c77e4..b7cb7b8 100644 --- a/pom.xml +++ b/pom.xml @@ -2,6 +2,12 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.4.3 + info.peper rest-vz 0.0.1-SNAPSHOT @@ -13,7 +19,15 @@ org.mariadb.jdbc mariadb-java-client - 3.5.2 + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa \ No newline at end of file diff --git a/src/main/java/info/peper/vz/rest/Aggregate.java b/src/main/java/info/peper/vz/rest/Aggregate.java new file mode 100644 index 0000000..c64b80c --- /dev/null +++ b/src/main/java/info/peper/vz/rest/Aggregate.java @@ -0,0 +1,72 @@ +package info.peper.vz.rest; + +import java.io.Serializable; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; + +@Entity(name = "tobias_aggregate_spring") +@IdClass(Aggregate.CompositeKey.class) +class Aggregate { + static class CompositeKey implements Serializable { + private static final long serialVersionUID = 3097284483123288289L; + private int channelId; + private long timestampStart; + private long timestampEnd; + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + channelId; + result = prime * result + (int) (timestampEnd ^ (timestampEnd >>> 32)); + result = prime * result + (int) (timestampStart ^ (timestampStart >>> 32)); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CompositeKey other = (CompositeKey) obj; + if (channelId != other.channelId) + return false; + if (timestampEnd != other.timestampEnd) + return false; + if (timestampStart != other.timestampStart) + return false; + return true; + } + } + + Aggregate() { + super(); + } + + Aggregate(int channelId, long timestampStart, long timestampEnd, long sumPositive, long sumNegative) { + super(); + this.channelId = channelId; + this.timestampStart = timestampStart; + this.timestampEnd = timestampEnd; + this.sumPositive = sumPositive; + this.sumNegative = sumNegative; + } + @Id + @Column(name="channel_id") + private int channelId; + @Id + @Column(name="timestamp_start") + private long timestampStart; + @Id + @Column(name="timestamp_end") + private long timestampEnd; + @Column(name="sum_positive") + private long sumPositive; + @Column(name="sum_negative") + private long sumNegative; +} diff --git a/src/main/java/info/peper/vz/rest/AggregateRepository.java b/src/main/java/info/peper/vz/rest/AggregateRepository.java new file mode 100644 index 0000000..94cf195 --- /dev/null +++ b/src/main/java/info/peper/vz/rest/AggregateRepository.java @@ -0,0 +1,7 @@ +package info.peper.vz.rest; + +import org.springframework.data.jpa.repository.JpaRepository; + +interface AggregateRepository extends JpaRepository { + +} diff --git a/src/main/java/info/peper/vz/rest/LoadDatabase.java b/src/main/java/info/peper/vz/rest/LoadDatabase.java new file mode 100644 index 0000000..b5b9427 --- /dev/null +++ b/src/main/java/info/peper/vz/rest/LoadDatabase.java @@ -0,0 +1,79 @@ +package info.peper.vz.rest; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; + +@Configuration +class LoadDatabase { + + private static final DateTimeFormatter DTF = DateTimeFormatter.ISO_LOCAL_DATE_TIME.withZone(ZoneId.systemDefault()); + private static final int[] CHANNELS_TO_USE = new int[] {1, 4, }; + private static final Logger LOG = LoggerFactory.getLogger(LoadDatabase.class); + + static class Data { + final int channelId; + final long timestamp; + final int value; + + Data(int channelId, long timestamp, int value) { + super(); + this.channelId = channelId; + this.timestamp = timestamp; + this.value = value; + } + + } + + @Autowired + JdbcTemplate jdbcTemplate; + + @Bean + CommandLineRunner initDatabase(AggregateRepository repository) { + + return args -> { + LOG.info("################ jdbcTemplate: " + jdbcTemplate.getClass().getName()); + final long startTimestamp = getTimestamp("2022-06-01T00:00:00"); + final long endTimestamp = getTimestamp("2022-06-02T00:00:00"); + final List data = jdbcTemplate.query( + "SELECT * FROM volkszaehler.data WHERE channel_id=? AND timestamp>? AND timestamp<=? ORDER BY timestamp;", + (rs, rowNum) -> new Data(rs.getInt("channel_id"), rs.getLong("timestamp"), rs.getInt("value")), + 1 ,startTimestamp, endTimestamp); + + LOG.info("########## Count: " + data.size()); + long currentTimestamp = startTimestamp; + long wattMillisecondsPos = 0; + long wattMillisecondsNeg = 0; + for (Data d : data) { + final long rsTimestamp = d.timestamp; + final long rsValue = d.value; + final long tsDiff = rsTimestamp - Math.min(endTimestamp, currentTimestamp); + currentTimestamp = rsTimestamp; + if (rsValue > 0) { + wattMillisecondsPos += (rsValue * tsDiff); + } else if (rsValue < 0) { + wattMillisecondsNeg += (-rsValue * tsDiff); + } + } + repository.save(new Aggregate(1, startTimestamp, endTimestamp, wattMillisecondsPos, wattMillisecondsNeg)); + }; + } + + long getTimestamp(final String dateTime) { + final TemporalAccessor tempAccessor = DTF.parse(dateTime); + final Instant instant = Instant.from(tempAccessor); + return Instant.EPOCH.until(instant, ChronoUnit.MILLIS); + } + +} diff --git a/src/main/java/info/peper/vz/rest/ReadDbMain.java b/src/main/java/info/peper/vz/rest/ReadDbMain.java index 83168c2..feb4cfe 100644 --- a/src/main/java/info/peper/vz/rest/ReadDbMain.java +++ b/src/main/java/info/peper/vz/rest/ReadDbMain.java @@ -19,28 +19,34 @@ import java.time.temporal.TemporalAccessor; public class ReadDbMain { private static final DateTimeFormatter DTF = DateTimeFormatter.ISO_LOCAL_DATE_TIME.withZone(ZoneId.systemDefault()); + private static final int[] CHANNELS_TO_USE = new int[] {1, 4, }; public static void main(String[] args) throws Exception { try (final Connection con = DriverManager.getConnection( "jdbc:mariadb://mariadb.fritz.box/volkszaehler", "vz", getPassword())) { - long startTimeStamp = getTimestamp("2023-01-01T00:00:00"); + long startTimeStamp = getTimestamp("2022-06-01T00:00:00"); long endTimeStamp = startTimeStamp + (24*60*60*1000); - final long finalEndTimeStamp = getTimestamp("2025-02-23T00:00:00"); + final long finalEndTimeStamp = getTimestamp("2025-02-24T00:00:00"); while (endTimeStamp < finalEndTimeStamp) { - final long[] zaehler = getValues(con, startTimeStamp, endTimeStamp, 1); - final long[] solar = getValues(con, startTimeStamp, endTimeStamp, 4); - System.out.println(DTF.format(Instant.ofEpochMilli(startTimeStamp)) + - "\t" + - DTF.format(Instant.ofEpochMilli(endTimeStamp)) + - "\t" + - zaehler[0] + - "\t" + - zaehler[1] + - "\t" + - solar[0] - ); + for (int channelId : CHANNELS_TO_USE) { + final long[] values = getValues(con, startTimeStamp, endTimeStamp, channelId); + saveValues(con, startTimeStamp, endTimeStamp, channelId, values); + System.out.println(DTF.format(Instant.ofEpochMilli(startTimeStamp)) + ": " + channelId); + } +// final long[] zaehler = getValues(con, startTimeStamp, endTimeStamp, 1); +// final long[] solar = getValues(con, startTimeStamp, endTimeStamp, 4); +// System.out.println(DTF.format(Instant.ofEpochMilli(startTimeStamp)) + +// "\t" + +// DTF.format(Instant.ofEpochMilli(endTimeStamp)) + +// "\t" + +// zaehler[0] + +// "\t" + +// zaehler[1] + +// "\t" + +// solar[0] +// ); startTimeStamp += 24*60*60*1000; endTimeStamp += 24*60*60*1000; } @@ -60,6 +66,22 @@ public class ReadDbMain { return Instant.EPOCH.until(instant, ChronoUnit.MILLIS); } + private static void saveValues(final Connection con, + final long startTimestamp, + final long endTimestamp, + final int channelId, + final long[] values) throws SQLException { + try (final PreparedStatement stmt = con.prepareStatement("INSERT INTO tobias_aggregate (channel_id, timestamp_start, timestamp_end, sum_positive, sum_negative) VALUES (?, ?, ?, ?, ?)")) { + stmt.setInt(1, channelId); + stmt.setLong(2, startTimestamp); + stmt.setLong(3, endTimestamp); + stmt.setLong(4, values[0]); + stmt.setLong(5, values[1]); + stmt.execute(); + } + } + + private static long[] getValues(final Connection con, final long startTimestamp, final long endTimestamp, @@ -70,7 +92,7 @@ public class ReadDbMain { long wattMillisecondsNeg = 0; stmt.setInt(1, channelId); stmt.setLong(2, startTimestamp); - stmt.setLong(3, endTimestamp + 1*60*1000); + stmt.setLong(3, endTimestamp + 24*60*60*1000); try (final ResultSet rs = stmt.executeQuery()) { while (rs.next() && currentTimestamp <= endTimestamp) { final long rsTimestamp = rs.getLong("timestamp"); diff --git a/src/main/java/info/peper/vz/rest/VzRestApplication.java b/src/main/java/info/peper/vz/rest/VzRestApplication.java new file mode 100644 index 0000000..e2c7988 --- /dev/null +++ b/src/main/java/info/peper/vz/rest/VzRestApplication.java @@ -0,0 +1,12 @@ +package info.peper.vz.rest; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class VzRestApplication { + + public static void main(String... args) { + SpringApplication.run(VzRestApplication.class, args); + } +} diff --git a/src/main/java/info/peper/vz/rest/VzRestController.java b/src/main/java/info/peper/vz/rest/VzRestController.java new file mode 100644 index 0000000..98db49d --- /dev/null +++ b/src/main/java/info/peper/vz/rest/VzRestController.java @@ -0,0 +1,29 @@ +package info.peper.vz.rest; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +class VzRestController { + + private final AggregateRepository repository; + + public VzRestController(final AggregateRepository repository) { + this.repository = repository; + } + + @GetMapping("/test/{name}") + String hello(@PathVariable("name")final String name) { + return "Hello " + name + "!"; + } + + @PostMapping("/test") + Aggregate newAggregate(@RequestBody final Aggregate aggregate) { + return repository.save(aggregate); + } + + +}