MySQL的事务与封装

一、事务

概述:一条或多条SQL语句的捆绑,要么都成立则提交,要么都失败则回滚

事务应用

事务的应用,放在业务层;因为一个业务功能可以包含多个SQL功能,所以在业务层可捆绑这些功能

事务应用:

通过连接对象调用方法,进行开启事务,提交和回滚事务

//---转账操作---
public int transfer(String sendCart,String acceptCart,String password,int money){
    Connection con = null;
    try{
        con = DBUtils.getConnection();  //获取连接对象
        con.setAutoCommit(false); //手动提交--开启事务
        sendAccount.setMoney(sendAccount.getMoney()-money); //发送方减钱
        int res = accountDao.updateAccMoney(sendAccount); //修改发送方账户金额
        System.out.println("发送方:"+res);

        int i = 1/0;  //模拟异常  跑到catch中,回滚事务

        acceptAcc.setMoney(acceptAcc.getMoney()+money); //接收方加钱
        res = accountDao.updateAccMoney(acceptAcc);
        System.out.println("接收方:"+res);
        con.commit();  //提交事务
        return  res;
    }catch (Exception e){
        e.printStackTrace();
        System.out.println("事务回滚");
        try {
            con.rollback();  //事务回滚
        } catch (SQLException ex) {}
    }finally {
        DBUtils.closeAll(con);  //关闭连接对象
    }
    return 0;
}

异常分析

问题:在业务层设计的事务无法回滚,原因是业务层的连接对象和DAO层的不是同一个对象

处理方案:只需要将连接对象统一为一个即可

方案1:将业务层的连接对象以传参方式传到AO层(不推荐-接口污染)

方案2:在工具类中,使用共享的连接对象(ThreadLocal),可以在业务层和DAO层进行使用

ThreadLocal

  • 可以将整个线程中(单线程)中,存储一个共享值,这个共享值就是Connection。
  • 线程拥有一个类似 Map 的属性,键值对结构<ThreadLocal对象,值>。

注意:在DAO层不要关闭连接对象,统一在事务处理完成后,再关闭和移除

public class DBUtils { //数据库的工具类
    //共享ThreadLocal,里面管理连接对象
    private static final ThreadLocal<Connection> TH = new ThreadLocal<>();
    //...加载驱动
    public static Connection getConnection(){ //封装连接对象
        Connection conn = null;
        try {
            conn = TH.get(); //从ThreadLocal中获取连接对象
            if(conn==null) {
                conn = DriverManager.getConnection(p.getProperty("url"),
                        p.getProperty("username"), p.getProperty("password"));
                TH.set(conn); //连接对象存储到ThreadLocal中
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return  conn;
    }

    public static void closeAll(AutoCloseable...ac){ //关闭资源
        for(AutoCloseable a:ac){
            if(a instanceof Connection){
                TH.remove();  //移除连接对象
                System.out.println("移除连接对象");
            }
            if(a!=null){
                try {
                    a.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

二、封装事务

说明

在前面的设计中已经说明,在一个类中尽可能的少出现其他类的对象(解耦合);所以,在业务层还需要对事务进一步封装,对代码的结构也会设计更清晰合理。

应用

在工具类中进行封装

public static void begin(){  //开启事务
    Connection conn = null;
    try {
        conn = getConnection();
        conn.setAutoCommit(false);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static void commit(){ //提交事务
    Connection conn = null;
    try {
        conn = getConnection();
        conn.commit();  //提交
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        TH.remove();  //移除连接对象
        closeAll(conn);
    }
}
public static void rollbock(){ //回滚
    Connection conn = null;
    try {
        conn = getConnection();
        conn.rollback();
    } catch (Exception e) {
        e.printStackTrace();
    }finally {
        TH.remove();  //移除连接对象
        closeAll(conn);
    }
}

三、三层架构

概述

前面的内容已经涉及到三层架构了,此处整理三层架构概述

三层架构包括,表示层(测试类),业务层,DAO层

表示层(测试类):搜集数据交给业务层处理,并接收返回数据,并展示数据

业务层:对数据进行逻辑判断,并调用dao层的一个或多个功能;事务操作

DAO层:和数据库进行交互处理

注意:在项目中,往往业务层和DAO层都是接口与实现类的设计(解耦合-实现类任意分离与更换)

应用

将三层架构的所有层次结构进行设计与描述:

1.数据表-实体类设计

2.DBUtils工具类与配置文件

3.编写测试类–调用业务层

4.编写DAO层的接口与实现类–增删改查功能

5.编写业务层的接口与实现类–代表业务的功能

案例:user表,有id,name和age字段,只需将三层架构的结构设计出来,无需具体代码

四、DAOUtils

概述

在DAO层的操作中,每个功能都需要JDBC的重复性的操作,一个模块的增删改查直接重复性的不是太多;但是在项目中,如果有多个模块,每个模块都需要这些重复代码,那么冗余特别多,不方便管理。

所以,需要有一个JDBC的工具类来提升复用性。

DML封装

封装的做法,往往需要将变动的代码从参数中传递;增删改中固定的代码可以直接写死。

此处的测试,无需使用service层,参数无需逻辑判断和无需dao功能的捆绑

//DML的封装操作 参数1:sql语句 参数2:可变长参数
public static int commomUpdate(String sql,Object...obs){
    Connection conn = null;
    PreparedStatement prst = null;
    try {
        conn = DBUtils.getConnection();
        prst = conn.prepareStatement(sql);
        for(int i=0;i<obs.length;i++){ //循环遍历传入的参数,即可完成值的注入
            prst.setObject(i+1,obs[i]);
        }
        return prst.executeUpdate();
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        DBUtils.closeAll(prst,conn);
    }
    return 0;
}

DQL封装

//DQL操作:
//注意此处不能使用List<Person>  而是泛型; 需定义泛型方法<T>
//参数1:sql语句 参数2:反射对象  参数3:接收传过来的参数
public static <T> List<T> commonQuery(String sql,Class<T> clazz ,Object...obs){
    Connection conn = null;
    PreparedStatement prst = null;
    ResultSet rs = null;
    List<T> list = new ArrayList<>();
    try {
        conn = DBUtils.getConnection();
        prst = conn.prepareStatement(sql);
        for(int i=0;i<obs.length;i++){ //设置参数,与DML封装类似
            prst.setObject(i+1,obs[i]);
        }
        rs = prst.executeQuery();
        Field[] fields = clazz.getDeclaredFields();  //获取所有的属性
        while(rs.next()){//循环进来就是一条记录
            T t = clazz.newInstance();  //获取实例对象
            for(Field f:fields){
                //从表中根据字段获取值  注意:表字段与实体属性要一致,否则无法取到值
                Object value = rs.getObject(f.getName()); //将属性充当字段获取字段值
                f.setAccessible(true);  //设置权限
                f.set(t,value); //设置注入实体的属性值
            }
            list.add(t);  //用集合存储
        }
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        DBUtils.closeAll(rs,prst,conn);
    }
    return list;
}

五、Druid连接池

回顾之前的线程池,容器中创建了多个线程对象;有用户访问从线程池中获取线程对象,用完后回收到线程池,进行复用; 好处-减少创建和销毁线程的数目,提升性能

概述

连接池类似线程池,预先创建一个容器,存多个连接对象;有用户过来,从连接池中获取连接对象;用完了,则回收到连接池进行复用–复用机制。

好处:减少创建和销毁连接对象的数目,提升性能

有多款连接池,例如:c3p0,dbcp,druid等连接池,其中阿里巴巴提供的druid连接池性能最高,使用最广

应用

druid连接池的应用很简单,已经封装成工具类,类似DBUtils

public class DruidUtils { //数据库的工具类
    private static DataSource dataSource;  //准备数据源
    private  static Properties p = new Properties(); //实例化Properties对象
    static { //静态代码块只执行一次
        try {
            p.load(new FileInputStream("druid.properties"));
            //传入Properties资源,得到druid的数据源
            dataSource = DruidDataSourceFactory.createDataSource(p);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Connection getConnection(){ //封装连接对象
        Connection conn = null;
        try {
            conn = dataSource.getConnection(); //从数据源中取出连接对象
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return  conn;
    }
}

六、总结与作业

总结

1.事务应用(重点)
回顾事务概述;使用-在业务层;异常分析-无法回滚的分析
ThreadLocal用法-解决事务异常处理
2.事务封装
解耦合-封装了开启事务,提交事务,回滚
3.三层架构(重点)
概述、使用-service和dao都是接口与实现类
4.DAOUtils(重点)
概述-dao层的jdbc的封装;DML封装;DQL封装-提升复用性
5.Druid连接池(重点)
连接池概述,应用

作业

1. 表结构: student表中有id,name ,age,stno(学号)
2. 编写DaoUtils,进行dao层DML和DQL的封装(可以写一个添加和查询功能进行测试即可)
3. 编写StudentService层,处理业务,判断学号不存在, 才添加
4. 将判断和插入学员的功能, 用事务绑定,(注意: 使用ThreadLocal存连接对象)