ulrich
2024-01-22 a59fcaadb5c928a243f198c91d69313234e07afe
commit | author | age
c387eb 1 /*
U 2  *  BaseLink - Generic object relational mapping
3  *  Copyright (C) 2011  Ulrich Hilger, http://uhilger.de
4  *
5  *  This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation, either version 3 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program.  If not, see http://www.gnu.org/licenses/
17  */
18 package de.uhilger.baselink;
19
20 import java.sql.Connection;
21 import java.sql.DriverManager;
22 import java.sql.PreparedStatement;
23 import java.sql.ResultSet;
a59fca 24  import java.sql.SQLException;
c387eb 25 import java.sql.Statement;
U 26 import java.util.List;
27 import java.util.Map;
28 import java.util.logging.Level;
29 import java.util.logging.Logger;
30
31 import javax.naming.Context;
32 import javax.naming.InitialContext;
33 import javax.naming.NamingException;
34 import javax.sql.DataSource;
35
36 /**
37  * Class <code>PersistenceManager</code> stores and retrieves Java objects to and from a 
38  * relational database. Any object that implements interface <code>Record</code> can be 
39  * read from or written to a database using this class.
40  * 
41  * <p>In addition, any other class can be used directly as PersistenceManager 
42  * wraps such other objects into <code>GenericRecord</code> objects implicitly.</p>
43  * 
44  * <p>Major aim of class <code>PersistenceManager</code> is to encapsulate the management 
45  * of database related objects such as <code>Connection</code>, <code>Statement</code> and 
46  * <code>ResultSet</code> to make sure proper opening and closing especially when used in 
47  * conjunction with a connection pool.</p>
48  * 
49  * <p>In addition <code>PersistenceManager</code> provides methods that allow for an 
50  * abundance of database read/write operations for convenience providing a base 
51  * class to extend for individual needs.</p> 
52  * 
53  * @author Copyright (c) Ulrich Hilger, <a href="http://uhilger.de">http://uhilger.de</a>
54  * @author Published under the terms and conditions of
55  * the <a href="http://www.gnu.org/licenses/" target="_blank">GNU General Public License</a>
56  * @version 3, March 22, 2011
57  */
58 public class PersistenceManager {
59   
60   private static final Logger logger = Logger.getLogger(PersistenceManager.class.getName());
61
62   public static final String NULL_STR = "null";
63   /** reference to data source to use for connections in production mode */
64   protected DataSource ds;
65   /** reference to JDBC url to use for connections in development mode */
66   protected String dburl;
67
68   /**
69    * Create a new object of class PersistenceManager
70    */
71   public PersistenceManager() {
72     super();
73   }
74
75   /**
76    * Set the JDBC driver class name
77    * @param driverName  name of JDBC driver class name
78    * @throws ClassNotFoundException  when driver is not found
79    */
80   public void setDriverName(String driverName) throws ClassNotFoundException {
81     Class.forName(driverName);
82   }
83
84   /**
85    * Set the database
86    *
87    * <p>Use either this method together with method setDriverName or
88    * method setDataSourceName to indicate the connection type</p>
89    *
90    * @param url  JDBC url
91    */
92   public void setDatabase(String url) {
93     this.dburl = url;
94   }
95
96   /**
97    * Set the database
98    *
99    * <p>Use either this method or method setDataSourceName to indicate the connection type</p>
100    *
101    * @param driverName class name of JDBC driver
102    * @param url JDBC url
103    * @throws ClassNotFoundException  when driver is not found
104    */
105   public void setDatabase(String driverName, String url) throws ClassNotFoundException {
106     Class.forName(driverName);
107     this.dburl = url;
108   }
109
110   /**
111    * Set name of DataSource
112    *
113    * <p>Use either this method or method setDatabase to indicate the connection type</p>
114    *
115    * @param dataSourceName  name of DataSource
116    * @throws NamingException  when JNDI lookup fails
117    */
118   public void setDataSourceName(String dataSourceName) throws NamingException {
e1568a 119     logger.finest(dataSourceName);
c387eb 120     Context initCtx = new InitialContext();
U 121     Context envCtx = (Context) initCtx.lookup("java:comp/env");
122     ds = (DataSource) envCtx.lookup(dataSourceName);
123   }
124
125   /**
126    * Get a database connection
127    * @return  a database connection
128    */
129   public Connection getConnection() {
130     Connection c = null;
131     try {
132       if (dburl != null) {
e1568a 133         logger.finest("getting Connection for URL " + dburl);
c387eb 134         c = DriverManager.getConnection(dburl);
U 135       } else if(ds != null) {
e1568a 136         logger.finest("getting Connection for DataSource " + ds.toString());
c387eb 137         c = ds.getConnection();
U 138       } else {
139         //throw new SQLException("Unable to get connection, DataSource and database URL are null.");
e1568a 140         logger.finest("Unable to get connection, DataSource and database URL are null.");
c387eb 141       }
U 142     } catch(Exception ex) {
e1568a 143       //ex.printStackTrace();
U 144       logger.log(Level.FINEST, ex.getLocalizedMessage(), ex);
c387eb 145     }
U 146     return c;
147   }
148   
149   /* -------------------- deletes ---------------------------- */
150
151   /**
152    * Delete a given object from the database
153    *
154    * <p>Use this method for single deletes. In cases where
155    * several subsequent deletes for objects of the same class
156    * are required the use of method <code>delete(Object, Record)</code>
157    * is recommended instead to minimise instantiation
158    * overhead.</p>
159    *
160    * @param o object to delete
161    * @return  the deleted object
162    */
163   public Object delete(Object o) {
a59fca 164     return new Eraser(this).delete(o, new GenericRecord(o.getClass()));
c387eb 165   }
U 166
167   /**
168    * Delete a given object from the database
169    *
170    * <p>Use this method for single deletes. In cases where
171    * several subsequent deletes for objects of the same class
172    * are required the use of method <code>delete(Connection, Object, Record)</code>
173    * is recommended instead to minimise instantiation
174    * overhead.</p>
175    *
176    * @param c  the connection to use, expected to be open and established
177    * @param o  object to delete
178    * @return  the deleted object
179    */
180   public Object delete(Connection c, Object o) {
a59fca 181     return new Eraser(this).delete(c, o, new GenericRecord(o.getClass()));
c387eb 182   }
U 183
184   /**
185    * Delete a given object from the database
186    * @param o object to delete
187    * @param record  reference to object to use to map between object and database
188    * @return  the deleted object
189    */
190   public Object delete(Object o, Record record) {
a59fca 191     return new Eraser(this).delete(o, record);
c387eb 192   }
U 193
194   /**
195    * Delete a given object from the database
196    * @param c  the connection to use, expected to be open and established
197    * @param o  object to delete
198    * @param record reference to object to use to map between object and database
199    * @return the deleted object
200    */
201   public Object delete(Connection c, Object o, Record record) {
a59fca 202     return new Eraser(this).delete(c, o, record);
c387eb 203   }
U 204   
205   /* ---------------------- inserts -------------------- */
206
207   /**
208    * Add an object to the database
209    *
210    * <p>Use this method for single inserts. In cases where
211    * several subsequent inserts for objects of the same class
212    * are required the use of method <code>insert(Object, Record)</code>
213    * is recommended instead to minimise instantiation
214    * overhead.</p>
215    *
216    * @param o  the object to add
217    * @return  the added object
218    */
219   public Object insert(Object o) {
a59fca 220     return new Inserter(this).insert(o, new GenericRecord(o.getClass()));
c387eb 221   }
U 222
223   /**
224    * Add an object to the database
225    *
226    * <p>Use this method for single inserts. In cases where
227    * several subsequent inserts for objects of the same class
228    * are required the use of method <code>insert(Connection, Object, Record)</code>
229    * is recommended instead to minimise instantiation
230    * overhead.</p>
231    *
232    * @param c  the connection to use, expected to be open and established
233    * @param o  the object to add
234    * @return  the object added to the database
235    */
236   public Object insert(Connection c, Object o) {
a59fca 237     return new Inserter(this).insert(c, o, new GenericRecord(o.getClass()));
c387eb 238   }
U 239
240   /**
241    * Add an object to the database
242    * @param o object to add
243    * @param record  reference to object to use to map between object and database
244    * @return  the added object
245    */
246   public Object insert(Object o, Record record) {
a59fca 247     return new Inserter(this).insert(o, record);
c387eb 248   }
U 249
250   /**
251    * Add an object to the database
252    * @param c the connection to use, expected to be open and established
253    * @param o object to add
254    * @param record reference to object to use to map between object and database
255    * @return  the object that was added
256    */
257   public Object insert(Connection c, Object o, Record record) {
a59fca 258     return new Inserter(this).insert(c, o, record);
c387eb 259   }
U 260   
261   /* --------------------------------- updates --------------------- */
262
263   /**
264    * update an object in the database
265    *
266    * <p>Use this method for single updates. In cases where
267    * several subsequent updates for objects of the same class
268    * are required the use of method <code>update(Object, Record)</code>
269    * is recommended instead to minimise instantiation
270    * overhead.</p>
271    *
272    * @param o  object to update
273    * @return  the object that was updated
274    */
275   public Object update(Object o) {
a59fca 276     return new Updater(this).update(o, new GenericRecord(o.getClass()));
c387eb 277   }
U 278
279   /**
280    * update an object in the database
281    *
282    * <p>Use this method for single updates. In cases where
283    * several subsequent updates for objects of the same class
284    * are required the use of method <code>update(Connection, Object, Record)</code>
285    * is recommended instead to minimise instantiation
286    * overhead.</p>
287    *
288    * @param c the connection to use, expected to be open and established
289    * @param o object to update
290    * @return  the object that was updated
291    */
292   public Object update(Connection c, Object o) {
a59fca 293     return new Updater(this).update(c, o, new GenericRecord(o.getClass()));
c387eb 294   }
U 295
296   /**
297    * update an object in the database
298    * @param o object to update
299    * @param record reference to object to use to map between object and database
300    * @return  the object that was updated
301    */
302   public Object update(Object o, Record record) {
a59fca 303     return new Updater(this).update(o, record);
c387eb 304   }
U 305
306   /**
307    * update an object in the database
308    * @param c the connection to use, expected to be open and established
309    * @param o object to update
310    * @param record reference to object to use to map between object and database
311    * @return  the object that was updated
312    */
313   public Object update(Connection c, Object o, Record record) {
a59fca 314     return new Updater(this).update(c, o, record);
c387eb 315   }
U 316   
317   /* --------------- selects ---------------- */
318
319   /**
320    * Select a list of objects through a given SQL statement
321    * @param sql  sql query string that designates the requested objects
322    * @param record object to use to map db records to objects
323    * @return a list of objects that match the given query
324    */
325   public List<Object> select(String sql, Record record) {
a59fca 326     return new Selector(this).select(sql, record, Record.WITH_BLOBS);
c387eb 327   }
U 328
329   /**
330    * Select a list of objects through a given SQL statement
331    * @param sql  sql query string that designates the requested objects
332    * @param record  object to use to map db records to objects
333    * @param includeBlobs  true when BLOB contents are to be retrieved, false if not
334    * @return  a list of objects that match the given query
335    */
336   public List<Object> select(String sql, Record record, boolean includeBlobs) {
a59fca 337     return new Selector(this).select(sql, record, includeBlobs);
c387eb 338   }
U 339
340   /**
341    * Select a list of objects through a given SQL statement
342    * @param sql  sql query string that designates the requested objects
343    * @param record  object to use to map db records to objects
344    * @param includeBlobs  true when BLOB contents are to be retrieved, false if not
345    * @param params  list of parameters in the order they appear in the SQL string
346    * @return  a list of objects that match the given query
347    */
348   public List<Object> select(String sql, Record record, boolean includeBlobs, Object... params) {
a59fca 349     return new Selector(this).select(sql, record, includeBlobs, params);
c387eb 350   }
U 351
352   /**
353    * Select a list of objects that match a given SQL statement
354    * @param c  the database connection to use for this query, expected to be established and open already
355    * @param sql  sql query string that designates the requested objects
356    * @param record  object to use to map db records to objects
357    * @param includeBlobs  true when BLOB contents are to be retrieved, false if not
358    * @return  a list of objects that match the given query
359    */
360   public List<Object> select(Connection c, String sql, Record record, boolean includeBlobs) {
a59fca 361     return new Selector(this).select(c, sql, record, includeBlobs);
c387eb 362   }
U 363
364   /**
365    * Select a list of objects that match a given SQL statement
366    * @param c  the database connection to use for this query, expected to be established and open already
367    * @param sql  sql query string that designates the requested objects
368    * @param record  object to use to map db records to objects
369    * @param includeBlobs  true when BLOB contents are to be retrieved, false if not
370    * @param params  list of parameters in the order they appear in the SQL string
371    * @return  a list of objects that match the given query
372    */
373   public List<Object> select(Connection c, String sql, Record record, boolean includeBlobs, Object... params) {
a59fca 374     return new Selector(this).select(c, sql, record, includeBlobs, params);
c387eb 375   }
U 376
377   /**
378    * Select a list of objects that match a given SQL statement
379    * @param sql  sql query string that designates the requested objects
380    * @return  a list of map objects, one for each record. An element in the
381    * list can be accessed with list.get(recordno).get("fieldname")
382    */
383   public List<Map<String, Object>> select(String sql) {
a59fca 384     return new Selector(this).select(sql);
c387eb 385   }
U 386
387   /**
388    * Select a list of objects that match a given SQL statement
389    * @param sql  sql query string that designates the requested objects
390    * @param includeBlobs true when content of blob coloumns should be returned, false if not
391    * @return  a list of list objects, one for each record. An element in the
392    * list can be accessed with list.get(recordno).get(fieldno), each element is of type String
393    */
394   public List<List<String>> select(String sql, boolean includeBlobs) {
a59fca 395     return new Selector(this).select(sql, includeBlobs);
c387eb 396   }
U 397
398   /**
399    * Select a list of objects that match a given SQL statement
400    * @param c  the database connection to use for this query, expected to be established and open already
401    * @param sql  sql query string that designates the requested objects
402    * @return  a list of map objects, one for each record. An element in the
403    * list can be accessed with list.get(recordno).get("fieldname")
404    */
405   public List<Map<String, Object>> select(Connection c, String sql) {
a59fca 406     return new Selector(this).select(c, sql);
c387eb 407   }
U 408
409   /**
410    * Select a list of objects that match a given SQL statement
411    * @param c  the database connection to use for this query, expected to be established and open already
412    * @param sql  sql query string that designates the requested objects
413    * @param includeBlobs true when content of blob coloumns should be returned, false if not
414    * @return  a list of list objects, one for each record. An element in the
415    * list can be accessed with list.get(recordno).get(fieldno), each element is of type String
416    */
417   public List<List<String>> select(Connection c, String sql, boolean includeBlobs) {
a59fca 418     return new Selector(this).select(c, sql, includeBlobs);
c387eb 419   }
U 420
421   /**
422    * Select a list of objects that match a given SQL statement
423    * @param sql  sql query string that designates the requested objects with ? at the position of params
424    * @param includeBlobs true when content of blob coloumns should be returned, false if not
425    * @param params list of parameters in the order they appear in the SQL string
426    * @return  a list of list objects, one for each record. An element in the
427    * list can be accessed with list.get(recordno).get(fieldno), each element is of type String
428    */
429   public List<List<String>> select(String sql, boolean includeBlobs, Object... params) {
a59fca 430     return new Selector(this).select(sql, includeBlobs, params);
c387eb 431   }
U 432   
433   /**
434    * Select a list of objects that match a given SQL statement
435    * @param c  the database connection to use for this query, expected to be established and open already
436    * @param sql  sql query string that designates the requested objects with ? at the position of params
437    * @param includeBlobs true when content of blob coloumns should be returned, false if not
438    * @param params list of parameters in the order they appear in the SQL string
439    * @return  a list of list objects, one for each record. An element in the
440    * list can be accessed with list.get(recordno).get(fieldno), each element is of type String
441    */
442   public List<List<String>> select(Connection c, String sql, boolean includeBlobs, Object... params) {
a59fca 443     return new Selector(this).select(c, sql, includeBlobs, params);
c387eb 444   }
U 445   
446   /* ------------------ generic SQL execution ---------- */
447
448   /**
449    * Execute an SQL statement and return keys generated in the database
450    * @param sql  the statement to execute
451    * @return  a list of generated keys
452    */
453   public List<Map<String, Object>> executeWithKeys(String sql) {
a59fca 454     return new Script(this).executeWithKeys(sql);
c387eb 455   }
U 456
457   /**
458    * Execute an SQL statement and return keys generated in the database
459    * @param c  database connection to use
460    * @param sql  the statement to execute
461    * @return  a list of generated keys
462    */
463   public List<Map<String, Object>> executeWithKeys(Connection c, String sql) {
a59fca 464     return new Script(this).executeWithKeys(c, sql);
c387eb 465   }
U 466
467   /**
468    * Execute an SQL statement
469    * @param sql  the statement to execute
470    * @return  the number of records affected by this statement or -1 if none
471    */
472   public int execute(String sql) {
a59fca 473     return new Script(this).execute(sql);
c387eb 474   }
U 475
476   /**
477    * Execute an SQL statement
478    * @param c  database connection to use
479    * @param sql  the statement to execute
480    * @return  the number of records affected by this statement or -1 if none
481    */
482   public int execute(Connection c, String sql) {
a59fca 483     return new Script(this).execute(c, sql);
c387eb 484   }
U 485
486   /**
487    * Execute an SQL statement
488    * @param sql the SQL string with ? at the position of params
489    * @param params list of parameters in the order they appear in the SQL string
490    * @return  the number of records affected by this statement or -1 if none
491    */
492   public int execute(String sql, Object... params) {
a59fca 493     return new Script(this).execute(sql, params);
c387eb 494   }
U 495
496   /**
497    * Execute an SQL statement
498    * @param c  database connection to use
499    * @param sql the SQL string with ? at the position of params
500    * @param params list of parameters in the order they appear in the SQL string
501    * @return  the number of records affected by this statement or -1 if none
502    */
503   public int execute(Connection c, String sql, Object... params) {
a59fca 504     return new Script(this).execute(c, sql, params);
c387eb 505   }
U 506   
507   /**
508    * Execute an SQL script
509    * @param sqlScript  the SQL script to execute
510    * @return an array of row counts referring to the number of affected rows of 
511    * each sql statement in the script in the order of SQL commands in the script 
512    * Statement.EXECUTE_FAILED indicates failure of single steps in the script 
513    * Statement.SUCCESS_NO_INFO indicates successful execution without information 
514    * about the number of affected rows
515    */
516   public int[] executeScript(String sqlScript) {
a59fca 517     return new Script(this).executeScript(sqlScript);
c387eb 518   }
U 519   
520   /**
521    * Execute an SQL script
522    * @param c  the Connection object to use
523    * @param sqlScript  the SQL script to execute
524    * @return an array of row counts referring to the number of affected rows of 
525    * each sql statement in the script in the order of SQL commands in the script 
526    * Statement.EXECUTE_FAILED indicates failure of single steps in the script 
527    * Statement.SUCCESS_NO_INFO indicates successful execution without information 
528    * about the number of affected rows
529    */
530   public int[] executeScript(Connection c, String sqlScript) {
a59fca 531     return new Script(this).executeScript(c, sqlScript);
c387eb 532   }
U 533   
534   /* -------- transactions ------------ */
535   
536   /*
537       24.4.2013: BaseLink wurde um Transaktionen erweitert, 
538       sodass Start und Ende von Transaktionen jeweils mit einer Zeile Code 
539       moeglich ist.
540       
541       Connection c = getConnection();
542       startTransaction(c);
543       // ...hier Daten anlegen und verknuepfen...
544       commit(c);
545       startTransaction(c);
546       // ...hier Daten anlegen und verknuepfen...
547       commit(c);
548       closeConnectionFinally(c);
549       
550   */
551   
552   
553   public void startTransaction(Connection c) {
554     try {
555       c.setAutoCommit(false);
556     } catch(SQLException ex) {
964f7d 557       logger.log(Level.SEVERE, ex.getMessage(), ex);
c387eb 558     } finally {
U 559       // ..
560     }
561   }
562   
563   public void commit(Connection c) {
564     try {
565       c.commit();
566     } catch(SQLException ex) {
964f7d 567       logger.log(Level.SEVERE, ex.getMessage(), ex);
c387eb 568     } finally {
964f7d 569       try {
U 570         c.setAutoCommit(true);
571       } catch (SQLException ex) {
572         logger.log(Level.SEVERE, ex.getMessage(), ex);
573       }
c387eb 574     }
U 575   }
576   
577   public void rollback(Connection c) {
578     try {
579       c.rollback();
580     } catch(SQLException ex) {
581       logger.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
582     } finally {
964f7d 583       try {
U 584         c.setAutoCommit(true);
585       } catch (SQLException ex) {
586         logger.log(Level.SEVERE, ex.getMessage(), ex);
587       }
c387eb 588     }
U 589   }
590   
591   /* -------- closing methods --------- */
592
593   /**
594    * Close a given result set
595    * @param rs  the result set to close
596    */
597   public void closeResultSetFinally(ResultSet rs) {
598     if (rs != null) {
599       try {
600         rs.close();
601         rs = null;
602       } catch (SQLException ex) {
603         logger.log(Level.SEVERE, ex.getMessage(), ex);
604       }
605     }
606   }
607
608   /**
609    * Close a given statement
610    * @param s  the statement to close
611    */
612   public void closeStatementFinally(Statement s) {
613     if (s != null) {
614       try {
615         s.close();
616         s = null;
617       } catch (SQLException ex) {
618         logger.log(Level.SEVERE, ex.getMessage(), ex);
619       }
620     }
621   }
622
623   /**
624    * close a given connection
625    * @param c  the connection to close
626    */
627   public void closeConnectionFinally(Connection c) {
628     if (c != null) {
629       try {
630         c.close();
631         c = null;
632       } catch (SQLException ex) {
633         logger.log(Level.SEVERE, ex.getMessage(), ex);
634       }
635     }
636   }
637   
638   /* ----------------------- helper methods -------------------------- */
639   
640   /**
641    * Create an SQL statement from an SQL string and an array of parameters
642    * @param sql the SQL string with ? at the position of params
643    * @param params list of parameters in the order they appear in the SQL string
644    * @return the SQL statement with given parameters applied
645    */
646   public PreparedStatement buildQuery(Connection c, String sql, Object[] params) throws Exception {
647     PreparedStatement ps =
648       c.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
649     for(int i= 0; i < params.length; i++) {
650       //buildQueryParam(ps, params[i], i);
651       ps.setObject(i+1, params[i]);
652     }
653     return ps;
654   }
655     
656   /**
657    * Helper method that converts a ResultSet into a list of lists, one per row,
658    * each row is a list of strings
659    *
660    * @param rs  result set to convert
661    * @param includeBlobs  true when blob columns should be returned, false if not
662    * @return a list of list objects, one for each record. An element in the
663    * list can be accessed with list.get(recordno).get(fieldno), each element is of type String.
664    * This first row has the field names
665    */
666   public List<List<String>> toList(ResultSet rs, boolean includeBlobs) throws SQLException {
a59fca 667     return new ListConverter().toList(rs, includeBlobs);
c387eb 668   }
U 669
670   /**
671    * Helper method that converts a ResultSet into a list of maps, one per row
672    *
673    * @param rs  database content to transform
674    * @return list of maps, one per row, with column name as the key
675    * @throws SQLException if the connection fails
676    */
a59fca 677   public List<Map<String, Object>> toList(ResultSet rs) throws SQLException {
U 678     return new ListConverter().toList(rs);
c387eb 679   }
U 680
681   /**
682    * Helper method that maps a ResultSet into a list of maps, one per row
683    *
684    * @param rs  database content to transform
685    * @param wantedColumnNames list of columns names to include in the result map
686    * @return list of maps, one per column row, with column names as keys
687    * @throws SQLException if the connection fails
688    */
a59fca 689   public List<Map<String, Object>> toList(ResultSet rs, List<String> wantedColumnNames) throws SQLException {
U 690     return new ListConverter().toList(rs, wantedColumnNames);
c387eb 691   }
U 692
693 }