1 事务处理
通过事务处理,我们可以控制一组数据库操作,这组操作要么全部成功,要么全部失败;
比如,在 account 表中,记录了账户 a 和账户 b 均有 1000 元,账户 a 想要往账户 b 转入 100 元。通常的流程是两部操作:
//起始
update account set money=money-100 where name='a';
//意外
update account set money=money+100 where name='b';
但是如果这两条 sql 语句执行过程中出现了意外,应该回到起始点。
在 MySQL 中通过 Transaction 来实现事务处理,使用的流程为:
1 Start Transaction 开启事务
2 Rollback 回滚事务, 事务失效;
3 Commit 提交事务, 事务生效;
利用事务处理改造一下开头提出的转账问题后,流程就变成了:
start transaction;
update account set money=money-100 where name='a';
//意外
rollback;
update account set money=money+100 where name='b';
//成功
commit;
在 Java 中封装的 JDBC 使用事务处理通过 Connection 来实现,通过方法setAutoCommit
和 commit
实现事务的开启和提交。还可以通过 setSavepoint
方法设置回滚点,控制事务回滚过程,下面是一个使用的范例,不清楚 JDBCUtils 的童鞋,请参考我的上篇文章 App 开发者使用 JDBC 驱动操纵 MySql 数据库
public void test2(){
Connection connection = null;
Statement stmt = null;
Savepoint sp = null;
try{
connection = JDBCUtils.getConnection();
connection.setAutoCommit(false);
stmt = connection.createStatement();
stmt.executeUpdate("update account set money=money-100 where name='aaa';");
stmt.executeUpdate("update account set money=money+100 where name='bbb';");
sp = connection.setSavepoint();
stmt.executeUpdate("update account set money=money-100 where name='bbb';");
stmt.executeUpdate("update account set money=money+100 where name='ccc';");
int i = 1/0;
connection.commit();
}catch (Exception e){
e.printStackTrace();
try {
connection.rollback(sp);
connection.commit();//回滚点前的操作要生效;
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally {
try {
connection.commit();//回滚点前的操作要生效;
} catch (SQLException e) {
e.printStackTrace();
}
JDBCUtils.release(null, stmt, connection);
}
}
2 数据库事务的特性
数据库事务特性可以概括为 ACID:
1 Atomicity:原子性;
2 Consistency: 一致性;
3 Isolation: 隔离性;
4 Durable: 持久性;
其中隔离性在多事务操作中显得比较重要,如果隔离性处理不好,可能会造成如下错误:
1 脏读(dirty read): 一个事务读到了其他事务未提交的数据;
2 不可重复读(unreatable read): 一个事务在操作时,读到其他事务已经提交的数据, 主要是 update 语句更新;
3 虚读(phantom read): 一个事务在操作时,读到其他事务已经提交的数据;主要是 insert 语句插入记录;
MySQL 数据库中设置了如下 4 个类型的隔离性级别,用来解决不同的多事务处理场景:
1 read uncommitted (读未提交) 会产生脏读,不重复读,虚度可能发生;
2 read commited (读已提交) 不会脏读,会引发不可重复读,虚读可能发生;
3 repeatable read (可以重复读) 不会脏读,不可重复读,虚度有可能;
4 serializable(串行化) 不会发生三类问题;事务有先后顺序;
MySQL 设置事务处理隔离级别的命令是:set session transaction isolation level 隔离值;
,默认的隔离级别是 repeatable read
。
3 数据库连接池优化程序性能
多事务处理中,需要反复连接,释放对数据库的操作。当数量众多时会导致数据库服务器内存溢出、宕机。Sun 公司给出的解决方案是采用连接池优化对数据库进行访问,其大概原理是:一次性创造多个连接对象放到连接池中,当需要操作数据库时,就从连接池取用,避免了不断创建和释放连接的过程,进而显著提高程序性能。
模拟一个数据库连接池如下:
public class MyDataSource implements DataSource {
//增删操作,使用 LinkedList 效率会更高;
private java.util.LinkedList<Connection> pool = new LinkedList<Connection>();
public MyDataSource(){
//创建连接池
System.out.println("创建 10 个连接池");
for (int i = 0; i < 10; i++) {
Connection connection = JDBCUtils.getConnection();
pool.add(connection);
}
}
@Override
public Connection getConnection() throws SQLException {
//TODO
if (pool.isEmpty()){
for (int i = 0;i < 3;i++){
Connection connection = JDBCUtils.getConnection();
pool.add(connection);
}
}
final Connection conn = pool.removeFirst();
//代理对象
Connection proxyConn = (Connection)Proxy.newProxyInstance(MyDataSource.class.getClassLoader(),
conn.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("close")){
//close 放回池子
addBack2Pool(conn);
return null;
}
return method.invoke(conn, args);
}
});
return conn;
}
public void addBack2Pool(Connection conn){
System.out.println("放回");
pool.add(conn);
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
使用 MyDatabase:
public class MyDataSourceTest {
@Test
public void test1(){
Connection connection = null;
PreparedStatement stmt =null;
MyDataSource ds = null;
try {
ds = new MyDataSource();
connection = ds.getConnection();
stmt = connection.prepareStatement("update" +
"account set money=? where name=?;");
stmt.setDouble(1, 99999);
stmt.setString(2, "aaa");
stmt.executeUpdate();
}catch (Exception e){
e.printStackTrace();
}finally {
// ds.addBack2Pool(connection);
//对close方法进行加强;
JDBCUtils.release(null, stmt , connection);
}
}
}
在创建 Connection 时,利用代理重写了 connection 的 close 方法,这样在使用 JDBCUtils 对 connection 释放时,能主动将其重放回重用池。
Java 最好用的一点就是有各种成型的 API ,重用池也是。Steve Waldman 维护的开源框架 c3p0 就是非常好用的重用池,使用很方便,先导入 jar 包,使用示例如下:
public void test1(){
Connection conn = null;
PreparedStatement stmt = null;
try{
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass( "com.mysql.jdbc.Driver" ); //loads the jdbc driver
cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/数据库名" );
cpds.setUser("root");
cpds.setPassword("11111111");
conn = cpds.getConnection();
stmt = conn.prepareStatement("update " +
"account set money=? where name=?;");
stmt.setDouble(1, 888888);
stmt.setString(2, "a");
stmt.executeUpdate();
}catch (Exception e){
e.printStackTrace();
}finally {
//对 close 方法进行了加强;
JDBCUtils.release(null, stmt, conn);
}
}
c3po 还能通过 xml 文件和 properties 文件实现预定义配置,感觉超级人性,有木有?更多实现请参看它的 git。
网友评论