MySQL jdbc,事务,连接池

news2024/11/20 7:01:08

​​​

3-MySQL jdbc,事务,连接

1 jdbc

1.1 jdbc概述#

        JDBC(Java DataBase Connectivity,java数据库连接技术)是一种用于执行SQL语句的Java API。

        JDBC是Java访问数据库的标准规范,可以为不同的关系型数据库提供统一访问,它由一组用Java语言编写的接口和类组成。

        JDBC需要连接驱动,驱动是两个设备要进行通信,满足一定通信数据格式,数据格式由设备提供商规定,设备提供商为设备提供驱动软件,通过软件可以与该设备进行通信。

JDBC与数据库驱动的关系:接口与实现的关系。

file

1.2 jdbc原理#

Java提供访问数据库规范称为JDBC,而生产厂商提供规范的实现类称为驱动。  

file

其中:

        DriverManager:用于注册驱动

        Connection:表示与数据库创建的连接

        Statement:操作数据库sql语句的对象

        ResultSet:结果集或一张虚拟表

1.3 jdbc编程步骤#

file

1.3.1 加载数据库驱动#

Class.forName(“com.mysql.jdbc.Driver”);

创建 com.mysql.jdbc.Driver 这个类的对象供连接数据库使用

JAVA 规范中明确规定:所有的驱动程序必须在静态初始化代码块中将驱动注册到驱动程序管理器中。

注意:mysql5之后的驱动jar包可以省略注册驱动的步骤。在jar包中,存在一个java.sql.Driver配置文件,文件中指定了com.mysql.jdbc.Driver

1.3.2 通过DriverManager 获取数据库连接#

// 获取数据库连接
Connection conn = DriverManager.getConnection(String url, String user, String password);
//参数说明:
//        1)url:需要连接数据库的位置(网址) 
//        2)user:登录数据库用户名
//        3)password:登录数据库密码

Connection 连接是通过 DriverManager 的静态方法 getConnection(.....)来得到的, 这个方法的实质是把参数传到实际的 Driver 中的 connect()方法中来获得数据库连接的。

Mysql 的 url 格式:jdbc:mysql://localhost或ip:3306/要连接的数据库名称 [?characterEncoding=UTF-8]

如:

        本地连接:jdbc:mysql://localhost:3306/hainiudb

        远程连接:jdbc:mysql://192.168.31.131:3306/hainiudb

注意:如果出现关于SSL的警告,可以在连接后添加useSSL=false

1.3.3 获得一个 Statement 对象#

Statement stmt = conn.createStatement();

1.3.4 通过 Statement 执行 Sql 语句#

file

1.3.5 处理结果集#

        使用 Connection 对象获得一个 Statement, Statement 中的 executeQuery(String sql) 方法可以使用 select 语句查询,并且返回一个结果集 ResultSet 通过遍历这个结果集,可以获得 select语句的查寻结果,.

        ResultSet 的 next()方法会操作一个游标从第一条记录的前面开始读取,直到最后一条记录。

        executeUpdate(String sql) 方法用于执行 DDL 和 DML 语句, 比如可以update, delete 操作。

只有执行 select 语句才有结果集返回。

1.3.6 关闭数据库连接(释放资源)#

rs.close(); //关闭结果集ResultSet 
stmt.close();// 关闭Statement
con.close();// 关闭数据库连接Connection 
//ResultSet Statement Connection 是依次依赖的。

注意:要按先 ResultSet 结果集,后 Statement,最后 Connection 的顺序关闭资源,

因为 Statement 和 ResultSet 是需要连接时才可以使用的,所以在使用结束之后有

可能其它的 Statement 还需要连接,所以不能先关闭 Connection。

2 jdbc编程实例

2.1 环境准备#

创建mysql工程项目

file

加载mysql驱动时,要添加mysql 的驱动包。

在pom文件汇总引入mysql驱动包

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>

2.2 数据准备#

沿用 hainiudb 数据库的 product 表 和 category 表

2.3 jdbc添加一行数据#

需求:用java jdbc给category表添加一行图书类别

package com.hainiu.demo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

/**
 * 需求:用java jdbc给category表添加一行图书类别
 */
public class JDBCInsertDemo {
    public static void main(String[] args) {
        // 数据库连接四大属性
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://10.88.10.9:3306/hainiudb?characterEncoding=UTF-8";
        String user = "root";
        String password = "12345678";

        // 获取数据库连接
        Connection conn = null;
        Statement stat = null;
        try{
            // 加载驱动
            Class.forName(driver);
            conn = (Connection) DriverManager.getConnection(url, user, password);
            // 编写SQL语句
            String sql = "insert into category(cid, cname) values (7, '图书');";
            // 创建SQL语句的执行对象
            stat = conn.createStatement();
            // 执行SQL语句并获取结果
            int executeUpdate = stmt.executeUpdate(sql);
            if(executeUpdate > 0){
                System.out.println("插入成功");
            }else{
                System.out.println("插入失败");
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally {            
            //--关闭连接            
            if(conn!=null) {                
                try {                    
                    conn.close();                
                } catch (SQLException e) {                                 
                        e.printStackTrace();                
                }finally {                    
                    conn=null;                
                }            
            }            

            if(stat!=null) {                
                try {                    
                    stat.close();                
                } catch (SQLException e) {                                       
                        e.printStackTrace();                
                }finally {                    
                    stat=null;                
                }            
            }        
        }   
    }
}

添加完毕,去数据库查看结果。

2.4 jdbc修改数据#

需求:用java jdbc 把category表中 图书 类别 改成  图书/电子书 类别

package com.hainiu.demo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

/**
 * 需求:用java jdbc 把category表中 图书 类别 改成  图书/电子书 类别
 */
public class JDBCUpdateDemo {
    public static void main(String[] args) {
        // 数据库连接四大属性
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://127.0.0.1:3306/hainiudb?characterEncoding=UTF-8";
        String user = "root";
        String password = "12345678";

        // 获取数据库连接
        Connection conn = null;
        Statement stat = null;
        try{
            // 加载驱动
            Class.forName(driver);

            conn = (Connection) DriverManager.getConnection(url, user, password);
            // 编写SQL语句
            String sql = "update category set cname='图书/电子书' where cname='图书';";
            // 创建SQL语句的执行对象
            stat = conn.createStatement();
            // 执行SQL语句并获取结果
            int executeUpdate = stmt.executeUpdate(sql);
            if(executeUpdate > 0){
                System.out.println("修改成功");
            }else{
                System.out.println("修改失败");
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally {            
            //--关闭连接            
            if(conn!=null) {                
                try {                    
                    conn.close();                
                } catch (SQLException e) {                                 
                        e.printStackTrace();                
                }finally {                    
                    conn=null;                
                }            
            }            

            if(stat!=null) {                
                try {                    
                    stat.close();                
                } catch (SQLException e) {                                       
                        e.printStackTrace();                
                }finally {                    
                    stat=null;                
                }            
            }      
        }
    }
}

修改完毕,去数据库查看结果。

2.5 jdbc删除数据#

需求:用java jdbc 把category表中  图书/电子书 类别删除

package com.hainiu.demo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;

public class JDBCDeleteDemo {
    public static void main(String[] args) {
        // 数据库连接四大属性
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://127.0.0.1:3306/hainiudb?characterEncoding=UTF-8";
        String user = "root";
        String password = "12345678";

        // 获取数据库连接
        Connection conn = null;
        Statement stat = null;
        try{
            // 加载驱动
            Class.forName(driver);

            conn = DriverManager.getConnection(url, user, password);
            // 编写SQL语句
            String sql = "delete from category where cname='图书/电子书';";
            // 创建SQL语句的执行对象
            stat = conn.createStatement();
            // 执行SQL语句并获取结果
            int executeUpdate = stmt.executeUpdate(sql);
            if(executeUpdate > 0){
                System.out.println("删除成功");
            }else{
                System.out.println("删除失败");
            }
        }catch(Exception e){
            e.printStackTrace();
        }finally {            
            //--关闭连接            
            if(conn!=null) {                
                try {                    
                    conn.close();                
                } catch (SQLException e) {                                 
                        e.printStackTrace();                
                }finally {                    
                    conn=null;                
                }            
            }            
            if(stat!=null) {                
                try {                    
                    stat.close();                
                } catch (SQLException e) {                                       
                        e.printStackTrace();                
                }finally {                    
                    stat=null;                
                }            
            }      
        }
    }
}

删除完毕,去数据库查看结果。

2.6 jdbc查询数据#

需求:用java jdbc查询product表中价格>5000的商品, 并把多条商品信息封装成Product对象列表返回。

通过查询SQL : select pname, price from product where price>5000; 

发现查询会两个字段,要把这两个字段的数据封装到对象中

        首先要定义Product类

        然后再将查询结果放到该对象中

        最后再把对象放到列表中返回

1)定义Product类

package com.hainiuxy;

/**
 * 描述商品的实体类
 */
public class Product {
    /**
     * 商品名称
     */
    private String pname;

    /**
     * 商品价格
     */
    private int price;

    public Product() {
    }

    public Product(String pname, int price) {
        this.pname = pname;
        this.price = price;
    }

    public String getPname() {
        return pname;
    }

    public void setPname(String pname) {
        this.pname = pname;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Product[" + this.pname + ", " + this.price + "]";
    }
}

2)代码实现

package com.hainiuxy;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

/**
 * 需求:用java jdbc查询product表中价格>5000的商品, 并把多条商品信息封装成Product对象列表返回。
 */
public class JDBCSearchDemo {

    public static void main(String[] args) throws Exception {
        // 数据库连接四大属性
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://127.0.0.1:3306/hainiudb?characterEncoding=UTF-8";
        String user = "root";
        String password = "12345678";

        // 加载驱动
        Class.forName(driver);

        // 采用自动关闭连接方式
        try (
                // 获取数据库连接
                Connection conn = (Connection) DriverManager.getConnection(url, user, password);
        ) {

            List<Product> products = getProducts(conn);
            for (Product p : products) {
                System.out.println(p);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 将查询结果封装成Product对象列表返回
     *
     * @param conn 连接
     */
    private static List<Product> getProducts(Connection conn) {
        // 编写SQL语句
        String sql = "select pname, price from product where price>5000";
        List<Product> list = new ArrayList<Product>();
        try (
                // 创建SQL语句的执行对象
                Statement stmt = conn.createStatement();
                // 执行SQL语句并获取结果
                ResultSet rs = stmt.executeQuery(sql);
        ) {
            // 遍历数据
            while (rs.next()) {
                String pname = rs.getString(1);
                int price = rs.getInt(2);
                // 将一行的结果封装成 Product 对象
                Product product = new Product(pname, price);
                list.add(product);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }
}

在控制台查看输出结果。

2.7 封装ConnectionUtil工具用于获取连接#

发现上面的代码除了业务逻辑外,就剩下获取连接的代码了

为了使程序员更专注于业务,现封装ConnectionUtil工具类来获取连接

ConnectionUtil工具类代码如下:

public class ConnectionUtil {
    // 私有构造 不让其他人创建对象
    private ConnectionUtil() {}

    // 数据库连接四大属性
    private static final String DRIVER = "com.mysql.jdbc.Driver";
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/hainiudb?characterEncoding=UTF-8";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "12345678";

    // 静态代码块 -- 加载驱动
    static {
        try {
            Class.forName(DRIVER);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取数据库连接
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() throws SQLException {
        Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        return connection;
    }

    /**
     * 关闭结果集、SQL处理对象和连接
     * @param rs
     * @param stat
     * @param conn
     * @throws SQLException
     */
    public static void close(ResultSet rs, Statement stat, Connection conn) throws SQLException {
       //--关闭连接            
            if(conn!=null) {                
                try {                    
                    conn.close();                
                } catch (SQLException e) {                                 
                        e.printStackTrace();                
                }finally {                    
                    conn=null;                
                }            
            }            
            if(rs!=null) {                
                try {                    
                    rs.close();             
                } catch (SQLException e) {                                       
                        e.printStackTrace();                
                }finally {                    
                    rs=null;                
                }            
            }            
            if(stat!=null) {                
                try {                    
                    stat.close();                
                } catch (SQLException e) {                                       
                        e.printStackTrace();                
                }finally {                    
                    stat=null;                
                }            
            }      
    }

}

改造后代码如下:

/**
 * 需求:用java jdbc查询product表中价格>5000的商品, 并把多条商品信息封装成Product对象列表返回。
 */
public class JDBCSearchDemo1 {
    public static void main(String[] args) throws Exception {
        try (
                // 获取数据库连接
                Connection conn = ConnectionUtil.getConnection();
        ) {

            List<Product> products = getProducts(conn);
            for (Product p : products) {
                System.out.println(p);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 将查询结果封装成Product对象列表返回
     * 
     * @param conn
     *            连接
     */
    private static List<Product> getProducts(Connection conn) {
        // 编写SQL语句
        String sql = "select pname, price from product where price>5000";
        List<Product> list = new ArrayList<Product>();
        try (
                // 创建SQL语句的执行对象
                Statement stmt = conn.createStatement();
                // 执行SQL语句并获取结果
                ResultSet rs = stmt.executeQuery(sql);) {
            // 遍历数据
            while (rs.next()) {
                String pname = rs.getString(1);
                int price = rs.getInt(2);
                // 将一行的结果封装成 Product 对象
                Product product = new Product(pname, price);
                list.add(product);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }
}

3 预处理对象

3.1 SQL注入问题#

SQL注入:用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义。

比如:

package com.hainiu.test;
import java.sql.Connection; 
import java.sql.ResultSet; 
import java.sql.Statement; 
import java.util.Scanner;
import com.hainiu.util.JdbcUtil;
/** @author 作者 薪牛: 
@version 创建时间:2022年12月5日 下午6:49:30 
 **/
public class LoginTest {    
public static void main(String[] args) {        
    // 要求用户输入用户名和密码        
    Scanner sc = new Scanner(System.in);        
    System.out.println("请求输入用户名:");        
    String name = sc.nextLine();  
    String name = sc.nextLine();        
    System.out.println("请输入密码:");        
    String password = sc.nextLine();        
    loginTest(name, password);        
    // loginTestPrepared(name,password);    
}
    private static void loginTest(String name, String password) {        
    // 将获取到的值和数据库中的数据作比对        
    Connection conn = null;        
    Statement stat = null;        
    ResultSet rs = null;        
    try {            
    conn = JdbcUtil.getConnection();            
    stat = conn.createStatement();            
    rs = stat.executeQuery("select * from user1 where name='" + name + "' and password='" + password + "'");            
    if (rs.next()) {                
    System.out.println("登录成功");            
    } else {                
    System.out.println("登录失败");            }
        } catch (Exception e) {            
        e.printStackTrace();        
        } finally {            
        JdbcUtil.close(conn, stat, rs);        
        }
    }
}

SQL注入是很危险的,如果有个user表,登录时需要校验用户名和密码,那要是被SQL注入,系统就可以随意登录了。

如何解决?

3.2 预处理对象#

PreparedStatement:

    预编译对象,是Statement对象的子类。

特点:

    性能高

    会把sql语句先编译

    能过滤掉用户输入的关键字。

PreparedStatement预处理对象,处理的每条sql语句中所有的实际参数,都必须使用占位符?替换。

使用预处理对象解决SQL注入问题

示例:

private static void loginTest2(String name, String password) {
// 将获取到的值和数据库中的数据作比对
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
    conn = JdbcUtil.getConnection();
    ps = conn.prepareStatement("select * from user1 where name=? and password=?");
    ps.setString(1, name);
    ps.setString(2, password);
    rs=ps.executeQuery();
    if (rs.next()) {

System.out.println("登录成功");
    } else {

System.out.println("登录失败");
    }

} catch (Exception e) {
    e.printStackTrace();
} finally {
    JdbcUtil.close(conn, ps, rs);
}
}

4 JDBC事务

4.1 什么是事务#

数据库的事务(Transaction)是一种机制、一个操作序列,包含了一组数据库操作命令;

事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令要么同时成功,要么同时失败。

比如转账业务:   

        小强 给 小亮 转账 100 元, 小强-100, 小亮+100,只有两个update操作都成功时转账才能成功。

        如果有一方update 失败, 转账都会失败。

        为了 保证两个update操作都成功,需要把这两个操作放在一个事务中。 

不是所有的存储引擎支持事务,MyISAM不支持事务;InnoDB支持事务。

4.2 事务的4个特性#

  • 原子性(Atomicity): 事务是不可分割的最小操作单位,要么同时成功,要么同时失败

  • 一致性(Consistency) :执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的

  • 隔离性(Isolation) :多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。

  • 持久性(Durability) :事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。

4.3 MySQL事务操作#

MySQL数据库是默认自动提交事务的。

准备数据:

-- 创建账号表
create table account(
    id int primary key auto_increment,
    `name` varchar(20),
    money double
);
-- 添加数据
insert into account values (null,'小明',10000);
insert into account values (null,'小强',10000);
insert into account values (null,'小亮',10000);

查看事务的提交方式:

-- 查询结果:1 表示自动提交; 0 表示手动提交
SELECT @@autocommit;

-- 修改事务的提交方式
SET @@autocommit = 0; -- 设置手动提交

事务操作:

-- 开启事务
START TRANSACTION;
或者
BEGIN;

-- 增删改操作

-- 提交事务
COMMIT; -- 操作生效,数据发生变化 -- 持久化

-- 回滚事务
ROLLBACK; -- 操作无效,数据回滚到开启事务之前的状态

注意:当autocommit的值为0时,可以省略START TRANSACTION 或者 BEGIN 。此时当前连接下的所有SQL语句都是事务的形式,需要手动提交或回滚。

示例:

-- 开启事务(提交或者回滚事务会关闭事务)
START TRANSACTION;

-- 给小明的账户减少1000元
UPDATE account SET money = money - 1000 WHERE `name` = '小明';

-- 给小强的账户增加1000元
UPDATE account SET money = money + 1000 WHERE `name` = '小强';

-- 提交事务
COMMIT;

此时,两个sql语句执行成功,我们提交事务,数据被持久化到数据库。

file

-- 开启事务(提交或者回滚事务会关闭事务)
START TRANSACTION;

-- 给小明的账户减少1000元
UPDATE account SET money = money - 1000 WHERE `name` = '小明';

-- 出错了

-- 给小强的账户增加1000元
UPDATE account SET money = money + 1000 WHERE `name` = '小强';

-- 回滚事务
ROLLBACK;

如果在多条SQL语句执行过程中,产生错误,回滚事务,此时数据回退到开启事务之前的状态。

file

4.4 JDBC事务操作#

语法:

try {
    // 设置开启事务
    conn.setAutoCommit(false);
    // jdbc操作1
    ...
    // jdbc操作n
    // 提交事务 -- 如果SQL语句全部执行成功,没有异常,提交事务
    conn.commit();    
} catch(Exception e) {
    // 事务回滚 -- 如果捕获到异常, 回滚事务
    conn.rollback();
}

代码示例:

版本一:不加入事务管理

public class TransactionDemo01 {
    public static void main(String[] args) throws SQLException {
        // 获取连接
        Connection conn = ConnectionUtil.getConnection();
        int money = 1000;
        String sql1 = "update account set money = money-? where name=?";
        String sql2 = "update account set money = money-? where name=?";

        try (
                PreparedStatement state1 = conn.prepareStatement(sql1);
                PreparedStatement state2 = conn.prepareStatement(sql2);
        ) {
            // 第一个操作
            state1.setInt(1, money);
            state1.setString(2, "小明");
            // 执行
            int result1 = state1.executeUpdate();
            System.out.println(result1);

            // 出现异常
            System.out.println(1/0);

            // 第二个操作
            state2.setInt(1, money);
            state2.setString(2, "小强");
            // 执行
            int result2 = state1.executeUpdate();
            System.out.println(result2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

此时,我们查看数据库就会发现数据出现问题,小明的账户减少了,但小强的账户却没有增加。

版本2:加入事务管理

public class TransactionDemo02 {
    public static void main(String[] args) throws SQLException {
        // 获取连接
        Connection conn = ConnectionUtil.getConnection();
        int money = 1000;
        String sql1 = "update account set money = money-? where name=?";
        String sql2 = "update account set money = money-? where name=?";

        try (
                PreparedStatement state1 = conn.prepareStatement(sql1);
                PreparedStatement state2 = conn.prepareStatement(sql2);
        ) {
            // 开启事务 -- 通过连接对象设置自动提交为false
            conn.setAutoCommit(false);
            // 第一个操作
            state1.setInt(1, money);
            state1.setString(2, "小明");
            // 执行
            int result1 = state1.executeUpdate();
            System.out.println(result1);

            // 出现异常
            System.out.println(1/0);

            // 第二个操作
            state2.setInt(1, money);
            state2.setString(2, "小强");
            // 执行
            int result2 = state1.executeUpdate();
            System.out.println(result2);

            // 如果SQL语句全部执行成功,提交事务
            conn.commit();
        } catch (Exception e) {
            e.printStackTrace();
            // 如果捕获到异常,回滚事务
            conn.rollback();
        }
    }
}

此时,我们查看数据库,发现数据仍然保持原来的状态。

4.5 事务的隔离级别#

4.5.1 事务的并发访问问题#

如果不考虑隔离性,事务存在3种并发访问问题。

1)脏读:一个事务读到了另一个事务未提交的数据。

file

2)不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。

file

3)虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。

file

4.5.2 事务并发问题的解决方案——隔离级别#

数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。

  1. read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。
  2. read committed 读已提交,一个事务读到另一个事务已经提交的数据。
  3. repeatable read  可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
  4. serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。

这四种隔离级别解决的并发访问问题如下:

file

安全和性能对比

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

性能: serializable < repeatable read < read committed < read uncommitted

SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。 
Read Uncommitted(读取未提交内容)

       在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
Read Committed(读取提交内容)

       这是大多数数据库系统的默认隔离级别(例如oracle,但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
Repeatable Read(可重读)

       这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。

Serializable(可串行化) 
       这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

         这四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题。例如:

         脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。

         不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新了原有的数据。

         幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。

查询和设置数据库隔离级别的命令:

-- 查询数据库事务隔离级别
SELECT @@tx_isolation;

-- 修改数据库事务隔离级别
-- 设置全局事务隔离级别 -- 在重启MySQL数据库之后失效
-- 设置全局事务隔离级别 读未提交: READ UNCOMMITTED
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 设置全局事务隔离级别 读已提交: READ COMMITTED
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 设置全局事务隔离级别 可重复读: REPEATABLE READ
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 设置全局事务隔离级别 串行化: SERIALIZABLE
SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- ----------------------------------------------------

-- 设置会话事务隔离级别 -- 在重新建立连接之后失效
-- 设置会话事务隔离级别 读未提交: READ UNCOMMITTED
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

-- 设置会话事务隔离级别 读已提交: READ COMMITTED
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 设置会话事务隔离级别 可重复读: REPEATABLE READ
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 设置会话事务隔离级别 串行化: SERIALIZABLE
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

4.5.3 四种隔离级别演示#

脏读问题:

file

再开启一个事务进行一下修改金额操作

file

此时查看第一个事务,虽然第二个事务还没有提交事务操作,但是数据发生了变化

file

怎么解决这种问题?--将隔离级别改为读已提交

脏读问题解决:

file

通过第二个会话窗口开启事务,并修改小明账户的金额,此时不提交事务,发现两次查看到的信息一直

file

如果第二个事务发生了commit操作,则看到的数据则不一致,这样就解决了一个事务读取到另一个事务还未提交的脏读的情况。

file

file

但是对于第一个事务来说,第二个事务的操作是透明的,但是当第一个事务多次的时候,看到的东西是不一样的,这就是不可重复读的问题。

怎么解决呢--》将隔离级别改为可重复读即可

不可重复读问题解决:

第一个事务修改隔离级别并查看账户信息。

file

第二个事务,修改隔离级别,并进行账户金额修改,并且commit

file

发现第一个事务多次查看账户信息,是不变的,这样就解决了不可重复读的问题。

file

知道第一个事务结束后,才能看到第二个事务修改后的结果

file

但是不可重复读的问题解决了,新的问题出现了,幻读的问题出现了

第一个事务开启事务查看账户信息

file

第二个账户开启事务,新增’小赵‘的账户信息

file

第一个事务此时想新增‘小王’信息,发现报错,但是对于小王来说,查的信息是没有id为4的信息,为什么不让插入,难道有幻觉吗?这就是幻读的问题

file

怎么解决幻读的问题,将隔离级别改为串行化

幻读问题解决:

第一个事务修改隔离级别为 串行化 并开启事务进行查询账户信息

file

第二个事务修改隔离级别并开启事务添加小王的账户信息,发现事务卡着不动,添加不进去

file

知道第一个事务进行了commit之后,第二个事务才能进行操作,串行化能保证同一时刻有一个事务进行操作,避免了幻读的问题,但是性能不高,所以一般不用

file

第二个事务执行成功

file

5 数据库连接池

5.1 为什么用连接池#

file

 如果每一次数据访问请求都需经历下面的过程:

         建立数据库连接 --> 打开数据库 --> 存取数据 --> 关闭数据库连接

        而连接并打开数据库是一件既消耗资源又费时的工作,那么频繁发生这种数据库操作时,系统的性能必然会急剧下降。

5.2 连接池原理#

    理解为存放多个连接的集合。

file

    目的:解决建立数据库连接耗费资源和时间很多的问题,提高性能。

    连接池的使用:

            连接池初始化时,会创建多个连接放池子里

            在使用连接时,从连接池中申请连接使用,使用完成之后,再将连接交还给连接池,以备后续重复利用

5.3 编写标准的数据源(规范)#

        Java为数据库连接池提供了公共的接口:javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便的切换不同厂商的连接池。

常见的连接池技术

        DBCP(DataBase Connection Pool)数据库连接池,是java数据库连接池的一种,由Apache开发,通过数据库连接池,可以让程序自动管理数据库连接的释放和断开。

        Druid 是Java语言中最好的数据库连接池。Druid能够提供强大的监控和扩展功能。

        C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。

        Tomcat-JDBC是Spring Boot中自动配置优先级最高的连接池方案,它的出现是用来替代Apache早期的连接池产品——DBCP 1.x。

        HikariCP同样是一个十分快速、简单、可靠的及十分轻量级的连接池,只有130KB,在GitHub上看到的是"光HikariCP"的名称,光就是说明它十分快。

下面从连接性能和查询性能上比较

file

file

结论 : 

        性能表现:hikariCP > druid > tomcat-jdbc > dbcp > c3p0。

        根据几种数据源的对比 hikari 无疑性能最优秀的,但是因为是最新技术可能存在潜在的bug,所以我们要使用目前比较稳定的阿里的druid数据源;

5.4 druid数据源配置#

1)添加jar包

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.9</version>
</dependency>

2)修改工具类:

public class JDBCUtil {
    // 私有构造 不让其他人创建对象
    private JDBCUtil() {}

    // 数据库连接四大属性
    private static final String DRIVER = "com.mysql.jdbc.Driver";
    private static final String URL = "jdbc:mysql://127.0.0.1:3306/hainiu_43_test05?useSSL=false";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "root";

    // 初始化druid连接池
    private static DruidDataSource dataSource = null;

    // 静态代码块 -- 初始化连接池
    static {
        // 创建连接池对象
        dataSource = new DruidDataSource();
        dataSource.setDriverClassName(DRIVER);
        dataSource.setUrl(URL);
        dataSource.setUsername(USERNAME);
        dataSource.setPassword(PASSWORD);
    }

    /**
     * 获取数据库连接
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() {
        // 从连接池数据源 获取一条连接
        DruidPooledConnection conn = null;
        try {
            conn = dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    /**
     * 关闭结果集、SQL处理对象和连接
     * @param resultSet
     * @param statement
     * @param connection
     * @throws SQLException
     */
    public static void close(ResultSet rs, Statement stat, Connection conn) throws SQLException {
        //--关闭连接            
            if(conn!=null) {                
                try {                    
                    conn.close();                
                } catch (SQLException e) {                                 
                        e.printStackTrace();                
                }finally {                    
                    conn=null;                
                }            
            }            
            if(rs!=null) {                
                try {                    
                    rs.close();             
                } catch (SQLException e) {                                       
                        e.printStackTrace();                
                }finally {                    
                    rs=null;                
                }            
            }            
            if(stat!=null) {                
                try {                    
                    stat.close();                
                } catch (SQLException e) {                                       
                        e.printStackTrace();                
                }finally {                    
                    stat=null;                
                }            
            }      
    }

}

3)编写测试类

public class DruidTest {
    public static void main(String[] args) {

        // 获取连接
        Connection conn = JDBCUtil.getConnection();

        String sql = "insert into account(id, name, money) values (?,?,?)";

        PreparedStatement statement = null;
        try {
            statement = conn.prepareStatement(sql);
            // 设置参数
            statement.setInt(1, 0);
            statement.setString(2, "测试x");
            statement.setDouble(3, 7777);

            // 执行操作
            int num = statement.executeUpdate();
            System.out.println(num);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                JDBCUtil.close(statement, conn);
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

最终优化:将配置信息放到配置文件中,读取配置文件传递参数。

druid.properties文件配置如下

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/hainiudb
username=root
password=12345678
#初始化连接数10个
initialSize=10
# 连接池中容许保持空闲状态的最小连接数量,低于这个数量将创建新的连接
minIdle=5
# 连接池在同一时间能够分配的最大活动连接的数量 20
maxActive=10
#我最多等待6s,6s的时间还没拿到,就放弃本次的索求
maxWait=6000

修改工具类:

public class JDBCUtil {
    // 私有构造 不让其他人创建对象
    private JDBCUtil() {}

    // 初始化druid连接池
    private static DruidDataSource dataSource = null;

    // 静态代码块 -- 初始化连接池
    static {
           Properties properties = new Properties();
            //使用ClassLoader加载配置文件,获取字节输入流
            InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("druid.properties");
            properties.load(is)

        // 创建连接池对象
        dataSource = DruidDataSourceFactory.createDataSource(properties);

    }

    /**
     * 获取数据库连接
     * @return
     * @throws SQLException
     */
    public static Connection getConnection() {
        // 从连接池数据源 获取一条连接
        DruidPooledConnection conn = null;
        try {
            conn = dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    /**
     * 关闭结果集、SQL处理对象和连接
     * @param resultSet
     * @param statement
     * @param connection
     * @throws SQLException
     */
    public static void close(ResultSet rs, Statement stat, Connection conn) throws SQLException {
        //--关闭连接            
            if(conn!=null) {                
                try {                    
                    conn.close();                
                } catch (SQLException e) {                                 
                        e.printStackTrace();                
                }finally {                    
                    conn=null;                
                }            
            }            
            if(rs!=null) {                
                try {                    
                    rs.close();             
                } catch (SQLException e) {                                       
                        e.printStackTrace();                
                }finally {                    
                    rs=null;                
                }            
            }            
            if(stat!=null) {                
                try {                    
                    stat.close();                
                } catch (SQLException e) {                                       
                        e.printStackTrace();                
                }finally {                    
                    stat=null;                
                }            
            }      
    }

}

​​​​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1094819.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

php对接微信支付简要流程?面试时你会描述吗?

一、微信支付申请&#xff1a;微信公众号平台-->功能中找到微信支付-->申请接入 1.如果没有微信支付商会号&#xff0c;需要进行申请 提交营业执照、身份证、银行账户 2.如果有微信支付商会号 可进行直接关联 登录微信商户平台—产品中心—APPID授权管理—新增授权申…

视频编辑软件 Premiere Pro 2024 macv24.0中文版 (pr2024)

Premiere Pro 2024 mac编辑任何现代格式的素材&#xff0c;从8K到虚拟现实。广泛的原生文件支持和简单的代理工作流程可以轻松使用您的媒体&#xff0c;即使在移动工作站上也是如此。提供针对任何屏幕或平台优化的内容比以往任何时候都快。 Premiere Pro 2024 Mac版软件介绍 视…

深度强化学习 第 2 章 蒙特卡洛

2.1随机变量 强化学习中会经常用到两个概念&#xff1a; 随机变量、 观测值。 本书用大写字母表示随机变量&#xff0c;小写字母表示观测值&#xff0c;避免造成混淆。 下面我们定义概率质量函数&#xff08;probability mass function&#xff0c;缩写 PMF&#xff09;和概率…

SpringBoot面试题5:SpringBoot Starter的工作原理是什么?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:SpringBoot Starter的工作原理是什么? Spring Boot Starter 是一种便捷的方式来为 Spring Boot 应用程序引入一组特定功能的依赖项。它简化了项目…

【Java并发】聊聊LongAdder应用场景及其原理

应用场景 我们知道在实际的应用场景中&#xff0c;可能会对某个商品进行浏览次数进行迭代&#xff0c;或者抖音视频的点击&#xff0c;那么如何高效记录呢&#xff0c;首先如果是使用普通的num 进行多线程操作的话&#xff0c;那么一定会带来数据一致性问题&#xff0c;所以一…

【Unity基础】6.动画状态机

【Unity基础】6.动画状态机 大家好&#xff0c;我是Lampard~~ 欢迎来到Unity基础系列博客&#xff0c;所学知识来自B站阿发老师~感谢 &#xff08;一&#xff09;Animator Controller组件 &#xff08;1&#xff09;创建组件 Animator Controller组件是unity用于控制管…

【细读经典】delay model and timing analysis

Technology-Dependent LogicOptimization, part 1 序言 如图所示是现代工业流程中对于一个高层次的抽象描述如何到最后的芯片的流程图&#xff0c;其中逻辑综合作为一个非常重要的部分&#xff0c;主要被分为两个阶段&#xff1a; 工艺无关的优化(technology-independent opt…

shell脚本学习笔记03(小滴课堂)

在shell脚本中&#xff0c;表示变量除了可以使用$a(a是一个变量)&#xff0c;还可以使用${a} 那这两种表示方式有什么区别么&#xff1f; 花括号可以和其它字符或者字母区分开来。 >追加内容 我们发现使用>会把原来的内容覆盖。 我们使用>>就不会覆盖了&#xff…

MAYA教程之模型的UV拆分与材质介绍

什么是UV 模型制作完成后&#xff0c;需要给模型进行贴图&#xff0c;就需要用到UV功能 UV编译器介绍 打开UI编译器 主菜单有一个 UV->UV编译器&#xff0c;可以点击打开 创建一个模型&#xff0c;可以看到模型默认的UV UV编译器功能使用 UV模式的选择 在UV编译器中…

从裸机启动开始运行一个C++程序(八)

前序文章请看&#xff1a; 从裸机启动开始运行一个C程序&#xff08;七&#xff09; 从裸机启动开始运行一个C程序&#xff08;六&#xff09; 从裸机启动开始运行一个C程序&#xff08;五&#xff09; 从裸机启动开始运行一个C程序&#xff08;四&#xff09; 从裸机启动开始运…

【C++】C++11 ——lambda表达式

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C学习 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C】C11…

阿里云香港云服务器公网带宽价格表及测试IP地址

阿里云服务器香港地域公网带宽价格表&#xff0c;1M带宽价格是30.0元/月&#xff0c;按使用流量1GB价格是1.0元&#xff0c;阿里云香港服务器测试IP地址&#xff1a;47.75.18.101&#xff0c;阿里云百科aliyunbaike.com来详细说下阿里云香港服务器1M带宽、5M带宽、6M带宽、10M带…

民宿酒店订房房态商城小程序的作用是什么

外出旅游出差&#xff0c;酒店民宿总是很好的选择&#xff0c;随着经济复苏&#xff0c;各地旅游及外出办公人次增多&#xff0c;酒店成绩随之增加&#xff0c;市场呈现多品牌酒店经营形式。 区别于以前&#xff0c;如今互联网深入各个行业&#xff0c;酒店经营也面临着困境。…

删除字符串特定的字符(fF)C语言

代码&#xff1a; #include <stdio.h> void funDel(char *str) {int i, j;for (i j 0; str[i] ! \0; i)if (str[i] ! f && str[i] ! F)str[j] str[i];str[j] \0; }int main() {char str[100];printf("请输入一个字符串&#xff1a;");gets(str);pr…

转化限制+分析变量变化引起的答案变化:Gym - 104065D

https://vjudge.net/contest/587311#problem/H 先转化一波条件&#xff1a; p i ≥ 1 X p_i\ge \frac 1 X pi​≥X1​ p i ≤ 1 1 − Y p_i\le \frac 1 {1-Y} pi​≤1−Y1​ 所以我们按 p p p 排序&#xff0c; s u m x sum_x sumx​ 必然是后缀&#xff0c; s u m y sum_y …

Python算术运算符:加减乘除 整除 取余 幂指数 小括号

运算案例 需求&#xff1a;用户手工输入梯形的上底、下底以及高&#xff0c;能直接通过Python打印出梯形的面积为多少。 做这个需求前&#xff0c;首先要知道Python的算数运算符有哪些。 2、算术运算符 所谓的算数运算符就是我们日常生活中的加减乘除等待。 运算符描述实例…

汉服商城小程序的作用是什么

汉服在日常生活中越来越常见&#xff0c;大街小巷也有不少年轻人装扮甚是漂亮帅气&#xff0c;有些地区甚至还有相关的比赛等&#xff0c;作为近几年曝光的服饰&#xff0c;汉服市场规模持续增加中&#xff0c;各地线上线下商家也多了起来。 然而在实际经营中&#xff0c;汉服…

Anylogic 读取和写入Excel文件

1、选择面板-连接-Excel文件&#xff0c;拖入到视图中 然后在excel文件的属性中进行绑定外部excel文件。 绑定完之后&#xff0c;在你需要读取的地方进行写代码&#xff0c; //定义开始读取的行数 //这里设为2&#xff0c;是因为第一行是数据名称 int row12; //读取excel文件信…

SpringBoot-黑马程序员-学习笔记(六)

目录 76.常用计量单位使用 77.bean属性校验 81.测试表现层 82.发送虚拟请求 94.springboot读写redis的客户端 100.ElasticSearch&#xff08;简称ES&#xff09; 一个分布式全文搜索引擎 76.常用计量单位使用 Data Component ConfigurationProperties(prefix "serve…

SpringMVC的响应处理

目录 传统同步业务数据的响应 请求资源转发 请求资源重定向 响应数据模型 直接回写数据给客户端 前后端分离异步业务数据响应 在前面的文章中&#xff0c;我们已经介绍了Spring接收请求的部分&#xff0c;接下来看Spring如何给客户端响应数据 传统同步业务数据的响应 准…