java.net.BindException: Address already in use: connectデータベースに接続できなくなる!Spring + Hibernate + MySQL 環境で連続で更新クエリを実行すると以下のような例外がスローされる。 2006-10-20 23:43:59,468 [http-8080-Processor25] INFO org.springframework.beans.factory.xml.XmlBeanDef initionReader - Loading XML bean definitions from class path resource [org/springframework/jdbc/suppo rt/sql-error-codes.xml] 2006-10-20 23:43:59,500 [http-8080-Processor25] INFO org.springframework.jdbc.support.SQLErrorCodesFa ctory - SQLErrorCodes loaded: [DB2, HSQL, MS-SQL, MySQL, Oracle, Informix, PostgreSQL, Sybase] 2006-10-20 23:44:58,203 [http-8080-Processor25] WARN org.hibernate.util.JDBCExceptionReporter - SQL E rror: 0, SQLState: 08S01 2006-10-20 23:44:58,203 [http-8080-Processor25] ERROR org.hibernate.util.JDBCExceptionReporter - Comm unications link failure due to underlying exception: ** BEGIN NESTED EXCEPTION ** java.net.SocketException MESSAGE: java.net.BindException: Address already in use: connect STACKTRACE: java.net.SocketException: java.net.BindException: Address already in use: connect at com.mysql.jdbc.StandardSocketFactory.connect(StandardSocketFactory.java:156) 改善前は、単一のエンティティを受け取ってgetHibernateTemplate().saveを呼ぶだけ。これを12万回繰り返していた。
/**
* 登録処理
*
* @param entity
*/
public void save(Address entity) {
getHibernateTemplate().save(entity);
}
理由は、MySQLへのTCPコネクションが、更新クエリ発行毎に張られてしまいポート番号が実行環境(WindowsXP)で使い切ってしまった為。 ※参考 http://www.mysql.gr.jp/mysqlml/mysql/msg/9648 より一部抜粋。 Windows2000(そしてXPもだと思いますが)には、ポートが足りなくなった場合に、TIME_WAITに遷移してから60秒 以上経過した接続を強制的にCLOSEDの状態へ遷移させる(つまりポートを空ける)緊急クリーンアップの機能を持 っていますし、短時間に大量の接続/切断を繰り返すのはプログラミング的にアウチですから、通常はあまりポ ートが足りなくなってエラーが発生する事はありません。 けれども、MSのTCP/IP関係のレジストリ設定を変更するして(例えば、HKEY_LOCAL_MACHINE\SYSTEM\CurrentCont rolSet\Services\Tcpip\Parametersで、MaxFreeTWTCBsを6000、MaxUserPortを5000とするとか)、短時間に大量 の接続/切断を繰り返すと、ポートが足りなくてエラーが出る状態を作りだす事が出来ます。 ※もろにこの状態を作り出してしまっているじゃん^^; 対応方法エンティティのListを受け取ってJDBCのexecuteBatch()メソッドで実行するようにした。 ソースを以下のように改善した。
/**
* 登録処理。
*
* @param List<Address>
*/
public void save(List<Address> addressList) throws SystemException {
Connection con = null;
PreparedStatement stmt = null;
try {
String insertSql
= "INSERT INTO ADDRESS VALUES(null,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
con = getJdbcTemplate().getDataSource().getConnection();
stmt = con.prepareStatement(insertSql);
for (Iterator<Address> ite = addressList.iterator(); ite.hasNext();) {
Address address = ite.next();
stmt.setInt(1, address.getLocalAuthorityCd());
stmt.setString(2, address.getOldZipcode());
stmt.setString(3, address.getZipcode());
stmt.setString(4, address.getPrefKana());
stmt.setString(5, address.getCityKana());
stmt.setString(6, address.getAddrKana());
stmt.setString(7, address.getPref());
stmt.setString(8, address.getCity());
stmt.setString(9, address.getAddr());
stmt.setString(10, address.getFlg1());
stmt.setString(11, address.getFlg2());
stmt.setString(12, address.getFlg3());
stmt.setString(13, address.getUpdateFlg());
stmt.setString(14, address.getUpdateReason());
stmt.addBatch();
}
int result[] = stmt.executeBatch();
logger.debug(result.length + " 行インサートされました。");
stmt.close();
con.close();
} catch (SQLException e) {
throw new SystemException(e);
} finally {
try {
if (stmt!=null) stmt.close();
if (con!=null) con.close();
} catch (Exception e) {
throw new SystemException("DB切断に失敗", e);
}
}
}
これだと、TCPコネクションが1個だけになって、全く問題無い。
TCP 127.0.0.1:3306 127.0.0.1:1626 ESTABLISHED
|