Erster REST-Service mit SpringBoot

This commit is contained in:
tobias 2025-02-24 22:45:20 +01:00
parent 80937edde2
commit 8105baa83d
9 changed files with 255 additions and 16 deletions

View File

@ -0,0 +1,3 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding/<project>=UTF-8

View File

@ -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

16
pom.xml
View File

@ -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">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-parent -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.3</version>
</parent>
<groupId>info.peper</groupId>
<artifactId>rest-vz</artifactId>
<version>0.0.1-SNAPSHOT</version>
@ -13,7 +19,15 @@
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.5.2</version>
</dependency>
<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>
</dependencies>
</project>

View File

@ -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;
}

View File

@ -0,0 +1,7 @@
package info.peper.vz.rest;
import org.springframework.data.jpa.repository.JpaRepository;
interface AggregateRepository extends JpaRepository<Aggregate, Aggregate.CompositeKey> {
}

View File

@ -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> 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);
}
}

View File

@ -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");

View File

@ -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);
}
}

View File

@ -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);
}
}