事务(transaction)是指一组操作,里面包含许多个单一的逻辑. 只要其中一个逻辑没执行成功,那么都算整个事务执行失败,所有的数据都回归到最初的状态(回滚).
事务是确保连续逻辑的成功.例如:银行转账
# 开启事务
start transaction;
# 下面写一系列sql语句
# ...(sql)语句
# 若中途没有发生错误,则使用commit提交事务,将更改提交到磁盘上
commit;
# 若中途发生错误,则使用rollback回滚事务,数据不会写到磁盘上,会维持最初的状态
rollback;
JDBC连接对象默认是自动提交的,因此需要使用conn.setAutoCommit(false)
语句开启事务.
@Test
public void showTransaction() {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtil.getConn();
// JDBC连接对象默认是自动提交的,只有关闭自动提交才能执行事务
conn.setAutoCommit(false);
// 创建一个statement用于执行转账操作,转账过程中两个SQL语句之间不应该发生错误.
String sql = "update account set money = money - ? where id = ?";
ps = conn.prepareStatement(sql);
// 执行SQL语句1
ps.setInt(1, 100);
ps.setInt(2, 1);
ps.executeUpdate();
// SQL语句之间发生错误
int a = 10 / 0;
// 执行SQL语句2
ps.setInt(1, -100);
ps.setInt(2, 2);
ps.executeUpdate();
// 若上述SQL语句执行过程中没有发生错误才提交事务,否则上边对数据库的更改不会被保存
conn.commit();
} catch (SQLException e1) {
try {
// 若上述SQL语句执行过程中发生了错误,则回滚
conn.rollback();
} catch (SQLException e2) {
e2.printStackTrace();
}
e1.printStackTrace();
} finally {
JDBCUtil.release(conn, ps, rs);
}
}
事务之间要求隔离性,即事务在执行期间不应该受到其他事务的影响.因此要对事务设置隔离级别.
不考虑隔离级别设置,会出现以下问题:
未提交读(Read Uncommitted): 事务中的修改,即使没有提交,其他事务也可以看得到. 会导致"脏读”、“幻读”和“不可重复读取”.
提交读(Read Commited): 大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据. 避免了“脏读取”,但不能避免“幻读”和“不可重复读取”,该级别适用于大多数系统.
重复读(Repeatable Read): 保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据. 避免了“脏读取”和“不可重复读取”的情况,但不能避免“幻读”,但是带来了更多的性能损失.
串行化(Serializable): 事务串行执行,一个时刻只能有一个事务被执行. 可以避免"脏读”、“幻读”和“不可重复读取”,但资源消耗最大.
悲观锁假定数据一定会发生丢失更新的情况. 每次在查询数据时都上锁.
在Mysql中查询数据时,在SQL语句后加for update
可以给被查询字段上悲观锁.
select * from 表名 for update;
乐观锁假定数据一定不会发生丢失更新的情况. 查询数据时不会上锁,但是会在表中添加一个版本字段,通过比对版本字段判定是否发生冲突.
乐观锁要有程序员自己实现.
JDBC链接对象的创建工作消耗性能,因此我们一开始先在内存中开辟一块空间存放多个连接对象. 后面需要连接时直接从池中取.
数据库连接池中通过装饰设计模式重写了Connection
对象的close()
方法.调用close()
方法时,不销毁Coonection对象,而是将其放回到连接池中.
开源连接池常用的有 DBCP 和 C3P0
不使用配置文件,使用其set属性()
方法进行初始化,并通过getConnection()
方法得到数据库连接对象.
public void showDBCP() {
Connection conn = null;
PreparedStatement ps = null;
try {
// 1. 构建连接池对象
BasicDataSource dataSource = new BasicDataSource();
// 使用BasicDataSource的方法配置初始化参数
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost/数据库名");
dataSource.setUsername("username");
dataSource.setPassword("password");
// 2. 使用getConnection方法得到连接对象
conn = dataSource.getConnection();
// 下面创建PreStatement对象执行查询
// ...
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtil.release(conn, ps);
}
}
使用连接池工厂的createDataSource(Properties)
方法创建数据库连接池,传入的参数为一个Properties
对象.
连接池工厂会自动从配置文件中读取JDBC的初始化参数.
public void showDBCP() {
Connection conn = null;
PreparedStatement ps = null;
try {
// 1. 建立通过连接池工厂创建连接池
BasicDataSourceFactory factory = new BasicDataSourceFactory();
Properties properties = new Properties();
properties.load(new FileInputStream("src//dbconfig.properties"));
DataSource dataSource = factory.createDataSource(properties);
// 2. 得到连接对象的方法与上面相同
conn = dataSource.getConnection();
// 下面创建PreStatement对象执行查询
// ...
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtil.release(conn, ps);
}
}
不使用配置文件时,C3P0的使用方法与DBCP类似,通过set属性()
方法传入JDBC初始化参数.
@Test
public void testC3P0() {
Connection conn = null;
PreparedStatement ps = null;
try {
// 1. 创建数据库连接池
ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 2. 设置数据库初始化参数
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost/bank");
dataSource.setUser("root");
dataSource.setPassword("root");
//3. 得到数据库连接对象
conn = dataSource.getConnection();
// ...
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtil.release(conn, ps);
}
}
C3P0使用的配置文件为xml格式,可以在一个xml文件中写多套配置,其格式如下所示:
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driverproperty>
<property name="jdbcUrl">jdbc:mysql://localhost/bankproperty>
<property name="user">rootproperty>
<property name="password">rootproperty>
<property name="initialPoolSize">10property>
<property name="maxIdleTime">30property>
<property name="maxPoolSize">100property>
<property name="minPoolSize">10property>
<property name="maxStatements">200property>
default-config>
<named-config name="oracle">
<property name="acquireIncrement">50property>
<property name="initialPoolSize">100property>
<property name="minPoolSize">50property>
<property name="maxPoolSize">1000property>
<property name="maxStatements">0property>
<property name="maxStatementsPerConnection">5property>
<user-overrides user="master-of-the-universe">
<property name="acquireIncrement">1property>
<property name="initialPoolSize">1property>
<property name="minPoolSize">1property>
<property name="maxPoolSize">5property>
<property name="maxStatementsPerConnection">50property>
user-overrides>
named-config>
c3p0-config>
public void testC3P0(){
Connection conn = null;
PreparedStatement ps = null;
try {
// 参数传入配置名,缺省为默认配置
ComboPooledDataSource dataSource = new ComboPooledDataSource("配置名");
// 得到连接对象
conn = dataSource.getConnection();
// ...
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtil.release(conn, ps);
}
}
数据库连接池帮我们获得连接对象,而DBUtils帮助我们完成CRUD.
DBUtils提供了一种通用的方法来执行CRUD,而无关具体查询参数.
使用QueryRunner
对象的update("SQL语句", 参数...)
方法完成数据的增删改.
QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
// 增加
queryRunner.update("insert into account values (null , ? , ? )", "aa" ,1000);
// 删除
queryRunner.update("delete from account where id = ?", 5);
// 更新
queryRunner.update("update account set money = ? where id = ?", 10000000 , 6);
底层PreparedStatement
对象在执行SQL查询语句后,会返回一个ResultSet
对象.而使用DBUtils,我们希望执行查询后直接返回的是Java对象.
因此需要给QueryRunner
对象的query()
函数传入一个实现ResultSetHandler
接口的类的对象,这个类中的T handle(ResultSet rs)
方法指明了如何将ResultSet
对象转换为T
对象.
这个接口的实现类可以自己定义,也可以使用现成的类.
QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
List<Account> accounts = queryRunner.query("select * from account where id = ?",
new ResultSetHandler<List<Account>>() {
@Override
public List<Account> handle(ResultSet rs) throws SQLException {
List<Account> accounts;
while (rs.next()) {
Account account = new Account(rs.getString("name"), rs.getInt("money"));
accounts.add(account);
}
return accounts;
}
}, 6);
QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
// 查询单个对象
Account account = queryRunner.query( "select * from account where id = ?",
new BeanHandler<Account>(Account.class), 8);
// 查询多个对象
List<Account> list = queryRunner.query("select * from account ",
new BeanListHandler<Account>(Account.class));
ResultSetHandler
的常用实现类如下: