事务与数据库连接池

事务

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. 一致性
    指的是事务执行前后数据完整
  3. 隔离性
    事务在执行期间不应该受到其他事务的影响
  4. 持久性
    事务执行成功,那么数据持久保存到磁盘上

事务的隔离级别

  1. Read Uncommitted【读未提交】

  2. Read Committed【读已提交】

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

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

事务的安全隐患【这里都涉及到两个事务】

  • 不考虑隔离级别设置,那么会出现以下问题:
  • 脏读
    一个事务读到了另外一个事务还未提交的数据

    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
使用代码方法创建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

猜你喜欢

转载自blog.csdn.net/qq_32682177/article/details/84966847