|
1 | 1 | Data Access |
2 | | -=========== |
| 2 | +=========== |
| 3 | + |
| 4 | +DatabaseTemplate |
| 5 | +---------------- |
| 6 | + |
| 7 | +Writing SQL-based programs has a familiar pattern that must be repeated over |
| 8 | +and over. The DatabaseTemplate resolves that by handling the plumbing of these |
| 9 | +operations while leaving you in control of the part that matters the most, |
| 10 | +the SQL. |
| 11 | + |
| 12 | +External dependencies |
| 13 | ++++++++++++++++++++++ |
| 14 | + |
| 15 | +DatabaseTemplate requires the use of external libraries for connecting to |
| 16 | +SQL databases. Depending on which SQL connection factory you're about to use, |
| 17 | +you need to install following dependencies: |
| 18 | + |
| 19 | +* *springpython.database.factory.MySQLConnectionFactory* - |
| 20 | + needs `MySQLdb <http://sourceforge.net/projects/mysql-python/>`_ for connecting to MySQL, |
| 21 | + |
| 22 | +* *springpython.database.factory.PgdbConnectionFactory* - |
| 23 | + needs `PyGreSQL <http://www.pygresql.org/>`_ for connecting to PostgreSQL, |
| 24 | + |
| 25 | +* *springpython.database.factory.Sqlite3ConnectionFactory* - |
| 26 | + needs `PySQLite <http://pypi.python.org/pypi/pysqlite/>`_ for connecting to SQLite 3, note that PySQLite is part |
| 27 | + of Python 2.5 and later so you need to install it separately only if you're |
| 28 | + using Python 2.4, |
| 29 | + |
| 30 | +* *springpython.database.factory.cxoraConnectionFactory* - |
| 31 | + needs `cx_Oracle <http://pypi.python.org/pypi/cx_Oracle>`_ for connecting to Oracle, |
| 32 | + |
| 33 | +* *springpython.database.factory.SQLServerConnectionFactory* - |
| 34 | + needs `PyODBC <http://pypi.python.org/pypi/pyodbc>`_ for connecting to SQL Server. |
| 35 | + |
| 36 | +Traditional Database Query |
| 37 | +++++++++++++++++++++++++++ |
| 38 | + |
| 39 | +If you have written a database SELECT statement following Python's |
| 40 | +`DB-API 2.0 <http://www.python.org/dev/peps/pep-0249/>`_, it would something |
| 41 | +like this (MySQL example):: |
| 42 | + |
| 43 | + conn = MySQL.connection(username="me", password="secret", hostname="localhost", db="springpython") |
| 44 | + cursor = conn.cursor() |
| 45 | + results = [] |
| 46 | + try: |
| 47 | + cursor.execute("select title, air_date, episode_number, writer from tv_shows where name = %s", ("Monty Python",)) |
| 48 | + for row in cursor.fetchall(): |
| 49 | + tvShow = TvShow(title=row[0], airDate=row[1], episodeNumber=row[2], writer=row[3]) |
| 50 | + results.append(tvShow) |
| 51 | + finally: |
| 52 | + try: |
| 53 | + cursor.close() |
| 54 | + except Exception: |
| 55 | + pass |
| 56 | + conn.close() |
| 57 | + return results |
| 58 | + |
| 59 | +I know, you don't have to open and close a connection for every query, but |
| 60 | +let's look past that part. In every definition of a SQL query, you must create |
| 61 | +a new cursor, execute against the cursor, loop through the results, and most |
| 62 | +importantly (and easy to forget) *close the cursor*. Of course you will wrap this |
| 63 | +in a method instead of plugging in this code where ever you need the information. |
| 64 | +But every time you need another query, you have to repeat this dull pattern over |
| 65 | +and over again. The only thing different is the actual SQL code you must write |
| 66 | +and converting it to a list of objects. |
| 67 | + |
| 68 | +I know there are many object relational mappers (ORMs) out there, but sometimes |
| 69 | +you need something simple and sweet. That is where *DatabaseTemplate* comes in. |
| 70 | + |
| 71 | +Database Template |
| 72 | ++++++++++++++++++ |
| 73 | + |
| 74 | +The same query above can be written using a *DatabaseTemplate*. The only thing |
| 75 | +you must provide is the SQL and a *RowMapper* to process one row of data. The |
| 76 | +template does the rest:: |
| 77 | + |
| 78 | + """ The following part only has to be done once.""" |
| 79 | + from springpython.database.core import * |
| 80 | + from springpython.database.factory import * |
| 81 | + connectionFactory = MySQLConnectionFactory(username="me", password="secret", hostname="localhost", db="springpython") |
| 82 | + dt = DatabaseTemplate(connectionFactory) |
| 83 | + |
| 84 | + class TvShowMapper(RowMapper): |
| 85 | + """This will handle one row of database. It can be reused for many queries if they |
| 86 | + are returning the same columns.""" |
| 87 | + def map_row(self, row, metadata=None): |
| 88 | + return TvShow(title=row[0], airDate=row[1], episodeNumber=row[2], writer=row[3]) |
| 89 | + |
| 90 | + |
| 91 | + results = dt.query("select title, air_date, episode_number, writer from tv_shows where name = %s", \ |
| 92 | + ("Monty Python",), TvShowMapper()) |
| 93 | + |
| 94 | +Well, no sign of a cursor anywhere. If you didn't have to worry about opening |
| 95 | +it, you don't have to worry about closing it. I know this is about the same |
| 96 | +amount of code as the traditional example. Where DatabaseTemplate starts to |
| 97 | +shine is when you want to write ten different TV_SHOW queries:: |
| 98 | + |
| 99 | + results = dt.query("select title, air_date, episode_number, writer from tv_shows where episode_number < %s", \ |
| 100 | + (100,), TvShowMapper()) |
| 101 | + results = dt.query("select title, air_date, episode_number, writer from tv_shows where upper(title) like %s", \ |
| 102 | + ("%CHEESE%",), TvShowMapper()) |
| 103 | + results = dt.query("select title, air_date, episode_number, writer from tv_shows where writer in ('Cleese', 'Graham')", |
| 104 | + rowhandler=TvShowMapper()) |
| 105 | + |
| 106 | +You don't have to reimplement the rowhandler. For these queries, you can focus |
| 107 | +on the SQL you want to write, not the mind-numbing job of managing database |
| 108 | +cursors. |
| 109 | + |
| 110 | +Mapping rows into objects using convention over configuration |
| 111 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| 112 | + |
| 113 | +A powerful feature provided by databases is the ability to look up column names. |
| 114 | +Spring Python harnesses this by providing an out-of-the-box row mapper that |
| 115 | +will automatically try matching a query column name to an class attribute name. |
| 116 | +This is known as *convention over configuration* because it relieves you of the |
| 117 | +need to code the *RowMapper* provided you follow the convention of naming the |
| 118 | +attributes of your :abbr:`POPO (Plain Old Python Object)` after query columns. |
| 119 | +The only requirement is that class have a default constructor that doesn't |
| 120 | +require any arguments:: |
| 121 | + |
| 122 | + results = dt.query("select title, air_date, episode_number, writer from tv_shows where episode_number < %s", \ |
| 123 | + (100,), SimpleRowMapper(TvShow)) |
| 124 | + results = dt.query("select title, air_date, episode_number, writer from tv_shows where upper(title) like %s", \ |
| 125 | + ("%CHEESE%",), SimpleRowMapper(TvShow)) |
| 126 | + results = dt.query("select title, air_date, episode_number, writer from tv_shows where writer in ('Cleese', 'Graham')", |
| 127 | + rowhandler=SimpleRowMapper(TvShow)) |
| 128 | + |
| 129 | +.. note:: |
| 130 | + |
| 131 | + Convention is based on query, not tables |
| 132 | + |
| 133 | + Query metadata is based on the column names as defined in the query, NOT what |
| 134 | + is in the table. This is important when you use expressions like COUNT(*). |
| 135 | + These columns should be aliased to fit the attribute name. |
| 136 | + |
| 137 | + |
| 138 | +Mapping rows into dictionaries |
| 139 | +++++++++++++++++++++++++++++++ |
| 140 | + |
| 141 | +A convenient alternative to mapping database rows into python objects, it |
| 142 | +to map them into dictionaries. Spring Python offers *springpython.database.DictionaryRowMapper* |
| 143 | +as an out-of-the-box way to query the database, and return a list of dictionary |
| 144 | +entries, based on the column names of the queries. Using this mapper, you don't |
| 145 | +have to code a *TvRowMapper* as shown earlier:: |
| 146 | + |
| 147 | + results = dt.query("select title, air_date, episode_number, writer from tv_shows where episode_number < %s", \ |
| 148 | + (100,), DictionaryRowMapper()) |
| 149 | + results = dt.query("select title, air_date, episode_number, writer from tv_shows where upper(title) like %s", \ |
| 150 | + ("%CHEESE%",), DictionaryRowMapper()) |
| 151 | + results = dt.query("select title, air_date, episode_number, writer from tv_shows where writer in ('Cleese', 'Graham')", |
| 152 | + rowhandler=DictionaryRowMapper()) |
| 153 | + |
| 154 | + |
| 155 | +.. note:: |
| 156 | + |
| 157 | + Dictionary keys are based on query not original tables |
| 158 | + |
| 159 | + Query metadata is based on the column names as defined in the query, NOT what |
| 160 | + is in the table. This is important when you use expressions like COUNT(*). |
| 161 | + These columns should be aliased in order to generate a useful key in the |
| 162 | + dictionary. |
| 163 | + |
| 164 | + |
| 165 | +What is a Connection Factory? |
| 166 | ++++++++++++++++++++++++++++++ |
| 167 | + |
| 168 | +You may have noticed I didn't make a standard connection in the example above. |
| 169 | +That is because to support `Dependency Injection <http://en.wikipedia.org/wiki/Dependency_injection>`_, |
| 170 | +I need to setup my credentials in an object before making the actual connection. |
| 171 | +*MySQLConnectionFactory* holds credentials specific to the MySQL DB-API, but |
| 172 | +contains a common function to actually create the connection. I don't have |
| 173 | +to use it myself. *DatabaseTemplate* will use it when necessary to create a |
| 174 | +connection, and then proceed to reuse the connection for subsequent database |
| 175 | +calls. |
| 176 | + |
| 177 | +That way, I don't manage database connections and cursors directly, but instead |
| 178 | +let Spring Python do the heavy lifting for me. |
| 179 | + |
| 180 | +Creating/altering tables, databases, and other DDL |
| 181 | +++++++++++++++++++++++++++++++++++++++++++++++++++ |
| 182 | + |
| 183 | +Data Definition Language includes the database statements that involve creating |
| 184 | +and altering tables, and so forth. DB-API defines an execute function for this. |
| 185 | +*DatabaseTemplate* offers the same. Using the execute() function will pass |
| 186 | +through your request to a cursor, along with the extra exception handler |
| 187 | +and cursor management. |
| 188 | + |
| 189 | +SQL Injection Attacks |
| 190 | ++++++++++++++++++++++ |
| 191 | + |
| 192 | +You may have noticed in the first three example queries I wrote with the |
| 193 | +*DatabaseTemplate*, I embedded a "%s" in the SQL statement. These are called |
| 194 | +*binding variables*, and they require a tuple argument be included after the SQL |
| 195 | +statement. Do *NOT* include quotes around these variables. The database connection |
| 196 | +will handle that. This style of SQL programming is *highly recommended* to avoid |
| 197 | +`SQL injection attacks <http://en.wikipedia.org/wiki/SQL_injection>`_. |
| 198 | + |
| 199 | +For users who are familiar with Java database APIs, the binding variables are |
| 200 | +cited using "?" instead of "%s". To make both parties happy and help pave the |
| 201 | +way for existing Java programmers to use this framework, I have included |
| 202 | +support for both. You can mix-and-match these two binding variable types |
| 203 | +as you wish, and things will still work. |
| 204 | + |
| 205 | +Have you used Spring Framework's JdbcTemplate? |
| 206 | +++++++++++++++++++++++++++++++++++++++++++++++ |
| 207 | + |
| 208 | +If you are a user of Java's `Spring framework <http://www.springsource.org/>`_ |
| 209 | +and have used the `JdbcTemplate <http://static.springsource.org/spring/docs/1.2.x/api/org/springframework/jdbc/core/JdbcTemplate.html>`_, |
| 210 | +then you will find this template has a familiar feel. |
| 211 | + |
| 212 | +================================================================= ================================================================================================ |
| 213 | +execute(sql_statement, args = None) execute any statement, return number of rows affected |
| 214 | +query(sql_query, args = None, rowhandler = None query, return list converted by rowhandler |
| 215 | +query_for_list(sql_query, args = None) query, return list of DB-API tuplesTrue |
| 216 | +query_for_int(sql_query, args = None) query for a single column of a single row, and return an integer (throws exception otherwise) |
| 217 | +query_for_long(sql_query, args = None) query for a single column of a single row, and return a long (throws exception otherwise) |
| 218 | +query_for_object(sql_query, args = None, required_type = None) query for a single column of a single row, and return the object with possibly no checking |
| 219 | +update(sql_statement, args = None) update the database, return number of rows updated |
| 220 | +================================================================= ================================================================================================ |
| 221 | + |
| 222 | +*Inserts* are implemented through the execute() function, just like in JdbcTemplate. |
| 223 | + |
| 224 | +Notes on using SQLServerConnectionFactory |
| 225 | ++++++++++++++++++++++++++++++++++++++++++ |
| 226 | + |
| 227 | +*SQLServerConnectionFactory* uses ODBC for connecting to SQL Server instances |
| 228 | +and it expects you to pass the ODBC parameters when creating connection |
| 229 | +factories or when injecting factory settings through IoC. The ODBC parameters |
| 230 | +you provide are directly translated into an ODBC connection string. |
| 231 | + |
| 232 | +That means that you use the exact ODBC parameters your ODBC provider understands |
| 233 | +and not the standard username, password, hostname and db parameters as with |
| 234 | +other connection factories. |
| 235 | + |
| 236 | +A simple example will demonstrate this. Here's how you would create |
| 237 | +a *DatabaseTemplate* on Windows for running queries against an SQL Server |
| 238 | +instance:: |
| 239 | + |
| 240 | + from springpython.database.core import DatabaseTemplate |
| 241 | + from springpython.database.factory import SQLServerConnectionFactory |
| 242 | + |
| 243 | + driver = "{SQL Server}" |
| 244 | + server = "localhost" |
| 245 | + database = "springpython" |
| 246 | + uid = "springpython" |
| 247 | + pwd = "cdZS*RQRBdc9a" |
| 248 | + |
| 249 | + factory = SQLServerConnectionFactory(DRIVER=driver, SERVER=server, DATABASE=database, UID=uid, PWD=pwd) |
| 250 | + dt = DatabaseTemplate(factory) |
| 251 | + |
| 252 | +.. note:: |
| 253 | + |
| 254 | + SQLServerConnectionFactory is dictionary driven |
| 255 | + |
| 256 | + Due to SQLServerConnectionFactory's pass-through nature, it is coded to |
| 257 | + accept a dictionary. For pure python, this means you MUST name the arguments |
| 258 | + and NOT rely on argument position. |
| 259 | + |
| 260 | +.. highlight:: xml |
| 261 | + |
| 262 | +For an XML-based application context, you must populate the argument |
| 263 | +odbc_info with a dictionary. See the following example:: |
| 264 | + |
| 265 | + <?xml version="1.0" encoding="UTF-8"?> |
| 266 | + <objects xmlns="http://www.springframework.org/springpython/schema/objects/1.1" |
| 267 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| 268 | + xsi:schemaLocation="http://www.springframework.org/springpython/schema/objects/1.1 |
| 269 | + http://springpython.webfactional.com/schema/context/spring-python-context-1.1.xsd"> |
| 270 | + |
| 271 | + <object id="connection_factory" class="springpython.database.factory.SQLServerConnectionFactory"> |
| 272 | + <property name="odbc_info"> |
| 273 | + <dict> |
| 274 | + <entry> |
| 275 | + <key><value>DRIVER</value></key> |
| 276 | + <value>{SQL Server}</value> |
| 277 | + </entry> |
| 278 | + <entry> |
| 279 | + <key><value>SERVER</value></key> |
| 280 | + <value>localhost</value> |
| 281 | + </entry> |
| 282 | + <entry> |
| 283 | + <key><value>DATABASE</value></key> |
| 284 | + <value>springpython</value> |
| 285 | + </entry> |
| 286 | + <entry> |
| 287 | + <key><value>UID</value></key> |
| 288 | + <value>springpython</value> |
| 289 | + </entry> |
| 290 | + <entry> |
| 291 | + <key><value>PWD</value></key> |
| 292 | + <value>cdZS*RQRBdc9a</value> |
| 293 | + </entry> |
| 294 | + </dict> |
| 295 | + </property> |
| 296 | + </object> |
| 297 | + |
| 298 | + </objects> |
0 commit comments