10-27 程宗武

10-27(JDBC)

/*
    JDBC本质: 官方定义的一套操作所有关系型数据库的规则,即接口
    各个厂商根据各自的jar包去实现这个接口,真正执行的代码大是驱动jar包中的实现类
 */

image-20201027205717447

/*
JDBC各个对象的作用:
    1.DriverManager:驱动管理对象
        *功能:
            1.注册驱动:
                static void registerDriver(Driver driver, DriverAction da)注册与给定的驱动程序 DriverManager 。
                写代码使用: lass.forName("com.mysql.cj.jdbc.Driver");
            2.获取数据库链接
    2.Connection:数据库连接对象
    3.Statement:执行SQL的对象
        用于执行静态SQL语句并返回其生成的结果的对象。默认情况下,每个 Statement对象只能有一个               ResultSet对象同时打开。
    4.ResultSet:结果集对象
    5.PrepareStatement:执行SQL的对象
         表示预编译的SQL语句的对象,SQL语句已预编译并存储在 PreparedStatement对象中。 然后可以使用该         对象多次有效地执行此语句.该接口继承自 Statement 接口,扩展了以下常用方法:
        void setObject(int parameterIndex, Object x) :使用给定对象设置指定参数的值                    ResultSet executeQuery() :执行此 PreparedStatement对象中的SQL查询,并返回查询              PreparedStatement的 ResultSet对象 int executeUpdate() :执行在该SQL语句                   PreparedStatement对象,它必须是一个SQL数据操纵 语言(DML)语句,如INSERT , UPDATE或           DELETE ; 或不返回任何内容的SQL语句,例如DDL语 句 
 */
//查询数据
 @Test
    public void test() {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            Connection conn = DriverManager.getConnection("jdbc:mysql://192.168.66.128:3306/jdbcdemo?useUnicode=true&characterEncoding=utf8", "root", "123456");
            String sql = "SELECT u.id,s.id,u.username 用户名,u.password 密码,u.createDate 日期 FROM users u,sutdents s";
            PreparedStatement statement = conn.prepareStatement(sql);
            ResultSet resultSet = statement.executeQuery();
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 0; i < metaData.getColumnCount(); i++) {
                System.out.print(metaData.getColumnLabel(i + 1) + "\t");//这里获取的是列的别名,如果要获取原来的列名,使用getColumnName()
            }
            System.out.println();
            while (resultSet.next()) {
                System.out.println(
                        resultSet.getObject("u.id") + "\t" + //这里如果只查询列名为id的列的话,只会查询出Sql语句中写在前面的id
                                resultSet.getObject("s.id") + "\t" +
                                resultSet.getObject("用户名") + "\t" +//这里使用了别名的方式进行获取
                                resultSet.getObject("密码") + "\t" +
                                resultSet.getObject("日期") + "\t");
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
    }
//添加数据
@Test
    public void test() {
        try {
            Class.forName("com.mysql.jdbc.Driver");
            Connection conn = DriverManager.getConnection("jdbc:mysql://192.168.66.128:3306/jdbcdemo?useUnicode=true&characterEncoding=utf8", "root", "123456");;
            String sql = "INSERT INTO users(username,password) VALUES (?,?)";
            PreparedStatement statement = conn.prepareStatement(sql);
            statement.setObject(1, "王五");
            statement.setObject(2, "789");
            int row = statement.executeUpdate();
            if (row > 0) {
                System.out.println("添加成功");
            }else {
                System.out.println("添加失败");
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }
    }
/**
* 事务的四大特性
*  原子性:事务中所有操作是不可分割的原子单位。事务中的所有操作要么全部执行成功
*         要么全部执行失败。
*  一致性:事务执行以后数据库状态与其他业务规则保持一致。如转账业务,无论事务是否执行成功,
*          参与转账的两个账号余额之和应该是不变的。
*  隔离性:隔离性是指在并发操作中,不同事务之间因该隔离开来,使每个并发中的事务不会相互
*         干扰
*  持久性:一旦事务提交成功,事务中所有的数据操作都必须持久化到数据库中,
*
*/
/**
 *  *JDBC中的事物(事务开启之后, 所有的操作都会临时保存到事务日志, 事务日志只有在得到 commit 命令才会同              步到数据 表中,其他任何情况都会清空事务日志( rollback ,断开连接))
 *      *Connection的三个方法与事务相关(同一事物中所有的操作,都在使用一个Connection对象)
 *          *setAutoCommit(boolean)设置是否为自动提交事物,如果true(默认为true)表示自动提交,
 *              也就是每条执行的SQL语句都是一个单独的事物,如果设置为false,那么就相当于开启了事务(不是关闭事务)
 *          *commit():提交结束事物
 *          *rollback():回滚结束事物
 *      *jdbc处理事务的代码格式:
 *          try{
 *              con.setAutoCommit(false);//开启事务
 *              ...
 *              con.commit();//最后提交事务
 *          }catch(){
 *              conn.rollback();//回滚事务
 *          }
 *      *事物的并发读问题
 *          *脏读:读取到另一个事务未提交的数据
 *          *不可重复读:两次读取不一致
 *          *幻读(虚读):读取到另一事务已提交的数据
 *      *四大隔离级别
 *          *串行化
 *              不会引发任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的,但性能最差
 *          *可重复读
 *              防止脏读和不可重复读,性能优于串行化
 *          *读已提交的数据
 *              防止脏读,性能优于可重复读
 *          *读未提交数据
 *              可能出现任何事务并发问题,性能最好
 */
//没有开启事务时所遇到的问题
@Test
    public void test1() {

        PreparedStatement ps = null;
        try {
            Connection conn = DBHelper.getConn();
            //王思聪转出100元
            String sql = "UPDATE customer SET balance = balance + ? WHERE customerName = ?";
            ps = conn.prepareStatement(sql);
            ps.setObject(1, -100);
            ps.setObject(2, "王思聪");
            ps.executeUpdate();
            int j = 10 / 0; // 这里抛出了异常,会导致王思聪的账户余额减少了100元,而王健林的账户余额却没有改变原因是抛出了异常,下面的代码不会被执行(原本应该是王健林的账户余额增加100)
            //王健林转入100元
            ps.setObject(1, 100);
            ps.setObject(2, "王健林");
            ps.executeUpdate();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
//解决办法:开启事务。
    @Test
    public void test1() {

        PreparedStatement ps = null;
        Connection conn = null;
        try {
            conn = DBHelper.getConn();
            //王思聪转出100元
            //开启事务(设置自动提交为false就是开启事务)
            conn.setAutoCommit(false);
            String sql = "UPDATE customer SET balance = balance + ? WHERE customerName = ?";
            ps = conn.prepareStatement(sql);
            ps.setObject(1, -100);
            ps.setObject(2, "王思聪");
            ps.executeUpdate();
            //这里抛出异常
            int j = 10 / 0; //这里抛出了异常,这个事务不会被提交并持久化到数据库中,而是回到事务发生前的状态,账户的余额不会发生变化。
            //王健林收入100元
            ps.setObject(1, 100);
            ps.setObject(2, "王健林");
            ps.executeUpdate();
            //提交事务
        } catch (SQLException e) {
            e.printStackTrace();
            //回滚结束事务(这里数据会回到事务开始前的状态)
            try {
                conn.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        } finally {
            DBHelper.closeConn();
        }
    }

回滚点

在某些成功的操作完成之后,后续的操作有可能成功有可能失败,但是不管成功还是失败,前面操作都 已经成功,可以在当前成功的位置设置一个回滚点。可以供后续失败操作返回到该位置,而不是返回所 有操作,这个点称之为回滚点。
设置回滚点语法: savepoint 回滚点名字 ; 回到回滚点语法: rollback to 回滚点名字 ;
@Test
    public void test3() {

        PreparedStatement ps = null;
        Connection conn = null;
        Savepoint point = null;
        try {
            conn = DBHelper.getConn();
            String sql = "UPDATE customer SET balance = balance - 100 WHERE id = 1";
            ps = conn.prepareStatement(sql);
            //让账户减少300
            for (int i = 0; i < 3; i++) {
                ps.executeUpdate();
            }
            //设置回滚点
            point = conn.setSavepoint();
            //抛出一个异常
            int m = 10 / 0;
            //再次让账户减少300
            for (int i = 0; i < 3; i++) {
                ps.executeUpdate();
            }
            //提交事务
            conn.commit();
        } catch (SQLException e) {
            //回滚到指定的回滚点
            try {
                conn.rollback(point);
                conn.commit();//这里需要手动提交一次事务,不然回滚点前的事务没有提交,数据库中的数据也不会发生变化
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        }
    }

事务隔离

image-20201027210238819

查询全局事务的隔离级别

show variables like '%isolation%'; 
-- 或 
select @@tx_isolation;

设置事务隔离级别

set global transaction isolation level 级别字符串; 
-- 如: 
set global transaction isolation level read uncommitted;(读未提交)

标签

评论

this is is footer