文章目录
事务
Transaction:指的是一组操作,里面包含多个单一的逻辑,只要有一个逻辑没有执行成功,那么都算失败,所有数据回归最初的状态。
为什么要有事务?
为了确保逻辑的成功。
使用代码方式演示事务
代码里面的事务,主要是针对连接的。
通过 conn.setAutoCommit(false) 来关闭自动提交设置。
public class TestTransaction {
@Test
public void testTransaction(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//连接,默认是自动提交的
conn = JDBCUtil.getConn();
//关闭自动提交
conn.setAutoCommit(false);
String sql = "update account set money = money - ? where id = ?";
//String sql = "select * from account";
ps = conn.prepareStatement(sql);
//扣钱
ps.setInt(1,100);
ps.setInt(2, 1);
ps.executeUpdate();
//加钱
ps.setInt(1,-100);
ps.setInt(2,2);
ps.executeUpdate();
//成功,提交事务
conn.commit();
} catch (SQLException e) {
//事变,回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
JDBCUtil.release(conn, ps, rs);
}
}
}
事务的特性(ACID)
- 原子性
指的是事务中包含的逻辑,不可分割 - 一致性
指的是事务执行前后数据完整 - 隔离性
事务在执行期间不应该受到其他事务的影响 - 持久性
事务执行成功,那么数据持久保存到磁盘上
事务的隔离级别
-
Read Uncommitted【读未提交】
-
Read Committed【读已提交】
- 设置A窗口的隔离级别为读已提交
- A、B两个窗口都开启事务,在B窗口执行更新操作
- 在A窗口执行的查询结果不一致,一次是在B窗口提交事务之前,一次是在B窗口提交事务之后
- 这个个隔离级别能够屏蔽脏读现象,但是引发了另一个问题,不可重复读
-
Repeatable Read【重复读】
-
Serialiable 【可串行化】
如果有一个连接的隔离级别设置为了可串行化,那么谁先打开了事务,谁就有了先执行的权力,谁后打开事务,谁就只能等着,等前面那个事务提交或者回滚后才能执行。但是这种隔离级别一般比较少用,容易造成性能上的问题,效率较低。
事务的安全隐患【这里都涉及到两个事务】
- 不考虑隔离级别设置,那么会出现以下问题:
- 读
-
脏读
一个事务读到了另外一个事务还未提交的数据- 设置A窗口的隔离级别为读未提交
- 两个窗口都分别开启事务
-
不可重复读
一个事务读到了另外一个事务提交的数据,造成了前后两次查询不一致 -
幻读
一个事务读到了另一个事务已提交的插入的数据,导致多次查询结果不一致。
- 写
丢失更新 - B事务如果提交会造成A事务修改的姓名没有了。
- B事务回滚,那么也会造成A事务更新没有了,因为B事务记得自己最初拿出来的数据是lisi 1000。
解决丢失更新
悲观锁
可以在查询的时候加入 fro update
乐观锁
要求程序员自己控制。A事务先提交:数据库version变成1。B事务在提交的时候,比对数据库version和自己的version不一样,不允许提交,要先更新。
事务总结
- 事务只是针对连接对象,如果再打开一个连接对象,那么默认提交。
- 事务是会自动提交的。
数据库连接池
- 数据库的连接对象创建工作比较消耗性能。
- 一开始先在内存中开辟一块空间(集合),一开始先往池中放置多个连接对象,后面需要连接的话,直接从池子中取,不要自己取创建连接,使用完毕要记得归还连接,确保连接对象能够循环利用。
连接池的简单使用
/*
* 这是一个数据库连接池
* 一开始先往池子里放10个连接
*
* 1. 开始创建10个连接
* 2. 来的程序通过getConnection获取连接
* 3. 用完之后,使用addBack归还连接
* 4. 扩容
* */
public class MyDataSource implements DataSource {
List<Connection> list = new ArrayList<Connection>();
public MyDataSource() {
for (int i = 0; i < 10; i++) {
Connection conn = JDBCUtil.getConn();
list.add(conn);
}
}
@Override
//该连接池对外公布的获取连接的方法
public Connection getConnection() throws SQLException {
//来拿连接的时候先看看池子里有没有
if(list.size() == 0){
for (int i = 0; i < 5; i++) {
Connection conn = JDBCUtil.getConn();
list.add(conn);
}
}
//移除的是集合中的第一个元素
Connection conn = list.remove(0);
return conn;
}
//用完连接,归还连接
public void addBack(Connection conn){
list.add(conn);
}
}
public class TestPool {
@Test
public void testPool(){
Connection conn = null;
PreparedStatement ps = null;
MyDataSource dataSource = new MyDataSource();
try {
conn = dataSource.getConnection();
String sql = "insert into account values (null, 'xilali', 10)";
ps = conn.prepareStatement(sql);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
//归还连接
dataSource.addBack(conn);
}
}
}
自定义数据库连接池出现的问题
问题:
sun公司针对数据库连接池定义了一套规范
- 需要额外记住addBack()方法
- 单例
- 无法面型接口编程,因为接口里面没有定义addBack()方法
- 怎么解决?以addBack() 为切入点
解决自定义数据库连接池的问题
- 由于多了一个addBack() 方法,所以使用这个连接池的地方需要额外记住这个方法,并且不能面向接口编程。
- 我们打算接口中的那个close()方法,原来的Connection对象的close() 方法是真的关闭连接。
- 打算修改close() 方法,以后在调用 close() 方法,并不是真的关闭,而是归还连接对象。
如何扩展某一个方法?
原有的方法逻辑,不是我们想要的,想修改成自己的逻辑
- 直接改源码,无法实现
- 继承,必须得知道这个接口的具体实现是谁
- 使用装饰者模式
- 动态代理
装饰者模式
使用装饰者模式解决上面自定义数据库连接池出现的问题
public class MyDataSource implements DataSource {
List<Connection> list = new ArrayList<Connection>();
public MyDataSource() {
for (int i = 0; i < 10; i++) {
Connection conn = JDBCUtil.getConn();
list.add(conn);
}
}
@Override
//该连接池对外公布的获取连接的方法
public Connection getConnection() throws SQLException {
//来拿连接的时候先看看池子里有没有
if(list.size() == 0){
for (int i = 0; i < 5; i++) {
Connection conn = JDBCUtil.getConn();
list.add(conn);
}
}
//移除的是集合中的第一个元素
Connection conn = list.remove(0);
//在把这个对象抛出去的时候,对这个对象进行包装,这里将conn抛到ConnectionWrap类中,产生一个新的对象connection,可以利用这个新的对象去完成close()方法的操作
Connection conntion = new ConnectionWrap(conn, list);
return conntion;
}
// //用完连接,归还连接
// public void addBack(Connection conn){
// list.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;
}
}
//装饰者类
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 Statement createStatement() throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return conn.prepareStatement(sql);
}
}
开源连接池
DBCP
使用代码方法创建DBCP
- 导入 jar 文件
- 代码
public class DBCPDemo {
@Test
public void testDBCP01(){
Connection conn = null;
PreparedStatement ps = null;
try {
//1.构建数据源对象
BasicDataSource dataSource = new BasicDataSource();
//连的是什么类型的数据库,访问的是哪个数据库,用户名,密码
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
//主协议:子协议://本地/数据库
dataSource.setUrl("jdbc:mysql://localhost/bank?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("1183787376");
//2.得到连接对象
conn = dataSource.getConnection();
String sql = "insert into account values(null, ?, ?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "admin");
ps.setInt(2,100);
ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtil.release(conn,ps);
}
}
}
使用配置文件方式
public class DBCPDemo02 {
@Test
public void testDBCP02(){
//BasicDataSource dataSource = new BasicDataSource();
//dataSource("dbcpconfig.properties");
Connection conn = null;
PreparedStatement ps = null;
try {
BasicDataSourceFactory factory = new BasicDataSourceFactory();
Properties properties = new Properties();
InputStream is = new FileInputStream("dbcpconfig.properties");
properties.load(is);
DataSource dataSource = factory.createDataSource(properties);
conn = dataSource.getConnection();
String sql = "insert into account values(null, ?, ?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "梁朝伟");
ps.setInt(2,100);
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtil.release(conn,ps);
}
}
}
C3P0
不使用配置的方式
要学习查阅相关文档
public class C3P0Demo {
@Test
public void testC3P0(){
Connection conn = null;
PreparedStatement ps = null;
try {
//创建dataSource
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost/bank?serverTimezone=UTC");
dataSource.setUser("root");
dataSource.setPassword("1183787376");
conn = dataSource.getConnection();
String sql = "insert into account values(null, ?, ?)";
ps = conn.prepareStatement(sql);
ps.setString(1, "梁");
ps.setInt(2,100);
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtil.release(conn,ps);
}
}
}