Skip to content

Commit cc1e470

Browse files
authored
Initial attempt at adding jOOQ (StubbornJava#59)
* Initial attempt at adding jOOQ
1 parent f533ce5 commit cc1e470

File tree

10 files changed

+516
-11
lines changed

10 files changed

+516
-11
lines changed

docker/mysql/setup.sh

+2
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22

33
echo 'creating databases'
44
mysql -u root -e 'CREATE DATABASE IF NOT EXISTS stubbornjava;'
5+
6+
mysql -u root -e 'CREATE DATABASE IF NOT EXISTS sj_access;'

stubbornjava-common/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,6 @@ dependencies {
3838
compile libs.jooqCodegen
3939

4040
testCompile libs.junit
41+
testCompile libs.hsqldb
4142
}
4243
// {{end:dependencies}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package com.stubbornjava.common.db;
2+
3+
import java.util.function.Supplier;
4+
5+
import javax.sql.DataSource;
6+
7+
import org.jooq.DSLContext;
8+
import org.slf4j.Logger;
9+
import org.slf4j.LoggerFactory;
10+
11+
import com.stubbornjava.common.Configs;
12+
import com.stubbornjava.common.HealthChecks;
13+
import com.stubbornjava.common.Metrics;
14+
import com.typesafe.config.Config;
15+
import com.zaxxer.hikari.HikariDataSource;
16+
17+
public class DSLs {
18+
private static final Logger logger = LoggerFactory.getLogger(DSLs.class);
19+
private static final Config conf = Configs.properties();
20+
21+
private DSLs() {}
22+
23+
// Letting HikariDataSource leak out on purpose here. It won't go very far.
24+
private enum Transactional {
25+
INSTANCE(ConnectionPool.getDataSourceFromConfig(conf.getConfig("pools.transactional"), Metrics.registry(), HealthChecks.getHealthCheckRegistry()));
26+
private final HikariDataSource dataSource;
27+
private Transactional(HikariDataSource datasource) {
28+
this.dataSource = datasource;
29+
}
30+
public HikariDataSource getDataSource() {
31+
return dataSource;
32+
}
33+
}
34+
private static HikariDataSource getTransactionalDataSource() {
35+
return Transactional.INSTANCE.getDataSource();
36+
}
37+
38+
private enum Processing {
39+
INSTANCE(ConnectionPool.getDataSourceFromConfig(conf.getConfig("pools.processing"), Metrics.registry(), HealthChecks.getHealthCheckRegistry()));
40+
private final HikariDataSource dataSource;
41+
private Processing(HikariDataSource datasource) {
42+
this.dataSource = datasource;
43+
}
44+
public HikariDataSource getDataSource() {
45+
return dataSource;
46+
}
47+
}
48+
49+
private static HikariDataSource getProcessingDataSource() {
50+
return Processing.INSTANCE.getDataSource();
51+
}
52+
53+
public static DSLContext any() {
54+
return ThreadLocalJooqConfig.getCurrentContext();
55+
}
56+
57+
public static DSLContextWrapper transactional() {
58+
return new DSLContextWrapper(Transactional.INSTANCE.getDataSource());
59+
}
60+
61+
public static DSLContextWrapper processing() {
62+
return new DSLContextWrapper(Processing.INSTANCE.getDataSource());
63+
}
64+
65+
public static final class DSLContextWrapper {
66+
private final HikariDataSource ds;
67+
68+
public DSLContextWrapper(HikariDataSource ds) {
69+
this.ds = ds;
70+
}
71+
72+
public final DSLContext get() {
73+
return ThreadLocalJooqConfig.ensureNamedConfiguration(ds.getPoolName());
74+
}
75+
76+
public final void newTransaction(Runnable runnable) {
77+
ThreadLocalJooqConfig.threadLocalTransaction(ds.getPoolName(), ds, runnable);
78+
}
79+
80+
public final <T> T newTransactionResult(Supplier<T> supplier) {
81+
return ThreadLocalJooqConfig.threadLocalTransactionResult(ds.getPoolName(), ds, supplier);
82+
}
83+
}
84+
85+
public static void main(String[] args) {
86+
logger.debug("starting");
87+
DataSource processing = DSLs.getProcessingDataSource();
88+
logger.debug("processing started");
89+
DataSource transactional = DSLs.getTransactionalDataSource();
90+
logger.debug("transactional started");
91+
logger.debug("done");
92+
}
93+
}

stubbornjava-common/src/main/java/com/stubbornjava/common/db/jooq/JooqConfig.java stubbornjava-common/src/main/java/com/stubbornjava/common/db/JooqConfig.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
package com.stubbornjava.common.db.jooq;
1+
package com.stubbornjava.common.db;
22

33
import java.util.Arrays;
44
import java.util.List;
55

66
import javax.sql.DataSource;
77

8+
import org.jooq.Configuration;
89
import org.jooq.SQLDialect;
910
import org.jooq.impl.DataSourceConnectionProvider;
1011
import org.jooq.impl.DefaultConfiguration;
@@ -14,11 +15,14 @@
1415

1516
public class JooqConfig {
1617

17-
public static DefaultConfiguration defaultConfigFromDataSource(DataSource ds) {
18+
public static Configuration defaultConfigFromDataSource(DataSource ds) {
1819
DataSourceConnectionProvider dcp = new DataSourceConnectionProvider(ds);
19-
DefaultConfiguration jooqConfig = new DefaultConfiguration();
20+
Configuration jooqConfig = new DefaultConfiguration();
2021
jooqConfig.set(SQLDialect.MYSQL);
2122
jooqConfig.set(dcp);
23+
//jooqConfig.set(new ThreadLocalTransactionProvider(dcp));
24+
jooqConfig.settings()
25+
.withExecuteWithOptimisticLockingExcludeUnversioned(true);
2226
return jooqConfig;
2327
}
2428

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.stubbornjava.common.db;
2+
3+
import org.jooq.Configuration;
4+
5+
public class NamedConfiguration {
6+
private final String name;
7+
private final Configuration configuration;
8+
9+
public NamedConfiguration(String name, Configuration configuration) {
10+
super();
11+
this.name = name;
12+
this.configuration = configuration;
13+
}
14+
15+
public String getName() {
16+
return name;
17+
}
18+
19+
public Configuration getConfiguration() {
20+
return configuration;
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package com.stubbornjava.common.db;
2+
3+
import java.util.ArrayList;
4+
import java.util.Arrays;
5+
import java.util.Collection;
6+
import java.util.Collections;
7+
import java.util.List;
8+
import java.util.function.Function;
9+
import java.util.function.Supplier;
10+
11+
import org.jooq.Condition;
12+
import org.jooq.DSLContext;
13+
import org.jooq.Field;
14+
import org.jooq.Record;
15+
import org.jooq.RecordMapper;
16+
import org.jooq.RecordUnmapper;
17+
import org.jooq.UniqueKey;
18+
import org.jooq.UpdatableRecord;
19+
import org.jooq.impl.TableImpl;
20+
21+
public class TableCrud<Rec extends UpdatableRecord<Rec>, T> {
22+
private final TableImpl<Rec> table;
23+
private final RecordMapper<Record, T> mapper;
24+
private final RecordUnmapper<T, Rec> unmapper;
25+
private final Supplier<DSLContext> configSupplier;
26+
public TableCrud(TableImpl<Rec> table,
27+
RecordMapper<Rec, T> mapper,
28+
RecordUnmapper<T, Rec> unmapper,
29+
Supplier<DSLContext> configSupplier) {
30+
super();
31+
this.table = table;
32+
this.mapper = (RecordMapper<Record, T>) mapper;
33+
this.unmapper = unmapper;
34+
this.configSupplier = configSupplier;
35+
}
36+
37+
public T insertReturning(T obj) {
38+
Rec rec = records(Collections.singletonList(obj), false).get(0);
39+
rec.insert();
40+
return rec.map(mapper);
41+
}
42+
43+
public void insert(T obj) {
44+
insert(Collections.singletonList(obj));
45+
}
46+
47+
@SuppressWarnings("unchecked")
48+
public void insert(T... objects) {
49+
insert(Arrays.asList(objects));
50+
}
51+
52+
public void insert(Collection<T> objects) {
53+
// Execute a batch INSERT
54+
if (objects.size() > 1) {
55+
configSupplier.get().batchInsert(records(objects, false)).execute();
56+
}
57+
58+
// Execute a regular INSERT
59+
else if (objects.size() == 1) {
60+
records(objects, false).get(0).insert();
61+
}
62+
}
63+
64+
public void update(T obj) {
65+
update(Collections.singletonList(obj));
66+
}
67+
68+
@SuppressWarnings("unchecked")
69+
public void update(T... objects) {
70+
update(Arrays.asList(objects));
71+
}
72+
73+
public void update(Collection<T> objects) {
74+
// Execute a batch UPDATE
75+
if (objects.size() > 1) {
76+
configSupplier.get().batchUpdate(records(objects, false)).execute();
77+
}
78+
79+
// Execute a regular UPDATE
80+
else if (objects.size() == 1) {
81+
records(objects, false).get(0).update();
82+
}
83+
}
84+
85+
public void delete(T obj) {
86+
delete(Collections.singletonList(obj));
87+
}
88+
89+
@SuppressWarnings("unchecked")
90+
public void delete(T... objects) {
91+
delete(Arrays.asList(objects));
92+
}
93+
94+
public void delete(Collection<T> objects) {
95+
// Execute a batch DELETE
96+
if (objects.size() > 1) {
97+
configSupplier.get().batchDelete(records(objects, false)).execute();
98+
}
99+
100+
// Execute a regular DELETE
101+
else if (objects.size() == 1) {
102+
records(objects, false).get(0).delete();
103+
}
104+
}
105+
106+
public T findOne(Function<TableImpl<Rec>, Condition> func) {
107+
return configSupplier.get().fetchOne(table, func.apply(table)).map(mapper);
108+
}
109+
110+
public List<T> find(Function<TableImpl<Rec>, Condition> func) {
111+
return configSupplier.get().fetch(table, func.apply(table)).map(mapper);
112+
}
113+
114+
public int deleteWhere(Function<TableImpl<Rec>, Condition> func) {
115+
return configSupplier.get().deleteFrom(table).where(func.apply(table)).execute();
116+
}
117+
118+
// Copy pasted from jOOQ's DAOImpl.java
119+
private /* non-final */ Field<?>[] pk() {
120+
UniqueKey<?> key = table.getPrimaryKey();
121+
return key == null ? null : key.getFieldsArray();
122+
}
123+
124+
// Copy pasted from jOOQ's DAOImpl.java
125+
private /* non-final */ List<Rec> records(Collection<T> objects, boolean forUpdate) {
126+
List<Rec> result = new ArrayList<>();
127+
Field<?>[] pk = pk();
128+
129+
for (T object : objects) {
130+
Rec record = unmapper.unmap(object);
131+
record.attach(configSupplier.get().configuration());
132+
133+
if (forUpdate && pk != null)
134+
for (Field<?> field : pk)
135+
record.changed(field, false);
136+
137+
resetChangedOnNotNull(record);
138+
result.add(record);
139+
}
140+
141+
return result;
142+
}
143+
144+
// Copy pasted from jOOQ's Tools.java
145+
/**
146+
* [#2700] [#3582] If a POJO attribute is NULL, but the column is NOT NULL
147+
* then we should let the database apply DEFAULT values
148+
*/
149+
private static final void resetChangedOnNotNull(Record record) {
150+
int size = record.size();
151+
152+
for (int i = 0; i < size; i++)
153+
if (record.get(i) == null)
154+
if (!record.field(i).getDataType().nullable())
155+
record.changed(i, false);
156+
}
157+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package com.stubbornjava.common.db;
2+
3+
import java.util.function.Supplier;
4+
5+
import javax.sql.DataSource;
6+
7+
import org.jooq.Configuration;
8+
import org.jooq.DSLContext;
9+
import org.jooq.impl.DSL;
10+
import org.slf4j.Logger;
11+
import org.slf4j.LoggerFactory;
12+
13+
import com.google.common.base.Preconditions;
14+
15+
public class ThreadLocalJooqConfig {
16+
private static final Logger log = LoggerFactory.getLogger(ThreadLocalJooqConfig.class);
17+
private static final ThreadLocal<NamedConfiguration> configurations = new ThreadLocal<>();
18+
19+
// This is used to fetch any currently existing configuration.
20+
public static DSLContext getCurrentContext() {
21+
return DSL.using(safeGet().getConfiguration());
22+
}
23+
24+
// Make sure we have the expected named config.
25+
// This is useful if we know we are running a long running query
26+
// and want to ensure we are using a different connection pool than
27+
// the default pool.
28+
public static DSLContext ensureNamedConfiguration(String name) {
29+
NamedConfiguration config = safeGet();
30+
if (!name.equals(config.getName())) {
31+
log.error("Expected to find a {} configuration but found {}", name, config.getName());
32+
throw new IllegalStateException(String.format("Expected to find a %s configuration but found %s", name, config.getName()));
33+
}
34+
return DSL.using(config.getConfiguration());
35+
}
36+
37+
private static void setNamedConfiguration(String name, Configuration configuration) {
38+
configurations.set(new NamedConfiguration(name, configuration));
39+
log.debug("Set ThreadLocal configuration with name {}", name);
40+
}
41+
42+
public static void threadLocalTransaction(String name, DataSource ds, Runnable runnable) {
43+
threadLocalTransactionResult(name, ds, () -> { runnable.run(); return null;} );
44+
}
45+
46+
public static <T> T threadLocalTransactionResult(String name, DataSource ds, Supplier<T> supplier) {
47+
return DSL.using(JooqConfig.defaultConfigFromDataSource(ds))
48+
.transactionResult(ctx -> {
49+
try {
50+
ThreadLocalJooqConfig.setNamedConfiguration(name, ctx);
51+
return supplier.get();
52+
} finally {
53+
configurations.remove();
54+
log.debug("Removed ThreadLocal configuration with name {}", name);
55+
}
56+
});
57+
}
58+
59+
private static NamedConfiguration safeGet() {
60+
NamedConfiguration config = configurations.get();
61+
Preconditions.checkNotNull(config, "No Configuration has been initialized on this thread.");
62+
log.debug("Found ThreadLocal configuration with name {}", config.getName());
63+
return config;
64+
}
65+
}

0 commit comments

Comments
 (0)