Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ This repo contains the sample for [Keploy's](https://keploy.io) Java Application
7. [Java Dynamic Deduplication](https://github.com/keploy/samples-java/tree/main/java-dedup) - A Spring Boot sample used by CI to validate Enterprise Java dynamic dedup in native, Docker, and restricted Docker replay runs. CI uses checked-in fixtures and does not record this sample in the pipeline.
8. [Dropwizard Dynamic Deduplication](https://github.com/keploy/samples-java/tree/main/dropwizard-dedup) - A Dropwizard/Jersey sample used by Enterprise CI to validate that Java dynamic dedup works outside Spring Boot with the runtime Java agent, checked-in HTTP fixtures, native launch, classpath launch, Docker, distroless, and restricted Docker.
9. [Simple Java Dynamic Deduplication](https://github.com/keploy/samples-java/tree/main/simple-java-dedup) - A minimal plain-Java HTTP server used to smoke-test Java dynamic dedup on Java 8 and Java 17 in native and Docker launch modes.
10. [MySQL CRUD](https://github.com/keploy/samples-java/tree/main/mysql-crud) - A minimal Spring Boot + JDBC CRUD app used by Enterprise CI to validate the self-hosted cloud-replay pipeline's JDBC secret-obfuscation and object-storage mock upload/download paths against a real MySQL 8 backend.

## Community Support ❤️

Expand Down
2 changes: 2 additions & 0 deletions mysql-crud/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target/
/*.log
14 changes: 14 additions & 0 deletions mysql-crud/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Multi-stage build. NOTE: deliberately NO -javaagent — the Keploy enterprise
# sidecar auto-injects the JSSE javaagent at record/replay time.
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /src
COPY pom.xml .
RUN mvn -q -B dependency:go-offline
COPY src ./src
RUN mvn -q -B package -DskipTests

FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /src/target/app.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
52 changes: 52 additions & 0 deletions mysql-crud/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# MySQL CRUD Sample

A minimal Spring Boot + JDBC application used by Keploy Enterprise CI to validate the
self-hosted cloud-replay pipeline: JDBC-manifest secret obfuscation and object-storage
mock upload/download, exercised end-to-end via a real MySQL 8 backend.

## Endpoints

| Method | Path | Description |
|--------|----------------------|------------------------------------------------|
| GET | `/health` | Runs `SELECT 1` against the configured DB |
| GET | `/users` | Lists users + aggregate order stats |
| GET | `/users/{id}` | Single user, their orders, and order totals |
| POST | `/users` | Creates a user |
| POST | `/users/{id}/orders` | Creates an order for a user |
| GET | `/stats` | Aggregate user/order counts and amounts |

## Configuration

The datasource is fully env-driven so the same jar runs against any MySQL instance:

```
DB_URL (default: jdbc:mysql://localhost:3306/appdb)
DB_USER (default: root)
DB_PASS (default: empty)
```

`schema.sql` / `data.sql` run on every startup (idempotent — `IF NOT EXISTS` / `INSERT IGNORE`).

## MySQL auth-plugin note

MySQL 8's default `caching_sha2_password` auth plugin cannot be captured by Keploy's
MySQL recorder mid-handshake. When running this sample against a container you control,
start MySQL with:

```
--default-authentication-plugin=mysql_native_password
```

## Build & run

```bash
mvn -q -B clean package -DskipTests
DB_URL="jdbc:mysql://localhost:3306/appdb" DB_USER=root java -jar target/app.jar
```

## Docker

```bash
docker build -t mysql-crud .
docker run -p 8080:8080 -e DB_URL="jdbc:mysql://<mysql-host>:3306/appdb" mysql-crud
```
48 changes: 48 additions & 0 deletions mysql-crud/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
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>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
<relativePath/>
</parent>

<groupId>com.keploy.sample</groupId>
<artifactId>mysql-crud</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>

<properties>
<java.version>17</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>

<build>
<finalName>app</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
106 changes: 106 additions & 0 deletions mysql-crud/src/main/java/com/keploy/sample/ApiController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.keploy.sample;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@RestController
public class ApiController {

private final JdbcTemplate jdbc;

public ApiController(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}

@GetMapping("/health")
public Map<String, Object> health() {
Integer one = jdbc.queryForObject("SELECT 1", Integer.class);
Map<String, Object> r = new LinkedHashMap<>();
r.put("status", (one != null && one == 1) ? "ok" : "degraded");
return r;
}

@GetMapping("/users")
public Map<String, Object> listUsers() {
List<Map<String, Object>> users = jdbc.queryForList("SELECT id,name,email FROM users ORDER BY id");
Integer userCount = jdbc.queryForObject("SELECT COUNT(*) FROM users", Integer.class);
Integer orderCount = jdbc.queryForObject("SELECT COUNT(*) FROM orders", Integer.class);
BigDecimal total = jdbc.queryForObject("SELECT COALESCE(SUM(amount),0) FROM orders", BigDecimal.class);
Map<String, Object> r = new LinkedHashMap<>();
r.put("users", users);
r.put("userCount", userCount);
r.put("orderCount", orderCount);
r.put("totalOrderAmount", total);
return r;
}

@GetMapping("/users/{id}")
public Map<String, Object> getUser(@PathVariable long id) {
List<Map<String, Object>> u = jdbc.queryForList("SELECT id,name,email FROM users WHERE id=?", id);
List<Map<String, Object>> orders = jdbc.queryForList(
"SELECT id,amount,status FROM orders WHERE user_id=? ORDER BY id", id);
Integer cnt = jdbc.queryForObject("SELECT COUNT(*) FROM orders WHERE user_id=?", Integer.class, id);
BigDecimal sum = jdbc.queryForObject(
"SELECT COALESCE(SUM(amount),0) FROM orders WHERE user_id=?", BigDecimal.class, id);
jdbc.update("INSERT INTO audit_log(action,detail) VALUES(?,?)", "view_user", "id=" + id);
Map<String, Object> r = new LinkedHashMap<>();
r.put("user", u.isEmpty() ? null : u.get(0));
r.put("orders", orders);
r.put("orderCount", cnt);
r.put("orderTotal", sum);
return r;
}

@PostMapping("/users")
public Map<String, Object> createUser(@RequestBody Map<String, Object> body) {
String name = String.valueOf(body.getOrDefault("name", "unknown"));
String email = String.valueOf(body.getOrDefault("email", "unknown@example.com"));
jdbc.update("INSERT INTO users(name,email) VALUES(?,?)", name, email);
Long id = jdbc.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
jdbc.update("INSERT INTO audit_log(action,detail) VALUES(?,?)", "create_user", "name=" + name);
Integer userCount = jdbc.queryForObject("SELECT COUNT(*) FROM users", Integer.class);
Map<String, Object> created = jdbc.queryForMap("SELECT id,name,email FROM users WHERE id=?", id);
Comment thread
pathakharshit marked this conversation as resolved.
Outdated
Map<String, Object> r = new LinkedHashMap<>();
r.put("created", created);
r.put("userCount", userCount);
return r;
}

@PostMapping("/users/{id}/orders")
public Map<String, Object> createOrder(@PathVariable long id, @RequestBody Map<String, Object> body) {
Object amount = body.getOrDefault("amount", 0);
String status = String.valueOf(body.getOrDefault("status", "PENDING"));
Integer userExists = jdbc.queryForObject("SELECT COUNT(*) FROM users WHERE id=?", Integer.class, id);
jdbc.update("INSERT INTO orders(user_id,amount,status) VALUES(?,?,?)", id, amount, status);
Long orderId = jdbc.queryForObject("SELECT LAST_INSERT_ID()", Long.class);
Map<String, Object> order = jdbc.queryForMap("SELECT id,user_id,amount,status FROM orders WHERE id=?", orderId);
Integer orderCount = jdbc.queryForObject("SELECT COUNT(*) FROM orders WHERE user_id=?", Integer.class, id);
jdbc.update("INSERT INTO audit_log(action,detail) VALUES(?,?)", "create_order", "user=" + id);
Map<String, Object> r = new LinkedHashMap<>();
r.put("userExists", userExists != null && userExists > 0);
r.put("order", order);
r.put("orderCountForUser", orderCount);
return r;
}
Comment thread
pathakharshit marked this conversation as resolved.

@GetMapping("/stats")
public Map<String, Object> stats() {
Integer users = jdbc.queryForObject("SELECT COUNT(*) FROM users", Integer.class);
Integer orders = jdbc.queryForObject("SELECT COUNT(*) FROM orders", Integer.class);
BigDecimal sum = jdbc.queryForObject("SELECT COALESCE(SUM(amount),0) FROM orders", BigDecimal.class);
BigDecimal avg = jdbc.queryForObject("SELECT COALESCE(AVG(amount),0) FROM orders", BigDecimal.class);
BigDecimal max = jdbc.queryForObject("SELECT COALESCE(MAX(amount),0) FROM orders", BigDecimal.class);
Map<String, Object> r = new LinkedHashMap<>();
r.put("userCount", users);
r.put("orderCount", orders);
r.put("sumAmount", sum);
r.put("avgAmount", avg);
r.put("maxAmount", max);
return r;
}
}
11 changes: 11 additions & 0 deletions mysql-crud/src/main/java/com/keploy/sample/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.keploy.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
14 changes: 14 additions & 0 deletions mysql-crud/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
server.port=8080

spring.datasource.url=${DB_URL:jdbc:mysql://localhost:3306/appdb}
spring.datasource.username=${DB_USER:root}
spring.datasource.password=${DB_PASS:}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# Run schema.sql + data.sql on startup (idempotent: IF NOT EXISTS / INSERT IGNORE)
spring.sql.init.mode=always
spring.sql.init.continue-on-error=false

# Give the pool time while wait-for-db init container / MySQL warms up
spring.datasource.hikari.initialization-fail-timeout=60000
spring.datasource.hikari.connection-timeout=30000
9 changes: 9 additions & 0 deletions mysql-crud/src/main/resources/data.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
INSERT IGNORE INTO users (id, name, email) VALUES
(1, 'Alice', 'alice@example.com'),
(2, 'Bob', 'bob@example.com'),
(3, 'Carol', 'carol@example.com');

INSERT IGNORE INTO orders (id, user_id, amount, status) VALUES
(1, 1, 99.50, 'PAID'),
(2, 1, 15.00, 'PENDING'),
(3, 2, 250.00, 'PAID');
21 changes: 21 additions & 0 deletions mysql-crud/src/main/resources/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
CREATE TABLE IF NOT EXISTS users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
amount DECIMAL(10,2) NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS audit_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
action VARCHAR(100) NOT NULL,
detail VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
Loading