Skip to content

Commit a66b3b0

Browse files
committed
Implementation of a JPA example. A few corrects in existing example
1 parent 7a13970 commit a66b3b0

File tree

11 files changed

+274
-37
lines changed

11 files changed

+274
-37
lines changed

clickhouse-jdbc/pom.xml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,6 @@
4545
<groupId>org.apache.commons</groupId>
4646
<artifactId>commons-compress</artifactId>
4747
</dependency>
48-
49-
<dependency>
50-
<groupId>org.lz4</groupId>
51-
<artifactId>lz4-java</artifactId>
52-
</dependency>
5348
<dependency>
5449
<groupId>com.google.code.gson</groupId>
5550
<artifactId>gson</artifactId>
@@ -90,7 +85,7 @@
9085
<dependency>
9186
<groupId>org.lz4</groupId>
9287
<artifactId>lz4-java</artifactId>
93-
<scope>provided</scope>
88+
<version>${lz4.version}</version>
9489
</dependency>
9590
<dependency>
9691
<groupId>com.github.luben</groupId>

examples/demo-service/README.md

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,46 @@ Application uses `system.numbers` table to generate dataset of any size. It is v
1212
- server generates data as if it was a real table
1313
- no need to change schema or create tables
1414

15-
To run
15+
Run clickhouse instance in docker:
16+
```
17+
docker run --rm -e CLICKHOUSE_USER=default -e CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 -e CLICKHOUSE_PASSWORD=secret\
18+
-p 8123:8123 clickhouse/clickhouse-server
19+
20+
```
21+
22+
Create table:
23+
```
24+
CREATE TABLE ui_events
25+
(
26+
`id` UUID,
27+
`timestamp` DateTime64,
28+
`eventName` String
29+
)
30+
ENGINE = MergeTree
31+
ORDER BY timestamp
32+
```
33+
34+
To run
1635

17-
```bash
36+
```shell
1837
./gradlew bootRun
1938
```
2039

2140
To test
2241

23-
```bash
42+
```shell
2443
curl http://localhost:8080/direct/dataset/0?limit=100000
2544
```
2645

46+
JPA Insert
47+
```shell
48+
curl -v -X POST -H "Content-Type: application/json" -d '{"id": "123", "timestamp": "2025-04-07T14:30:00.000Z", "eventName": "Login", "tags": ["security", "activity"]}' http://localhost:8080/events/ui_events
49+
```
50+
JPA Select
51+
```shell
52+
curl -v -X GET http://localhost:8080/events/ui_events
53+
```
54+
2755
## Features
2856

2957
- [x] Client V2 New Implementation

examples/demo-service/build.gradle.kts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
plugins {
22
java
3-
id("org.springframework.boot") version "3.2.8"
4-
id("io.spring.dependency-management") version "1.1.6"
3+
id("org.springframework.boot") version "3.4.4"
4+
id("io.spring.dependency-management") version "1.1.7"
55
}
66

77
group = "com.clickhouse"
@@ -29,22 +29,26 @@ val ch_java_client_version: String by extra
2929

3030
dependencies {
3131

32-
// -- clickhouse dependencies
33-
// Main dependency
34-
implementation("com.clickhouse:client-v2:${ch_java_client_version}-SNAPSHOT:all") // local or nightly build
32+
// Add this if working with client directly (not JDBC)
33+
// implementation("com.clickhouse:client-v2:${ch_java_client_version}-SNAPSHOT:all") // local or nightly build
3534
// implementation("com.clickhouse:client-v2:${ch_java_client_version}:all") // release version
3635

37-
// -- application dependencies
36+
// OR this if working with JDBC (or both)
37+
implementation("com.clickhouse:clickhouse-jdbc:${ch_java_client_version}-SNAPSHOT:all") // local or nightly build
38+
// implementation("com.clickhouse:clickhouse-jdbc:${ch_java_client_version}:all") // release version
39+
40+
// Other dependencies
3841
implementation("org.springframework.boot:spring-boot-starter-actuator")
3942
implementation("org.springframework.boot:spring-boot-starter-web")
43+
44+
// To enable JPA
45+
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
46+
4047
compileOnly("org.projectlombok:lombok")
4148
annotationProcessor("org.projectlombok:lombok")
4249

4350
implementation("io.micrometer:micrometer-core:1.14.3")
4451

45-
46-
47-
4852
// -- test dependencies
4953
testImplementation("org.springframework.boot:spring-boot-starter-test")
5054
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

examples/demo-service/src/main/java/com/clickhouse/demo_service/DbConfiguration.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@
22

33

44
import com.clickhouse.client.api.Client;
5-
import io.micrometer.core.instrument.logging.LoggingMeterRegistry;
5+
import io.micrometer.core.instrument.MeterRegistry;
66
import org.springframework.beans.factory.annotation.Value;
77
import org.springframework.context.annotation.Bean;
88
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
10+
import org.springframework.transaction.annotation.EnableTransactionManagement;
911

1012
@Configuration
13+
@EnableJpaRepositories
14+
@EnableTransactionManagement
1115
public class DbConfiguration {
1216

1317
@Bean
14-
public Client chDirectClient(LoggingMeterRegistry loggingMeterRegistry, @Value("${db.url}") String dbUrl, @Value("${db.user}") String dbUser,
18+
public Client chDirectClient(MeterRegistry meterRegistry, @Value("${db.url}") String dbUrl, @Value("${db.user}") String dbUser,
1519
@Value("${db.pass}") String dbPassword) {
1620
return new Client.Builder()
1721
.addEndpoint(dbUrl)
@@ -27,7 +31,9 @@ public Client chDirectClient(LoggingMeterRegistry loggingMeterRegistry, @Value("
2731
.setSocketSndbuf(500_000)
2832
.setClientNetworkBufferSize(500_000)
2933
.allowBinaryReaderToReuseBuffers(true) // using buffer pool for binary reader
30-
.registerClientMetrics(loggingMeterRegistry, "clickhouse-client-metrics")
34+
.registerClientMetrics(meterRegistry, "clickhouse-client-metrics")
3135
.build();
3236
}
37+
38+
// JPA configured via application.properties
3339
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.clickhouse.demo_service;
2+
3+
import com.clickhouse.demo_service.data.UIEvent;
4+
import com.clickhouse.demo_service.jpa.UIEventsDbRepository;
5+
import jakarta.annotation.PostConstruct;
6+
import jakarta.persistence.EntityManager;
7+
import jakarta.transaction.Transactional;
8+
import lombok.extern.java.Log;
9+
import org.springframework.beans.factory.annotation.Autowired;
10+
import org.springframework.transaction.PlatformTransactionManager;
11+
import org.springframework.transaction.support.TransactionTemplate;
12+
import org.springframework.web.bind.annotation.*;
13+
14+
import java.util.Collection;
15+
16+
/**
17+
* Class demonstrates usage of ClickHouse JDBC driver with JPA
18+
*
19+
*/
20+
@RestController
21+
@RequestMapping("/events/")
22+
@Log
23+
public class JPAInsertController {
24+
25+
@Autowired
26+
private UIEventsDbRepository uiEvents;
27+
28+
@Autowired
29+
private PlatformTransactionManager transactionManager;
30+
31+
@Autowired
32+
private EntityManager entityManager;
33+
34+
private TransactionTemplate transactionTemplate;
35+
36+
@PostConstruct
37+
public void setUp() {
38+
transactionTemplate = new TransactionTemplate(transactionManager);
39+
}
40+
41+
@PostMapping("/ui_events")
42+
@Transactional(Transactional.TxType.NOT_SUPPORTED)
43+
public void addUIEvent(@RequestBody UIEvent event) {
44+
// do input validation and conversion
45+
transactionTemplate.executeWithoutResult(transactionStatus -> {
46+
entityManager.persist(event);
47+
});
48+
}
49+
50+
@GetMapping("/ui_events")
51+
public Collection<UIEvent> lastUIEvents() {
52+
return uiEvents.findAll();
53+
}
54+
}

examples/demo-service/src/main/java/com/clickhouse/demo_service/MetricsConfig.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22
import io.micrometer.core.instrument.Clock;
33
import io.micrometer.core.instrument.logging.LoggingMeterRegistry;
44
import io.micrometer.core.instrument.logging.LoggingRegistryConfig;
5+
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
6+
import org.springframework.beans.factory.annotation.Value;
7+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
58
import org.springframework.context.annotation.Bean;
9+
import org.springframework.context.annotation.Condition;
10+
import org.springframework.context.annotation.Conditional;
611
import org.springframework.context.annotation.Configuration;
712

813
import java.time.Duration;
@@ -32,10 +37,19 @@ public Duration step() {
3237

3338
// Create the LoggingMeterRegistry bean.
3439
@Bean
40+
@ConditionalOnProperty("app.log_metrics")
3541
public LoggingMeterRegistry loggingMeterRegistry(LoggingRegistryConfig config) {
3642
LoggingMeterRegistry registry = new LoggingMeterRegistry(config, Clock.SYSTEM);
3743
// Start the registry’s internal scheduler so that metrics are published periodically.
3844
registry.start();
3945
return registry;
4046
}
47+
48+
@Bean
49+
@ConditionalOnProperty(value = "app.log_metrics", havingValue = "false")
50+
public SimpleMeterRegistry simpleMeterRegistry() {
51+
SimpleMeterRegistry registry = new SimpleMeterRegistry();
52+
53+
return registry;
54+
}
4155
}

examples/demo-service/src/main/java/com/clickhouse/demo_service/DatasetController.java renamed to examples/demo-service/src/main/java/com/clickhouse/demo_service/QueryController.java

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,7 @@
1616
import jakarta.annotation.PostConstruct;
1717
import jakarta.servlet.http.HttpServletResponse;
1818
import lombok.extern.java.Log;
19-
import org.springframework.web.bind.annotation.GetMapping;
20-
import org.springframework.web.bind.annotation.RequestMapping;
21-
import org.springframework.web.bind.annotation.RequestParam;
22-
import org.springframework.web.bind.annotation.ResponseBody;
23-
import org.springframework.web.bind.annotation.RestController;
19+
import org.springframework.web.bind.annotation.*;
2420

2521
import java.util.ArrayList;
2622
import java.util.LinkedList;
@@ -31,23 +27,22 @@
3127
import java.util.stream.Collectors;
3228

3329
/**
34-
* Dataset API:
35-
* - /direct/dataset/0/?limit=N - uses client v2 directly to fetch N rows from a virtual dataset.
36-
*
37-
* <p>Example: {@code curl -v http://localhost:8080/direct/dataset/0?limit=10}</p>
30+
* Class demonstrates using ClickHouse client directly from a service.
31+
* It avoids JDBC overhead and much easier to use.
32+
* Data may be streamed from database directly to the service response.
3833
*/
3934
@RestController
40-
@RequestMapping("/")
35+
@RequestMapping("/dataset")
4136
@Log
42-
public class DatasetController {
37+
public class QueryController {
4338

4439
private final Client chDirectClient;
4540

4641
private static final int MAX_LIMIT = 100_000;
4742

4843
private BasicObjectsPool<ObjectsPreparedCollection<VirtualDatasetRecord>> pool;
4944

50-
public DatasetController(Client chDirectClient) {
45+
public QueryController(Client chDirectClient) {
5146
this.chDirectClient = chDirectClient;
5247
}
5348

@@ -95,7 +90,7 @@ VirtualDatasetRecord create() {
9590
* @param limit
9691
* @return
9792
*/
98-
@GetMapping("/dataset/reader")
93+
@GetMapping("/reader")
9994
public List<VirtualDatasetRecord> directDatasetFetch(@RequestParam(name = "limit", required = false) Integer limit) {
10095
limit = limit == null ? 100 : limit;
10196

@@ -140,7 +135,7 @@ public List<VirtualDatasetRecord> directDatasetFetch(@RequestParam(name = "limit
140135
* @param httpResp
141136
* @param limit
142137
*/
143-
@GetMapping("/dataset/json_each_row_in_and_out")
138+
@GetMapping("/json_each_row_in_and_out")
144139
@ResponseBody
145140
public void directDataFetchJSONEachRow(HttpServletResponse httpResp, @RequestParam(name = "limit", required = false) Integer limit) {
146141
limit = limit == null ? 100 : limit;
@@ -184,7 +179,7 @@ public void directDataFetchJSONEachRow(HttpServletResponse httpResp, @RequestPar
184179
* @param limit
185180
* @return
186181
*/
187-
@GetMapping("/dataset/read_to_pojo")
182+
@GetMapping("/read_to_pojo")
188183
public CalculationResult directDatasetReadToPojo(@RequestParam(name = "limit", required = false) Integer limit,
189184
@RequestParam(name = "cache", required = false) Boolean cache) {
190185
limit = limit == null ? 100 : limit;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.clickhouse.demo_service.data;
2+
3+
import com.clickhouse.demo_service.jpa.ClickHouseStringArrayType;
4+
import jakarta.persistence.Entity;
5+
import jakarta.persistence.Id;
6+
import jakarta.persistence.Table;
7+
import lombok.Data;
8+
import org.hibernate.annotations.Array;
9+
import org.hibernate.annotations.JdbcType;
10+
import org.hibernate.annotations.Type;
11+
import org.hibernate.type.descriptor.jdbc.ArrayJdbcType;
12+
13+
import java.sql.JDBCType;
14+
import java.sql.Timestamp;
15+
import java.util.Collection;
16+
import java.util.List;
17+
18+
@Entity
19+
@Data
20+
@Table(name = "ui_events")
21+
public class UIEvent {
22+
23+
@Id
24+
private String id;
25+
26+
private Timestamp timestamp;
27+
28+
private String eventName;
29+
30+
@JdbcType(ArrayJdbcType.class)
31+
@Type(ClickHouseStringArrayType.class)
32+
private Collection<String> tags;
33+
}

0 commit comments

Comments
 (0)