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;
}
}
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
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
@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