2016-06-13 47 views
1

我寫了一個DAO類,它允許ExecutorServices調用的幾個線程寫入MySQL DB。使用JDBC和c3p0在MySQL DB上多線程寫入

編輯:我正在使用c3p0來創建一個JDBC連接池。因此,每一個新的線程將通過調用

DataBaseManager.getInstance().getConnection() 

得到一個新的JDBC Connection似乎是隨機的併發性問題,同時執行,e.g:

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

我無法瞭解所有的代碼問題。我應該只是同步整個writeData()

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

您可以從不同的主題訪問成員變量(Connection,PreparedStatement)。或者讓它們成爲writeData()的方法變量,或者確保每個線程都有自己的類的實例。 – Michal

+1

我不認爲'volatile'會給你帶來的好處,你認爲它會在那裏(無論如何不是沒有'write'上的同步)。爲什麼預備陳述是一個類級變量? – kolossus

+0

@ writeData()的Micrill(Connection,PreparedStatement)方法變量將類的自身實例傳遞給每個線程將無法使用ConnectionPool的當前邏輯。謝謝! – Anurag

回答

1

這裏發生了什麼?

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

也就是說,cpds.getConnection()是做什麼的?當您撥打:

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

連接對象是一個什麼樣的應該是這裏一個單例類,而是寫數據每次調用()成員有一個新的getConnection()調用將其覆蓋它。 getConnection()調用線程也不安全?

此外,爲什麼連接對象聲明爲類成員,然後每次調用writeData()時都會覆蓋它?在多線程環境中,它存在的代碼允許在調用prepareStatement()之前立即由另一個getConnection()調用覆蓋連接對象,因爲writeData()訪問未被鎖定。相同的preparedStatement。將這些移動到writeData()方法中。

+0

所以'cpds​​'是'ComboPooledDataSource'。這是使用c3p0創建JDBC連接池的方式。所以在這裏: 'connection = DataBaseManager.getInstance()。getConnection();'將始終返回一個到數據庫的新連接。 – Anurag

+0

爲什麼連接對象聲明爲類成員,並且每次調用writeData()時都會覆蓋這個連接對象?在多線程環境中,它存在的代碼允許在調用prepareStatement()之前立即由另一個getConnection()調用覆蓋連接對象,因爲writeData()訪問未被鎖定。相同的preparedStatement。 – AWT

+0

你能解釋一下嗎?如何使Connection對象成爲解決此問題的方法變量?多個線程也會同時訪問它。 – Anurag

2

connectionpreparedStatement變量必須是本地的,而不是實例成員。

不需要同步。