It has been a while since I started evaluating Redmine and later started using as well. Earlier we were using Bugzilla for issue tracking, which contains good amount of data (current and historical as well). This data can't be left behind just for the sake of using some new tool. I was looking at ways to migrate this data. Robert Heath's PHP script for migration was the best solution I found.
His scripts deletes all Redmine data and migrates everything from Bugzilla to Redmine, which I did not want and also we have a highly customized instance of Bugzilla, so using that script out of box was not possible. I wanted to pick and choose the data for migration instead of dumping everything in Redmine. The Bugzilla was having internal authentication for users while we wanted to have a mix of LDAP and internal authentication in Redmine.
To meet all these (and many more) requirements, big changes were required to Robert's script. Being a long time Java programmer, I hardly have any knowledge of PHP. Making changes to Robert's PHP script was difficult to me, so I rewrote the script again in Java. I used Robert's script as a reference to map Redmine and Bugzilla types and for SQL scripts. Without that it would have been quite difficult to write the script.
Though it took some effort to write the migration script but the effort spent was worth it. Now with Redmine we have a consolidated view of issues and source code. Migration has been seamless and users are much happier with Redmine. Now I am looking at possibilities about writing few plugins for Redmine to customize it for some innovative usages.
Update Feb 13, 2010: As I was being asked for the script frequently, here is the code I used for migration. This may not work against each and every Bugzilla installation, modify it according to your needs.
/**
*
* 1. All internal IDs are associated as Developer (4) to projects, while external as
* Client (6).
*
*/
public class Migrator {
private Log log = LogFactory.getLog(this.getClass());
String filePath = "D:\\bugzillaAttachments"; // where to store bugzilla attachments
SimpleDateFormat sdf = new SimpleDateFormat("yyMMddhhmmss");
private String projectList = "1, 2, 3, 4, 5";
private static Map
private static Map
private static Map
// old values are key and new ones values
private Map
private Map
private Map
private Map
private Map
// key > product_id + version name
private Map
static {
priority.put("P1", 5);
priority.put("P2", 4);
priority.put("P3", 3);
priority.put("P4", 2);
priority.put("P5", 1);
status.put("UNCONFIRMED", 1);
status.put("NEW", 1);
status.put("ASSIGNED", 2);
status.put("REOPENED", 7);
status.put("RESOLVED", 3);
status.put("VERIFIED", 5);
status.put("CLOSED", 5);
tracker.put("trivial", 1);
tracker.put("minor", 1);
tracker.put("normal", 1);
tracker.put("major", 1);
tracker.put("critical", 1);
tracker.put("blocker", 1);
}
public Migrator() {
Connection destCon = null;
Connection srcCon = null;
try {
Class.forName("org.postgresql.Driver");
destCon = DriverManager.getConnection("jdbc:postgresql://redmine_server:5432/redmine", "user",
"pwd");
destCon.setAutoCommit(false);
log.info("PostgreSQL/Redmine connection opened");
Class.forName("com.mysql.jdbc.Driver");
srcCon = DriverManager.getConnection("jdbc:mysql://bugzilla_server:3306/test", "user", "pwd");
log.info("MySql/Bugzilla connection opened");
loadExistingUsers(destCon);
createUsers(destCon, srcCon); // create users
createProjects(destCon, srcCon); // create projects
destCon.commit();
} catch (Exception e) {
try {
destCon.rollback();
log.info("Rolled back changes");
} catch (Exception e3) {
log.error("Unable to rollback", e3);
}
log.error("", e);
} finally {
try {
destCon.close();
log.info("PostgreSQL/Redmine connection closed");
srcCon.close();
log.info("MySql/Bugzilla connection closed");
} catch (Exception e2) {
log.error("Error while closing connections", e2);
}
}
}
public static void main(String[] args) {
new Migrator();
}
// load existing users and don't recreate them
private void loadExistingUsers(Connection destCon) throws Exception {
String SELECT = "SELECT mail, id FROM users";
Statement stmt = null;
ResultSet rs = null;
try {
stmt = destCon.createStatement();
rs = stmt.executeQuery(SELECT);
while (rs.next()) {
existingUserMap.put(rs.getString(1), rs.getInt(2));
}
} finally {
close(rs);
close(stmt);
}
}
private void createUsers(Connection destCon, Connection srcCon) throws Exception {
String SELECT = "SELECT userid, login_name, realname, disabledtext FROM profiles "
+ "WHERE userid IN (SELECT ugm.user_id FROM user_group_map ugm, group_control_map gcm "
+ " WHERE ugm.group_id = gcm.group_id AND gcm.product_id IN (" + projectList + "))";
String INSERT = "INSERT INTO users (login, mail, firstname, lastname, language, status, auth_source_id, created_on, updated_on) "
+ "VALUES (?, ?, ?, ?, 'en', ?, ?, now(), now())";
log.info("Creating uses . . .");
Statement selectStmt = null;
PreparedStatement insertStmt = null;
ResultSet selectRs = null;
ResultSet keysRs = null;
try {
insertStmt = destCon.prepareStatement(INSERT, new String[] { "id" });
selectStmt = srcCon.createStatement();
selectRs = selectStmt.executeQuery(SELECT);
while (selectRs.next()) {
int userId = selectRs.getInt(1);
String loginName = selectRs.getString(2).trim();
// don't recreate an existing user
if (existingUserMap.containsKey(loginName)) {
userMap.put(userId, existingUserMap.get(loginName));
log.info("Bugzilla user [" + userId + ", " + loginName
+ "] > already exists as Redmine user [" + existingUserMap.get(loginName)
+ "]");
continue;
}
String realName = selectRs.getString(3).trim();
String disabledText = selectRs.getString(4);
// Keep non internal user ids with full email address, this will enable
// future employees with same id
String newLoginName;
if (loginName.endsWith("@domain.com")) {
newLoginName = loginName.substring(0, loginName.indexOf("@"));
if (newLoginName.length() > 20) {
// Our LDAP (AD) has a limit of 20 chars
newLoginName = loginName.substring(0, 20);
}
} else {
newLoginName = loginName;
}
String firstName;
String lastName;
if (realName.contains(" ")) {
firstName = realName.substring(0, realName.indexOf(' '));
lastName = realName.substring(realName.lastIndexOf(' ') + 1);
} else {
// in few cases 'realName' is email address
if (realName.contains("@"))
realName = realName.substring(0, loginName.indexOf("@"));
firstName = realName.length() <= 30 ? realName : realName.substring(0, 30);
lastName = "";
}
// insert the user in Redmine
insertStmt.setString(1, newLoginName);
insertStmt.setString(2, loginName);
insertStmt.setString(3, firstName);
insertStmt.setString(4, lastName);
// Wherever 'disabledtext' contains something mark his status as 3 (lock)
if (disabledText != null && disabledText.trim().length() > 0)
insertStmt.setInt(5, 3);
else
insertStmt.setInt(5, 1);
// If user id is not @domain.com, mark their auth source as internal
if (loginName.endsWith("@domain.com"))
insertStmt.setInt(6, 1);
else
insertStmt.setNull(6, java.sql.Types.INTEGER);
insertStmt.executeUpdate();
// update new key in bugzilla
keysRs = insertStmt.getGeneratedKeys();
keysRs.next();
int newId = keysRs.getInt(1);
userMap.put(userId, newId); // put in map for later retrieval
close(keysRs);
log.info("Bugzilla user [" + userId + ", " + loginName + " ] > Redmine user [" + newId + ", "
+ newLoginName + "]");
}
log.info(userMap.size() + " users created.");
} finally {
close(selectStmt);
close(insertStmt);
close(selectRs);
close(keysRs);
}
}
private void createProjects(Connection destCon, Connection srcCon) throws Exception {
log.info("Creating project: ABC . . .");
createProject(destCon, srcCon, "INSERT INTO projects (name, description, identifier, created_on, "
+ "updated_on) VALUES ('ABC', 'ABC', 'abc', now(), now())", 77);
// repaet it for more projects
// Following projects already exist there, just migrate their issues
log.info("Migrating project: XYZ . . . "
+ "Redmine project id is 13 for old Bugzilla project id 91");
createProjectArtifacts(destCon, srcCon, 91, 13);
projectMap.put(91, 13);
}
private void createProject(Connection destCon, Connection srcCon, String insertSql, int bugzillaProjId)
throws Exception {
Statement insertStmt = null;
ResultSet rs = null;
try {
insertStmt = destCon.createStatement();
insertStmt.executeUpdate(insertSql, new String[] { "id" });
rs = insertStmt.getGeneratedKeys();
rs.next();
int redmineProjId = rs.getInt(1);
// put in map for later retrieval
projectMap.put(bugzillaProjId, redmineProjId);
log.info(" Redmine project id is " + redmineProjId + " for old Bugzilla project id "
+ bugzillaProjId);
createProjectArtifacts(destCon, srcCon, bugzillaProjId, redmineProjId);
} finally {
close(rs);
close(insertStmt);
}
}
private void createProjectArtifacts(Connection destCon, Connection srcCon, int bugzillaProjId,
int redmineProjId) throws Exception {
int[] trackers = { 1, 2, 3 };
Statement insertStmt = null;
try {
insertStmt = destCon.createStatement();
// enable issue tracker module
insertStmt.executeUpdate("INSERT INTO enabled_modules (project_id, name) VALUES ("
+ redmineProjId + ", 'issue_tracking')");
log.info(" Enabled tracker module");
// create trackers
log.info(" Creating trackers");
for (int tracker : trackers)
insertStmt.executeUpdate("INSERT INTO projects_trackers (project_id, tracker_id) VALUES ("
+ redmineProjId + ", " + tracker + ")");
Thread.sleep(1000);
log.info(" Creating versions");
createVersions(destCon, srcCon, bugzillaProjId, redmineProjId);
Thread.sleep(1000);
log.info(" Assigining project members");
assignProjectMembers(destCon, srcCon, bugzillaProjId, redmineProjId);
Thread.sleep(1000);
log.info(" Creating issue categories");
createCategories(destCon, srcCon, bugzillaProjId, redmineProjId);
Thread.sleep(1000);
log.info(" Migrating issues");
createIssues(destCon, srcCon, bugzillaProjId, redmineProjId);
} finally {
close(insertStmt);
}
}
private void createVersions(Connection destCon, Connection srcCon, int bugzillaProjId, int redmineProjId)
throws Exception {
String SELECT = "SELECT value FROM versions WHERE product_id = " + bugzillaProjId;
String INSERT = "INSERT INTO versions (project_id, name, created_on, updated_on) VALUES (?, ?, now(), now())";
Statement selectStmt = null;
PreparedStatement insertStmt = null;
ResultSet selectRs = null;
ResultSet keysRs = null;
try {
insertStmt = destCon.prepareStatement(INSERT, new String[] { "id" });
selectStmt = srcCon.createStatement();
selectRs = selectStmt.executeQuery(SELECT);
while (selectRs.next()) {
String name = selectRs.getString(1);
log.info(" Creating version: " + name);
insertStmt.setInt(1, redmineProjId);
insertStmt.setString(2, name);
insertStmt.executeUpdate();
keysRs = insertStmt.getGeneratedKeys();
keysRs.next();
int newId = keysRs.getInt(1);
versionMap.put(bugzillaProjId + name, newId); // store for later retrieval
keysRs.close();
}
} finally {
close(selectStmt);
close(insertStmt);
close(selectRs);
}
}
private void assignProjectMembers(Connection destCon, Connection srcCon, int bugzillaProjId,
int redmineProjId) throws Exception {
String SELECT = "SELECT DISTINCT p.userid, pd.id, p.login_name "
+ "FROM group_control_map gcm, user_group_map ugm, profiles p, products pd "
+ "WHERE gcm.group_id = ugm.group_id AND ugm.user_id = p.userid AND gcm.product_id = pd.id "
+ "AND pd.id = " + bugzillaProjId;
String INSERT = "INSERT INTO members (user_id, project_id, role_id, created_on) VALUES (?, ?, ?, now())";
Statement selectStmt = null;
PreparedStatement insertStmt = null;
ResultSet selectRs = null;
try {
insertStmt = destCon.prepareStatement(INSERT);
selectStmt = srcCon.createStatement();
selectRs = selectStmt.executeQuery(SELECT);
int member = -1;
while (selectRs.next()) {
member = selectRs.getInt(1);
String login_name = selectRs.getString(3);
log
.info(" Assigning member: " + member + "(" + userMap.get(member) + "), "
+ login_name);
insertStmt.setInt(1, userMap.get(member));
insertStmt.setInt(2, redmineProjId);
if (login_name.endsWith("@domain.com"))
insertStmt.setInt(3, 4); // mark everyone as developer
else
insertStmt.setInt(3, 6); // client
insertStmt.addBatch();
}
insertStmt.executeBatch();
} finally {
close(selectRs);
close(selectStmt);
close(insertStmt);
}
}
private void createCategories(Connection destCon, Connection srcCon, int bugzillaProjId, int redmineProjId)
throws Exception {
String SELECT = "SELECT id, product_id, name, initialowner FROM components WHERE product_id = "
+ bugzillaProjId;
String INSERT = "INSERT INTO issue_categories (project_id, name, assigned_to_id) VALUES (?, ?, ?)";
Statement selectStmt = null;
PreparedStatement insertStmt = null;
ResultSet selectRs = null;
ResultSet keysRs = null;
try {
insertStmt = destCon.prepareStatement(INSERT, new String[] { "id" });
selectStmt = srcCon.createStatement();
selectRs = selectStmt.executeQuery(SELECT);
while (selectRs.next()) {
insertStmt.setInt(1, redmineProjId);
String name = selectRs.getString(3);
name = name.length() <= 30 ? name : name.substring(0, 30);
log.info(" Creating category: " + name);
insertStmt.setString(2, name);
insertStmt.setInt(3, userMap.get(selectRs.getInt(4)));
insertStmt.executeUpdate();
keysRs = insertStmt.getGeneratedKeys();
keysRs.next();
int newCatId = keysRs.getInt(1);
close(keysRs);
categoryMap.put(selectRs.getInt(1), newCatId);
}
} finally {
close(selectRs);
close(keysRs);
close(insertStmt);
close(selectStmt);
}
}
private void createIssues(Connection destCon, Connection srcCon, int bugzillaProjId, int redmineProjId)
throws Exception {
String SELECT = "SELECT bugs.bug_id, bugs.short_desc, longdescs.thetext, bugs.bug_file_loc AS url, bugs.assigned_to, "
+ "bugs.reporter, bugs.creation_ts, longdescs.bug_when, bugs.priority, bugs.version, bugs.component_id, "
+ "bugs.bug_status, bugs.resolution, bugs.bug_severity, longdescs.comment_id, longdescs.who, lastdiffed, "
+ "bugs.cf_phases1, bugs.cf_work "
+ "FROM bugs, longdescs WHERE bugs.bug_id = longdescs.bug_id AND bugs.product_id = "
+ bugzillaProjId;
String INSERT_ISSUE = "INSERT INTO issues (project_id, subject, description, assigned_to_id, author_id, created_on, "
+ "updated_on, start_date, priority_id, fixed_version_id, category_id, tracker_id, status_id) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
String INSERT_JOURNAL = "INSERT INTO journals (journalized_id, journalized_type, user_id, notes, created_on) "
+ " VALUES (?, ?, ?, ?, ?)";
String INSERT_CUSTOM = "INSERT INTO custom_values (customized_type, customized_id, custom_field_id, value) "
+ "VALUES ('Issue', ?, ?, ?)";
Statement selectStmt = null;
PreparedStatement insertIssueStmt = null;
PreparedStatement insertJournalStmt = null;
PreparedStatement insertCustomStmt = null;
ResultSet selectRs = null;
ResultSet keysRs = null;
try {
insertIssueStmt = destCon.prepareStatement(INSERT_ISSUE, new String[] { "id" });
insertJournalStmt = destCon.prepareStatement(INSERT_JOURNAL);
insertCustomStmt = destCon.prepareStatement(INSERT_CUSTOM);
selectStmt = srcCon.createStatement();
selectRs = selectStmt.executeQuery(SELECT);
int bugId = -1;
int redmineBugId = -1;
while (selectRs.next()) {
int currentBugId = selectRs.getInt(1);
// first record for the bug
if (currentBugId != bugId) {
bugId = currentBugId;
log.info(" " + bugId + " >>>");
insertIssueStmt.setInt(1, redmineProjId);
insertIssueStmt.setString(2, selectRs.getString(2)); // subject
// description along with URL
insertIssueStmt.setString(3, selectRs.getString(3) + "\n\n" + selectRs.getString(4));
insertIssueStmt.setInt(4, userMap.get(selectRs.getInt(5))); // assigned_to
insertIssueStmt.setInt(5, userMap.get(selectRs.getInt(6)));// reporter
insertIssueStmt.setTimestamp(6, selectRs.getTimestamp(7)); // created_on
insertIssueStmt.setTimestamp(7, selectRs.getTimestamp(17)); // updated_on
insertIssueStmt.setDate(8, selectRs.getDate(7)); // start_date
insertIssueStmt.setInt(9, priority.get(selectRs.getString(9)));// priority
// fixed_version_id - do post processing to set 0 values to null/empty
insertIssueStmt.setInt(10, versionMap.get(bugzillaProjId + selectRs.getString(10)));
insertIssueStmt.setInt(11, categoryMap.get(selectRs.getInt(11))); // component/category
insertIssueStmt.setInt(12, tracker.get(selectRs.getString(14))); // tracker
insertIssueStmt.setInt(13, status.get(selectRs.getString(12))); // status
// (resolution)
insertIssueStmt.executeUpdate();
keysRs = insertIssueStmt.getGeneratedKeys();
keysRs.next();
redmineBugId = keysRs.getInt(1);
close(keysRs);
issueMap.put(bugId, redmineBugId);
// Custom values Severity(5), Defect Injection(3) and Defect Cause(4)
insertCustomStmt.setInt(1, redmineBugId);
insertCustomStmt.setInt(2, 5); // severity
insertCustomStmt.setString(3, selectRs.getString(14));
insertCustomStmt.addBatch();
insertCustomStmt.setInt(1, redmineBugId);
insertCustomStmt.setInt(2, 3); // injection
insertCustomStmt.setString(3, selectRs.getString(18));
insertCustomStmt.addBatch();
insertCustomStmt.setInt(1, redmineBugId);
insertCustomStmt.setInt(2, 4); // cause
insertCustomStmt.setString(3, selectRs.getString(19));
insertCustomStmt.addBatch();
// Map Bugzilla CC to Redmine Watchers
createWatchers(destCon, srcCon, bugId, redmineBugId);
// log.info(" Watchers (CC list) migrated");
// process attachments (11931 has attachments)
handleAttachments(destCon, srcCon, bugId, redmineBugId);
// log.info(" Attachments migrated");
} else {
// Create journal entries (comments on issues)
insertJournalStmt.setInt(1, redmineBugId);
insertJournalStmt.setString(2, "Issue");
insertJournalStmt.setInt(3, userMap.get(selectRs.getInt(16)));
insertJournalStmt.setString(4, selectRs.getString(3));
insertJournalStmt.setTimestamp(5, selectRs.getTimestamp(8));
insertJournalStmt.addBatch();
}
}
insertJournalStmt.executeBatch();
log.info(" Created Journal entries");
insertCustomStmt.executeBatch();
log.info(" Created severity, cuase and injection");
// Map Bugzilla Dependencies to Redmine Relations and Duplicates
for (Integer issue : issueMap.keySet()) {
updateIssueRelations(destCon, srcCon, issue);
// log.info(" Updated related and duplicate issues");
}
} finally {
close(selectRs);
close(selectStmt);
close(insertIssueStmt);
close(insertJournalStmt);
close(insertCustomStmt);
}
}
private void createWatchers(Connection destCon, Connection srcCon, int bugzillaBugId, int redmineBugId)
throws Exception {
String INSERT = "INSERT INTO watchers (watchable_id, user_id, watchable_type) VALUES (?, ?, 'Issue')";
String SELECT = "SELECT bug_id, who FROM cc where bug_id = " + bugzillaBugId;
Statement selectStmt = null;
PreparedStatement insertStmt = null;
ResultSet selectRs = null;
String cc = null;
try {
insertStmt = destCon.prepareStatement(INSERT);
selectStmt = srcCon.createStatement();
selectRs = selectStmt.executeQuery(SELECT);
int count = 0;
while (selectRs.next()) {
insertStmt.setInt(1, redmineBugId);
int who = selectRs.getInt(2);
if (count == 0)
cc = " Adding as watcher: " + who + "(" + userMap.get(who) + ")";
else
cc += ", " + who + "(" + userMap.get(who) + ")";
insertStmt.setInt(2, userMap.get(who));
insertStmt.addBatch();
count++;
}
insertStmt.executeBatch();
} finally {
close(selectRs);
close(selectStmt);
close(insertStmt);
if (cc != null && cc.length() > 0)
log.info(cc);
}
}
private void handleAttachments(Connection destCon, Connection srcCon, int bugzillaBugId, int redmineBugId)
throws Exception {
String SELECT = "SELECT a.filename, a.creation_ts, ad.thedata, a.attach_id, a.bug_id, a.mimetype, a.submitter_id, a.description "
+ "FROM attachments a, attach_data ad WHERE a.attach_id = ad.id and a.bug_id = "
+ bugzillaBugId;
String INSERT = "INSERT INTO attachments (container_id, container_type, filename, disk_filename, filesize, "
+ "content_type, digest, downloads, author_id, created_on, description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
Statement selectStmt = null;
PreparedStatement insertStmt = null;
ResultSet selectRs = null;
try {
insertStmt = destCon.prepareStatement(INSERT);
selectStmt = srcCon.createStatement();
selectRs = selectStmt.executeQuery(SELECT);
while (selectRs.next()) {
String fileName = selectRs.getString(1);
Timestamp fileTs = selectRs.getTimestamp(2);
// disk file name in redmine is yymmddhhmmss_filename.ext
String diskFileName = sdf.format(fileTs) + "_" + fileName;
log.info(" Saving attachment: " + fileName + "(" + diskFileName + ") for issue "
+ bugzillaBugId + "(" + redmineBugId + ")");
Blob blob = selectRs.getBlob(3);
byte[] allBytesInBlob = blob.getBytes(1, (int) blob.length());
File file = new File(filePath, diskFileName);
file.createNewFile();
FileOutputStream fos = new FileOutputStream(file);
fos.write(allBytesInBlob);
fos.flush();
fos.close();
insertStmt.setInt(1, redmineBugId);
insertStmt.setString(2, "Issue");
insertStmt.setString(3, fileName);
insertStmt.setString(4, diskFileName);
insertStmt.setInt(5, allBytesInBlob.length);
insertStmt.setString(6, selectRs.getString(6)); // mime type
insertStmt.setString(7, ""); // digest- leave it blank
insertStmt.setInt(8, 0); // downloads
insertStmt.setInt(9, userMap.get(selectRs.getInt(7))); // author
insertStmt.setTimestamp(10, fileTs);
insertStmt.setString(11, selectRs.getString(8));
insertStmt.addBatch();
}
insertStmt.executeBatch();
} finally {
close(selectRs);
close(selectStmt);
close(insertStmt);
}
}
private void updateIssueRelations(Connection destCon, Connection srcCon, int bugzillaBugId)
throws Exception {
String SELECT_DUP = "SELECT dupe_of, dupe FROM duplicates WHERE dupe_of = " + bugzillaBugId;
String SELECT_DEP = "SELECT blocked, dependson FROM dependencies WHERE blocked = " + bugzillaBugId;
String INSERT = "INSERT INTO issue_relations (issue_from_id, issue_to_id, relation_type) VALUES (?, ?, ?)";
Statement selectStmt = null;
PreparedStatement insertStmt = null;
ResultSet rs = null;
try {
insertStmt = destCon.prepareStatement(INSERT);
selectStmt = srcCon.createStatement();
rs = selectStmt.executeQuery(SELECT_DUP);
while (rs.next()) {
insertStmt.setInt(1, issueMap.get(rs.getInt(1)));
insertStmt.setInt(2, issueMap.get(rs.getInt(2)));
insertStmt.setString(3, "duplicates");
insertStmt.addBatch();
}
close(rs);
rs = selectStmt.executeQuery(SELECT_DEP);
while (rs.next()) {
insertStmt.setInt(1, rs.getInt(1));
insertStmt.setInt(2, rs.getInt(2));
insertStmt.setString(3, "blocks");
insertStmt.addBatch();
}
insertStmt.executeBatch();
} finally {
close(rs);
close(selectStmt);
close(insertStmt);
}
}
private void close(ResultSet rs) {
if (rs == null)
return;
try {
rs.close();
} catch (Exception e) {
log.error("Failed to close ResultSet", e);
}
}
private void close(Statement stmt) {
if (stmt == null)
return;
try {
stmt.close();
} catch (Exception e) {
log.error("Failed to close PreparedStatement", e);
}
}
}
Sunday, October 04, 2009
Migrating from Bugzilla to Redmine
Subscribe to:
Posts (Atom)