2016-06-13 12 views
1

Ich habe eine DAO-Klasse geschrieben, die mehrere Threads erlaubt, die von ExecutorServices aufgerufen werden, um MySQL DB zu schreiben.Multithreaded schreibe auf MySQL DB mit JDBC und c3p0

EDIT: Ich benutze c3p0, um einen JDBC ConnectionPool zu erstellen. So wird jeder neuen Thread einen neuen JDBC Connection erhalten, indem

DataBaseManager.getInstance().getConnection() 

Es Aufruf scheint zufällige Gleichzeitigkeit Problem zu sein, während der Ausführung, z:

java.sql.SQLException: No value specified for parameter 1 
at com.eanurag.dao.DataBaseManager.writeData(DataBaseManager.java:102) 

Ich bin nicht in der Lage, alle Probleme mit dem Code zu verstehen. Sollte ich nur ganze writeData() synchronisieren?

public class DataBaseManager { 

    private final static Logger logger = Logger.getLogger(DataBaseManager.class); 

    private static volatile DataBaseManager dbInstance = null; 

    private DataBaseManager() { 
     cpds = new ComboPooledDataSource(); 
     try { 
      cpds.setDriverClass("com.mysql.jdbc.Driver"); 
     } catch (PropertyVetoException e) { 
      logger.error("Error in Initializing DB Driver class", e); 
     } 
     cpds.setJdbcUrl("jdbc:mysql://" + DB_HOST + "/" + DB_NAME); 
     cpds.setUser(DB_USER); 
     cpds.setPassword(DB_PASS); 

     cpds.setMinPoolSize(MINIMUM_POOL_SIZE); 
     cpds.setAcquireIncrement(INCREMENT_SIZE); 
     cpds.setMaxPoolSize(MAXIMUM_POOL_SIZE); 
     cpds.setMaxStatements(MAX_STATEMENTS); 
    } 

    public static DataBaseManager getInstance() { 
     if (dbInstance == null) { 
      synchronized (WorkerManager.class) { 
       if (dbInstance == null) { 
        dbInstance = new DataBaseManager(); 
       } 
      } 
     } 

     return dbInstance; 
    } 

    private ComboPooledDataSource cpds; 

    private static final Integer MINIMUM_POOL_SIZE = 10; 
    private static final Integer MAXIMUM_POOL_SIZE = 1000; 
    private static final Integer INCREMENT_SIZE = 5; 
    private static final Integer MAX_STATEMENTS = 200; 

    private volatile Connection connection = null; 
    private volatile Statement statement = null; 
    private volatile PreparedStatement preparedStatement = null; 

    private static final String DB_HOST = "localhost"; 
    private static final String DB_PORT = "3306"; 
    private static final String DB_USER = "root"; 
    private static final String DB_PASS = ""; 
    private static final String DB_NAME = "crawly"; 
    private static final String URL_TABLE = "url"; 


    public Connection getConnection() throws SQLException { 
     logger.info("Creating connection to DB!"); 
     return this.cpds.getConnection(); 
    } 

    public Boolean writeData(URL url) { 
     StringBuffer writeDBStatement = new StringBuffer(); 
     writeDBStatement.append("insert into"); 
     writeDBStatement.append(" "); 
     writeDBStatement.append(DB_NAME); 
     writeDBStatement.append("."); 
     writeDBStatement.append(URL_TABLE); 
     writeDBStatement.append(" "); 
     writeDBStatement.append("values (?,?,default)"); 

     Boolean dbWriteResult = false; 

     try { 
      connection = DataBaseManager.getInstance().getConnection(); 

       preparedStatement = connection.prepareStatement(writeDBStatement.toString()); 
       preparedStatement.setString(1, url.getURL()); 
       preparedStatement.setString(2, String.valueOf(url.hashCode())); 
       dbWriteResult = (preparedStatement.executeUpdate() == 1) ? true : false; 


      if(dbWriteResult){ 
       logger.info("Successfully written to DB!"); 
      } 
     } catch (SQLException e) { 
      logger.error("Error in writing to DB", e); 
     } finally { 
      try { 
       preparedStatement.close(); 
       connection.close(); 
      } catch (SQLException e) { 
       e.printStackTrace(); 
      } 
     } 
     return dbWriteResult; 
    } 


} 
+2

Sie greifen von verschiedenen Threads auf die Elementvariablen (Connection, PreparedStatement) zu. Machen Sie sie zu Methodenvariablen von writeData() oder stellen Sie sicher, dass jeder Thread eine eigene Instanz Ihrer Klasse hat. – Michal

+1

Ich glaube nicht, dass 'volatile' Ihnen den Vorteil bringen wird, von dem Sie denken, dass es dort ankommt (nicht ohne irgendeine Synchronisation auf' write' auf jeden Fall). Warum ist das vorbereitete Statement eine Variable auf Klassenebene? – kolossus

+0

@Michal macht (Connection, PreparedStatement) Methodenvariablen von writeData(), da das Übergeben der eigenen Instanz der Klasse an jeden Thread nicht mit der aktuellen Logik von ConnectionPool funktioniert. Vielen Dank! – Anurag

Antwort

1

Was passiert hier?

public Connection getConnection() throws SQLException { 
    logger.info("Creating connection to DB!"); 
    return this.cpds.getConnection(); 
} 

Nämlich, was macht cpds.getConnection()? Wenn Sie anrufen:

connection = DataBaseManager.getInstance().getConnection(); 

Ihr Connection-Objekt ist ein Mitglied von dem, was sollte hier eine Singleton-Klasse sein, aber jeder Aufruf zu writedata() überschreibt er es mit einem neuen getConnection() -Aufruf. Ist der getConnection() - Aufruf Thread auch unsicher?

Warum wird das Verbindungsobjekt auch als Klassenmember deklariert und dann bei jedem Aufruf von writeData() überschrieben? In einer Umgebung mit mehreren Threads ermöglicht der vorhandene Code das Überschreiben des Verbindungsobjekts durch einen anderen Aufruf von getConnection() unmittelbar vor dem Aufruf von prepareStatement(), da der Zugriff auf writeData() nicht gesperrt ist. Gleiches gilt für preparedStatement. Verschieben Sie diese in die writeData() -Methode.

+0

Also 'cpds' ist' ComboPooledDataSource'. Auf diese Weise kann c3p0 zum Erstellen eines JDBC-Verbindungspools verwendet werden. Also hier: 'Verbindung = DataBaseManager.getInstance(). GetConnection();' wird immer eine neue Verbindung zur Datenbank zurückgeben. – Anurag

+0

Warum wird das Verbindungsobjekt als Klassenmitglied deklariert und dann bei jedem Aufruf von writeData() überschrieben? In einer Umgebung mit mehreren Threads ermöglicht der vorhandene Code das Überschreiben des Verbindungsobjekts durch einen anderen Aufruf von getConnection() unmittelbar vor dem Aufruf von prepareStatement(), da der Zugriff auf writeData() nicht gesperrt ist. Gleiches gilt für preparedStatement. – AWT

+0

können Sie bitte etwas mehr erklären? Wie wird das Connection-Objekt zu einer Methodenvariable, die das Problem löst? Mehrere Threads greifen gleichzeitig darauf zu. – Anurag

2

Die Variablen connection und preparedStatement müssen lokal sein, keine Instanzelemente.

Keine Synchronisation erforderlich.

Verwandte Themen