1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 package org.spf4j.jdbc;
33
34 import com.google.common.annotations.Beta;
35 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
36 import java.sql.Connection;
37 import java.sql.SQLException;
38 import java.sql.SQLTimeoutException;
39 import java.util.concurrent.Callable;
40 import java.util.concurrent.TimeUnit;
41 import java.util.concurrent.TimeoutException;
42 import javax.annotation.Nonnegative;
43 import javax.annotation.Signed;
44 import javax.sql.DataSource;
45 import org.spf4j.base.ExecutionContext;
46 import org.spf4j.base.ExecutionContexts;
47 import org.spf4j.base.HandlerNano;
48 import org.spf4j.base.JavaUtils;
49 import org.spf4j.base.TimeSource;
50 import org.spf4j.failsafe.RetryPolicy;
51
52
53
54
55
56
57 @Beta
58 public final class JdbcTemplate {
59
60
61
62
63
64 private static final int MAX_JDBC_TIMEOUTSECONDS =
65 Integer.getInteger("spf4j.jdbc.maxdbcTimeoutSeconds", 3600 * 24);
66
67
68 private final DataSource dataSource;
69
70 private final RetryPolicy<Object, Callable<? extends Object>> retryPolicy;
71
72 public JdbcTemplate(final DataSource dataSource) {
73 this(dataSource, RetryPolicy.newBuilder()
74 .withDefaultThrowableRetryPredicate()
75 .build());
76 }
77
78 @SuppressFBWarnings("EI_EXPOSE_REP2")
79 public JdbcTemplate(final DataSource dataSource, final RetryPolicy<Object, Callable<? extends Object>> retryPolicy) {
80 this.dataSource = dataSource;
81 this.retryPolicy = retryPolicy;
82 }
83
84 public static void checkJdbcObjectName(final CharSequence name) {
85 if (!JavaUtils.isJavaIdentifier(name) || name.length() > 30) {
86 throw new IllegalArgumentException("Invalid database Object identifier " + name);
87 }
88 }
89
90 public <R> R transactOnConnection(final HandlerNano<Connection, R, SQLException> handler,
91 final long timeout, final TimeUnit tu) throws SQLException, InterruptedException {
92 return transactOnConnection(handler, ExecutionContexts.computeDeadline(timeout, tu));
93 }
94
95
96 @SuppressFBWarnings("BED_BOGUS_EXCEPTION_DECLARATION")
97 public <R> R transactOnConnection(final HandlerNano<Connection, R, SQLException> handler,
98 final long deadlineNanos)
99 throws SQLException, InterruptedException {
100 try (ExecutionContext ctx = ExecutionContexts.start(handler.toString(), deadlineNanos)) {
101 return (R) retryPolicy.call(new Callable() {
102 @Override
103 public R call() throws SQLException {
104 try (Connection conn = dataSource.getConnection()) {
105 boolean autocomit = conn.getAutoCommit();
106 if (autocomit) {
107 conn.setAutoCommit(false);
108 }
109 try {
110 R result = handler.handle(conn, ctx.getDeadlineNanos());
111 conn.commit();
112 return result;
113 } catch (SQLException | RuntimeException ex) {
114 conn.rollback();
115 throw ex;
116 } finally {
117 if (autocomit) {
118 conn.setAutoCommit(true);
119 }
120 }
121 }
122 }
123 }, SQLException.class, ctx.getDeadlineNanos());
124 } catch (TimeoutException ex) {
125 throw new SQLTimeoutException(ex);
126 }
127 }
128
129 @SuppressFBWarnings("BED_BOGUS_EXCEPTION_DECLARATION")
130 public <R> R transactOnConnectionNonInterrupt(final HandlerNano<Connection, R, SQLException> handler,
131 final long timeout, final TimeUnit tu)
132 throws SQLException {
133 try {
134 return transactOnConnection(handler, timeout, tu);
135 } catch (InterruptedException ex) {
136 Thread.currentThread().interrupt();
137 throw new SQLException(ex);
138 }
139 }
140
141
142
143
144
145
146 @Nonnegative
147 public static int getTimeoutToDeadlineSeconds(final long deadlineNanos) throws SQLTimeoutException {
148 long toSeconds = TimeUnit.NANOSECONDS.toSeconds(deadlineNanos - TimeSource.nanoTime());
149 if (toSeconds < 0L) {
150 throw new SQLTimeoutException("deadline exceeded by " + (-toSeconds) + " seconds");
151 }
152 if (toSeconds == 0) {
153 return 1;
154 }
155 if (toSeconds > MAX_JDBC_TIMEOUTSECONDS) {
156 return MAX_JDBC_TIMEOUTSECONDS;
157 } else {
158 return (int) toSeconds;
159 }
160 }
161
162 @Signed
163 public static int getTimeoutToDeadlineSecondsNoEx(final long deadlineNanos) {
164 long toSeconds = TimeUnit.NANOSECONDS.toSeconds(deadlineNanos - TimeSource.nanoTime());
165 if (toSeconds == 0) {
166 return 1;
167 }
168 if (toSeconds > MAX_JDBC_TIMEOUTSECONDS) {
169 return MAX_JDBC_TIMEOUTSECONDS;
170 } else {
171 return (int) toSeconds;
172 }
173 }
174
175 @Override
176 public String toString() {
177 return "JdbcTemplate{" + "dataSource=" + dataSource + '}';
178 }
179
180 }