事务与连接池

一、事务

事务就是一个事情,组成这个事情可能有多个单元,要求这些单元,要么全都成功,要么全都不成功。
在开发中,有事务的存在,可以保证数据完整性。

事务的操作

create table account(
   id int primary key auto_increment,
   name varchar(20),
   money double
);

insert into account values(null,'aaa',1000);
insert into account values(null,'bbb',1000);
insert into account values(null,'ccc',1000);
  1. mysql下怎样操作
    方式1:
    start transaction 开启事务
    rollback 事务回滚(回滚到最开始位置)
    commit 事务提交(没有commit就不会修改数据)
    方式2:
    show variables like '%commit%'; 可以查看当前autocommit值.在mysql数据库中它的默认值是"on",代表自动事务(执行任何一条mysql语句都会自动提交事务).
    测试:将autocommit的值设置为off
    1.set autocommit=off 关闭自动事务。
    2.必须手动commit才可以将事务提交。
    注意:mysql默认autocommit=on oracle默认的autocommit=off;
  2. jdbc下怎样操作
    java.sql.Connection接口中有几个方法是用于可以操作事务
    1.setAutocommit(boolean flag) 如果flag=false;它就相当于start transaction;
    2.rollBack() 事务回滚
    3.commit() 事务提交
// 随便抛异常版,仅限演示,开发中不这么写
public class TransactionTest1 {

    public static void main(String[] args) throws SQLException {

        // 修改id=2这个人的money=500;

        String sql = "update account set money=500 where id=2";

        Connection con = JdbcUtils.getConnection();
        con.setAutoCommit(false); //开启事务,相当于  start transaction;

        Statement st = con.createStatement();
        st.executeUpdate(sql);

        //事务回滚
        //con.rollback();

        con.commit(); //事务提交
        st.close();
        con.close();

    }
}
// 开发中应该这么写
public class TransactionTest2 {

    public static void main(String[] args) {

        // 修改id=2这个人的money=500;

        String sql = "update account set money=500 where id=2";

        Connection con = null;
        Statement st = null;

        try {
            con = JdbcUtils.getConnection();
            con.setAutoCommit(false); // 开启事务,相当于 start transaction;

            st = con.createStatement();
            st.executeUpdate(sql);
        } catch (SQLException e) {
            e.printStackTrace();
            // 事务回滚
             try {
                con.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {

            try {
                con.commit(); // 事务提交
                st.close();
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

二、事务特性(重点)

  1. 原子性:事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  2. 一致性:事务前后数据的完整性必须保持一致。
  3. 隔离性:多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
  4. 持久性:一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

如果不考虑事务的隔离性,会出现什么问题?

  1. 脏读:一个事务读取到另一个事务的未提交数据
  2. 不可重复读:两次读取的数据不一致(强调update)
  3. 虚读(幻读):两次读取的数据不一致(强调insert)
  4. 丢失更新:两个事务对同一条记录进行操作,后提交的事务,将先提交的事务的修改覆盖了。

解决方案

  1. 事务的隔离级别有哪些?
    • Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)
    • Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)不可以避免虚读
    • Read committed:可避免脏读情况发生(读已提交)
    • Read uncommitted:最低级别,以上情况均无法保证。(读未提交)
  2. 怎样设置事务的隔离级别?
    1.mysql中设置
1.查看事务隔离级别
    select @@tx_isolation   查询当前事务隔离级别(默认为Repeatable read).
    扩展:oracle中默认是Read committed

2.mysql中怎样设置事务隔离级别
    set session transaction isolation level 事务隔离级别

2.jdbc中设置

使用java.sql.Connection接口中提供的方法
    void setTransactionIsolation(int level) throws SQLException
    参数level可以取以下值:
        level - 以下 Connection 常量之一:
        Connection.TRANSACTION_READ_UNCOMMITTED、
        Connection.TRANSACTION_READ_COMMITTED、
        Connection.TRANSACTION_REPEATABLE_READ
        Connection.TRANSACTION_SERIALIZABLE。
        (注意,不能使用 Connection.TRANSACTION_NONE,因为它指定了不受支持的事务。)
  1. 演示
1.脏读
    一个事务读取到另一个事务的为提交数据
    设置A,B事务隔离级别为   Read uncommitted

    set session transaction isolation level  read uncommitted;

    1.在A事务中
        start transaction;
        update account set money=money-500 where name='aaa';
        update account set money=money+500 where name='bbb';

    2.在B事务中
        start transaction;
        select * from account;

    这时,B事务读取时,会发现,钱已经汇完。那么就出现了脏读。

    当A事务提交前,执行rollback,在commit, B事务在查询,就会发现,钱恢复成原样
    也出现了两次查询结果不一致问题,出现了不可重复读.

2.解决脏读问题
    将事务的隔离级别设置为 read committed来解决脏读

    设置A,B事务隔离级别为   Read committed

    set session transaction isolation level  read committed;

    1.在A事务中
        start transaction;
        update account set money=money-500 where name='aaa';
        update account set money=money+500 where name='bbb';

    2.在B事务中
        start transaction;
        select * from account;

    这时B事务中,读取信息时,是不能读到A事务未提交的数据的,也就解决了脏读。

    让A事务,提交数据 commit;

    这时,在查询,这次结果与上一次查询结果又不一样了,还存在不可重复读。

3.解决不可重复读
    将事务的隔离级别设置为Repeatable read来解决不可重复读。
    设置A,B事务隔离级别为   Repeatable read;
    set session transaction isolation level  Repeatable read;

    1.在A事务中
            start transaction;
            update account set money=money-500 where name='aaa';
            update account set money=money+500 where name='bbb';

    2.在B事务中
            start transaction;
            select * from account;
    当A事务提交后commit;B事务在查询,与上次查询结果一致,解决了不可重复读。

4.设置事务隔离级别Serializable ,它可以解决所有问题
    set session transaction isolation level Serializable;

    如果设置成这种隔离级别,那么会出现锁表。也就是说,一个事务在对表进行操作时,
    其它事务操作不了。
  1. 总结
脏读:一个事务读取到另一个事务为提交数据
不可重复读:两次读取数据不一致(读提交数据)---update
虚读:两次读取数据不一致(读提交数据)----insert

事务隔离级别:
    read uncommitted 什么问题也解决不了.
    read committed 可以解决脏读,其它解决不了.
    Repeatable read 可以解决脏读,可以解决不可重复读,不能解决虚读.
    Serializable 它会锁表,可以解决所有问题.

    安全性:serializable > repeatable read > read committed > read uncommitted
    性能 :serializable < repeatable read < read committed < read uncommitted

    结论: 实际开发中,通常不会选择 serializable 和 read uncommitted ,
    mysql默认隔离级别 repeatable read ,oracle默认隔离级别 read committed

三、丢失更新

多个事务对同一条记录进行了操作,后提交的事务将先提交的事务操作覆盖了。
解决办法:

  1. 悲观锁:(假设丢失更新一定会发生 ) ----- 利用数据库内部锁机制,管理事务
    提供的锁机制
    1.共享锁
    select * from table lock in share mode(读锁、共享锁)
    2.排它锁
    select * from table for update (写锁、排它锁)
    update语句默认添加排它锁
  2. 乐观锁:(假设丢失更新不会发生) ----- 采用程序中添加版本字段解决丢失更新问题

解决丢失更新:在数据表添加版本字段,每次修改过记录后,版本字段都会更新,如果读取是版本字段,与修改时版本字段不一致,说明别人进行修改过数据 (重改)

四、连接池

就是创建一个容器,用于装入多个Connection对象,在使用连接对象时,从容器中获取一个Connection,使用完成后,在将这个Connection重新装入到容器中。这个容器就是连接池。(DataSource)也叫做数据源.
我们可以通过连接池获取连接对象.
优点:节省创建连接与释放连接 性能消耗 ---- 连接池中连接起到复用的作用 ,提高程序性能

自定义连接池

  1. 创建一个MyDataSource类,在这个类中创建一个LinkedList<Connection>
private LinkedList<Connection> ll;
ll = new LinkedList<Connection>();
  1. 在其构造方法中初始化List集合,并向其中装入5个Connection对象
for (int i = 0; i < 5; i++) {
    Connection con = JdbcUtils.getConnection();
    ll.add(con);
}
  1. 创建一个public Connection getConnection() 从List集合中获取一个连接对象返回.

  2. 创建一个public void readd(Connection) 这个方法是将使用完成后的Connection对象重新装入到List集合中.

代码问题

1.连接池的创建是有标准的.
    在javax.sql包下定义了一个接口 DataSource          
    简单说,所有的连接池必须实现javax.sql.DataSource接口,

    我们的自定义连接池必须实现DataSource接口。

2.我们操作时,要使用标准,怎样可以让 con.close()它不是销毁,而是将其重新装入到连接池.

    要解决这个问题,其本质就是将Connection中的close()方法的行为改变。

    怎样可以改变一个方法的行为(对方法功能进行增强)
        1.继承
        2.装饰模式
            1.装饰类与被装饰类要实现同一个接口或继承同一个父类
            2.在装饰类中持有一个被装饰类引用
            3.对方法进行功能增强。
        3.动态代理
            可以对行为增强
            Proxy.newProxyInstance(ClassLoacer ,Class[],InvocationHandler);

    结论:Connection对象如果是从连接池中获取到的,那么它的close方法的行为已经改变了,不在是销毁,而是重新装入到连接池。

方法增强

  1. 继承增强(不好)

    public class Demo1 {
        public static void main(String[] args) {
            Person1 p=new Student1();
            p.eat();
        }
    }
    
    class Person1 {
        public void eat(){
            System.out.println("吃两个馒头");
        }
    }
    
    class Student1 extends Person1 {
        public void eat(){
            super.eat();
            System.out.println("加两个鸡腿");
        }
    }
    
  2. 装饰模式(不好)

  3. 动态代理

    import javax.sql.DataSource;
    
    public class MyDataSource implements DataSource {
        private LinkedList<Connection> ll; // 用于装Connection对象的容器。
    
        public MyDataSource() throws SQLException {
            ll = new LinkedList<Connection>();
            // 当创建MyDateSource对象时,会向ll中装入5个Connection对象。
            for (int i = 0; i < 5; i++) {
                Connection con = JdbcUtils.getConnection();
                ll.add(con);
            }
        }
    
        public Connection getConnection() throws SQLException {
            if (ll.isEmpty()) {
                for (int i = 0; i < 3; i++) {
                    Connection con = JdbcUtils.getConnection();
                    ll.add(con);
                }
            }
    
            final Connection con = ll.removeFirst();
    
            Connection proxyCon = (Connection) Proxy.newProxyInstance(con.getClass().getClassLoader(), con.getClass().getInterfaces(), new InvocationHandler() {
                        public Object invoke(Object proxy, Method method,
                                Object[] args) throws Throwable {
                            if ("close".equals(method.getName())) {
                                // 这代表是close方法,它要做的事情是将con对象重新装入到集合中.
                                ll.add(con);
                                System.out.println("重新将连接对象装入到集合中");
                                return null;
                            } else {
                                return method.invoke(con, args);// 其它方法执行原来操作
                            }
                        }
                    });
            return proxyCon;
        }
    }
    

五、dbcp连接池(了解)

导入两个jar包:commons-dbcp-1.4.jarcommons-pool-1.5.6.jar

  1. 手动配置(手动编码)
    BasicDataSource bds = new BasicDataSource();
    
    // 需要设置连接数据库最基本四个条件
    bds.setDriverClassName("com.mysql.jdbc.Driver");
    bds.setUrl("jdbc:mysql:///day18");
    bds.setUsername("root");
    bds.setPassword("abc");
    
    // 得到一个Connection
    Connection con = bds.getConnection();
    

示例:
```java
public class JdbcDemo{
public void test() throws Exception {
BasicDataSource bds = new BasicDataSource();

        // 需要设置连接数据库最基本四个条件
        bds.setDriverClassName("com.mysql.jdbc.Driver");
        bds.setUrl("jdbc:mysql:///day18");
        bds.setUsername("root");
        bds.setPassword("123");

        Connection con = bds.getConnection();

        ResultSet rs = con.createStatement().executeQuery("select * from account");

        while(rs.next()){
            System.out.println(rs.getInt("id")+" "+rs.getString("name"));
        }

        rs.close();
        con.close();
    }

    public static void main(String[] args) throws Exception {
        JdbcDemo jd = new JdbcDemo();
        jd.test();
    }
}
```
  1. 自动配置(使用配置文件)
    Properties props = new Properties();
    FileInputStream fis = new FileInputStream("D:\\java1110\\workspace\\day18_2\\src\\dbcp.properties");
    props.load(fis);
    
    DataSource ds = BasicDataSourceFactory.createDataSource(props);
    
    示例:
    dbcp.properties
    driverClassName=com.mysql.jdbc.Driver
    url=jdbc:mysql:///day18
    username=root
    password=123
    
    JdbcDemo.java
    import org.apache.commons.dbcp2.BasicDataSource;
    import org.apache.commons.dbcp2.BasicDataSourceFactory;
    import java.sql.*;
    import javax.sql.DataSource;
    import java.io.FileInputStream;
    import java.util.Properties;
    
    public class JdbcDemo {
        public void test2() throws Exception {
            Properties props = new Properties();
            // props.setProperty("driverClassName","com.mysql.jdbc.Driver");
            // props.setProperty("url","jdbc:mysql:///day18");
            // props.setProperty("username","root");
            // props.setProperty("password","123");
            FileInputStream fis = new FileInputStream("D:\\code\\java\\JDBC\\src\\dbcp.properties");
            props.load(fis);
    
            DataSource ds = BasicDataSourceFactory.createDataSource(props);
            Connection con = ds.getConnection();
    
            ResultSet rs = con.createStatement().executeQuery("select * from account");
    
            while(rs.next()){
                System.out.println(rs.getInt("id")+" "+rs.getString("name"));
            }
    
            rs.close();
            con.close();
        }
    
        public static void main(String[] args) throws SQLException {
            JdbcDemo jd = new JdbcDemo();
            jd.test2();
        }
    }
    

六、c3p0连接池(必须掌握)

C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
dbcp没有自动回收空闲连接的功能,c3p0有自动回收空闲连接功能,它的性能更强大。

导入包:c3p0-0.9.5.2.jar

  1. 手动
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql:///day18");
cpds.setUser("root");
cpds.setPassword("abc");

事例:

public void test() throws Exception {
    BasicDataSource bds = new BasicDataSource();

    ComboPooledDataSource cpds = new ComboPooledDataSource();
    cpds.setDriverClass("com.mysql.jdbc.Driver");
    cpds.setJdbcUrl("jdbc:mysql:///day18");
    cpds.setUser("root");
    cpds.setPassword("123");

    Connection con = cpds.getConnection();

    ResultSet rs = con.createStatement().executeQuery("select * from account");

    while(rs.next()){
        System.out.println(rs.getInt("id")+" "+rs.getString("name"));
    }

    rs.close();
    con.close();
}
  1. 自动(使用配置文件)
    c3p0的配置文件可以是properties也可以是xml.
    c3p0的配置文件如果名称叫做 c3p0.properties or c3p0-config.xml 并且放置在classpath路径下(对于web应用就是classes目录),那么c3p0会自动查找。
    注意:我们其时只需要将配置文件放置在src下就可以。

    使用:ComboPooledDataSource cpds = new ComboPooledDataSource(); 它会在指定的目录下查找指定名称的配置文件,并将其中内容加载。

    c3p0-config.xml

    <?xml version="1.0" encoding="UTF-8"?>
    
    <c3p0-config>
        <default-config>
            <property name="driverClass">com.mysql.jdbc.Driver</property>
            <property name="jdbcUrl">jdbc:mysql:///day18</property>
            <property name="user">root</property>
            <property name="password">123</property>
        </default-config>
    </c3p0-config>
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 问题:事务是什么,有什么用? 事务就是一个事情,组成这个事情可能有多个单元,要求这些单元,要么全都成功,要么全都不...
    yeller阅读 758评论 0 0
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,767评论 18 399
  • JDBC概述 在Java中,数据库存取技术可分为如下几类:JDBC直接访问数据库、JDO技术、第三方O/R工具,如...
    usopp阅读 3,558评论 3 75
  • 1.事务 1.1事务的四大特性(ACID) 原子性:事务中的所有操作要么全部执行成功,要么执行全部失败。 一致性:...
    joshul阅读 441评论 0 1
  • 自从开学以来,一个星期没有写东西了呢,也不是因为忙,还是因为自己太懒了吧,宁愿扣手机也不愿去读读书呀看看报,, 刚...
    ZScissors阅读 159评论 0 0