Overview
The Contador classes demonstrate different approaches to implementing database counters, focusing on concurrency control, atomic operations, and transaction management. These classes illustrate common pitfalls and best practices when working with concurrent database access.
ContadorEnBD
ContadorEnBD Basic database counter with initialization logic
Main Method
public static void main ( String [] args)
Database Configuration
"SELECT cuenta FROM contadores WHERE nombre=?"
"UPDATE contadores SET cuenta=? WHERE nombre=?"
claveContador
final String
default: "contador1"
Counter identifier key
"jdbc:hsqldb:file:./contadores_atomico;hsqldb.lock_file=false"
Username: SA
Password: (empty)
Key Methods
inicializarBD()
private static void inicializarBD ( Connection connection) throws SQLException
Creates the counter table and initializes the counter if it doesn’t exist
try ( Statement stmt = connection . createStatement ()) {
// Create table if not exists
stmt . execute ( "CREATE TABLE IF NOT EXISTS contadores (nombre VARCHAR(50) PRIMARY KEY, cuenta INT)" );
// Check if counter exists, insert if not
ResultSet rs = stmt . executeQuery ( "SELECT COUNT(*) FROM contadores WHERE nombre='contador1'" );
if ( rs . next () && rs . getInt ( 1 ) == 0 ) {
stmt . execute ( "INSERT INTO contadores VALUES ('contador1', 0)" );
System . out . println ( "Contador inicializado a 0" );
}
}
Counter Logic
for ( int i = 0 ; i < 1000 ; i ++ ) {
ResultSet res = consulta . executeQuery ();
if ( res . next ()) {
cuenta = res . getInt ( 1 ) + 1 ;
actualización . setInt ( 1 , cuenta);
actualización . executeUpdate ();
} else {
System . out . println ( "Error: No se encontró el contador" );
break ;
}
}
System . out . println ( "Valor final: " + cuenta);
This approach has race condition problems in concurrent scenarios
ContadorEnBDUpdatable
ContadorEnBDUpdatable Uses updatable ResultSet for counter modification
Main Method
public static void main ( String [] args)
Configuration
claveContador
final String
default: "contador1"
Counter key identifier
"SELECT nombre,cuenta FROM contadores WHERE nombre=?"
URL: jdbc:hsqldb:file:./contadores_atomico;hsqldb.lock_file=false
User: contadores
Password: 987654321
ResultSet Configuration
ResultSet.TYPE_SCROLL_INSENSITIVE - Scrollable but not sensitive to changes
ResultSet.CONCUR_UPDATABLE - Allows ResultSet updates
Updatable ResultSet Implementation
PreparedStatement consulta = connection . prepareStatement (
sqlConsulta,
ResultSet . TYPE_SCROLL_INSENSITIVE ,
ResultSet . CONCUR_UPDATABLE
);
consulta . setString ( 1 , claveContador);
for ( int i = 0 ; i < 1000 ; i ++ ) {
if ( consulta . execute ()) {
ResultSet res = consulta . getResultSet ();
if ( res . next ()) {
cuenta = res . getInt ( 2 ) + 1 ;
res . updateInt ( 2 , cuenta);
res . updateRow ();
}
}
}
ContadorEnBDsoloUpdateSql
ContadorEnBDsoloUpdateSql Atomic counter update using SQL increment operation
Main Method
public static void main ( String [] args)
Atomic SQL Update
"UPDATE contador SET cuenta=cuenta+1 WHERE nombre='contador1';"
This approach is atomic - the increment happens entirely within the database
Atomic Update Implementation
Class . forName ( "org.hsqldb.jdbc.JDBCDriver" );
Connection connection = DriverManager . getConnection (
"jdbc:hsqldb:file:./contadores_atomico;hsqldb.lock_file=false" ,
"contadores" ,
"987654321"
);
PreparedStatement actualización = connection . prepareStatement (sqlActualización);
for ( int i = 0 ; i < 1000 ; i ++ ) {
if ( actualización . executeUpdate () != 1 ) break ;
}
No race conditions - the database handles the read-modify-write as a single atomic operation
ContadorSqlTransaccional
ContadorSqlTransaccional Transaction-based counter with row locking using FOR UPDATE
Main Method
public static void main ( String [] args) throws ClassNotFoundException
SQL Queries
"select nombre,cuenta from contadores where nombre='contador1' for update;"
The FOR UPDATE clause locks the row for the duration of the transaction
"update contadores set cuenta=? where nombre='contador1';"
Transaction Management
Transactional Implementation
Class . forName ( "org.hsqldb.jdbc.JDBCDriver" );
try ( Connection connection = DriverManager . getConnection (
"jdbc:hsqldb:file:./contadores_atomico;hsqldb.lock_file=false" ,
"contadores" ,
"987654321" ))
{
PreparedStatement consulta = connection . prepareStatement (sqlConsulta);
PreparedStatement actualización = connection . prepareStatement (sqlActualización);
int cuenta = 0 ;
for ( int i = 0 ; i < 1000 ; i ++ ) {
connection . setAutoCommit ( false ); // Start transaction
ResultSet res = consulta . executeQuery (); // Locks row with FOR UPDATE
if ( res . next ()) {
cuenta = res . getInt ( 2 );
cuenta ++ ;
actualización . setInt ( 1 , cuenta);
actualización . executeUpdate ();
} else {
break ;
}
connection . commit (); // Commit and release lock
connection . setAutoCommit ( false ); // Prepare for next iteration
}
System . out . println ( "Valor final: " + cuenta);
}
FOR UPDATE locks the row during the transaction
Other processes must wait until commit/rollback
Ensures atomicity of read-modify-write operation
ContadorSqlTransaccionalResUpdateable
ContadorSqlTransaccionalResUpdateable Combines transactions, row locking, and updatable ResultSet
Main Method
public static void main ( String [] args) throws ClassNotFoundException
Configuration
"select nombre,cuenta from contadores where nombre='contador1' for update;"
Type: ResultSet.FETCH_FORWARD
Concurrency: ResultSet.CONCUR_UPDATABLE
Implementation
Transaction with Updatable ResultSet
PreparedStatement consulta = connection . prepareStatement (
sqlConsulta,
ResultSet . FETCH_FORWARD ,
ResultSet . CONCUR_UPDATABLE
);
int cuenta = 0 ;
for ( int i = 0 ; i < 1000 ; i ++ ) {
connection . setAutoCommit ( false );
ResultSet res = consulta . executeQuery ();
if ( res . next ()) {
cuenta = res . getInt ( 2 );
cuenta ++ ;
// Requires nombre to be primary key!
res . updateInt ( 2 , cuenta);
res . updateRow ();
} else {
break ;
}
connection . commit ();
connection . setAutoCommit ( false );
}
System . out . println ( "Valor final: " + cuenta);
The nombre column must be a PRIMARY KEY for updateRow() to work correctly
ContadorBuggy
ContadorBuggy Demonstrates a corrected approach using atomic updates within a single transaction
Main Method
public static void main ( String [] args)
Configuration
"jdbc:hsqldb:file:./contadores_atomico;hsqldb.lock_file=false"
User: SA
Password: (empty)
Methods
inicializarBD()
private static void inicializarBD ( Connection con) throws SQLException
try ( Statement stmt = con . createStatement ()) {
stmt . execute ( "CREATE TABLE IF NOT EXISTS contadores (nombre VARCHAR(50) PRIMARY KEY, cuenta INT)" );
ResultSet rs = stmt . executeQuery ( "SELECT COUNT(*) FROM contadores WHERE nombre='contador1'" );
if ( rs . next () && rs . getInt ( 1 ) == 0 ) {
stmt . execute ( "INSERT INTO contadores VALUES ('contador1', 0)" );
}
con . commit ();
}
Implementation
Atomic Update with Transaction
try ( Connection con = DriverManager . getConnection (url, "SA" , "" )) {
con . setAutoCommit ( false );
inicializarBD (con);
String sqlActualiza = "update contadores set cuenta = cuenta + 1 where nombre='contador1'" ;
try ( PreparedStatement actualiza = con . prepareStatement (sqlActualiza)) {
for ( int i = 0 ; i < 1000 ; i ++ ) {
actualiza . executeUpdate ();
}
con . commit ();
} catch ( SQLException e ) {
con . rollback ();
throw e;
}
// Verify result
try ( Statement consulta = con . createStatement ();
ResultSet res = consulta . executeQuery ( "select cuenta from contadores where nombre='contador1'" )) {
if ( res . next ()) {
System . out . println ( "HSQLDB - Valor final: " + res . getInt ( 1 ));
}
}
}
Single transaction for all 1000 updates
Atomic SQL increment (cuenta = cuenta + 1)
Rollback on any SQLException
Verification query at the end
Comparison Table
Class Concurrency Safe Uses Transactions Row Locking Approach ContadorEnBD ❌ No ❌ No ❌ No Separate SELECT and UPDATE ContadorEnBDUpdatable ❌ No ❌ No ❌ No Updatable ResultSet ContadorEnBDsoloUpdateSql ✅ Yes ❌ No ❌ No Atomic SQL increment ContadorSqlTransaccional ✅ Yes ✅ Yes ✅ Yes (FOR UPDATE) Transaction + row lock ContadorSqlTransaccionalResUpdateable ✅ Yes ✅ Yes ✅ Yes (FOR UPDATE) Transaction + updatable ResultSet ContadorBuggy ✅ Yes ✅ Yes ❌ No Single transaction + atomic SQL
Best Practices Demonstrated
Problem Classes: ContadorEnBD, ContadorEnBDUpdatableThese classes separate the read and write operations without proper locking, leading to race conditions in concurrent scenarios. Solution Classes: ContadorEnBDsoloUpdateSql, ContadorSqlTransaccional, ContadorBuggyUse atomic SQL operations or explicit row locking with transactions.
connection . setAutoCommit ( false ); // Start transaction
try {
// Perform operations
connection . commit ();
} catch ( SQLException e ) {
connection . rollback ();
throw e;
}
Row Locking with FOR UPDATE
String sql = "SELECT * FROM table WHERE id=? FOR UPDATE" ;
Locks the selected rows until the transaction commits or rolls back, preventing concurrent modifications.
// Atomic - happens entirely in database
"UPDATE contadores SET cuenta = cuenta + 1 WHERE nombre=?"
// Not atomic - vulnerable to race conditions
int value = SELECT cuenta FROM contadores ;
value ++ ;
UPDATE contadores SET cuenta = value;
JDBC Transaction Management
Database Concurrency Control
Optimistic vs Pessimistic Locking
ACID Properties