Advanced Application Development
Transactions
A database transaction groups multiple operations into a single atomic unit: either all operations succeed and are committed, or all are rolled back if any error occurs. This prevents the database from being left in an inconsistent state.
In the FRRUAS prototype (Python + psycopg2), we implement transactions using a context manager get_transaction() that automatically commits on success and rolls back on exception:
@contextmanager
def get_transaction():
p = _get_pool()
conn = p.getconn()
try:
conn.autocommit = False
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
p.putconn(conn)
Transaction 1: Make a Reservation (conflict check + insert)
When a user creates a reservation, two operations must happen atomically: checking for time conflicts and inserting the reservation. Without a transaction, a race condition exists: two users could both pass the conflict check simultaneously, and both insert overlapping reservations.
# uc_reserve.py — make_reservation()
with get_transaction() as conn:
with conn.cursor() as cur:
# Re-check conflicts inside the transaction to prevent
# race conditions (another user inserting between our
# check and insert)
cur.execute("""
SELECT COUNT(*) FROM reservations
WHERE resource_id = %s
AND status IN ('approved', 'pending')
AND start_time < %s AND end_time > %s
""", (rid, end_dt, start_dt))
if cur.fetchone()[0] > 0:
raise RuntimeError("Conflict: another reservation was just created for this time slot")
cur.execute("""
INSERT INTO reservations
(start_time, end_time, status, purpose, created_at, user_id, resource_id)
VALUES (%s, %s, 'pending', %s, CURRENT_TIMESTAMP, %s, %s)
RETURNING reservation_id, status, created_at
""", (start_dt, end_dt, purpose, user_id, rid))
result = cur.fetchone()
# Transaction committed automatically on successful exit
Transaction 2: Approve or Reject a Reservation (conflict re-check + status update)
When an administrator approves a reservation, the system must verify no conflicting reservation was approved in the meantime. The conflict check and status update are wrapped in a single transaction to prevent two administrators from simultaneously approving overlapping reservations.
# uc_approve.py — _review_reservation()
with get_transaction() as conn:
with conn.cursor() as cur:
# For approvals, re-check conflicts within the transaction
if new_status == "approved":
cur.execute("""
SELECT COUNT(*) FROM reservations
WHERE resource_id = %s
AND reservation_id != %s
AND status = 'approved'
AND start_time < %s AND end_time > %s
""", (res["resource_id"], res["reservation_id"],
res["end_time"], res["start_time"]))
if cur.fetchone()[0] > 0:
raise RuntimeError("Conflict: another reservation was approved for this time slot")
cur.execute("""
UPDATE reservations
SET status = %s, approved_by = %s
WHERE reservation_id = %s AND status = 'pending'
RETURNING reservation_id, status
""", (new_status, admin_user_id, res["reservation_id"]))
result = cur.fetchone()
# Transaction committed automatically
Transaction 3: Register a New User (email uniqueness check + insert)
User registration checks email uniqueness and inserts the new user atomically. Without a transaction, two simultaneous registrations with the same email could both pass the uniqueness check and both insert, violating the UNIQUE constraint.
# uc_users.py — register_user()
with get_transaction() as conn:
with conn.cursor() as cur:
cur.execute("SELECT COUNT(*) FROM users WHERE email = %s", (email,))
if cur.fetchone()[0] > 0:
raise ValueError(f"A user with email '{email}' already exists.")
cur.execute("""
INSERT INTO users (first_name, last_name, email, password, type_id)
VALUES (%s, %s, %s, %s, %s)
RETURNING user_id, first_name, last_name, email
""", (first_name, last_name, email, hashed, type_id))
result = cur.fetchone()
# Transaction committed automatically
Database Connection Pooling
Connection pooling reuses open database connections instead of creating a new one for every query. Opening a database connection involves TCP handshake, authentication, and session setup, which adds overhead. With pooling, these costs are paid once, and subsequent operations borrow pre-established connections from the pool.
Implementation
The prototype uses psycopg2.pool.SimpleConnectionPool to maintain a pool of reusable connections:
# db.py
from psycopg2 import pool
_pool = None
def _get_pool():
global _pool
if _pool is None:
_pool = pool.SimpleConnectionPool(minconn=2, maxconn=10, **DB_CONFIG)
return _pool
Pool configuration:
| Parameter | Value | Meaning |
| minconn | 2 | Minimum connections kept open and ready |
| maxconn | 10 | Maximum concurrent connections allowed |
The pool is initialized lazily on first use and cleaned up when the application exits via close_pool().
Connection Usage
Two context managers provide connections from the pool:
Read operations use get_connection() with autocommit enabled:
@contextmanager
def get_connection():
p = _get_pool()
conn = p.getconn()
try:
conn.autocommit = True
yield conn
finally:
conn.autocommit = False
p.putconn(conn)
Write operations use get_transaction() with explicit commit/rollback:
@contextmanager
def get_transaction():
p = _get_pool()
conn = p.getconn()
try:
conn.autocommit = False
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
p.putconn(conn)
In both cases, the connection is automatically returned to the pool when the with block exits, ensuring no connection leaks.
Example: Read Operation Using Pool
# uc_browse.py — fetching resources
with get_connection() as conn:
with conn.cursor() as cur:
cur.execute("""
SELECT r.resource_id, r.name, rt.type_name, l.building, l.room
FROM resources r
JOIN resource_types rt ON r.type_id = rt.type_id
LEFT JOIN locations l ON r.location_id = l.location_id
ORDER BY rt.type_name, r.name
""")
resources = cur.fetchall()
# Connection returned to pool automatically
Pool Lifecycle
# main.py
def main():
try:
while True:
user = login() # uses pooled connections
if user is None:
break
main_menu(...) # all operations use pooled connections
except KeyboardInterrupt:
pass
finally:
close_pool() # close all connections on exit
