二、JDBC进阶
1、事务
(一)概念
(二)事务的ACID(acid)属性
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响
(三)JDBC中的事务处理
Connection的三个方法与事务相关:
setAutoCommit(boolean):设置是否为自动提交事务,如果true(默认值就是true)表示自动提交,也就是每条执行的SQL语句都是一个单独的事务,如果设置false,那么就相当于开启了事务了;con.setAutoCommit(false)表示开启事务!!!
commit():提交结束事务;con.commit();表示提交事务
rollback():回滚结束事务。con.rollback();表示回滚事务
jdbc处理事务的代码格式:
try {
con.setAutoCommit(false);//开启事务…
….
…
con.commit();//try的最后提交事务
} catch() {
con.rollback();//回滚事务
}
public void transfer(boolean b) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = JdbcUtils.getConnection();
//手动提交
con.setAutoCommit(false);
String sql = "update account set balance=balance+? where id=?";
pstmt = con.prepareStatement(sql);
//操作
pstmt.setDouble(1, -10000);
pstmt.setInt(2, 1);
pstmt.executeUpdate();
// 在两个操作中抛出异常
if(b) {
throw new Exception();
}
pstmt.setDouble(1, 10000);
pstmt.setInt(2, 2);
pstmt.executeUpdate();
//提交事务
con.commit();
} catch(Exception e) {
//回滚事务
if(con != null) {
try {
con.rollback();
} catch(SQLException ex) {}
}
throw new RuntimeException(e);
} finally {
//关闭
JdbcUtils.close(con, pstmt);
}
}
总结:
1、设置为手动提交事务,即开启了事务。conn.setAutoCommit(boolean);
2、如果出现了异常就回滚结束事务 conn.rollback();
3、当两个操作都执行完了,提交结束事务。 conn.commit();
(四)保存点
新的 JDBC 3.0 还原点接口提供了额外的事务控制。大部分现代的数据库管理系统的环境都支持设定还原点,例如 Oracle 的 PL/SQL。
当你在事务中设置一个还原点来定义一个逻辑回滚点。如果在一个还原点之后发生错误,那么可以使用 rollback 方法来撤消所有的修改或在该还原点之后所做的修改。
Connection 对象有两个新的方法来管理还原点-
-
setSavepoint(String savepointName): 定义了一个新的还原点。它也返回一个 Savepoint 对象。
- releaseSavepoint(Savepoint savepointName): 删除一个还原点。请注意,它需要一个作为参数的 Savepoint 对象。这个对象通常是由 setSavepoint() 方法生成的一个还原点。
有一个 rollback (String savepointName) 方法,该方法可以回滚到指定的还原点。
下面的例子说明了如何使用 Savepoint 对象-
try{
//Assume a valid connection object conn
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
//set a Savepoint
Savepoint savepoint1 = conn.setSavepoint("Savepoint1");
String SQL = "INSERT INTO Employees " +
"VALUES (106, 20, 'Rita', 'Tez')";
stmt.executeUpdate(SQL);
//Submit a malformed SQL statement that breaks
String SQL = "INSERTED IN Employees " +
"VALUES (107, 22, 'Sita', 'Tez')";
stmt.executeUpdate(SQL);
// If there is no error, commit the changes.
conn.commit();
}catch(SQLException se){
// If there is any error.
conn.rollback(savepoint1);
}
在这种情况下,之前的 INSERT 语句不会成功,一切都将被回滚到最初状态。
(五)并发事务问题和事务的隔离级别
JDBC设置隔离级别
con.setTransactionIsolation(int level)
参数可选值如下:
Connection.TRANSACTION_READ_UNCOMMITTED;
Connection.TRANSACTION_READ_COMMITTED;
Connection.TRANSACTION_REPEATABLE_READ;
Connection.TRANSACTION_SERIALIZABLE。
查看和设置数据库的隔离级别(MYSQL)
2、数据库连接池
1)数据库连接池的概念
用池来管理Connection,这可以重复使用Connection。有了池,所以我们就不用自己来创建Connection,而是通过池来获取Connection对象。当使用完Connection后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池。池就可以再利用这个Connection对象了。
2)JDBC数据库连接池接口(DataSource)
Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商可以让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池!
3)DBCP数据库连接池
使用:
/**
* 使用方法一:
* 直接newBasicDataSource对象
* 然后手动设置四大参数和数据库连接池的基本信息
* @throws SQLException
*/
@Test
public void testDBCP() throws SQLException {
//BasicDataSource是DataSource接口的实现类,由DBCP提供
final BasicDataSource dataSource = new BasicDataSource();
//2、为数据源指定必须的属性
dataSource.setUsername("root");
dataSource.setPassword("000");
dataSource.setUrl("jdbc:mysql:///mydb1");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
//3指定数据源的一些可选属性
//指定数据库连接池中,初始化连接数的个数
dataSource.setInitialSize(5);
//指定最大连接数:即可以向数据库申请的最大连接数
dataSource.setMaxActive(5);
//指定最小连接数:在数据库连接池中保存的最少的空闲连接数量
dataSource.setMinIdle(2);
//等待数据库连接池分配连接的最长时间 毫秒
dataSource.setMaxWait(1000*5);
//4、从数据源中获取数据库连接
Connection connection = dataSource.getConnection();
System.out.println(connection.getClass());
}
/**
* 使用方法二:
* 1、加载dbcp的properties配置文件:配置文件的键需要来自BasicDataSource的属性
* (因为你的代码中没有显式的提取键的值和为BasicData Source属性赋值
* 所以,这些必然是实现在源码中的,故肯定要求使用默认的键名称,与属性名称匹配)
*
* driverClassName url username password
*
* 2、调用BasicDataSourceFactory 的createDataSource方法创建DataSource对象
*
* 3、从DataSource对象中获取数据库连接
*
* 4、四大参数:driverClassName url username password
* 和数据库连接池的基本信息全都在dbcp.properties文件中
*
* 调用此方法:BasicDataSourceFactory.createDataSource(properties);
* 上述信息会自动设置
*
* org.apache.commons.dbcp.BasicDataSource
* BasicDataSource 来自 dbcp数据库连接池jar包
* @throws IOException
*/
@Test
public void testDBCPWithDataSourceFactory() throws Exception {
Properties properties = new Properties();
//"dbcp.properties" --> inputStream
//不能用this,因为是单元测试方法
InputStream inputStream = JDBCTest.class.getClassLoader().getResourceAsStream("dbcp.properties");
properties.load(inputStream);
//不需要显式提取书属性文件的键的值,并为Data Source属性赋值
//因为BasicDataSource中都封装好了,因此,也对键的名称有规定
DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
/**
* 向Mysql的customers 数据表中插入1万条记录
* 测试如何插入,用时最短
* 1、使用PreparedStatement:
* 非批处理:10773
* 批处理:12218
*/
@Test
public void testBatchWithPreparedStatement(){
Connection connection = null;
PreparedStatement preparedStatement = null;
String sql = null;
try {
connection = JDBCTools.getConnection();
JDBCTools.beginTx(connection);
sql = "INSERT INTO customers VALUES(?,?,?)";
preparedStatement = connection.prepareStatement(sql);
Date date = new Date(new java.util.Date().getTime());
long begin = System.currentTimeMillis();
for(int i = 0; i < 100000; i++){
preparedStatement.setInt(1, i + 1);
preparedStatement.setString(2, "name_" + i);
preparedStatement.setDate(3, date);
preparedStatement.executeUpdate();
//preparedStatement.addBatch();
}
//preparedStatement.executeBatch();
long end = System.currentTimeMillis();
System.out.println("Time: " + (end - begin));
JDBCTools.commit(connection);
} catch (Exception e) {
e.printStackTrace();
JDBCTools.rollback(connection);
} finally{
JDBCTools.release(null, preparedStatement, connection);
}
}
dbcp.properties文件的内容:
username=root
password=000
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///mydb1
initialSize=10
maxActive=50
minIdle=5
maxWait=5000
4)C3P0数据库连接池
使用:
/**
* 使用方法一:
* @throws Exception
*/
@Test
public void testC3P0() throws Exception {
Properties properties = new Properties();
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass( "com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql:///mydb1");
ds.setUser("root");
ds.setPassword("000");
Connection connection = ds.getConnection();
System.out.println(connection.getClass().getName());
}
使用方法二:
配置文件要求:
文件名称:必须叫c3p0-config.xml
文件位置:必须在src下
c3p0也可以指定配置文件,而且配置文件可以是properties,也可骒xml的。当然xml的高级一些了。但是c3p0的配置文件名必须为c3p0-config.xml,并且必须放在类路径下。
c3p0的配置文件中可以配置多个连接信息,可以给每个配置起个名字,这样可以方便的通过配置名称来切换配置信息。
c3p0.xml文件的内容:
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!- 默认的配置 ->
<default-config>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">123</property>
<property name="acquireIncrement">3</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">2</property>
<property name="maxPoolSize">10</property>
</default-config>
<named-config name="mysql-config">
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="user">root</property>
<property name="password">123</property>
<property name="acquireIncrement">3</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">2</property>
<property name="maxPoolSize">10</property>
</named-config>
</c3p0-config>
public void fun2() throws PropertyVetoException, SQLException {
ComboPooledDataSource ds = new ComboPooledDataSource(); //不指定配置文件名称,则使用默认配置
Connection con = ds.getConnection();
System.out.println(con);
con.close();
}
public void fun2() throws PropertyVetoException, SQLException {
ComboPooledDataSource ds = new ComboPooledDataSource("mysql-config") ;//指定配置文件名称,
Connection con = ds.getConnection(); //则使用指定的配置
System.out.println(con);
con.close();
}
3、Apache—DBUtils简介