Java sql transactions. What am I doing wrong?
我写这个小测试的唯一目的是为了更好地理解jdbc中的事务。尽管我已经按照文档进行了所有操作,但是该测试并不希望正常运行。
这是表结构:
1 2 3 4 5 | CREATE TABLE `default_values` ( `id` INT UNSIGNED NOT auto_increment, `is_default` BOOL DEFAULT false, PRIMARY KEY(`id`) ); |
测试包含3个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 | public class DefaultDeleter implements Runnable { public synchronized void deleteDefault() throws SQLException { Connection conn = null; Statement deleteStmt = null; Statement selectStmt = null; PreparedStatement updateStmt = null; ResultSet selectSet = null; try { conn = DriverManager.getConnection("jdbc:mysql://localhost/xtest","root",""); conn.setAutoCommit(false); conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); // Deleting current default entry deleteStmt = conn.createStatement(); deleteStmt.executeUpdate("DELETE FROM `default_values` WHERE `is_default` = true"); // Selecting first non default entry selectStmt = conn.createStatement(); selectSet = selectStmt.executeQuery("SELECT `id` FROM `default_values` ORDER BY `id` LIMIT 1"); if (selectSet.next()) { int id = selectSet.getInt("id"); // Updating found entry to set it default updateStmt = conn.prepareStatement("UPDATE `default_values` SET `is_default` = true WHERE `id` = ?"); updateStmt.setInt(1, id); if (updateStmt.executeUpdate() == 0) { System.err.println("Failed to set new default value."); System.exit(-1); } } else { System.err.println("Ooops! I've deleted them all."); System.exit(-1); } conn.commit(); conn.setAutoCommit(true); } catch (SQLException e) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } throw e; } finally { try { selectSet.close(); } catch (Exception e) {} try { deleteStmt.close(); } catch (Exception e) {} try { selectStmt.close(); } catch (Exception e) {} try { updateStmt.close(); } catch (Exception e) {} try { conn.close(); } catch (Exception e) {} } } public void run() { while (true) { try { deleteDefault(); } catch (SQLException e) { e.printStackTrace(); System.exit(-1); } try { Thread.sleep(20); } catch (InterruptedException e) {} } } } public class DefaultReader implements Runnable { public synchronized void readDefault() throws SQLException { Connection conn = null; Statement stmt = null; ResultSet rset = null; try { conn = DriverManager.getConnection("jdbc:mysql://localhost/xtest","root",""); conn.setAutoCommit(false); conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); stmt = conn.createStatement(); rset = stmt.executeQuery("SELECT * FROM `default_values` WHERE `is_default` = true"); int count = 0; while (rset.next()) { count++; } if (count == 0) { System.err.println("Default entry not found. Fail."); System.exit(-1); } else if (count > 1) { System.err.println("Count is" + count +"! Wtf?!"); } conn.commit(); conn.setAutoCommit(true); } catch (SQLException e) { try { conn.rollback(); } catch (Exception ex) { ex.printStackTrace(); } throw e; } finally { try { rset.close(); } catch (Exception e) {} try { stmt.close(); } catch (Exception e) {} try { conn.close(); } catch (Exception e) {} } } public void run() { while (true) { try { readDefault(); } catch (SQLException e) { e.printStackTrace(); System.exit(-1); } try { Thread.sleep(20); } catch (InterruptedException e) {} } } } public class Main { public static void main(String[] args) { try { Driver driver = (Driver) Class.forName("com.mysql.jdbc.Driver") .newInstance(); DriverManager.registerDriver(driver); Connection conn = null; try { conn = DriverManager.getConnection("jdbc:mysql://localhost/xtest","root",""); System.out.println("Is transaction isolation supported by driver?" + (conn.getMetaData() .supportsTransactionIsolationLevel( Connection.TRANSACTION_SERIALIZABLE) ?"yes" :"no")); } finally { try { conn.close(); } catch (Exception e) {} } (new Thread(new DefaultReader())).start(); (new Thread(new DefaultDeleter())).start(); System.in.read(); System.exit(0); } catch (Exception e) { e.printStackTrace(); } } } |
我已经编写了脚本,每次运行都会为该表填充100k条记录(其中一个是默认记录)。但是每次我运行此测试时,输出为:
Is transaction isolation supported by driver? yes
Default entry not found. Fail.
此代码有什么问题?
请确保您正在创建InnoDB表,MyISAM(默认值)不支持事务。您可以将您的数据库创建更改为此:
1 2 3 4 5 | CREATE TABLE `default_values` ( `id` INT UNSIGNED NOT auto_increment, `is_default` BOOL DEFAULT false, PRIMARY KEY(`id`) ) Engine=InnoDB; |
另一个示例:带有记帐应用程序的MySQL事务
有两点值得一提:
在测试真正起作用之前,您的脚本是否会填充数据库?尝试从Java代码内部在表上执行
请勿在所有地方执行
答案很简单:您创建了两个线程。它们彼此完全独立运行。由于您不以任何方式进行同步,因此无法确定哪个人先进入数据库。如果阅读器是第一个阅读器,则删除器尚未开始,并且不会有
接下来,您已经完全隔离了这两个事务(
如果不是这种情况,则删除器的速度比读取器慢,因此在读取器寻找记录时用
[EDIT]现在,您说测试开始时应该有一个带有
我建议您添加一些断点并逐步执行每个数据库操作,以检查它们是否在按预期进行。您可以在数据库服务器上打开会话并设置事务隔离级别,以便可以读取未提交的数据。
还要检查在布尔值类型的数字值1上使用'true'在MySql中是否有效。
如果允许容器管理事务,则可以执行以下操作:
1 2 | @Resource private UserTransaction utx; |
,然后在您的代码中使用它:
1 2 3 4 5 | utx.begin(); // atomic operation in here utx.commit(); |
那么您就不必担心事务管理的复杂性了。
编辑:@Gris:是的,您是正确的。我以为您正在开发Web应用程序。正如pjp所说,在这种情况下,spring是一个不错的选择。或者-根据应用程序的大小和复杂性-您可以通过管理自己的事务来实现。