Skip to content

Commit 3b1709d

Browse files
committed
Add provider JDBC helpers
1 parent fc053e3 commit 3b1709d

6 files changed

Lines changed: 282 additions & 12 deletions

File tree

README.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Use the official Dubbo stack instead when you need full Dubbo governance, config
3131
<dependency>
3232
<groupId>com.reactor</groupId>
3333
<artifactId>java-rust-dubbo</artifactId>
34-
<version>0.2.1</version>
34+
<version>0.2.2</version>
3535
</dependency>
3636
```
3737

@@ -77,7 +77,7 @@ For the smallest static-provider native setup, use the `native-static` classifie
7777
<dependency>
7878
<groupId>com.reactor</groupId>
7979
<artifactId>java-rust-dubbo</artifactId>
80-
<version>0.2.1</version>
80+
<version>0.2.2</version>
8181
<classifier>native-static</classifier>
8282
</dependency>
8383
```
@@ -112,6 +112,8 @@ Common provider classes:
112112
- `DubboProviderSupport`
113113
- `PlainDubboProvider`
114114
- `ZookeeperDubboProviderRegistration`
115+
- `com.reactor.rust.dubbo.provider.jdbc.JdbcRepository`
116+
- `com.reactor.rust.dubbo.provider.jdbc.HikariDataSources`
115117

116118
Classes under `com.reactor.rust.dubbo.internal.*` are implementation details. They are separated by responsibility so the runtime is easier to maintain:
117119

@@ -157,6 +159,11 @@ List<DubboProviderSupport.ExportedService<?>> exported =
157159
BEST: keep the service list explicit and move only duplicated lifecycle code to these helpers.
158160
ANTI-PATTERN: adding an automatic provider scanner that exports every interface on the classpath.
159161

162+
Provider-side DB helpers are optional. Use them when a plain Dubbo provider needs the same
163+
low-boilerplate JDBC/Hikari lifecycle pattern as the sample provider. They do not generate SQL and
164+
they do not map rows automatically. Your provider still owns SQL, indexes, row mapping, transaction
165+
boundaries, and write idempotency.
166+
160167
## Quick Start
161168

162169
This section shows the complete flow for adding a Dubbo consumer to a Java/Rust REST application.
@@ -498,12 +505,12 @@ mvn clean verify
498505

499506
Release artifacts are produced under `target/`:
500507

501-
- `java-rust-dubbo-0.2.1.jar`
502-
- `java-rust-dubbo-0.2.1-native-static.jar`
503-
- `java-rust-dubbo-0.2.1-sources.jar`
508+
- `java-rust-dubbo-0.2.2.jar`
509+
- `java-rust-dubbo-0.2.2-native-static.jar`
510+
- `java-rust-dubbo-0.2.2-sources.jar`
504511

505512
## Documentation
506513

507514
- [Production Guide](docs/PRODUCTION_GUIDE.md)
508-
- [Release Notes](docs/RELEASE_NOTES_v0.2.1.md)
515+
- [Release Notes](docs/RELEASE_NOTES_v0.2.2.md)
509516
- [Turkish README](README.tr.md)

README.tr.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Bu kütüphane, "dependency ekleyince her şeyi otomatik yapsın" yaklaşımınd
3636
<dependency>
3737
<groupId>com.reactor</groupId>
3838
<artifactId>java-rust-dubbo</artifactId>
39-
<version>0.2.1</version>
39+
<version>0.2.2</version>
4040
</dependency>
4141
```
4242

@@ -82,7 +82,7 @@ En küçük static-provider native kurulum için full JAR yerine `native-static`
8282
<dependency>
8383
<groupId>com.reactor</groupId>
8484
<artifactId>java-rust-dubbo</artifactId>
85-
<version>0.2.1</version>
85+
<version>0.2.2</version>
8686
<classifier>native-static</classifier>
8787
</dependency>
8888
```
@@ -117,6 +117,8 @@ Sık kullanılan provider sınıfları:
117117
- `DubboProviderSupport`
118118
- `PlainDubboProvider`
119119
- `ZookeeperDubboProviderRegistration`
120+
- `com.reactor.rust.dubbo.provider.jdbc.JdbcRepository`
121+
- `com.reactor.rust.dubbo.provider.jdbc.HikariDataSources`
120122

121123
`com.reactor.rust.dubbo.internal.*` altındaki sınıflar kütüphanenin iç uygulama detaylarıdır. Bu paketler sorumluluklarına göre ayrılmıştır:
122124

@@ -162,6 +164,11 @@ List<DubboProviderSupport.ExportedService<?>> exported =
162164
BEST: servis listesini explicit tutun, sadece tekrar eden lifecycle kodunu bu yardımcı sınıflara
163165
taşıyın. ANTI-PATTERN: classpath'teki her interface'i otomatik export eden gizli scanner eklemek.
164166

167+
Provider tarafındaki DB yardımcıları opsiyoneldir. Plain Dubbo provider aynı sample provider gibi
168+
düşük boilerplate JDBC/Hikari lifecycle kalıbı istiyorsa kullanılmalıdır. Bu sınıflar SQL üretmez ve
169+
row mapping'i otomatik tahmin etmez. SQL, index, row mapping, transaction sınırı ve write idempotency
170+
kararı provider uygulamasında kalır.
171+
165172
## Hızlı Başlangıç
166173

167174
Bu bölüm, bir Java/Rust REST uygulamasını adım adım Dubbo consumer haline getirir.
@@ -497,6 +504,6 @@ mvn clean verify
497504

498505
Üretilen paketler:
499506

500-
- `target/java-rust-dubbo-0.2.1.jar`
501-
- `target/java-rust-dubbo-0.2.1-native-static.jar`
502-
- `target/java-rust-dubbo-0.2.1-sources.jar`
507+
- `target/java-rust-dubbo-0.2.2.jar`
508+
- `target/java-rust-dubbo-0.2.2-native-static.jar`
509+
- `target/java-rust-dubbo-0.2.2-sources.jar`

docs/RELEASE_NOTES_v0.2.2.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# java-rust-dubbo 0.2.2
2+
3+
`0.2.2` keeps the native Dubbo behavior unchanged and adds optional provider-side JDBC helpers for
4+
plain Java Dubbo providers.
5+
6+
## What Changed For Users
7+
8+
- `com.reactor.rust.dubbo.provider.jdbc.JdbcRepository` is available for repeated JDBC
9+
connection/query/lifecycle code.
10+
- `com.reactor.rust.dubbo.provider.jdbc.HikariDataSources` can create a Hikari pool from
11+
`sample.db.*`-style properties.
12+
- Hikari is optional. Static/catalog-only providers do not need to load it.
13+
- Provider SQL and row mapping stay explicit in application code.
14+
- Existing consumer APIs and native response handle paths are unchanged.
15+
16+
## Provider Example
17+
18+
```java
19+
public final class CustomerRepository extends JdbcRepository {
20+
private CustomerRepository(Properties properties) {
21+
super(HikariDataSources.create(properties, "sample.db"), true);
22+
}
23+
24+
public List<Customer> findCustomers() {
25+
return query("Find customers", SQL, SqlBinder.none(), CustomerRepository::toCustomer);
26+
}
27+
}
28+
```
29+
30+
## Compatibility Notes
31+
32+
- Native ABI did not change.
33+
- `0.2.1` applications can upgrade without changing Dubbo consumer call paths.
34+
- Provider DB helpers are additive. They are not required for non-DB providers.

pom.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.reactor</groupId>
88
<artifactId>java-rust-dubbo</artifactId>
9-
<version>0.2.1</version>
9+
<version>0.2.2</version>
1010
<packaging>jar</packaging>
1111

1212
<name>java-rust-dubbo</name>
@@ -28,6 +28,7 @@
2828
<netty.version>4.2.1.Final</netty.version>
2929
<zookeeper.version>3.7.2</zookeeper.version>
3030
<slf4j.version>1.7.35</slf4j.version>
31+
<hikaricp.version>5.1.0</hikaricp.version>
3132
<junit.version>5.11.4</junit.version>
3233
</properties>
3334

@@ -47,6 +48,12 @@
4748
</dependencyManagement>
4849

4950
<dependencies>
51+
<dependency>
52+
<groupId>com.zaxxer</groupId>
53+
<artifactId>HikariCP</artifactId>
54+
<version>${hikaricp.version}</version>
55+
<optional>true</optional>
56+
</dependency>
5057
<dependency>
5158
<groupId>org.apache.dubbo</groupId>
5259
<artifactId>dubbo-rpc-dubbo</artifactId>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package com.reactor.rust.dubbo.provider.jdbc;
2+
3+
import com.zaxxer.hikari.HikariConfig;
4+
import com.zaxxer.hikari.HikariDataSource;
5+
6+
import java.util.Locale;
7+
import java.util.Properties;
8+
9+
public final class HikariDataSources {
10+
11+
private HikariDataSources() {}
12+
13+
public static HikariDataSource create(Properties properties, String prefix) {
14+
String root = requirePrefix(prefix);
15+
HikariConfig config = new HikariConfig();
16+
config.setPoolName(get(properties, root + ".pool-name"));
17+
config.setDriverClassName(get(properties, root + ".driver-class-name"));
18+
config.setJdbcUrl(get(properties, root + ".jdbc-url"));
19+
config.setUsername(get(properties, root + ".username"));
20+
config.setPassword(getOptional(properties, root + ".password"));
21+
config.setMaximumPoolSize(getInt(properties, root + ".maximum-pool-size"));
22+
config.setMinimumIdle(getInt(properties, root + ".minimum-idle"));
23+
config.setConnectionTimeout(getLong(properties, root + ".connection-timeout-ms"));
24+
config.setValidationTimeout(getLong(properties, root + ".validation-timeout-ms"));
25+
config.setIdleTimeout(getLong(properties, root + ".idle-timeout-ms"));
26+
config.setMaxLifetime(getLong(properties, root + ".max-lifetime-ms"));
27+
config.setLeakDetectionThreshold(getLong(properties, root + ".leak-detection-threshold-ms"));
28+
config.setInitializationFailTimeout(getLong(properties, root + ".initialization-fail-timeout-ms"));
29+
config.setAutoCommit(getBoolean(properties, root + ".auto-commit"));
30+
config.setReadOnly(getBoolean(properties, root + ".read-only"));
31+
config.setRegisterMbeans(getBoolean(properties, root + ".register-mbeans"));
32+
33+
String applicationName = getOptional(properties, root + ".postgresql.application-name");
34+
if (!applicationName.isBlank()) {
35+
config.addDataSourceProperty("ApplicationName", applicationName);
36+
}
37+
return new HikariDataSource(config);
38+
}
39+
40+
private static String get(Properties properties, String key) {
41+
String value = getOptional(properties, key);
42+
if (value.isBlank()) {
43+
throw new IllegalStateException("Missing required JDBC property: " + key);
44+
}
45+
return value;
46+
}
47+
48+
private static String getOptional(Properties properties, String key) {
49+
String value = System.getProperty(key);
50+
if (value == null || value.isBlank()) {
51+
value = System.getenv(toEnvKey(key));
52+
}
53+
if ((value == null || value.isBlank()) && properties != null) {
54+
value = properties.getProperty(key);
55+
}
56+
return value == null ? "" : value.trim();
57+
}
58+
59+
private static int getInt(Properties properties, String key) {
60+
String value = get(properties, key);
61+
try {
62+
return Integer.parseInt(value);
63+
} catch (NumberFormatException e) {
64+
throw new IllegalArgumentException("JDBC property must be an integer: " + key + "=" + value, e);
65+
}
66+
}
67+
68+
private static long getLong(Properties properties, String key) {
69+
String value = get(properties, key);
70+
try {
71+
return Long.parseLong(value);
72+
} catch (NumberFormatException e) {
73+
throw new IllegalArgumentException("JDBC property must be a long: " + key + "=" + value, e);
74+
}
75+
}
76+
77+
private static boolean getBoolean(Properties properties, String key) {
78+
String value = get(properties, key);
79+
if ("true".equalsIgnoreCase(value) || "1".equals(value) || "yes".equalsIgnoreCase(value)) {
80+
return true;
81+
}
82+
if ("false".equalsIgnoreCase(value) || "0".equals(value) || "no".equalsIgnoreCase(value)) {
83+
return false;
84+
}
85+
throw new IllegalArgumentException("JDBC property must be a boolean: " + key + "=" + value);
86+
}
87+
88+
private static String requirePrefix(String prefix) {
89+
if (prefix == null || prefix.isBlank()) {
90+
throw new IllegalArgumentException("prefix must not be blank");
91+
}
92+
return prefix.trim();
93+
}
94+
95+
private static String toEnvKey(String key) {
96+
return key.toUpperCase(Locale.ROOT).replace('.', '_').replace('-', '_');
97+
}
98+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.reactor.rust.dubbo.provider.jdbc;
2+
3+
import javax.sql.DataSource;
4+
import java.sql.Connection;
5+
import java.sql.PreparedStatement;
6+
import java.sql.ResultSet;
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
import java.util.Objects;
10+
11+
public abstract class JdbcRepository implements AutoCloseable {
12+
13+
private final DataSource dataSource;
14+
private final boolean schemaInit;
15+
private volatile boolean initialized;
16+
17+
protected JdbcRepository(DataSource dataSource, boolean schemaInit) {
18+
this.dataSource = Objects.requireNonNull(dataSource, "dataSource");
19+
this.schemaInit = schemaInit;
20+
}
21+
22+
protected final void ensureInitialized() {
23+
if (!schemaInit || initialized) {
24+
return;
25+
}
26+
synchronized (this) {
27+
if (initialized) {
28+
return;
29+
}
30+
try (Connection connection = dataSource.getConnection()) {
31+
initializeSchema(connection);
32+
initialized = true;
33+
} catch (Exception e) {
34+
throw new IllegalStateException("JDBC schema initialization failed", e);
35+
}
36+
}
37+
}
38+
39+
protected void initializeSchema(Connection connection) throws Exception {
40+
// Default: no schema bootstrap.
41+
}
42+
43+
protected final <T> List<T> query(
44+
String operation,
45+
String sql,
46+
SqlBinder binder,
47+
RowMapper<T> mapper) {
48+
ensureInitialized();
49+
try (Connection connection = dataSource.getConnection();
50+
PreparedStatement statement = connection.prepareStatement(sql)) {
51+
binder.bind(statement);
52+
try (ResultSet resultSet = statement.executeQuery()) {
53+
List<T> rows = new ArrayList<>();
54+
while (resultSet.next()) {
55+
rows.add(mapper.map(resultSet));
56+
}
57+
return rows;
58+
}
59+
} catch (Exception e) {
60+
throw new IllegalStateException(operation + " failed", e);
61+
}
62+
}
63+
64+
protected final <T> T queryOne(
65+
String operation,
66+
String sql,
67+
SqlBinder binder,
68+
RowMapper<T> mapper,
69+
T emptyValue) {
70+
ensureInitialized();
71+
try (Connection connection = dataSource.getConnection();
72+
PreparedStatement statement = connection.prepareStatement(sql)) {
73+
binder.bind(statement);
74+
try (ResultSet resultSet = statement.executeQuery()) {
75+
return resultSet.next() ? mapper.map(resultSet) : emptyValue;
76+
}
77+
} catch (Exception e) {
78+
throw new IllegalStateException(operation + " failed", e);
79+
}
80+
}
81+
82+
protected final int update(String operation, String sql, SqlBinder binder) {
83+
ensureInitialized();
84+
try (Connection connection = dataSource.getConnection();
85+
PreparedStatement statement = connection.prepareStatement(sql)) {
86+
binder.bind(statement);
87+
return statement.executeUpdate();
88+
} catch (Exception e) {
89+
throw new IllegalStateException(operation + " failed", e);
90+
}
91+
}
92+
93+
@Override
94+
public void close() {
95+
if (dataSource instanceof AutoCloseable closeable) {
96+
try {
97+
closeable.close();
98+
} catch (Exception e) {
99+
throw new IllegalStateException("JDBC datasource close failed", e);
100+
}
101+
}
102+
}
103+
104+
@FunctionalInterface
105+
public interface SqlBinder {
106+
void bind(PreparedStatement statement) throws Exception;
107+
108+
static SqlBinder none() {
109+
return statement -> {};
110+
}
111+
}
112+
113+
@FunctionalInterface
114+
public interface RowMapper<T> {
115+
T map(ResultSet row) throws Exception;
116+
}
117+
}

0 commit comments

Comments
 (0)