事务与数据库连接池

事务

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)
  1. 原子性
    指的是事务中包含的逻辑,不可分割。事务包含的所有操作要么全部成功,要么全部失败回滚。因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
  2. 一致性
    指的是事务执行前后数据完整。一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
    拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
  3. 隔离性
    事务在执行期间不应该受到其他事务的影响。隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
    即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
    关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
  4. 持久性
    事务执行成功,那么数据持久保存到磁盘上
事务的隔离级别
  1. Read Uncommitted【读未提交】

  2. Read Committed【读已提交】

    1. 设置A窗口的隔离级别为读已提交
    2. A、B两个窗口都开启事务,在B窗口执行更新操作
    3. 在A窗口执行的查询结果不一致,一次是在B窗口提交事务之前,一次是在B窗口提交事务之后
    4. 这个个隔离级别能够屏蔽脏读现象,但是引发了另一个问题,不可重复读
  3. Repeatable Read【重复读】

  4. Serialiable 【可串行化】
    如果有一个连接的隔离级别设置为了可串行化,那么谁先打开了事务,谁就有了先执行的权力,谁后打开事务,谁就只能等着,等前面那个事务提交或者回滚后才能执行。但是这种隔离级别一般比较少用,容易造成性能上的问题,效率较低。

事务的安全隐患【这里都涉及到两个事务】
  • 不考虑隔离级别设置,那么会出现以下问题:
  • 脏读
    一个事务读到了另外一个事务还未提交的数据。比如事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。也就是说,当前事务读到的数据是别的事务想要修改成为的但是没有修改成功的数据。

    1. 设置A窗口的隔离级别为读未提交
    2. 两个窗口都分别开启事务
  • 不可重复读
    一个事务读到了另外一个事务提交的数据,造成了前后两次查询不一致。 不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。

  • 幻读
    一个事务读到了另一个事务已提交的插入的数据,导致多次查询结果不一致。


  1. 丢失更新
  2. B事务如果提交会造成A事务修改的姓名没有了。
  3. B事务回滚,那么也会造成A事务更新没有了,因为B事务记得自己最初拿出来的数据是lisi 1000。
    在这里插入图片描述
解决丢失更新
悲观锁

可以在查询的时候加入 fro update
在这里插入图片描述

乐观锁

要求程序员自己控制。A事务先提交:数据库version变成1。B事务在提交的时候,比对数据库version和自己的version不一样,不允许提交,要先更新。
在这里插入图片描述

事务总结
  1. 事务只是针对连接对象,如果再打开一个连接对象,那么默认提交。
  2. 事务是会自动提交的。

数据库连接池

  1. 数据库的连接对象创建工作比较消耗性能。
  2. 一开始先在内存中开辟一块空间(集合),一开始先往池中放置多个连接对象,后面需要连接的话,直接从池子中取,不要自己取创建连接,使用完毕要记得归还连接,确保连接对象能够循环利用。
    在这里插入图片描述
连接池的简单使用
/*
* 这是一个数据库连接池
* 一开始先往池子里放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公司针对数据库连接池定义了一套规范

  1. 需要额外记住addBack()方法
  2. 单例
  3. 无法面型接口编程,因为接口里面没有定义addBack()方法
  4. 怎么解决?以addBack() 为切入点
解决自定义数据库连接池的问题
  1. 由于多了一个addBack() 方法,所以使用这个连接池的地方需要额外记住这个方法,并且不能面向接口编程。
  2. 我们打算接口中的那个close()方法,原来的Connection对象的close() 方法是真的关闭连接。
  3. 打算修改close() 方法,以后在调用 close() 方法,并不是真的关闭,而是归还连接对象。
如何扩展某一个方法?

原有的方法逻辑,不是我们想要的,想修改成自己的逻辑

  1. 直接改源码,无法实现
  2. 继承,必须得知道这个接口的具体实现是谁
  3. 使用装饰者模式
  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);
        //在把这个对象抛出去的时候,对这个对象进行包装,这里将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 和 C3P0。

DBCP
使用代码方法创建DBCP
  1. 导入 jar 文件
  2. 代码
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);
        }

    }
}
配置文件方式

DBUtils

为了让我们的对数据库的增删该查变得简单,我们使用 DBUtil 框架简化 CRUD 代码。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值