事务
Transaction 其实指的一组操作,里面包含许多个单一的逻辑。只要有一个逻辑没有执行成功,那么都算失败。 所有的数据都回归到最初的状态(回滚)
事务的作用:为了确保逻辑的成功。 例子: 银行的转账。
-
使用命令行方式演示事务。
-
关闭自动提交功能。
-
开启事务
start transaction;
-
提交或者回滚事务:事务提交后或者回滚就结束了
-
commit; 提交事务, 数据将会写到磁盘上的数据库
-
rollback ; 数据回滚,回到最初的状态
-
-
使用代码方式演示事务
public void testTransaction() throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JDBCUtil.getConn();
String sql = "update bank set money = money - ? where id = ?";
ps = conn.prepareStatement(sql);
// 关闭提交 事务只是针对连接连接对象,如果再开一个连接对象,那么还是默认的提交。
conn.setAutoCommit(false);
ps.setInt(1, 100);
ps.setInt(2, 1);
ps.executeUpdate();
int a = 10 / 0;
ps.setInt(1, -100);
ps.setInt(2, 2);
ps.executeUpdate();
// 两段代码都执行成功 提交事务
conn.commit();
} catch (SQLException e) {
// 出现异常,回滚事务
conn.rollback();
e.printStackTrace();
} finally {
JDBCUtil.release(conn, ps);
}
}
- 事务的特性
- 原子性:指的是 事务中包含的逻辑,不可分割。
- 一致性:指的是 事务执行前后。数据完整性
- 隔离性:指的是 事务在执行期间不应该受到其他事务的影响
- 持久性:指的是 事务执行成功,那么数据应该持久保存到磁盘上。
- 事务的安全隐患 :不考虑隔离级别设置,那么会出现以下问题
- 读
- 脏读:一个事务读到另外一个事务还未提交的数据
- 不可重复读 :一个事务读到了另外一个事务提交的数据 ,造成了前后两次查询结果不一致。
- 幻读:一个事务读到了另一个事务insert的数据 ,造成前后查询结果不一致 。
-
写:丢失更新
-
悲观锁(认为一定会丢失更新):可以在查询的时候,加入 for update(数据库锁机制,排他锁),有点类似序列化
-
乐观锁(认为一定不会丢失更新):要求程序员自己控制。
-
- 读
- 事务的隔离级别:mySql 默认的隔离级别是 可重复读,Oracle 默认的隔离级别是 读已提交
- 读未提交(Read Uncommitted):可以读到其他事务未提交的数据。引发问题: 脏读
- 读已提交(Read Committed):只能读到已提交的数据。解决: 脏读 , 引发: 不可重复读
- 可重复读(Repeatable Read):事务中读取的数据不受其他事务提交数据的影响,前后读取的数据一致。解决: 脏读 、 不可重复读 , 未解决: 幻读
- 可串行化(Serializable) :如果有一个连接的隔离级别设置为了串行化 ,那么谁先打开了事务, 谁就有了先执行的权利, 谁后打开事务,谁就只能得着,等前面的那个事务,提交或者回滚后,才能执行。 但是这种隔离级别一般比较少用。 容易造成性能上的问题。 效率比较低。解决: 脏读、 不可重复读 、 幻读。
- 按效率划分,从高到低
读未提交 > 读已提交 > 可重复读 > 可串行化
- 按拦截程度 ,从高到底
可串行化 > 可重复读 > 读已提交 > 读未提交
数据库连接池
- 数据库的连接对象创建工作,比较消耗性能。 一开始先在内存中开辟一块空间(集合) , 先往池子里面放置 多个连接对象。 后面需要连接的话,直接从池子里面去。不要去自己创建连接了。 使用完毕, 要记得归还连接,确保连接对象能循环利用。Sun公司定义了一个连接池接口
DataSource
- 自定义连接池
- 连接池类
** * 实现连接池,一开始在连接池中有十个连接对象 */ public class MyDataSource implements DataSource { List<Connection> list = new ArrayList<>(); // 连接池集合 public MyDataSource() { // 构造函数 在连接池中初始化十个连接对象 for (int i = 0; i < 10; i++) { list.add(JDBCUtil.getConn()); } } /** * 该连接池对外公布获取连接池的方法 * @return * @throws SQLException */ @Override public Connection getConnection() throws SQLException { if(list.size() == 0 ) { // 连接池中没有连接对象 扩容 for (int i = 0; i < 5; i++) { list.add(JDBCUtil.getConn()); } } // 移除连接池中第一个连接对象,包装过后并将它返回 Connection conn = list.remove(0); Connection connWrap = new ConnectionWrap(conn, list); return connWrap; }
- 使用装饰者模式解决连接池的归还问题,符合面向接口编程
public class ConnectionWrap implements Connection { Connection conn = null; List<Connection> list = null; public ConnectionWrap(Connection conn, List<Connection> list) { super(); this.conn = conn; this.list = list; } @Override public void close() throws SQLException { // 在这里写归还操作 System.out.println("归还前" + list.size()); list.add(conn); System.out.println("归还后" + list.size()); } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return conn.prepareStatement(sql); }
- 连接池的使用
@Test public void testPool() throws SQLException { Connection connection = null; PreparedStatement ps = null; ResultSet rs = null; MyDataSource dataSource = new MyDataSource(); try { // 其实在这里得到的是 ConnectionWrap 对象 connection = dataSource.getConnection(); String sql = "select * from bank"; ps = connection.prepareStatement(sql); rs = ps.executeQuery(); while(rs.next()) { String name = rs.getString("name"); int id = rs.getInt("id"); int money = rs.getInt("money"); System.out.println(id + "---" + name + "---" + money); } } catch (SQLException e) { e.printStackTrace(); }finally { JDBCUtil.release(connection, ps, rs); } }
- 开源连接池
- DBCP
- 导入jar包
- 不使用配置文件
public class DBCPDemo { @Test public void testDBCP01() { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { // 1. 得到连接池对象 BasicDataSource dataSource = new BasicDataSource(); // 2. 设置连接属性 dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3307/hgzdata"); dataSource.setUsername("root"); dataSource.setPassword("root"); // 3. 获取连接对象 conn = dataSource.getConnection(); // 4. 数据库操作 String sql = "select * from bank"; ps = conn.prepareStatement(sql); rs = ps.executeQuery(); while(rs.next()) { int id = rs.getInt("id"); String name = rs.getString("name"); int money = rs.getInt("money"); System.out.println(id + "---" + name + "---" + money); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtil.release(conn, ps, rs); } } }
- 使用配置文件 将配置文件
dbcpconfig.properties
放置在 src 目录下
BasicDataSourceFactory factory = new BasicDataSourceFactory(); Properties properties = new Properties(); InputStream is = new FileInputStream("src/dbcpconfig.properties"); properties.load(is); DataSource dataSource = factory.createDataSource(properties);
- C3P0
- 导入 jar 包
- 不使用配置文件
// 1. 得到连接池对象 ComboPooledDataSource dataSource = new ComboPooledDataSource(); // 2. 设置连接属性 dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3307/hgzdata"); dataSource.setUser("root"); dataSource.setPassword("root");
- 使用配置文件 将配置文件
c3p0-config.xml
放在 src 目录下
// 1. 得到连接池对象 默认读取配置文件 获取连接信息 ComboPooledDataSource dataSource = new ComboPooledDataSource("configname");
- DBCP
DBUtils
- dbutils 只是帮我们简化了CRUD 的代码, 但是连接的创建以及获取工作。 不在他的考虑范围,导入 jar 包
-
update
操作
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);
-
query
操作new接口的匿名实现类
public void queryTest() {
// 获取查询对象
QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
try {
User user = queryRunner.query("select * from bank where id = ?", new ResultSetHandler<User>() {
@Override
public User handle(ResultSet resultSet) throws SQLException {
User user = new User();
while (resultSet.next()) {
user.setName(resultSet.getString("name"));
user.setMoney(resultSet.getInt("money"));
}
return user;
}
}, 1);
System.out.println(user.toString());
} catch (SQLException e) {
e.printStackTrace();
}
}
-
query
操作 使用框架的 接口实现类 查询一行数据
public void queryTest1() {
QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
// 查询单行数据
try {
User user = queryRunner.query("select * from bank where id = ?",
new BeanHandler<User>(User.class)
, 2);
System.out.println(user.toString());
} catch (SQLException e) {
e.printStackTrace();
}
}
-
query
操作 使用框架的 接口实现类 查询多行数据
public void queryTest2() {
QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
// 查询单行数据
try {
List<User> users = queryRunner.query("select * from bank",
new BeanListHandler<User>(User.class));
for (User user: users
) {
System.out.println(user.toString());
}
} catch (SQLException e) {
e.printStackTrace();
}
}
-
ResultSetHandler
(queryRunner.query()
第二个参数) 常用的实现类- 最常用
BeanHandler, 查询到的单个数据封装成一个对象 BeanListHandler, 查询到的多个数据封装 成一个List<对象>
ArrayHandler, 查询到的单个数据封装成一个数组 ArrayListHandler, 查询到的多个数据封装成一个集合 ,集合里面的元素是数组。
MapHandler, 查询到的单个数据封装成一个map MapListHandler,查询到的多个数据封装成一个集合 ,集合里面的元素是map。
- 不常用
ColumnListHandler KeyedHandler ScalarHandler
- 模拟DBUtils功能代码
- update
public void update(String sql, Object ...args) { Connection conn = null; PreparedStatement ps = null; try { ComboPooledDataSource dataSource = new ComboPooledDataSource(); conn = dataSource.getConnection(); ps = conn.prepareStatement(sql); // 根据元数据参数 获取问号个数 ParameterMetaData parameterMetaData = ps.getParameterMetaData(); int paramerterCount = parameterMetaData.getParameterCount(); for (int i = 0; i < paramerterCount ; i++) { ps.setObject(i + 1, args[i]); } ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtil.release(conn,ps); } }
- query
结果集处理接口public <T> T query(String sql,ResultHandler<T> handler ,Object ...args) { Connection conn = null; PreparedStatement ps= null; ResultSet rs = null; // 1. 获取连接池 ComboPooledDataSource dataSource = new ComboPooledDataSource(); try { // 2. 获取连接对象 conn = dataSource.getConnection(); ps = conn.prepareStatement(sql); // 3. 根据问号个数填充参数 ParameterMetaData parameterMetaData = ps.getParameterMetaData(); int paramterCount = parameterMetaData.getParameterCount(); for (int i = 0; i < paramterCount ; i++) { ps.setObject(i+1, args[i]); } rs = ps.executeQuery(); // 4. 让传入的 结果处理对象来处理 结果集 T t = (T) handler.handle(rs); return t; } catch (Exception e) { e.printStackTrace(); return null; } finally { JDBCUtil.release(conn, ps, rs); } }
query 方法的调用public interface ResultHandler<T>{ T handle(ResultSet rs); }
public void testQuery(){ User user = query("select * from bank where id = ?", new ResultHandler<User>() { @Override public User handle(ResultSet rs) { User user = new User(); try { if (rs.next()) { user.setName(rs.getString("name")); user.setMoney(rs.getInt("money")); } } catch (SQLException e) { e.printStackTrace(); } return user; } }, 3); System.out.println(user.toString()); }