1 | /* |
2 | Copyright (C) 2004 MySQL AB |
3 | |
4 | This program is free software; you can redistribute it and/or modify |
5 | it under the terms of version 2 of the GNU General Public License as |
6 | published by the Free Software Foundation. |
7 | |
8 | There are special exceptions to the terms and conditions of the GPL |
9 | as it is applied to this software. View the full text of the |
10 | exception in file EXCEPTIONS-CONNECTOR-J in the directory of this |
11 | software distribution. |
12 | |
13 | This program is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | GNU General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU General Public License |
19 | along with this program; if not, write to the Free Software |
20 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
21 | */ |
22 | package com.mysql.jdbc; |
23 | |
24 | import java.sql.CallableStatement; |
25 | import java.sql.DatabaseMetaData; |
26 | import java.sql.PreparedStatement; |
27 | import java.sql.SQLException; |
28 | import java.sql.SQLWarning; |
29 | import java.sql.Savepoint; |
30 | import java.sql.Statement; |
31 | import java.util.Map; |
32 | import java.util.Properties; |
33 | |
34 | /** |
35 | * Connection that opens two connections, one two a replication master, and |
36 | * another to one or more slaves, and decides to use master when the connection |
37 | * is not read-only, and use slave(s) when the connection is read-only. |
38 | * |
39 | * @version $Id: ReplicationConnection.java,v 1.1.2.1 2005/05/13 18:58:38 |
40 | * mmatthews Exp $ |
41 | */ |
42 | public class ReplicationConnection implements java.sql.Connection { |
43 | private Connection currentConnection; |
44 | |
45 | private Connection masterConnection; |
46 | |
47 | private Connection slavesConnection; |
48 | |
49 | public ReplicationConnection(Properties masterProperties, |
50 | Properties slaveProperties) throws SQLException { |
51 | Driver driver = new Driver(); |
52 | |
53 | StringBuffer masterUrl = new StringBuffer("jdbc:mysql://"); |
54 | StringBuffer slaveUrl = new StringBuffer("jdbc:mysql://"); |
55 | |
56 | String masterHost = masterProperties |
57 | .getProperty(NonRegisteringDriver.HOST_PROPERTY_KEY); |
58 | |
59 | if (masterHost != null) { |
60 | masterUrl.append(masterHost); |
61 | } |
62 | |
63 | String slaveHost = slaveProperties |
64 | .getProperty(NonRegisteringDriver.HOST_PROPERTY_KEY); |
65 | |
66 | if (slaveHost != null) { |
67 | slaveUrl.append(slaveHost); |
68 | } |
69 | |
70 | String masterDb = masterProperties |
71 | .getProperty(NonRegisteringDriver.DBNAME_PROPERTY_KEY); |
72 | |
73 | masterUrl.append("/"); |
74 | |
75 | if (masterDb != null) { |
76 | masterUrl.append(masterDb); |
77 | } |
78 | |
79 | String slaveDb = slaveProperties |
80 | .getProperty(NonRegisteringDriver.DBNAME_PROPERTY_KEY); |
81 | |
82 | slaveUrl.append("/"); |
83 | |
84 | if (slaveDb != null) { |
85 | slaveUrl.append(slaveDb); |
86 | } |
87 | |
88 | this.masterConnection = (com.mysql.jdbc.Connection) driver.connect( |
89 | masterUrl.toString(), masterProperties); |
90 | this.slavesConnection = (com.mysql.jdbc.Connection) driver.connect( |
91 | slaveUrl.toString(), slaveProperties); |
92 | |
93 | this.currentConnection = this.masterConnection; |
94 | } |
95 | |
96 | /* |
97 | * (non-Javadoc) |
98 | * |
99 | * @see java.sql.Connection#clearWarnings() |
100 | */ |
101 | public synchronized void clearWarnings() throws SQLException { |
102 | this.currentConnection.clearWarnings(); |
103 | } |
104 | |
105 | /* |
106 | * (non-Javadoc) |
107 | * |
108 | * @see java.sql.Connection#close() |
109 | */ |
110 | public synchronized void close() throws SQLException { |
111 | this.masterConnection.close(); |
112 | this.slavesConnection.close(); |
113 | } |
114 | |
115 | /* |
116 | * (non-Javadoc) |
117 | * |
118 | * @see java.sql.Connection#commit() |
119 | */ |
120 | public synchronized void commit() throws SQLException { |
121 | this.currentConnection.commit(); |
122 | } |
123 | |
124 | /* |
125 | * (non-Javadoc) |
126 | * |
127 | * @see java.sql.Connection#createStatement() |
128 | */ |
129 | public Statement createStatement() throws SQLException { |
130 | return this.currentConnection.createStatement(); |
131 | } |
132 | |
133 | /* |
134 | * (non-Javadoc) |
135 | * |
136 | * @see java.sql.Connection#createStatement(int, int) |
137 | */ |
138 | public synchronized Statement createStatement(int resultSetType, |
139 | int resultSetConcurrency) throws SQLException { |
140 | return this.currentConnection.createStatement(resultSetType, |
141 | resultSetConcurrency); |
142 | } |
143 | |
144 | /* |
145 | * (non-Javadoc) |
146 | * |
147 | * @see java.sql.Connection#createStatement(int, int, int) |
148 | */ |
149 | public synchronized Statement createStatement(int resultSetType, |
150 | int resultSetConcurrency, int resultSetHoldability) |
151 | throws SQLException { |
152 | return this.currentConnection.createStatement(resultSetType, |
153 | resultSetConcurrency, resultSetHoldability); |
154 | } |
155 | |
156 | /* |
157 | * (non-Javadoc) |
158 | * |
159 | * @see java.sql.Connection#getAutoCommit() |
160 | */ |
161 | public synchronized boolean getAutoCommit() throws SQLException { |
162 | return this.currentConnection.getAutoCommit(); |
163 | } |
164 | |
165 | /* |
166 | * (non-Javadoc) |
167 | * |
168 | * @see java.sql.Connection#getCatalog() |
169 | */ |
170 | public synchronized String getCatalog() throws SQLException { |
171 | return this.currentConnection.getCatalog(); |
172 | } |
173 | |
174 | public synchronized Connection getCurrentConnection() { |
175 | return this.currentConnection; |
176 | } |
177 | |
178 | /* |
179 | * (non-Javadoc) |
180 | * |
181 | * @see java.sql.Connection#getHoldability() |
182 | */ |
183 | public synchronized int getHoldability() throws SQLException { |
184 | return this.currentConnection.getHoldability(); |
185 | } |
186 | |
187 | public synchronized Connection getMasterConnection() { |
188 | return this.masterConnection; |
189 | } |
190 | |
191 | /* |
192 | * (non-Javadoc) |
193 | * |
194 | * @see java.sql.Connection#getMetaData() |
195 | */ |
196 | public synchronized DatabaseMetaData getMetaData() throws SQLException { |
197 | return this.currentConnection.getMetaData(); |
198 | } |
199 | |
200 | public synchronized Connection getSlavesConnection() { |
201 | return this.slavesConnection; |
202 | } |
203 | |
204 | /* |
205 | * (non-Javadoc) |
206 | * |
207 | * @see java.sql.Connection#getTransactionIsolation() |
208 | */ |
209 | public synchronized int getTransactionIsolation() throws SQLException { |
210 | return this.currentConnection.getTransactionIsolation(); |
211 | } |
212 | |
213 | /* |
214 | * (non-Javadoc) |
215 | * |
216 | * @see java.sql.Connection#getTypeMap() |
217 | */ |
218 | public synchronized Map getTypeMap() throws SQLException { |
219 | return this.currentConnection.getTypeMap(); |
220 | } |
221 | |
222 | /* |
223 | * (non-Javadoc) |
224 | * |
225 | * @see java.sql.Connection#getWarnings() |
226 | */ |
227 | public synchronized SQLWarning getWarnings() throws SQLException { |
228 | return this.currentConnection.getWarnings(); |
229 | } |
230 | |
231 | /* |
232 | * (non-Javadoc) |
233 | * |
234 | * @see java.sql.Connection#isClosed() |
235 | */ |
236 | public synchronized boolean isClosed() throws SQLException { |
237 | return this.currentConnection.isClosed(); |
238 | } |
239 | |
240 | /* |
241 | * (non-Javadoc) |
242 | * |
243 | * @see java.sql.Connection#isReadOnly() |
244 | */ |
245 | public synchronized boolean isReadOnly() throws SQLException { |
246 | return this.currentConnection == this.slavesConnection; |
247 | } |
248 | |
249 | /* |
250 | * (non-Javadoc) |
251 | * |
252 | * @see java.sql.Connection#nativeSQL(java.lang.String) |
253 | */ |
254 | public synchronized String nativeSQL(String sql) throws SQLException { |
255 | return this.currentConnection.nativeSQL(sql); |
256 | } |
257 | |
258 | /* |
259 | * (non-Javadoc) |
260 | * |
261 | * @see java.sql.Connection#prepareCall(java.lang.String) |
262 | */ |
263 | public CallableStatement prepareCall(String sql) throws SQLException { |
264 | return this.currentConnection.prepareCall(sql); |
265 | } |
266 | |
267 | /* |
268 | * (non-Javadoc) |
269 | * |
270 | * @see java.sql.Connection#prepareCall(java.lang.String, int, int) |
271 | */ |
272 | public synchronized CallableStatement prepareCall(String sql, |
273 | int resultSetType, int resultSetConcurrency) throws SQLException { |
274 | return this.currentConnection.prepareCall(sql, resultSetType, |
275 | resultSetConcurrency); |
276 | } |
277 | |
278 | /* |
279 | * (non-Javadoc) |
280 | * |
281 | * @see java.sql.Connection#prepareCall(java.lang.String, int, int, int) |
282 | */ |
283 | public synchronized CallableStatement prepareCall(String sql, |
284 | int resultSetType, int resultSetConcurrency, |
285 | int resultSetHoldability) throws SQLException { |
286 | return this.currentConnection.prepareCall(sql, resultSetType, |
287 | resultSetConcurrency, resultSetHoldability); |
288 | } |
289 | |
290 | /* |
291 | * (non-Javadoc) |
292 | * |
293 | * @see java.sql.Connection#prepareStatement(java.lang.String) |
294 | */ |
295 | public PreparedStatement prepareStatement(String sql) throws SQLException { |
296 | return this.currentConnection.prepareStatement(sql); |
297 | } |
298 | |
299 | /* |
300 | * (non-Javadoc) |
301 | * |
302 | * @see java.sql.Connection#prepareStatement(java.lang.String, int) |
303 | */ |
304 | public synchronized PreparedStatement prepareStatement(String sql, |
305 | int autoGeneratedKeys) throws SQLException { |
306 | return this.currentConnection.prepareStatement(sql, autoGeneratedKeys); |
307 | } |
308 | |
309 | /* |
310 | * (non-Javadoc) |
311 | * |
312 | * @see java.sql.Connection#prepareStatement(java.lang.String, int, int) |
313 | */ |
314 | public synchronized PreparedStatement prepareStatement(String sql, |
315 | int resultSetType, int resultSetConcurrency) throws SQLException { |
316 | return this.currentConnection.prepareStatement(sql, resultSetType, |
317 | resultSetConcurrency); |
318 | } |
319 | |
320 | /* |
321 | * (non-Javadoc) |
322 | * |
323 | * @see java.sql.Connection#prepareStatement(java.lang.String, int, int, |
324 | * int) |
325 | */ |
326 | public synchronized PreparedStatement prepareStatement(String sql, |
327 | int resultSetType, int resultSetConcurrency, |
328 | int resultSetHoldability) throws SQLException { |
329 | return this.currentConnection.prepareStatement(sql, resultSetType, |
330 | resultSetConcurrency, resultSetHoldability); |
331 | } |
332 | |
333 | /* |
334 | * (non-Javadoc) |
335 | * |
336 | * @see java.sql.Connection#prepareStatement(java.lang.String, int[]) |
337 | */ |
338 | public synchronized PreparedStatement prepareStatement(String sql, |
339 | int[] columnIndexes) throws SQLException { |
340 | return this.currentConnection.prepareStatement(sql, columnIndexes); |
341 | } |
342 | |
343 | /* |
344 | * (non-Javadoc) |
345 | * |
346 | * @see java.sql.Connection#prepareStatement(java.lang.String, |
347 | * java.lang.String[]) |
348 | */ |
349 | public synchronized PreparedStatement prepareStatement(String sql, |
350 | String[] columnNames) throws SQLException { |
351 | return this.currentConnection.prepareStatement(sql, columnNames); |
352 | } |
353 | |
354 | /* |
355 | * (non-Javadoc) |
356 | * |
357 | * @see java.sql.Connection#releaseSavepoint(java.sql.Savepoint) |
358 | */ |
359 | public synchronized void releaseSavepoint(Savepoint savepoint) |
360 | throws SQLException { |
361 | this.currentConnection.releaseSavepoint(savepoint); |
362 | } |
363 | |
364 | /* |
365 | * (non-Javadoc) |
366 | * |
367 | * @see java.sql.Connection#rollback() |
368 | */ |
369 | public synchronized void rollback() throws SQLException { |
370 | this.currentConnection.rollback(); |
371 | } |
372 | |
373 | /* |
374 | * (non-Javadoc) |
375 | * |
376 | * @see java.sql.Connection#rollback(java.sql.Savepoint) |
377 | */ |
378 | public synchronized void rollback(Savepoint savepoint) throws SQLException { |
379 | this.currentConnection.rollback(savepoint); |
380 | } |
381 | |
382 | /* |
383 | * (non-Javadoc) |
384 | * |
385 | * @see java.sql.Connection#setAutoCommit(boolean) |
386 | */ |
387 | public synchronized void setAutoCommit(boolean autoCommit) |
388 | throws SQLException { |
389 | this.currentConnection.setAutoCommit(autoCommit); |
390 | } |
391 | |
392 | /* |
393 | * (non-Javadoc) |
394 | * |
395 | * @see java.sql.Connection#setCatalog(java.lang.String) |
396 | */ |
397 | public synchronized void setCatalog(String catalog) throws SQLException { |
398 | this.currentConnection.setCatalog(catalog); |
399 | } |
400 | |
401 | /* |
402 | * (non-Javadoc) |
403 | * |
404 | * @see java.sql.Connection#setHoldability(int) |
405 | */ |
406 | public synchronized void setHoldability(int holdability) |
407 | throws SQLException { |
408 | this.currentConnection.setHoldability(holdability); |
409 | } |
410 | |
411 | /* |
412 | * (non-Javadoc) |
413 | * |
414 | * @see java.sql.Connection#setReadOnly(boolean) |
415 | */ |
416 | public synchronized void setReadOnly(boolean readOnly) throws SQLException { |
417 | if (readOnly) { |
418 | if (currentConnection != slavesConnection) { |
419 | switchToSlavesConnection(); |
420 | } |
421 | } else { |
422 | if (currentConnection != masterConnection) { |
423 | switchToMasterConnection(); |
424 | } |
425 | } |
426 | } |
427 | |
428 | /* |
429 | * (non-Javadoc) |
430 | * |
431 | * @see java.sql.Connection#setSavepoint() |
432 | */ |
433 | public synchronized Savepoint setSavepoint() throws SQLException { |
434 | return this.currentConnection.setSavepoint(); |
435 | } |
436 | |
437 | /* |
438 | * (non-Javadoc) |
439 | * |
440 | * @see java.sql.Connection#setSavepoint(java.lang.String) |
441 | */ |
442 | public synchronized Savepoint setSavepoint(String name) throws SQLException { |
443 | return this.currentConnection.setSavepoint(name); |
444 | } |
445 | |
446 | /* |
447 | * (non-Javadoc) |
448 | * |
449 | * @see java.sql.Connection#setTransactionIsolation(int) |
450 | */ |
451 | public synchronized void setTransactionIsolation(int level) |
452 | throws SQLException { |
453 | this.currentConnection.setTransactionIsolation(level); |
454 | } |
455 | |
456 | // For testing |
457 | |
458 | /* |
459 | * (non-Javadoc) |
460 | * |
461 | * @see java.sql.Connection#setTypeMap(java.util.Map) |
462 | */ |
463 | public synchronized void setTypeMap(Map arg0) throws SQLException { |
464 | this.currentConnection.setTypeMap(arg0); |
465 | } |
466 | |
467 | private synchronized void switchToMasterConnection() throws SQLException { |
468 | swapConnections(this.masterConnection, this.slavesConnection); |
469 | } |
470 | |
471 | private synchronized void switchToSlavesConnection() throws SQLException { |
472 | swapConnections(this.slavesConnection, this.masterConnection); |
473 | } |
474 | |
475 | /** |
476 | * Swaps current context (catalog, autocommit and txn_isolation) from |
477 | * sourceConnection to targetConnection, and makes targetConnection |
478 | * the "current" connection that will be used for queries. |
479 | * |
480 | * @param switchToConnection the connection to swap from |
481 | * @param switchFromConnection the connection to swap to |
482 | * |
483 | * @throws SQLException if an error occurs |
484 | */ |
485 | private synchronized void swapConnections(Connection switchToConnection, |
486 | Connection switchFromConnection) throws SQLException { |
487 | String switchFromCatalog = switchFromConnection.getCatalog(); |
488 | String switchToCatalog = switchToConnection.getCatalog(); |
489 | |
490 | if (switchToCatalog != null && !switchToCatalog.equals(switchFromCatalog)) { |
491 | switchToConnection.setCatalog(switchFromCatalog); |
492 | } else if (switchFromCatalog != null) { |
493 | switchToConnection.setCatalog(switchFromCatalog); |
494 | } |
495 | |
496 | boolean switchToAutoCommit = switchToConnection.getAutoCommit(); |
497 | boolean switchFromConnectionAutoCommit = switchFromConnection.getAutoCommit(); |
498 | |
499 | if (switchFromConnectionAutoCommit != switchToAutoCommit) { |
500 | switchToConnection.setAutoCommit(switchFromConnectionAutoCommit); |
501 | } |
502 | |
503 | int switchToIsolation = switchToConnection |
504 | .getTransactionIsolation(); |
505 | |
506 | int switchFromIsolation = switchFromConnection.getTransactionIsolation(); |
507 | |
508 | if (switchFromIsolation != switchToIsolation) { |
509 | switchToConnection |
510 | .setTransactionIsolation(switchFromIsolation); |
511 | } |
512 | |
513 | this.currentConnection = switchToConnection; |
514 | } |
515 | } |