事务与连接池

一、事务

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

事务的操作

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>
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

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