小白学习Java第三十一天

今日内容

  1. 索引
  2. 锁(了解)
  3. JDBC概述
  4. JDBC连接数据库实现CRUD操作
  5. 单元测试
  6. SQL注入
  7. 事务
  8. 批处理

一.索引高效查询

(一)概念

1. 索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。更通俗的说,数据库索引好比是一本书前面的目录,能加快数据库的查询速度
2. 索引分为聚簇索引和非聚簇索引两种

聚簇索引表数据按照索引的顺序来存储的,也就是说索引项的顺序与表中记录的物理顺序一致。对于聚集索引,叶子结点即存储了真实的数据行,不再有另外单独的数据页。 在一张表上最多只能创建一个聚集索引,因为真实数据的物理顺序只能有一种。

非聚簇索引表数据存储顺序与索引顺序无关。对于非聚集索引,叶结点包含索引字段值及指向数据页数据行的逻辑指针,其行数量与数据表行数据量一致。

(二)索引的分类

1. 普通索引 :仅加速查询
2. 唯一索引 :加速查询 + 列值唯一(可以有null
3. 组合索引 :多列值组成一个索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用
4. 主键索引:加速查询 + 列值唯一 + 表中只有一个(不可以有null),同主键约束

5. 全文索引:主要用来查找文本中的关键字,尤其查询长文本时效果最佳

(三)索引的创建

  1. 在创建表的同时创建索引

create table 表名 (

字段名 数据类型 [约束说明],

         字段名 数据类型 [约束说明],

         ... ... ,

         [UNIQUE | FULLTEXT ]  INDEX   [索引名]  (字段名 [(长度)] )

);

语法说明:

  1. [UNIQUE | FULLTEXT ]为可选参数,分别表示唯一索引和全文索引;
  2. [索引名] 索引名为可选项,如果不指定,默认以字段名做为索引名;
  3. 字段名[(长度)]表示在表中哪个列上创建索引,长度为可选参数,只有字符串类型的字段才能指定索引长度,一般不设置长度。

案例:

-- 创建表同时创建索引

CREATE TABLE person(

id INT,-- 编号

`name` VARCHAR(30),-- 姓名

email VARCHAR(50),-- 邮箱

introduce TEXT,-- 自我介绍

PRIMARY KEY(id), -- 主键索引,主键约束就是主键索引index==key

INDEX ix_name (NAME),-- 普通索引

UNIQUE ix_email(email),-- 唯一索引

FULLTEXT ix_introduce(introduce),-- 全文索引

INDEX ix_name_emial (NAME,email)-- 组合索引

)ENGINE MYISAM;

查看表中的所有索引:SHOW INDEX FROM 表名;

  1. 表已存在,直接对表添加索引

语法格式:

create [unique|fulltext] index 索引名 on 表名(字段名);

案例:

1)先创建表结构

-- 先创建表结构

CREATE TABLE person(

id INT,-- 编号

`name` VARCHAR(30),-- 姓名

email VARCHAR(50),-- 邮箱

introduce TEXT-- 自我介绍

)ENGINE MYISAM;

2)再给表单独创建索引

-- 1.创建普通索引

CREATE INDEX ix_name ON person(NAME);

-- 2.创建唯一索引

CREATE UNIQUE INDEX ix_email ON person(email);

-- 3.创建组合索引

CREATE INDEX ix_name_email ON person(NAME,email);

-- 4.创建全文索引

CREATE FULLTEXT INDEX ix_introduce ON person (introduce);

查看表中索引:

补充:删除索引

语法格式:drop index 索引名 on 表名;

-- 删除索引

DROP INDEX ix_name ON person;

DROP INDEX ix_email ON person;

DROP INDEX ix_name_email ON person;

DROP INDEX ix_introduce ON person;

ALTER TABLE person DROP PRIMARY KEY;

使用索引案例:

创建一个测试表index_test

-- 创建测试表

CREATE TABLE text_index(

id INT PRIMARY KEY AUTO_INCREMENT,

NAME VARCHAR(30)

);

-- 查询

SELECT * FROM text_index;

-- 插入数据

INSERT INTO text_index(NAME)VALUES('name01'),('name02'),('name03'),('name04'),('name05');

-- 插入批量数据测试

INSERT INTO text_index(NAME) SELECT NAME FROM text_index;

-- 添加索引前后查询效率不一样

SELECT * FROM text_index WHERE NAME = 'name05';

添加索引前

添加索引后

(四)索引使用原则

1. 哪些情况需要创建索引

1. 主键自动建立唯一索引,建议每个表都要建主键
2. 频繁作为查询条件的字段应该创建索引
3. 查询中与其它表关联的字段,外键关系建立索引
4. 组合索引的选择问题,组合索引性价比更高
5. 查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度
6. 查询中统计或者分组字段创建索引

2. 哪些情况不要创建索引

1. 记录太少
2. 经常增删改的表或者字段,因为对表进行INSERTUPDATEDELETE
   因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。
3. Where条件里用不到的字段不创建索引

3. 使用索引注意事项

1. 如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。
2. 不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描。
3. mysql 在使用不等于(!= 或者<>)的时候无法使用索引会导致全表扫描
4. is not null 也无法使用索引,但是is null是可以使用索引的
5. like以通配符开头('%abc...')mysql索引失效会变成全表扫描的操作
6. 字符串不加单引号索引失效

二. 锁机制(了解)

(一)定义

1. 锁是计算机协调多个进程或线程并发访问某一资源的机制。
2. 在数据库中,除传统的计算资源(如CPURAMI/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

(二)锁的分类

1.从对数据操作的类型


1. 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。
2. 写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。

2.从对数据操作的粒度分

1. 表锁
    特点:MyISAM存储引擎只支持表锁,开销小,加锁快;无死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
2. 行锁
    特点:InnoDB存储引擎支持行锁和表锁,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

  1. 死锁

什么是死锁?

是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的过程称为死锁。

如何解决死锁?

1)通过 innodblockwait_timeout 来设置超时时间,一直等待直到超时;

2)发起死锁检测,发现死锁之后,主动回滚死锁中的某一个事务,让其它事务继续执行。

4. 锁的用法划分

1.悲观锁:顾名思义,就是很悲观,每次去数据的时候都认为别人此时对数据做修改,所以每次在数据的时候都会上锁,这样别人想拿这个数据就会被阻塞,直到它拿到锁。正因为如此,悲观锁需要耗费较多的时间,效率上比较低。
    我们使用悲观锁的话其实很简单(手动加行锁就行了)select * from 表名 for update,在select 语句后边加了for update相当于加了排它锁(写锁),加了写锁以后,其他事务就不能对它修改了。

2.乐观锁:乐观锁是用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。是程序员自己维护的,数据库本身不提供解决方案。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 version 字段来实现。当读取数据时,将 version 字段的值一同读出,数据每更新一次,对此 version 值加 1。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的 version 值相等,则予以更新,否则认为是过期数据,需要重试或者做其它操作

锁总结:
表锁其实我们程序员是很少关心它的:
MyISAM存储引擎中,当执行SQL语句的时候是自动加的。
InnoDB存储引擎中,如果没有使用索引,表锁也是自动加的。

三.JDBC简介

  • JDBC定义

JDBC全称Java DataBase Connectivity Java数据库连接技术。是Java语言和数据库之间的一座桥梁,它是一个规范而不是一个实现。简单来讲,我们可以通过Java代码对数据库进行操作管理。

JDBC的工作原理:由SUNOracle)提供一套访问数据库的规范(即一组接口),并提供连接数据库的协议标准,这组协议标准称之为JDBC API;然后各个数据库厂商会遵循SUNOracle)的规范提供一套访问自己公司数据库服务器的程序,称之为数据库驱动

JDBC API是接口,而JDBC驱动才是接口的具体实现,没有驱动是无法完成数据库连接操作的。所以每个数据库厂商都有自己的驱动,用来连接自己公司的数据库。

JDBC驱动:就是JDBC API的实现类。不同类型的数据库有自己的驱动程序,为了方便使用,已经将这个驱动程序打成jar文件,可以直接导入到项目中。

  • JDBC常见接口和类
  1. DriverManager:驱动管理类,管理各种不同的驱动程序
  2. Connection接口:连接对象
  3. Statement接口:执行SQL语句
  4. ResultSet接口:仅应用在查询操作中,保存查询的结果集

四个API都位于java.sql包。

四.JDBC快速入门

(一)下载JDBC驱动

下载地址:MySQL :: Download Connector/J

  • JDBC连接数据步骤
  1. 准备工作:创建项目并导入jar包
  1. 在项目的根目录下创建一个文件夹Folder,取名叫lib

  1. 将下载好的JDBC驱动jar包赋值粘贴到lib文件夹中
  2. 构建jar包的项目路径

  1. 编写代码

步骤:

  1. 注册驱动
  2. 创建连接对象
  3. 创建SQL编译器
  4. 执行sql语句并返回结果
  5. 释放资源

  1. 加载 JDBC驱动
Class.forName("com.mysql.jdbc.Driver");

  1. 创建连接对象                    
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/studb", "root", "root");

参数说明:

url:jdbc:mysql://localhost:3306/studb

localhost:表示本地服务器

3306:数据库默认端口号

studb:数据库名

username:root 表示连接数据库的用户名

password:root 表示 连接数据库的密码

  1. 创建SQL执行平台
stmt = conn.createStatement();

  1. 执行SQL语句并返回结果

int row = stmt.executeUpdate(sql);

  1. 资源释放:注意顺序问题,先创建的后关
try {

    if(stmt != null){

        stmt.close();

    }

    if(conn != null){

        conn.close();

    }

} catch (SQLException e) {

    e.printStackTrace();

}

  1. CRUD操作

添加操作:

package com.ujiuye.jdbc;

  

  import java.sql.Connection;

  import java.sql.DriverManager;

  import java.sql.SQLException;

  import java.sql.Statement;

  

  public class JDBCTest {

    /*(1)注册驱动

    (2)创建连接对象

    (3)创建SQL编译器

    (4)执行sql语句并返回结果

    (5)释放资源*/

    public static void main(String[] args) {

        Connection conn = null;

        Statement stmt = null;

        String sql = "INSERT INTO clazz (cid,cname)VALUES(7,'T07')";

        try {

            //1.注册驱动

            Class.forName("com.mysql.jdbc.Driver");

            //2.创建连接对象

            /*

            url:连接字符串jdbc:mysql://localhost:3306/studb

                localhost 数据库服务器的主机名 远程连接写ip地址

                3306:mysql数据库的默认端口

                studb:要连接的数据库名

            user:用户名

            password:密码

             */

            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/studb", "root", "root");

            System.out.println(conn);

            //3.创建SQL编译器Statement

            stmt = conn.createStatement();

            //4.执行sql语句并返回结果

            int row = stmt.executeUpdate(sql);

            System.out.println(row);

        } catch (Exception e) {

            e.printStackTrace();

        }finally {

            //5.释放资源(stmt,conn) 顺序,先创建的后关闭

            try {

                if(stmt!=null){

                    stmt.close();

                }

                if(conn!=null) {

                    conn.close();

                }

            } catch (SQLException e) {

                e.printStackTrace();

            }

        }

    }

}

修改操作:

@Test

  public void  update_test(){

    String sql = "UPDATE clazz SET cname = 'T08' WHERE cid = 8";

    Connection conn = null;

    Statement stmt = null;

    try {

        //1.注册驱动

        Class.forName("com.mysql.jdbc.Driver");

        //2.创建连接

        conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/studb", "root", "root");

        //3.创建执行SQL语句的命令平台

        stmt = conn.createStatement();

        //4.执行SQL语句

        int row = stmt.executeUpdate(sql);

        System.out.println(row);

    } catch (Exception e) {

        e.printStackTrace();

    }finally {

        try {

            if (stmt != null){

                stmt.close();

            }

            if (conn != null) {

                conn.close();

            }

        } catch (SQLException e) {

            e.printStackTrace();

        }

    }

  

}

删除操作:

@Test

  public void  delete_test(){

    String sql = "delete from clazz where cid = 8";

    Connection conn = null;

    Statement stmt = null;

    try {

        //1.注册驱动

        Class.forName("com.mysql.jdbc.Driver");

        //2.创建连接

        conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/studb", "root", "root");

        //3.创建执行SQL语句的命令平台

        stmt = conn.createStatement();

        //4.执行SQL语句

        int row = stmt.executeUpdate(sql);

        System.out.println(row);

    } catch (Exception e) {

        e.printStackTrace();

    }finally {

        try {

            if (stmt != null){

                stmt.close();

            }

            if (conn != null) {

                conn.close();

            }

        } catch (SQLException e) {

            e.printStackTrace();

        }

    }

}

查询操作:

object getXxx(count) 1开始 顺序号

object getXxx(“colname”) 列名

@Test

  public void select_test(){

    String sql = "SELECT *FROM clazz";

    Connection conn = null;

    Statement stmt = null;

    ResultSet rs = null;

    try {

        //1.注册驱动

        Class.forName("com.mysql.jdbc.Driver");

        //2.创建连接

        conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/studb", "root", "root");

        //3.创建执行SQL语句的命令平台

        stmt = conn.createStatement();

        //4.执行SQL语句

        rs = stmt.executeQuery(sql);

        //循环读取rs中数据

        while (rs.next()){

            int cid = rs.getInt(1);//使用顺序号,从1开始

            String cname = rs.getString("cname");//列名

            System.out.println(cid+"-"+cname);

        }

    } catch (Exception e) {

        e.printStackTrace();

    }finally {

        try {

            if(rs!=null){

                rs.close();

            }

            if (stmt != null){

                stmt.close();

            }

            if (conn != null) {

                conn.close();

            }

        } catch (SQLException e) {

            e.printStackTrace();

        }

    }

}

五.单元测试

(一)概述

JUnit是一个Java语言的单元测试框架。是程序员进行白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。Junit是一套框架,继承TestCase类,就可以用Junit进行自动测试了。

(二)JUnit的使用

单元测试方法的特点:

  1.方法必须用public修饰

  2.方法不能有返回值,只是void

  3.方法不能有参数

  4.必须使用@Test注解修饰

案例:

  1. 创建一个测试类,在类中创建一个普通方法,该方法上必须使用@Test修饰
  2. 导入JUnit4.Xjar包(需要连网环境下)

  1. 测试运行,想运行哪个测试方法就在哪个方法前面单击运行,如查想运行所有测试方法,在类上面单击运行即可。

如果想在测试方法是使用scanner接收数据,必须配置以下信息:

通过这里打开配置文件,在该文件最底部添加如下代码:

-Deditable.java.test.console=true

配置好后需要重新启动一下idea才能生效。

六.SQL注入

  • 登录功能SQL注入问题

此时,我们分析一下登录功能的SQL语句,根据账号和密码进行条件查询。

package com.ujiuye.jdbc;

import org.junit.Test;

import java.sql.*;

import java.util.Scanner;

public class LoginTest {

    @Test

    public void  login(){

        //接收用户名和密码

        Connection connection = null;

        Statement statement = null;

        Scanner input = new Scanner(System.in);

        System.out.print("请输入用户名:");

        String username = input.next();

        System.out.print("请输入密码:");

        String password = input.next();

        String sql = "SELECT * FROM admin WHERE username = '"+username+"' AND PASSWORD = '"+password+"'";

        try {

            //加载 JDBC驱动

            Class.forName("com.mysql.jdbc.Driver");

            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/myschool", "root", "root");

            statement = connection.createStatement();

            ResultSet resultSet = statement.executeQuery(sql);

            if (resultSet.next()){

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

            }else{

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

            }

        } catch (Exception e) {

            e.printStackTrace();

        }finally {

            //5.关闭资源

            try {

                if(statement != null){

                    statement.close();

                }

                if(connection != null){

                    connection.close();

                }

            } catch (SQLException e) {

                e.printStackTrace();

            }

        }

    }

}

效果演示:

产生问题的原因:SQL注入

SELECT * FROM admin WHERE username ='admin'  AND PASSWORD = 'abc'OR'1=1'

(二)PreparedStatement 解决SQL注入问题

PreparedStatement是位于java.sql包中的接口,是Statement接口的子接口。PreparedStatement对象会将SQL语句进行预编译,通过?占位符的方式进行SQL语句参数的拼接,这样就避免将字符中的内容当成SQL关键字参与编译执行,从而解决SQL注入问题。

@Test

    public void login2() {

        //接收用户名和密码

        Connection connection = null;

        PreparedStatement pstmt = null;

        Scanner input = new Scanner(System.in);

        System.out.print("请输入用户名:");

        String username = input.next();

        System.out.print("请输入密码:");

        String password = input.next();

        String sql = "SELECT * FROM admin WHERE username = ? AND PASSWORD = ?";

        try {

            //加载 JDBC驱动

            Class.forName("com.mysql.jdbc.Driver");

            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/myschool", "root", "root");

            pstmt = connection.prepareStatement(sql);

            //给参数赋值

            pstmt.setString(1, username);

            pstmt.setString(2, password);

            ResultSet resultSet = pstmt.executeQuery();

            if (resultSet.next()) {

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

            } else {

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

            }

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            //5.关闭资源

            try {

                if (pstmt != null) {

                    pstmt.close();

                }

                if (connection != null) {

                    connection.close();

                }

            } catch (SQLException e) {

                e.printStackTrace();

            }

        }

    }

StatementPreparedStatement的异同:

相同点:都可以执行SQL语句,都是接口

不同点:

  1. preparedStatement接口是statement接口子接口。
  2. preparedStatement预编译的sql平台,SQL语句是提前编译好的,当通过setXXX()方法给sql语句中的占位符?赋值时,不会进行重新编译,所以不会出现SQL注入。
  3. preparedStatement安全性更高。
  4. preparedStatement效率高于statement

七.事务

JDBC中管理事务的对象是Connection,必须要保证连接对象是可用状态,即保持连接状态,不能关闭。

  1. 开启事务:conn.setAutoCommit(false/true);如果false,表示是手动事务,如果true表示自动事务,默认值是true,表示是自动事务。这里需要设置为false
  2. conn.commit();提交事务
  3. conn.rollback();回滚事务

案例:

package com.ujiuye.jdbc;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.PreparedStatement;

import java.sql.SQLException;

public class TranTest {

    public static void main(String[] args) {

        Connection conn = null;

        PreparedStatement pstmt = null;

        try {

            Class.forName("com.mysql.jdbc.Driver");

            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/myschool", "root", "root");

            //开启事务,将参数设置为false表示手动提交事务

            conn.setAutoCommit(false);

            pstmt = conn.prepareStatement("UPDATE account SET balance = balance-100 WHERE username='张三'");

            int row1 = pstmt.executeUpdate();

            pstmt = conn.prepareStatement("UPDATE account SET balance = balance+100 WHERE username='李四'");

            int row2 = pstmt.executeUpdate();

            if (row1>0&&row2>0){

                System.out.println("转账成功,提交事务");

                //提交事务

                conn.commit();

            }

        } catch (Exception e) {

            try {

                //回滚事务

                conn.rollback();

                System.out.println("转账失败,回滚事务");

            } catch (SQLException e1) {

                e1.printStackTrace();

            }

        }finally {

            //5.关闭资源

            try {

                if (pstmt != null) {

                    pstmt.close();

                }

                if (conn != null) {

                    conn.close();

                }

            } catch (SQLException e) {

                e.printStackTrace();

            }

        }

    }

}

效果:

八.批处理

JDBC操作数据库的时候,需要一次性插入大量的数据的时候,如果每次只执行一条SQL语句,效率可能会比较低。这时可以使用batch操作,每次批量执行SQL语句,调高效率。

语法:

1)添加批处理: pstmt.addBatch();     

2)执行批处理: pstmt.executeBatch();

3)删除批处理: pstmt.clearBatch();

package com.ujiuye.jdbc;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.PreparedStatement;

import java.sql.SQLException;

  

public class BachTest {

    public static void main(String[] args) {

        Connection conn = null;

        PreparedStatement pstmt = null;

        String sql = "INSERT INTO admin (username,PASSWORD)VALUES(?,?)";

        try {

            //1.加载 驱动

            Class.forName("com.mysql.jdbc.Driver");

            //2.创建连接

            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/studb", "root", "root");

            //3.PreparedStatement

            pstmt = conn.prepareStatement(sql);

            for(int i =0 ;i<10 ;i++){

                pstmt.setString(1,"zhangsan"+i);

                pstmt.setString(2,"000"+i);

                //添加批处理

                pstmt.addBatch();

            }

            //执行批处理

            int[] ints = pstmt.executeBatch();

            for (int anInt : ints) {

                System.out.println(anInt);

            }

            //删除批处理

            pstmt.clearBatch();

        } catch (Exception e) {

            e.printStackTrace();

        }finally {

            try {

                if (pstmt != null){

                    pstmt.close();

                }

                if (conn != null) {

                    conn.close();

                }

            } catch (SQLException e) {

                e.printStackTrace();

            }

        }

  

    }

}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值