Skip to content

Commit 9050616

Browse files
author
Dariusz Suchojad
committed
DatabaseTemplate's Sphinx docs.
1 parent fa54e37 commit 9050616

1 file changed

Lines changed: 297 additions & 1 deletion

File tree

docs/sphinx/source/dao.rst

Lines changed: 297 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,298 @@
11
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

Comments
 (0)