JDBC处理事务
在JDBC的数据库操作中,一项事务是由一条或是多条表达式所组成的一个不可分割的工作单元。我们通过提交commit()或是回退rollback()来结束事务的操作。
在JDBC中,事务操作默认是自动提交。也就是说,一条对数据库的更新表达式代表一项事务操作。操作成功后,系统将自动调用commit()来提交,否则将调用rollback()来回退.
在JDBC中,可以通过调用setAutoCommit(false)来禁止自动提交。之后就可以把多个数据库操作的表达式作为一个事务,在操作完成 后调用commit()来进行整体提交。倘若其中一个表达式操作失败,都不会执行到commit(),并且将产生响应的异常。此时就可以在异常捕获时调用 rollback()进行回退。这样做可以保持多次更新操作后,相关数据的一致性
模拟两个账号之间的转账业务
有两个银行账号aaa,bbb,要求从aaa账号中向bbb账号转账200元。
1.创建数据表
CREATE TABLE `jdbc`.`account`( `name` VARCHAR(20), `money` DOUBLE );
INSERT INTO `jdbc`.`account` (`name`, `money`) VALUES ('aaa', '300');
INSERT INTO `jdbc`.`account` (`name`, `money`) VALUES ('bbb', '300');
2.不带事务处理的代码
public static void main(String[] args)
{
String outAccount="aaa";
String inAccount="bbb";
double amount=200;
Connection conn=null;
PreparedStatement pstmt1=null;
PreparedStatement pstmt2=null;
try {
conn=JDBCUtils.getConnection();
//账号转出200
String sql="update account set money=money-? where name=? and money>=200";
pstmt1=conn.prepareStatement(sql);
//设置参数
pstmt1.setDouble(1, amount);
pstmt1.setString(2, outAccount);
pstmt1.executeUpdate();
//账号转入200
String sql2="update account set money=money+? where name=?";
pstmt2=conn.prepareStatement(sql2);
pstmt2.setDouble(1, amount);
pstmt2.setString(2, inAccount);
pstmt2.executeUpdate();
System.out.println("转账成功");
} catch (Exception e) {
// TODO: handle exception
System.out.println(e.getMessage());
}
finally
{
JDBCUtils.release(pstmt1, conn);
JDBCUtils.release(pstmt2);
}
}
3.带事务处理的代码
public static void main(String[] args)
{
String outAccount="aaa";
String inAccount="bbb";
double amount=200;
Connection conn=null;
PreparedStatement pstmt1=null;
PreparedStatement pstmt2=null;
try {
conn=JDBCUtils.getConnection();
//控制事务,关闭事务的自动提交
conn.setAutoCommit(false);
//账号转出200
String sql="update account set money=money-? where name=? and money>=200";
pstmt1=conn.prepareStatement(sql);
//设置参数
pstmt1.setDouble(1, amount);
pstmt1.setString(2, outAccount);
pstmt1.executeUpdate();
//账号转入200
String sql2="update account2 set money=money+? where name=?";
pstmt2=conn.prepareStatement(sql2);
pstmt2.setDouble(1, amount);
pstmt2.setString(2, inAccount);
pstmt2.executeUpdate();
//提交事务
conn.commit();
System.out.println("转账成功");
} catch (Exception e) {
// TODO: handle exception
//回滚事务
try {
conn.rollback();
System.out.println("转账失败");
} catch (Exception e2) {
// TODO: handle exception
e2.printStackTrace();
}
}
finally
{
JDBCUtils.release(pstmt1, conn);
JDBCUtils.release(pstmt2);
}
}
数据库连接池
为什么使用连接池
JDBC 作为一种数据库访问技术,具有简单易用的优点。但使用这种模式进行 Web 应用 程序开发, 存在很多问题: 首先, 每一次 Web 请求都要建立一次数据库连接。 建立连接是一 个 费时的活动,每次都得花费 0.05s~1s 的时间,而且系统还要分配内存资源。这个时间对于一次或几次数 据库操作,或许感觉不出系统有多大的开销。可是 对于现在的 Web 应用,尤其是大型电子商务网站, 同 时有几百人甚至几千人在线是很正常的事。在这种情况下,频繁的进行数据库连接操作势必占用很多的系 统资源,网站的响应速度必定下降,严重的甚至会造成服 务器的崩溃
为解决上述问题,可以采用数据库连接池技术。数据库连接池的基 本思想就是为数据库连接 建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库 连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定 连接池最大连接数来 防止系统无尽的与数据库连接。更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量﹑ 使用情况,为系统开发﹑测试及性能调 整提供依据.
DataSource接口
JDBC1.0是原来是用DriverManager类来产生一个对数据源的连接。JDBC2.0用一种替代的方法,使用DataSource的实现,代码变的更小巧精致,也更容易控制。
一个DataSource对象代表了一个真正的数据源。根据DataSource的实现方法,数据源既可以是从关系数据库,也电子表格,还可以是一个表格形式的文件。当一个DataSource对象注册到名字服务中,应用程序就可以通过名字服务获得DataSource对象,并用它来产生一个与DataSource代表的数据源之间的连接。
关于数据源的信息和如何来定位数据源,例如数据库服务器的名字,在哪台机器上,端口号等等,都包含在DataSource对象的属性里面去了。
c3p0数据源(连接池)
C3P0是目前最流行的开源数据库连接池之一,它实现了DataSource数据源接口,支持JDBC2和JDBC3的标准规范,易于扩展并且性能优越。
ComboPooledDataSource是C3P0的核心类,实现了DataSource接口。
当使用C3P0数据源时,首先需要创建数据源对象,创建数据源对象有两种方式,第一种是通过ComboPooledDataSource类直接创建数据源对象,第二种是通过读取配置文件创建数据源对象
1.通过ComboPooledDataSource类直接创建数据源对象
public class C3P0Test1 {
public static DataSource ds=null;
//初始化c3p0数据源
static{
ComboPooledDataSource cpds=new ComboPooledDataSource();
//设置连接数据库需要的配置信息
try {
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc");
cpds.setUser("root");
cpds.setPassword("root");
//设置连接池的参数
cpds.setInitialPoolSize(5);
cpds.setMaxPoolSize(15);
ds=cpds;
} catch (Exception e) {
// TODO: handle exception
throw new ExceptionInInitializerError(e);
}
}
public static void main(String[] args) throws SQLException
{
//获取数据库连接对象
System.out.println(ds.getConnection());
}
}
2.通过读取配置文件创建数据源对象
通过ComboPooledDataSource(String configName)构造方法读取c3p0-config.xml配置文件,创建数据源对象,然后获取数据库连接对象。
c3p0-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="user">root</property>
<property name="password">root</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">
jdbc:mysql://localhost:3306/jdbc</property>
<property name="checkoutTimeout">30000</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
<property name="maxStatements">200</property>
</default-config>
<named-config name="itcast">
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">15</property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">
jdbc:mysql://localhost:3306/jdbc</property>
<property name="user">root</property>
<property name="password">root</property>
</named-config>
</c3p0-config>
在以上配置中配置了两套数据源,…中的信息是默认配置,在没有指定配置时默认使用该配置创建c3p0数据源对象;
<named-config>...</named-config>
中的信息是自定义配置,一个配置文件中可以有零个或多个自定义配置,当用户需要自定义配置时,调用ComboPooledDataSource(String configName)方法,传入<named-config>
节点中name属性的值即可创建C3P0数据源对象。这种设置的好处是,当程序在后期更换数据源配置时,只需要修改构造方法中对应的name值 即可。
public class C3P0Test2 {
public static DataSource ds=null;
//初始化C3P0数据源
static{
//使用c3p0-config.xml配置文件中的named-config节点中name属性的值
ComboPooledDataSource cpds=new ComboPooledDataSource("itcast");
ds=cpds;
}
public static void main(String[] args) throws SQLException
{
System.out.println(ds.getConnection());
}
}
注意:
1.配置文件名称必须为c3p0-config.xml,并且位于该项目的src根目录下
2.当传入的configName值为空或者不存在时,则会使用默认的配置方式创建数据源