• MySQL 数据库(下)


    事务

    什么是事务

    • A给B转账,B收到A的钱

      要么都成功,要么都失败

      将一组SQL放到一个批次中去执行

    • ACID原则

      原子性Atomicity,要么都完成,要么都不完成

      一致性Consistency,针对一个事务操作前与操作后状态一直

      持久性Durability,表示事务结束后的数据不随着外界原因导致数据丢失(事务一旦提交不可逆)

      隔离性Isolation,针对多个用户同时操作,主要是排除其他事务对本次事务的影响

    • 隔离导致的问题

      脏读:指一个事务读取了另外- -个事务未是交的数据。
      不可重复读:在一个事务内读取表中的某一行数据, 多次读取结果不同。(这个不一定是错误, 只是某些场合不对)
      虚读(幻读):是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。

    MySQL中的事务

    • 用法

      -- MySQL 是默认开启事务自动提交的
      SET autocommit = 0 -- 关闭
      SET autocommit = 1 -- 开启
      
      -- 手动处理事务
      SET autocommit = 0 -- 关闭自动提交
      -- 事务开启
      START TRANSACTION -- 标记一个事务的开始,从这个之后的sql都在同一个事务内
      INSERT XXX
      INSERT XXX
      -- 提交:持久化(成功)
      COMMIT
      -- 回滚:回到原来的样子(失败)
      ROLLBACK
      -- 事务结束
      SET autocommit = 1 -- 开启自动提交
      
      SAVEPOINT 保存点名 -- 设置一个事务的保存点
      ROLLBACK TO SAVEPOINT 保存点名 -- 回滚到保存点
      RELEASE SAVEPOINT 保存点名 -- 删除保存点
      
    • 模拟场景 转账

      -- 转账
      CREATE DATABASE shop CHARACTER SET utf8 COLLATE utf8_general_ci
      USE shop
      
      CREATE TABLE `account`(
      	`id` INT(3) NOT NULL AUTO_INCREMENT,
      	`name` VARCHAR(30) NOT NULL,
      	`money` DECIMAL(9,2) NOT NULL,
      	PRIMARY KEY(`id`)
      )ENGINE=INNODB DEFAULT CHARSET=utf8
      
      INSERT INTO account(`name`,`money`)
      VALUES ('A',2000.00),('B',10000.00)
      
      -- 模拟转账:事务
      SET autocommit = 0; -- 关闭自动提交
      START TRANSACTION; -- 开启一个事务
      
      UPDATE account SET money=money-500 WHERE `name` = 'A'; -- A减500
      UPDATE account SET money=money+500 WHERE `name` = 'B'; -- B加500
      
      COMMIT; -- 提交事务
      ROLLBACK; -- 回滚
      
      SET autocommit = 1; -- 恢复默认值
      
    • Java中操作事务

      方法(){
          try(){
              //正常的业务代码
              commit();
          }catch(){
              rollback();
          }
      }
      

    索引

    MySQL官方对索引的定义为:索引(Index) 是帮助MySQL高效获取数据的数据结构。提取句子主干,就可以得到索引的本质:索引是数据结构。

    • 索引的分类

      主键索引 PRIMARY KEY:唯一的标识,不可重复,只有一个列作为主键

      唯一索引 UNIQUE KEY:避免重复的列(字段)出现,可以重复,多个列都可以标识为唯一索引

      常规索引 KEY / INDEX:默认的

      全文索引 FULLTEXT INDEX:在特定的数据库引擎下才有,快速定位数据

    • 索引的使用

      -- 显示所有的索引信息
      SHOW INDEX FROM student
      
      -- 添加一个全文索引 索引名(列名)
      ALTER TABLE school.student ADD FULLTEXT INDEX `name`(`name`);
      
      -- EXPLAIN 分析sql执行的状况
      EXPLAIN SELECT * FROM student; -- 非全文索引
      SELECT * FROM student WHERE MATCH(`name`) AGAINST('刘')
      

    测试索引

    • 插入100万数据

      DELIMITER $$ -- 写函数之前必须要写的标志
      CREATE FUNCTION mock_data()
      RETURNS INT
      BEGIN
      	DECLARE num INT DEFAULT 1000000;
      	DECLARE i INT DEFAULT 0;
      	WHILE i<num DO
      		INSERT INTO app_user(`name`,`phone`,`password`,`age`) 
      		VALUES(CONCAT('用户',i),CONCAT('18',FLOOR(1000000000-RAND()*999999999)),UUID(),FLOOR(RAND()*100)); -- 插入语句
      		SET i = i+1;
      	END WHILE;
      	RETURN i;
      END;
      
      CREATE TABLE app_user(
      		`id` INT(3) NOT NULL AUTO_INCREMENT,
      		`name` VARCHAR(20) NOT NULL,
      		`phone` VARCHAR(11) NOT NULL,
      		`password` VARCHAR(50) NOT NULL,
      		`age` INT(3) NOT NULL,
      		PRIMARY KEY(`id`)
      )ENGINE=INNODB DEFAULT CHARSET=utf8;
      
      SELECT mock_data();
      
    • 测试

      SELECT * FROM app_user WHERE `name`='用户9999'; -- 将近1s才找到
      CREATE INDEX id_app_user_name ON app_user(`name`); -- 添加索引,用1s多
      SELECT * FROM app_user WHERE `name`='用户9999'; -- 添加索引后查询0.001s
      
    • 索引在小数据量的时候用处不大,大数据的时候效果明显变快

    索引原则

    • 索引不是越多越好

    • 不要对经常变动数据加索引

    • 小数据量的表不需要加索引

    • 索引一般加在常用来查询的字段上

    • 索引的数据结构

      Hash类型的索引

      Btree:INNODB的默认数据结构

    权限管理

    用户管理

    • Navicat可视化管理

    • mysql.user 用户表

    对用户表进行增删改查

    -- 创建用户
    CREATE USER peng IDENTIFIED BY '123456';
    
    -- 修改当前用户密码
    SET PASSWORD = PASSWORD('111111');
    
    -- 修改指定用户密码
    SET PASSWORD FOR peng = PASSWORD('111111');
    
    -- 重命名
    RENAME USER peng TO pengpeng;
    
    -- 授予权限  库.表  *.*表示全部库的全部表
    GRANT ALL PRIVILEGES ON *.* TO pengpeng; -- 除了GRANT别人,其他事都能干
    
    -- 查询权限
    SHOW GRANT FOR pengpeng;
    SHOW GRANT FOR root@localhost;
    
    -- ROOT用户的权限
    GRANT ALL PRIVILEGES ON *.* TO root@localhost WITH GRANT OPTION;
    
    -- 撤销权限
    REVOKE ALL PRIVILEGES ON *.* FROM pengpeng;
    
    -- 删除用户
    DROP USER pengpeng;
    

    MySQL备份

    • why

      1. 保证重要的数据不丢失

      2. 数据转换

      3. 存起来比较小

    • 备份方式

      1. 直接拷贝物理文件

      2. 可视化工具:导出的是SQL语句,拉过来执行就行了

      3. 命令行 mysqlump 导出

        mysqlump -h127.0.0.1 -uroot -p123456 school [student] >D:/a.sql #导出
        source D:/a.sql #导入(需要先登录)
        

    规范数据库设计

    为什么需要设计

    数据库比较复杂的时候,我们就需要设计

    • 设计
      1. 分析需求:分析业务和需要处理的数据库的需求
      2. 概要设计:设计关系图E-R图

    三大范式

    • 问题

      信息会重复

      更新会导致异常

      插入异常:无法正常显示信息

      删除异常:丢失有效信息

    • 三大范式(规范数据库)

      1. 第一范式(1NF):要求数据库表的每一列都是不可分割的原子数据项,原子性
      2. 第二范式(1NF):前提,满足第一范式;每张表只描述一件事情
      3. 第三范式(1NF):前提,满足第一、二范式;每一列都跟主键直接相关,不能间接相关
    • 规范性 和 性能 的问题

      一般地,关联查询的表不能超过三张表

      1. 考虑商业化的需求和目标,(成本, 用户体验! )数据库的性能更加重要
      2. 在规范性能的问题的时候,需要适当的考虑一下 规范性!
      3. 故意给某些表增加一些冗余的字段。(从多表查询中变为单表查询)
      4. 故意增加一-些计算列(从大数据量降低为小数据量的查询: 索引)

    JDBC

    数据库驱动

    • 驱动:声卡,显卡,数据库

      程序通过数据库驱动连接数据库

    • JDBC

      为了简化开发人员的(对数据库的)操作,提供的一个(Java操作数据库的)规范

    • Java包

      java.sql

      javax.sql

      导入一个数据库驱动包 mysql-connector-java-5.1.47.jar

    第一个JDBC程序

    准备工作(创建测试数据库):

    CREATE DATABASE jdbcStudy CHARACTER SET utf8 COLLATE utf8_general_ci;
    
    USE jdbcStudy;
    
    CREATE TABLE `users`(
    id INT PRIMARY KEY,
    NAME VARCHAR(40),
    PASSWORD VARCHAR(40),
    email VARCHAR(60),
    birthday DATE
    );
    
    INSERT INTO `users`(id,NAME,PASSWORD,email,birthday)
    VALUES(1,'zhansan','123456','zs@sina.com','1980-12-04'),
    (2,'lisi','123456','lisi@sina.com','1981-12-04'),
    (3,'wangwu','123456','wangwu@sina.com','1979-12-04')
    
    • 创建一个普通项目

    • 导入数据库驱动

      1. 右键项目,新建一个 lib 目录
      2. 把 jar 包拷贝进来
      3. 右键 lib,选择Add as Library...
    • 编写测试代码

      import java.sql.*;
      
      //我的第一个JDBC程序
      public class JdbcFirstDemo {
          public static void main(String[] args) throws Exception {
              //1.加载驱动
              Class.forName("com.mysql.jdbc.Driver"); //固定写法
      
              //2.连接信息,用户信息 和 url
              //SSL安全
              String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true";
              String username = "root";
              String password = "3306";
      
              //3.连接成功,数据库对象  Connection代表数据库
              Connection connection = null;
              connection = DriverManager.getConnection(url,username,password);
      
              //4.执行SQL的对象
              Statement statement = connection.createStatement();
      
              //5.执行SQL的对象 去执行SQL
              String sql = "SELECT * FROM users";
      
              ResultSet resultSet = statement.executeQuery(sql);
              //返回的结果集,结果集中封装了我们全部的查询出来的结果
      
              while (resultSet.next()){ //是个链表
                  System.out.println("id="+resultSet.getObject("id"));
                  System.out.println("name="+resultSet.getObject("NAME"));
                  System.out.println("pwd="+resultSet.getObject("PASSWORD"));
                  System.out.println("email="+resultSet.getObject("email"));
                  System.out.println("birth="+resultSet.getObject("birthday"));
                  System.out.println("=============================");
              }
      
              //6.释放连接
              resultSet.close();
              statement.close();
              connection.close();
          }
      }
      
      • JDBC 对象

        DriverManager:Class.forName("com.mysql.jdbc.Driver"); //固定写法 加载驱动

        Connection:代表数据库,数据库能做的他都能做

        connection.rollback();
        connection.commit();
        connection.setAutoCommit();
        ...
        

        URL:String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true";

        Statement:执行sql的对象

        statement.executeQuery(); //查
        statement.execute(); //任何sql语句
        statement.executeUpdate(); //增删改
        

        ResultSet:查询结果集

        resultSet.getObject(); //不知道什么类型
        resultSet.getString();
        resultSet.getFloat();
        ...
        resultSet.beforeFirst(); //移动到最前面
        resultSet.afterLast(); //移动到最后面
        resultSet.next(); //移动到下一个数据
        resultSet.previous(); //移动到前一行
        resultSet.absolute(row); //移动到指定行
        

        释放资源

        resultSet.close();
        statement.close();
        connection.close(); //耗资源,用完关闭
        

    statement对象

    • Jdbc中的statement对象用于向数据库发送SQL语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查语句即可。

    • Statement对象的executeUpdate方法,用于向数据库发送增、删、改的sq|语句,executeUpdate执行完后, 将会返回一个整数(即增删改语句导致了数据库几行数据发生了变化)。

    • Statement.executeQuery方法用于向数据库发送查询语句,executeQuery方法返回代表查询结果的ResultSet对象。

    • CRUD操作-create

      使用executeUpdate(String sq|)方法完成数据添加操作,示例操作:

      Statement st = conn.createStatement();
      Stirng sql = "insert into user(...) values(...)";
      int num = st.executeUpdate(sql);
      if(num>0){
          System.out.println("插入成功!!");
      }
      
    • CRUD操作-delete

      使用executeUpdate(String sq|)方法完成数据删除操作,示例操作:

      Statement st = conn.createStatement();
      Stirng sql = "delete from user where id=1";
      int num = st.executeUpdate(sql);
      if(num>0){
          System.out.println("删除成功!!");
      }
      
    • CRUD操作-update

      使用executeUpdate(String sq|)方法完成数据修改操作,示例操作:

      Statement st = conn.createStatement();
      Stirng sql = "update user set name='pengpeng' where name='peng'";
      int num = st.executeUpdate(sql);
      if(num>0){
          System.out.println("修改成功!!");
      }
      
    • CRUD操作-read

      使用executeQuery(String sq|)方法完成数据查询操作,示例操作:

      Statement st = conn.createStatement();
      Stirng sql = "select * from user where id=1";
      ResultSet rs = st.executeQuery(sql);
      while(rs.next){
          //根据获取列的数据类型,分别调用rs的相应方法映射到java对象中
      }
      
    • 代码

      1. 配置

    1. 工具类 JbdcUtils

      package lesson02.utils;
      
      import java.io.InputStream;
      import java.sql.*;
      import java.util.Properties;
      
      public class JdbcUtils {
      
          private static String driver = null;
          private static String url = null;
          private static String username = null;
          private static String password = null;
      
          static{
              try{
                  InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
                  Properties properties = new Properties();
                  properties.load(in);
      
                  driver = properties.getProperty("driver");
                  url = properties.getProperty("url");
                  username = properties.getProperty("username");
                  password = properties.getProperty("password");
      
                  //1.驱动只用加载一次
                  Class.forName(driver);
      
              }catch (Exception e){
                  e.printStackTrace();
              }
          }
      
          //获取连接
          public static Connection getConnection() throws SQLException {
              return DriverManager.getConnection(url,username,password);
          }
      
          //释放资源
          public static void release(Connection conn, Statement st, ResultSet rs){
              if (rs!=null){
                  try {
                      rs.close();
                  } catch (SQLException e) {
                      e.printStackTrace();
                  }
              }
              if (st!=null){
                  try {
                      st.close();
                  } catch (SQLException e) {
                      e.printStackTrace();
                  }
              }
              if (conn!=null){
                  try {
                      conn.close();
                  } catch (SQLException e) {
                      e.printStackTrace();
                  }
              }
          }
      }
      
    2. 测试类

      package lesson02;
      
      import lesson02.utils.JdbcUtils;
      
      import java.sql.Connection;
      import java.sql.ResultSet;
      import java.sql.SQLException;
      import java.sql.Statement;
      
      public class TestDelete {
          public static void main(String[] args) {
      
              Connection conn = null;
              Statement st = null;
              ResultSet rs = null;
      
              try {
                  conn = JdbcUtils.getConnection(); //获取数据库连接
                  st = conn.createStatement(); //获得执行sql的对象
                  String sql = "DELETE * FROM users WHERE id=1";
                  int i=st.executeUpdate(sql);
                  if (i>0){
                      System.out.println("删除成功!!");
                  }
              } catch (SQLException e) {
                  e.printStackTrace();
              }finally {
                  JdbcUtils.release(conn,st,rs);
              }
          }
      }
      
    • SQL注入的问题

      sql存在漏洞,会被攻击

      login(username=" ' or ' 1=1 ",password=" ' or ' 1=1 ")

    PrepareStatement

    • 是Statement的子类,可以防止sql注入,并且效率更高

      package lesson03;
      
      import lesson02.utils.JdbcUtils;
      
      import java.sql.Connection;
      import java.sql.PreparedStatement;
      import java.sql.SQLException;
      import java.util.Date;
      
      public class TestInsert {
          public static void main(String[] args) {
              Connection conn = null;
              PreparedStatement st = null;
      
              try {
                  conn = JdbcUtils.getConnection();
      
                  //区别
                  //使用?占位符代替参数
                  String sql = "INSERT INTO user(id,`name`,`password`,`birthday`) VALUES(?,?,?,?)";
      
                  st = conn.prepareStatement(sql); //预编译sql,先写sql,然后不执行
      
                  //手动给参数赋值
                  st.setInt(1,2018215555);
                  st.setString(2,"peng");
                  st.setString(3,"8098");
                  //注意点: sql.Date  数据库  java.sql.Date()转换
                  //        util.Date  Java   new Date().getTime() 获得时间戳
                  st.setDate(4,new java.sql.Date(new Date().getTime()));
      
                  //执行
                  int i = st.executeUpdate();
                  if (i>0){
                      System.out.println("插入成功!!");
                  }
              } catch (SQLException e) {
                  e.printStackTrace();
              } finally {
                  JdbcUtils.release(conn,st,null);
              }
          }
      }
      
    • 防注入本质

      传递过来的参数当作字符,有转义字符时直接忽略

    IDEA连接数据库

    事务

    • ACID原则

      原子性,一致性,隔离性,持久性

    • 隔离性的问题

      脏读:一个事务读取了另一个没有提交的事务
      不可重复读:在同一个事务内,重复读取表中的数据,表数据发生了改变
      虛读(幻读):在一个事务内,读取到了别人插入的数据,导致前后读出来结果不一致

      package lesson04;
      
      import lesson02.utils.JdbcUtils;
      
      import java.sql.Connection;
      import java.sql.PreparedStatement;
      import java.sql.ResultSet;
      import java.sql.SQLException;
      
      public class TestTransaction {
          public static void main(String[] args) {
              Connection conn = null;
              PreparedStatement st = null;
              ResultSet rs = null;
      
              try {
                  conn = JdbcUtils.getConnection();
                  //关闭数据库的自动提交,自动会开启事务
                  conn.setAutoCommit(false); //开启事务
      
                  String sql1 = "UPDATE account set money=money-100 WHERE name='A'";
                  st = conn.prepareStatement(sql1);
                  st.executeUpdate();
      
                  String sql2 = "UPDATE account set money=money+100 WHERE name='B'";
                  conn.prepareStatement(sql2);
                  st.executeUpdate();
      
                  //业务完毕,提交事务
                  conn.commit();
                  System.out.println("成功!!");
              } catch (SQLException e) {
                  //conn.rollback(); //失败则回滚,可以不写,失败默认回滚
                  e.printStackTrace();
              } finally {
                  try {
                      conn.setAutoCommit(true);
                  } catch (SQLException e) {
                      e.printStackTrace();
                  }
                  JdbcUtils.release(conn,st,rs);
              }
          }
      }
      

    数据库连接池

    连接 - - 释放,浪费系统资源

    • 池化技术

      准备一些预先的资源,过来就连接预先准备好的

      常用连接数 10个

      最小连接数 10

      最大连接数 15 业务最高承载上限

      等待超时 100ms

    • 编写连接池

      实现一个接口 DataSource

    • 开源数据源实现(拿来即用)

      DBCP,C3P0,Druid

      使用了这些连接池后,我们在项目开发中就不需要编写连接数据库的代码了

    • DBCP

      需要用到的jar包

      commons-dbcp-1.4、commons-pool-1.6

    • C3P0

      需要用到的jar包

      c3p0-0.9.5.5、mchange-commons-java-0.2.19

    • 结论

      无论使用什么数据源,本质还是一样的,DataSource接口不会变, 方法就不会变

  • 相关阅读:
    第十四周学习进度条
    对txt文本中字符的统计
    JAVA项目中的常用的异常处理情况总结
    动手动脑:异常处理
    第一次尝试连接数据库
    Vuejs 实现权限管理
    vue 中的 ... (三个点的用法)
    Vue 中怎么发起请求(二)
    Vue 中怎么发起请求(一)
    Vue添加请求拦截器
  • 原文地址:https://www.cnblogs.com/peng8098/p/db_02.html
Copyright © 2020-2023  润新知