1、Junit单元测试
1.1、测试概述
测试分类:
- 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
- 白盒测试:需要写代码的。关注程序具体的执行流程
1.2、Junit使用:白盒测试
- 步骤:
- 定义一个测试类(测试用例)
- 建议:
- 测试类名:被测试的类名Test CalculatorTest
- 包名:xxx.xxx.xx.test cn.itcast.test
- 建议:
- 定义测试方法:可以独立运行
- 建议:
- 方法名:test测试的方法名 testAdd()
- 返回值:void
- 参数列表:空参
- 建议:
- 给方法加@Test
- 导入junit依赖环境
- 定义一个测试类(测试用例)
package com.xh.test;
import org.junit.Test;
public class AddTest {
@Test
public void Test(){
System.out.println("我被执行了");
}
}
- 结果判定
- 红色:失败
- 绿色:成功
- 一般我们会使用断言操作来处理结果
- Assert.assertEquals(期望的结果,运算的结果)
- 补充:
- @Before:
- 修饰的方法会在测试方法之前被自定执行
- @After:
- 修饰的方法会在测试方法执行之后自动被执行
- @Before:
2、反射
2.1、反射:框架设计的灵魂
-
框架:半成品软件。可以在框架的基础上进行软件,简化编码
-
反射:将类的各个组成部分封装为其他对象,这就是反射机制
- 好处:
- 可以在程序运行过程中,操作这些对象。
- 可以解藕,提高程序的可扩展性。
- 好处:
2.2、获取class对象的方式
- Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象
- 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
- 类名.class:通过类名的属性class获取
- 多用于参数的传递
- 对象.getClass():getClass()方法在Object类中定义着
- 多用于对象的获取字节码的方式
-
结论:
同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
2.3、Class对象功能
-
获取功能:
-
获取成员变量们
-
Field[] getFields()
:获取所有public修饰的成员变量 -
Field getField(String name)
:获取指定名称的 public修饰的成员变量 -
Field[] getDeclaredFields()
:获取所有的成员变量,不考虑修饰符 -
Field getDeclaredField(String name)
:获取指定名称的成员变量,不考虑修饰符package com.xh.reflect; import java.lang.reflect.Field; /* Class对象功能 获取功能 获取成员变量们 `Field[] getFields()`:获取所有public修饰的成员变量 `Field[] getField(String name)`:获取指定名称的 public修饰的成员变量 `Field[] getDeclaredFields()`:获取所有的成员变量,不考虑修饰符 `Field[] getDeclaredField(String name)`:获取指定名称的成员变量,不考虑修饰符 */ public class reflectDome { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { //获取person的class对象 Class<Person> personClass = Person.class; //获取成员变量Field[] getFields():获取所有public修饰的成员变量 Field[] fields = personClass.getFields(); for (Field fd:fields){ System.out.println(fd); } System.out.println("-------------"); //Field[] getField(String name)`:获取指定名称的 public修饰的成员变量 Field a = personClass.getField("a"); System.out.println(a); System.out.println("-------------"); //Field[] getDeclaredFields()`:获取所有的成员变量,不考虑修饰符 Field[] declaredFields = personClass.getDeclaredFields(); for (Field ff : declaredFields){ System.out.println(ff); } System.out.println("-------------"); //Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符 Field name = personClass.getDeclaredField("name"); System.out.println(name); System.out.println("-----------"); } }
-
-
获取构造方法们
-
Constructor<?>[] getConstructors()
:获取所有public修饰的构造方法 -
Constructor<?> getConstructor(类<?>... parameterTypes)
:获取指定参数(可变参数可以传也可以不传)public修饰的有参/无参构造方法 -
Constructor<?>[] getDeclaredConstructors()
:获取所有的构造方法,不考虑修饰符 -
Constructor<?> getDeclaredConstructor(类<?>... parameterTypes)
:获取指定参数(可变参数可以传也可以不传)的有参/无参构造方法,不考虑修饰符package com.xh.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; /* 获取构造方法们 `Constructor<?>[] getConstructors()`:获取所有public修饰的构造方法 `Constructor<?> getConstructor(类<?>... parameterTypes)`: 获取指定参数(可变参数可以传也可以不传)public修饰的有参/无参构造方法 `Constructor<?>[] getDeclaredConstructors()`:获取所有的构造方法,不考虑修饰符 `Constructor<?> getDeclaredConstructor(类<?>... parameterTypes)`: 获取指定参数(可变参数可以传也可以不传)的有参/无参构造方法,不考虑修饰符 */ public class reflectDome2 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException { //获取person的class对象 Class<Person> personClass = Person.class; //`Constructor<?>[] getConstructors()`:获取所有public修饰的构造方法 Constructor<?>[] constructors = personClass.getConstructors(); for (Constructor constructor : constructors){ System.out.println(constructor); } System.out.println("------------"); //Constructor<?> getConstructor(类<?>... parameterTypes)`: //获取指定参数(可变参数可以传也可以不传)public修饰的有参/无参构造方法 //Constructor<?> constructors1 = personClass.getConstructor(String.class, int.class); Constructor<?> constructors2 = personClass.getConstructor(); //System.out.println(constructors1); System.out.println(constructors2); System.out.println("------------"); //`Constructor<?>[] getDeclaredConstructors()`:获取所有的构造方法,不考虑修饰符 Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors(); for (Constructor constructor : declaredConstructors){ System.out.println(constructor); } System.out.println("-----------"); //`Constructor<?> getDeclaredConstructor(类<?>... parameterTypes)`: //获取指定参数(可变参数可以传也可以不传)的有参/无参构造方法,不考虑修饰符 Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(); Constructor<Person> declaredConstructor2 = personClass.getDeclaredConstructor(String.class,int.class); System.out.println(declaredConstructor); System.out.println(declaredConstructor2); System.out.println("----------"); } }
-
-
获取成员方法们
-
Method[] getMethods()
:获取所有被public修饰的方法(包含父类中的方法) -
Method getMethods(String name,类<?>... parameterTypes)
:获取指定名称被public修饰的方法可以根据方法选择传参或不传参 -
Method[] getDeclaredMethods()
:获取所有的方法,不考虑修饰符(不包含父类中的方法) -
Method getDeclaredMethods(String name,类<?>... parameterTypes)
:获取指定名称的方法可以根据方法选择传参或不传参,不考虑修饰符package com.xh.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /* 获取成员方法们 `Method[] getMethods()`:获取所有被public修饰的方法(包含父类中的方法) `Method getMethods(String name,类<?>... parameterTypes)`: 获取指定名称被public修饰的方法可以根据方法选择传参或不传参 `Method[] getDeclaredMethods()`:获取所有的方法,不考虑修饰符(不包含父类中的方法) `Method getDeclaredMethod(String name,类<?>... parameterTypes)`: 获取指定名称的方法可以根据方法选择传参或不传参,不考虑修饰符 */ public class reflectDome3 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException { //获取person的class对象 Class<Person> personClass = Person.class; //Method[] getMethods()`:获取所有被public修饰的方法(包含父类中的方法) Method[] methods = personClass.getMethods(); for (Method method:methods){ System.out.println(method); } System.out.println("------------"); //Method getMethods(String name,类<?>... parameterTypes)`: //获取指定名称被public修饰的方法可以根据方法选择传参或不传参 Method eat = personClass.getMethod("eat"); Method eat2 = personClass.getMethod("eat",String.class); System.out.println(eat); System.out.println(eat2); System.out.println("----------"); //`Method[] getDeclaredMethods()`:获取所有的方法,不考虑修饰符(包含父类中的方法) Method[] declaredMethods = personClass.getDeclaredMethods(); for (Method method:declaredMethods){ System.out.println(method); } System.out.println("---------------"); //`Method getDeclaredMethod(String name,类<?>... parameterTypes)`: //获取指定名称的方法可以根据方法选择传参或不传参,不考虑修饰符 Method happy = personClass.getDeclaredMethod("happy"); Method happy1 = personClass.getDeclaredMethod("happy",String.class); System.out.println(happy); System.out.println(happy1); } }
-
-
获取类名
-
String getName()
:全包名//获取person的class对象 Class<Person> personClass = Person.class; //获取类名 //String getName() String name = personClass.getName(); System.out.println(name);//com.xh.reflect.Person
-
-
-
Field:成员变量
-
操作:
-
给成员变量设置值
void set(Object obj,Object value)
-
获取成员变量的值
get(Object obj)
-
忽略访问权限修饰符的安全检查
-
setAccessible(true)
:暴力反射package com.xh.reflect; import java.lang.reflect.Field; /* Field:成员变量 给成员变量设置值: void set(Object obj,Object value) 获取成员变量的值: get(Object obj) 忽略访问权限修饰符的安全检查 setAccessible(true):暴力反射 */ public class reflectDome { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { //获取person的class对象 Class<Person> personClass = Person.class; // 给成员变量设置值: // void set(Object obj,Object value) //创建需要赋值的对象 Person person = new Person(); //获取成员变量的对象Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符 //IllegalAccessException:表示没有访问权限的异常 // 需要使用暴力反射才能设置值\获取值 Field name1 = personClass.getDeclaredField("name"); name1.setAccessible(true); name1.set(person,"出家里"); System.out.println(person); System.out.println("------------"); //获取指定对象中的指定属性的值 Object o = name1.get(person); System.out.println(o); } }
-
-
-
-
Constructor:构造方法
-
创建对象:
-
T newInstance(Object… initargs)
-
如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法
package com.xh.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; /* Constructor:构造方法 创建对象 T newInstance(Object... initargs) 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法 */ public class reflectDome2 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException { //获取person的class对象 Class<Person> personClass = Person.class; // Constructor:构造方法 // 创建对象 // T newInstance(Object... initargs) //`Constructor<?> getDeclaredConstructor(类<?>... parameterTypes)`: //获取指定参数(可变参数可以传也可以不传)的有参/无参构造方法,不考虑修饰符 Constructor<Person> declaredConstructor3 = personClass.getDeclaredConstructor(String.class,int.class); //暴力反射 declaredConstructor3.setAccessible(true); Person person = declaredConstructor3.newInstance("zhangsna",18); System.out.println(person); System.out.println("--------------"); //如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法 Person person1 = personClass.newInstance(); System.out.println(person1); } }
-
-
-
Method:方法对象
-
执行方法:
object invoke(Object obj,Object... args)
-
获取方法名称:
-
String getName
:获取方法名package com.xh.reflect; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /* Method:方法对象 执行方法: object invoke(Object obj,Object... args) 获取方法名称: String getName:获取方法名 */ public class reflectDome3 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException { //获取person的class对象 Class<Person> personClass = Person.class; //`Method getDeclaredMethod(String name,类<?>... parameterTypes)`: //获取指定名称的方法可以根据方法选择传参或不传参,不考虑修饰符 Method happy = personClass.getDeclaredMethod("happy"); Method happy1 = personClass.getDeclaredMethod("happy",String.class); //创建一个真实对象 Person person = new Person(); happy.setAccessible(true); happy.invoke(person); //String getName:获取方法名 String name1 = happy.getName(); System.out.println(name1); System.out.println("-----------"); happy1.setAccessible(true); happy1.invoke(person,"足球"); String name2 = happy1.getName(); System.out.println(name2); } }
-
-
-
案例:
-
需求:写一个”框架“,不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法
-
实现:
- 配置文件
- 反射
-
步骤:
-
将需要创建的对象的全类名和需要执行的方法定义在配置文件中
-
在程序中加载读取配置文件
-
使用反射技术来加载类文件进内存
-
创建对象
-
执行方法
className=com.xh.reflect.Person methodName=eat
package com.xh.reflect; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Properties; /* 需求:写一个”框架“,不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法 实现: 1. 配置文件 2. 反射 步骤: 1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中 2. 在程序中加载读取配置文件 3. 使用反射技术来加载类文件进内存 4. 创建对象 5. 执行方法 */ public class reflectDome4 { public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { //在程序中加载读取配置文件 Properties properties = new Properties(); //创建类加载器对象获取文件的位置或者字节输出流 ClassLoader classLoader = reflectDome4.class.getClassLoader(); InputStream is = classLoader.getResourceAsStream("pro.properties"); //通过Properties对象中的方法读取配置文件 properties.load(is); //获取Properties对象中读取文件的数据 String className = properties.getProperty("className"); String methodName = properties.getProperty("methodName"); //通过读取配置文件中的className数据获取class对象 Class aClass = Class.forName(className); //通过class对象获取真实的对象 Object o = aClass.newInstance(); //通过class对象获取要执行的方法 Method method = aClass.getMethod(methodName); //执行方法 method.invoke(o); } }
-
-
-
3、注解
3.1、注解概述
- 概念:说明程序的。给计算机看的
- 注释:用文字描述程序的。给程序员看的
- 定义:注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
- 概念描述:
- JDK1.5之后的新特性
- 说明程序的
- 使用注解:@注解名称
- 作用分类:
- 编写文档:通过代码里标识的注解生成文档【生成文档doc文档】 可以使用cmd中的javadoc命令根据java文件中的注解生成文档
- 代码分析:通过代码里标识的注解对代码进行分析【使用反射】
- 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【Override】
3.2、JDK内置注解
@Override
:检测被该注解标注的方法是否是继承自父类(接口)的@Deprecated
:该注解标注的内容,表示已过时@SuppressWarnings
:压制警告- 一般传递参数all
@SuppressWarnings("all")
- 一般传递参数all
3.3、自定义注解
-
格式:
元注解
public @interface 注解名称{}
-
本质:注解本质上就是接口,该接口默认继承Annotation接口
public interface MyAnno extends java.lang.annotation.Annotation{}
-
属性:接口中的抽象方法
- 要求:
- 属性的返回值类型有下列取值
- 基本数据类型
- String
- 枚举
- 注解
- 以上类型的数组
- 定义了属性,在使用时需要给属性赋值
- 如果定义属性时,使用
default
关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值 - 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可。
- 枚举赋值时,值使用
枚举类.属性名
- 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}省略
- 如果定义属性时,使用
- 属性的返回值类型有下列取值
- 要求:
-
元注解:用于描述注解的注解
@Target
:描述注解能够作用的位置ElementType
取值:TYPE
:可以作用于类上METHOD
:可以作用于方法上FIELD
:可以作用于成员变量上
@Retention
:描述注解被保留的阶段@Retention(RetentionPolicy.RUNTIME)
:当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
@Documented
:描述注解是否被抽取到api文档中@Inherited
:描述注解是否被子类继承
3.4、解析注解
-
在程序使用(解析)注解:获取注解中定义的属性值
-
获取注解定义的位置的对象 (Class,Method,Field)
-
获取指定的注解
-
getAnntation(Class)
//其实就是在内存中生成一个该注解接口的子类实现对象 public class ProImpl implements Pro{ public String className(){ return "cn.itcast.annotation.Demo1"; } public String methodName(){ return "show"; } }
-
-
调用注解中的抽象方法获取配置的属性值
-
3.5、小结
- 以后大多数时候,我们会使用注解,而不是自定义注解
- 注解给谁用?
- 编译器
- 给解析程序用
- 注解不是程序的一部分,可以理解为注解就是一个标签
4、数据库
4.1、数据库的基本概念
-
数据库的英文单词:DataBase 简称 :DB
-
什么是数据库?
- 用于存储和管理数据的仓库。
-
数据库的特点:
- 持久化存储数据的。其实数据库就是一个文件系统
- 方便存储和管理数据
- 使用了统一的方式操作数据库 – SQL
-
常见的数据库
MySQL
:开源免费的数据库,小型的数据库,已经被Oracle收购了MySQL6.x版本也开始收费。Oracle
:收费的大型数据库,Oracle产品。Oracle收购SUN公司,收购MySQLDB2
:IBM公司的数据库产品,收费的。常应用在银行系统中SQLServer
:MicroSoft公司收费的中型的数据库。C#、.net等语言常使用ByBase
:已经淡出历史舞台。提供了一个非常专业数据建模的工具PowerDesigner。SQLite
:嵌入式的小型数据库,应用在手机端。- 常用数据库:
MySQL
,Oracle
在web应用中,使用的最多的就是MySQL数据库,原因如下:
- 开源、免费
- 功能足够强大,足以应付web应用开发(最高支持千万级别的并发访问)
4.2、MySQL数据库软件
4.2.1、安装
- 打开下载的 mysql 安装文件双击解压缩,运行“mysql-5.5.40-win32.msi”。
-
选择安装类型,有“Typical(默认)”、“Complete(完全)”、“Custom(用户自定义)”三个选项,选择“Custom”,
按“next”键继续。 -
点选“Browse”,手动指定安装目录。
-
填上安装目录,我的是“d:\Program Files (x86)\MySQL\MySQL Server 5.0”,按“OK”继续。
-
确认一下先前的设置,如果有误,按“Back”返回重做。按“Install”开始安装。
-
正在安装中,请稍候,直到出现下面的界面, 则完成 MYSQL 的安装
数据库安装好了还需要对数据库进行配置才能使用 MYSQL 的配置
-
安装完成了,出现如下界面将进入 mysql 配置向导。
-
选择配置方式,“Detailed Configuration(手动精确配置)”、“Standard Configuration(标准配置)”,我
们选择“Detailed Configuration”,方便熟悉配置过程 -
选择服务器类型,“Developer Machine(开发测试类,mysql 占用很少资源)”、“Server Machine(服务
器类型,mysql 占用较多资源)”、“Dedicated MySQL Server Machine(专门的数据库服务器,mysql 占
用所有可用资源)” -
选择 mysql 数据库的大致用途,“Multifunctional Database(通用多功能型,好)”、“Transactional
Database Only(服务器类型,专注于事务处理,一般)”、“Non-Transactional Database Only(非事务
处理型,较简单,主要做一些监控、记数用,对 MyISAM 数据类型的支持仅限于 non-transactional),按“Next”
继续。
-
选择网站并发连接数,同时连接的数目,“Decision Support(DSS)/OLAP(20个左右)”、“Online Transaction
Processing(OLTP)(500 个左右)”、“Manual Setting(手动设置,自己输一个数)” -
是否启用 TCP/IP 连接,设定端口,如果不启用,就只能在自己的机器上访问 mysql 数据库了,在这个页
面上,您还可以选择“启用标准模式”(Enable Strict Mode),这样 MySQL 就不会允许细小的语法错误。
如果是新手,建议您取消标准模式以减少麻烦。但熟悉 MySQL 以后,尽量使用标准模式,因为它可以降
低有害数据进入数据库的可能性。按“Next”继续 -
就是对 mysql 默认数据库语言编码进行设置(重要),一般选 UTF-8,按 “Next”继续
-
选择是否将 mysql 安装为 windows 服务,还可以指定 Service Name(服务标识名称),是否将 mysql 的 bin
目录加入到 Windows PATH(加入后,就可以直接使用 bin 下的文件,而不用指出目录名,比如连接,
“mysql.exe -uusername -ppassword;”就可以了,不用指出 mysql.exe 的完整地址,很方便),我这里全部
打上了勾,Service Name 不变。按“Next”继续。 -
询问是否要修改默认 root 用户(超级管理)的密码。“Enable root access from remote machines(是否允
许 root 用户在其它的机器上登陆,如果要安全,就不要勾上,如果要方便,就勾上它)”。最后“Create
An Anonymous Account(新建一个匿名用户,匿名用户可以连接数据库,不能操作数据,包括查询)”,
一般就不用勾了,设置完毕,按“Next”继续。 -
确认设置无误,按“Execute”使设置生效,即完成 MYSQL 的安装和配置
-
注意:设置完毕,按“Finish”后有一个比较常见的错误,就是不能“Start service”,一般出现在以前有安装 mysql
的服务器上,解决的办法,先保证以前安装的 mysql 服务器彻底卸载掉了;不行的话,检查是否按上面一步所说,
之前的密码是否有修改,照上面的操作;如果依然不行,将 mysql 安装目录下的 data 文件夹备份,然后删除,在
安装完成后,将安装生成的 data 文件夹删除,备份的 data 文件夹移回来,再重启 mysql 服务就可以了,这种情况
下,可能需要将数据库检查一下,然后修复一次,防止数据出错。
解决办法:卸载MySQL重装MySQL
4.2.2、卸载
-
停止 window 的 MySQL 服务。 找到“控制面板”-> “管理工具”-> “服务”,停止 MySQL 后台服务。
-
卸载 MySQL 安装程序。找到“控制面板”-> “程序和功能”,卸载 MySQL 程序。
-
删除 MySQL 安装目录下的所有文件。
-
删除 c 盘 ProgramDate 目录中关于 MySQL 的目录。路径为:C:\ProgramData\MySQL(是隐藏文件,需要显示
出来)
4.2.3、配置
- MySQL服务启动
- 手动
- cmd–> services.msc 打开服务的窗口
- 使用管理员打开cmd
net start mysql
:启动MySQL的服务net stop mysql
:关闭MySQL服务
- MySQL登录
mysql -uroot -p密码
mysql -hip -uroot -p连接目标的密码
mysql --host=ip --user=root --password=连接目标的密码
- MySQL退出
- exit
- quit
- MySQL目录结构
- MySQL安装目录
- 配置文件
my.ini
- 配置文件
- MySQL数据目录
- 几个概念
- 数据库:文件夹
- 表:文件
- 数据
- 几个概念
- MySQL安装目录
4.3、SQL
-
什么是SQL?
Structured Query Language:结构化查询语言
其实就是定义了操作所有关系型数据库的规则。每一种数据库操作的方式存在不一样的地方,称为”方言“
-
SQL通用语法:
- SQL语句可以单行或多行书写,以分号结尾。
- 可使用空格和缩进来增强语句的可读性
- MySQL 数据库的SQL语句不区分大小写,关键字建议使用大写
- 3种注释
- 单行注释:-- 注释内容 或 # 注释内容(MySQL特有)
- 多行注释:/* 注释 */
-
SQL分类
-
DDL(Data Definition Language)数据定义语言
用来定义数据库对象:数据库,表,列等。关键字:create,drop,alter等
-
DML(Data Manipulation Language)数据操作语言
用来对数据库中表的数据进行增删改。关键字:insert,delete,update等
-
DQL(Data Query Language)数据查询语言
用来查询数据库中表的记录(数据)。关键字:select,where等
-
DCL(Data Control Language)数据控制语言(了解)
用来定义数据库的访问权限和安全级别,及创建用户。关键字:GRANT,
REVOKE等
-
4.3.1、DDL:操作数据库、表
-
操作数据库:CRUD
- C(Create):创建
- 创建数据库:
create database 数据库名称;
- 创建数据库,判断不存在,再创建:
create database if not exists 数据库名称;
- 创建数据库,并制定字符集
create database 数据库名称 character set 字符集名;
- 练习:创建db4数据库,判断是否存在,并制定字符集为gbk
create database if not exists db4 character set gbk;
- 创建数据库:
- R(Retrieve):查询
- 查询所有数据库的名称:
show databases;
- 查询某个数据库的字符集:查询某个数据库的创建语句
show create database 数据库名称;
- 查询所有数据库的名称:
- U(Update):修改
- 修改数据库的字符集
alter database 数据库名称 character set 字符集名称;
- 修改数据库的字符集
- D(Delete):删除
- 删除数据库
drop database 数据库名称;
- 判断数据库存在,存在再删除
drop database if exists 数据库名称;
- 删除数据库
- 使用数据库
- 查询当前正在使用的数据库名称
- select database();
- 使用数据库
- use 数据库名称;
- 查询当前正在使用的数据库名称
- C(Create):创建
-
操作表
-
C(Create):创建
-
语法:
create table 表名{ 列名1 数据类型1, 列名2 数据类型2, .... 列名n 数据类型n };
- 注意:最后一列,不需要加逗号(,)
- 数据库类型:
- int:整数类型
age int
- double:小数类型
score double(5,2)
- data:日期,只包含年月日,yyyy-MM-dd
- datetime:日期,包含年月日时分秒 yyyy-MM-dd HH:mm:ss
- timestamp:时间戳类型 包含年月日时分秒 yyyy-MM-dd HH:mm:ss
- 如果将来不给这个字段赋值,或赋值为null,则默认使用当前的系统时间,来自动赋值
- varchar:字符串
name varchar(20)
:姓名最大20个字符- zhangsan 8个字符 张三 2个字符
- int:整数类型
- 创建表
create table student( id int, name varchar(32), age int, score double(4,1), birthday date, insert_time timestamp )
- 复制表:
create table 表名 like 被复制的表名
-
-
R(Retrieve):查询
- 查询某个数据库中所有的表名称
show tables;
- 查询表结构
desc 表名
- 查询某个数据库中所有的表名称
-
U(Update):修改
-
修改表名
alter table 表名 rename to 新表名;
-
修改表的字符集
alter table 表名 character set 字符集名称;
-
添加一列
alter table 表名 add 列名 数据类型;
-
修改列名称 类型
列名称和列类型全部修改
alter table 表名 change 列名 新列名 新数据类型;
只修改列的类型
alter table 表名 modify 列名 新数据类型;
-
删除列
alter table 表名 drop 列名;
-
-
D(Delete):删除
drop table 表名
drop table if exists 表名
-
4.3.2、DML:增删改表中数据
-
添加数据:
-
语法:
insert into 表名(列名1,列名2,...列名n) values(值1,值2,...值n);
-
注意:
-
列名和值要一一对应。
-
如果表名后,不定义列名,则默认给所有列添加值
insert into 表名 values(值1,值2,...值n);
-
除了数字类型,其他类型需要使用引号(单双都可以)引起来
-
-
-
删除数据
- 语法:
delete from 表名 [where 条件]
- 注意:
- 如果不加条件,则删除表中所有记录
- 如果要删除所有记录
delete from 表名;
– 不推荐使用。有多少条记录就会执行多少次删除操作TRUNCATE TABLE 表名;
– 推荐使用,效率更高,先删除表,然后再创建一张一样的表。
- 语法:
-
修改数据
- 语法:
update 表名 set 列名1 = 值1, 列名2 = 值2,...[where 条件]
- 注意:
- 如果不加任何条件,则会将表中所有记录全部修改
- 语法:
4.3.3、DQL:查询表中的记录
-
select * from 表名;
- 语法:
`select`
` 字段列表`
`from`
` 表名列表`
` where`
` 条件列表`
` group by`
` 分组字段`
` having`
` 分组之后的条件`
` order by`
` 排序`
` limit`
` 分页限定`
-
基础查询
-
多个字段的查询
select 字段名1,字段名2... from 表名;
- 注意:
- 如果查询所有字段,则使用*来替代字段列表。
- 注意:
-
去除重复
- distinct
-
计算列
- 一般可以使用四则运算计算一些列的值。(一般只会进行数值型的计算)
- ifnull(表达式1,表达式2):null参与的运算,计算结果都为null
- 表达式1:哪个字段需要判断是否为null
- 如果该字段为null后的替换值
-
起别名:
- as:as也可以省略
-
-
条件查询
-
where 子句后跟条件
-
运算符
-
>、<、<=、>=、=、<>
-
BETWEEN...AND
-
IN(集合)
-
LIKE
:模糊查询- 占位符:
_
:单个任意字符%
:多个任意字符
- 占位符:
-
IS NULL
-
and 或 &&
-
or 或 ||
-
not 或 !
-- 查询年龄等于20岁 select * from student where age = 20; -- 查询年龄不等于20岁 select * from student where age != 20; select * from student where age <> 20; -- 查询年龄大于等于20小于等于30 select * from student where age>=20 && age<=30; select * from student where age>=20 and age<=30; select * from student where age between 20 and 30; -- 查询年龄20岁,18岁,25岁的信息 select * from student where age = 20 or age = 18 or age = 25; select * from student where age in(20,18,25); -- 查询英语成绩为null select * from student where english = null;-- 不对的 -- null值不能使用 = (!=)判断 select * from student where english is null; -- 查询英语成绩不为null select * from student where english is not null; -- 查询姓马的有哪些?like select * from student where name like '马%'; -- 查询姓名第二个字是化的人 select * from student where name like "_化%"; -- 查询名字是3个字的人 select * from student where name like "___"; -- 查询姓名中包含德的人 select * from student where name like "%德%";
-
-
4.3.4、DQL:查询语句
4.3.4.1、排序查询
- 语法:order by子句
- order by 排序字段1 排序方式1, 排序字段2 排序方式2…
- 排序方式:
- ASC:升序,默认的
- DESC:降序
- 注意:
- 如果有多个排序条件,则当前边的条件值一样时,才会判断第二条件
4.3.4.2、聚合函数
-
聚合函数:将一列数据作为一个整体,进行纵向的计算。
- count:计算个数
- 一般选择没有null值的列:主键
- count(*)
- max:计算最大值
- min:计算最小值
- sum:计算和
- avg:计算平均值
-
注意:聚合函数的计算,排除null值。
解决方案:
- 选择没有null值的列进行计算
IFNULL
函数
- count:计算个数
4.3.4.3、分组查询
- 语法:
grop by 分组字段;
- 注意:
- 分组之后查询的字段:分组字段、聚合函数
- where 和 having 的区别?
- where 在分组之前进行限定,如果不满足条件,则不参与分组。having在分组之后进行限定,如果不满足结果,则不会被查询出来
- where后不可以跟聚合函数,having可以进行聚合函数的判断。
4.3.4.4、分页查询
-
语法:limit开始的索引,每页查询的条数;
-
公式:开始的索引 = (当前的页码 - 1) * 每页显示的条数
-- 每页显示3条记录 select * from student limit 0,3; -- 第一页 select * from student limit 3,3; -- 第二页 select * from student limit 6,3; -- 第三页
-
limit 是一个MySQL"方言"
4.4、约束
-
概念:对表中的数据进行限定,保证数据的正确性、有效性和完整性
-
分类:
- 主键约束:
primary key
- 非空约束:
not null
- 唯一约束:
unique
- 外键约束:
foreign key
- 主键约束:
-
非空约束:not null
-
创建表时添加约束
create table stu( id int, name varchar(20) not null -- name为非空 );
-
创建表完后,添加非空约束
alter table stu modify name varchar(20) not null;
-
删除name的非空约束
alter table stu modify name varchar(20);
-
-
唯一约束:unique,某一列的值不能重复
-
注意:
- 唯一约束可以有null值,但是只能有一条记录为null
-
在创建表时,条件唯一约束
create table stu( id int, phone_number varchar(20) unique -- 手机号 );
-
删除唯一约束
alter table stu drop index phone_number;
-
在表创建完后,添加唯一约束
alter table stu modify phone_number varchar(20) unique;
-
-
主键约束:primary key。
-
注意:
- 含义:非空且唯一
- 一张表只能有一个字段为主键
- 主键就是表中记录的唯一标识
-
在创建表时,添加主键约束
create table stu( id int primary key,--给id添加主键约束 name varchar(20) )
-
删除主键
-- 错误 alter table stu modify id int; alter table stu drop primary key;
-
创建完表后,添加主键
alter table stu modify id int primary key
-
自动增长:
-
概念:如果某一列是数值类型的,使用auto_increment可以来完成值得自动增长
-
在创建表时,添加主键约束,并且完成主键自增长
create table stu( id int primary key auto_increment,-- 给id添加主键约束 name varchar(20) )
-
删除自动增长
alter table stu modify id int;
-
添加自动增长
alter table stu modify id int auto_increment;
-
-
-
外键约束:foreign key,让表于表产生关系,从而保证数据的正确性
-
在创建表时,可以添加外键
-
语法:
create table 表名( ... 外键列 constraint 外键名称 foreign key (外键列名称) references 主表名称(主表列名称) )
-
-
删除外键
alter table 表名 drop foreign key 外键名称;
-
创建表之后,添加外键
alter table 表名 add constraint 外键名称 foreign key (外键字段名称) references 主表名称(主表列名称);
-
级联操作
-
添加级联操作
语法:
alter table 表名 add constraint 外键名称 foreign key (外键字段名称) references 主表名称(主表列名称) on update cascade on delete cascade;
-
分类:
- 级联更新:
on update cascade
当主表的主键发生更新时所关联的外键也会发生更新 - 级联删除:
on delete cascade
当主表的主键被删除时所关联的外键也被删除
- 级联更新:
-
-
4.5、数据库的设计
4.5.1、多表之间的关系
-
分类:
-
一对一(了解):
- 如:人和身份证
- 分析:一个人只有一个身份证,一个身份证只能对应一个人
-
一对多(多对一):
-
如:部门和员工
-
分析:一个部门有多个员工,一个员工只能对应一个部门
-
-
多对多:
-
如:学生和课程
-
分析:一个学生可以选择很多门课程,一个课程也可以被很多学生选择
-
-
-
实现关系:
-
一对多(多对一):
-
如:部门和员工
-
实现方式:在多的一方建立外键,指向一的一方的主键。
-
-
多对多:
-
如:学生和课程
-
实现方式:多对多关系实现需要借助第三张中间表。中间表至少包含两个字段,这两个字段作为第三张表的外键,分别指向两张表的主键
-
-
一对一(了解):
-
如:人和身份证
-
实现方式:一对一关系实现,可以在任意一方添加唯一外键指向另一方的主键
-
-
-
案例:
-- 创建旅游线路分类表 tab_category -- cid 旅游线路分类主键,自动增长 -- cname 旅游线路分类名称非空,唯一,字符串 100 create table tab_category ( cid int primary key auto_increment, cname varchar(100) not null unique ) -- 添加旅游线路分类数据: insert into tab_category (cname) values ('周边游'), ('出境游'), ('国内游'), ('港澳游'); select * from tab_category; -- 创建旅游线路表 tab_route /* rid 旅游线路主键,自动增长 rname 旅游线路名称非空,唯一,字符串 100 price 价格 rdate 上架时间,日期类型 cid 外键,所属分类 */ create table tab_route( rid int primary key auto_increment, rname varchar(100) not null unique, price double, rdate date, cid int, foreign key (cid) references tab_category(cid) ) -- 添加旅游线路数据 INSERT INTO tab_route VALUES (NULL, '【厦门+鼓浪屿+南普陀寺+曾厝垵 高铁 3 天 惠贵团】尝味友鸭面线 住 1 晚鼓浪屿', 1499, '2018-01-27', 1), (NULL, '【浪漫桂林 阳朔西街高铁 3 天纯玩 高级团】城徽象鼻山 兴坪漓江 西山公园', 699, '2018-02- 22', 3), (NULL, '【爆款¥1699 秒杀】泰国 曼谷 芭堤雅 金沙岛 杜拉拉水上市场 双飞六天【含送签费 泰风情 广州 往返 特价团】', 1699, '2018-01-27', 2), 23 / 26 (NULL, '【经典•狮航 ¥2399 秒杀】巴厘岛双飞五天 抵玩【广州往返 特价团】', 2399, '2017-12-23', 2), (NULL, '香港迪士尼乐园自由行 2 天【永东跨境巴士广东至迪士尼去程交通+迪士尼一日门票+香港如心海景酒店 暨会议中心标准房 1 晚住宿】', 799, '2018-04-10', 4); select * from tab_route;
/* 创建用户表 tab_user uid 用户主键,自增长 username 用户名长度 100,唯一,非空 password 密码长度 30,非空 name 真实姓名长度 100 birthday 生日 sex 性别,定长字符串 1 telephone 手机号,字符串 11 email 邮箱,字符串长度 100 */ create table tab_user ( uid int primary key auto_increment, username varchar(100) unique not null, password varchar(30) not null, name varchar(100), birthday date, sex char(1) default '男', telephone varchar(11), email varchar(100) ) -- 添加用户数据 INSERT INTO tab_user VALUES (NULL, 'cz110', 123456, '老王', '1977-07-07', '男', '13888888888', '66666@qq.com'), (NULL, 'cz119', 654321, '小王', '1999-09-09', '男', '13999999999', '99999@qq.com'); select * from tab_user; /* 创建收藏表 tab_favorite rid 旅游线路 id,外键 date 收藏时间 uid 用户 id,外键 rid 和 uid 不能重复,设置复合主键,同一个用户不能收藏同一个线路两次 */ create table tab_favorite ( rid int, date datetime, uid int, -- 创建复合主键 primary key(rid,uid), foreign key (rid) references tab_route(rid), foreign key(uid) references tab_user(uid) ) -- 增加收藏表数据 INSERT INTO tab_favorite VALUES (1, '2018-01-01', 1), -- 老王选择厦门 (2, '2018-02-11', 1), -- 老王选择桂林 (3, '2018-03-21', 1), -- 老王选择泰国 (2, '2018-04-21', 2), -- 小王选择桂林 (3, '2018-05-08', 2), -- 小王选择泰国 (5, '2018-06-02', 2); -- 小王选择迪士尼 select * from tab_favorite
4.5.2、数据库设计的范式
-
概念:设计数据库时,需要遵循的一些规范。要遵循后边的范式要求,必须先遵循前边的所有范式要求
设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次和规范,越高的范式数据库冗余越小。
目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称为完美范式)。
-
分类:
-
第一范式(1NF):每一列都是不可分割的原子数据项
-
第二范式(2NF):在1NF的基础上,非码属性必须完成依赖于码(在1NF基础上消除非主属性对主码的部分函数依赖)
-
几个概念:
-
函数依赖:A–>B,如果通过A属性(属性组)的值,可以确定唯一B属性的值。则称B依赖于A
例如:学号–>姓名。 (学号,课程名称) --> 分数
-
完全函数依赖:A–>B,如果A是一个属性组,则B属性值得确定需要依赖于A属性组中所有的属性值
例如:(学号,课程名称) --> 分数
-
部分函数依赖:A–>B,如果A是一个属性组,则B属性值得确定只需要依赖于A属性组中某一些值即可
例如:(学号,课程名称)–>姓名
-
传递函数依赖:A–>B,B–>C 如果通过A属性(属性组)的值,可以确定唯一B属性的值,在通过B属性(属性组)的值可以确定唯一C属性的值,则称C传递函数依赖于A
例如:学号–>系名,系名–>系主任
-
码:如果在一张表中,一个属性或属性组,被其他所有属性所完全依赖,则称这个属性(属性组)为该表的码
例如:该表中码为:(学号,课程名称)
- 主属性:码属性组中的所有属性
- 非主属性:除过码属性组的属性
-
-
-
第三范式(3NF):在2NF基础上,任何非主属性不依赖于其他非属性(在2NF基础消除传递依赖)
-
4.6、数据库的备份和还原
- 命令行:
- 语法:
- 备份:
mysqldump -u用户名 -p密码 数据库名称 > 保存的路径
- 还原:
- 登录数据库
- 创建数据库
- 使用数据库
- 执行文件。 source 文件路径
- 备份:
- 语法:
- 图形化工具:
4.7、多表查询
-
查询语句:
select
列名列表
from
表名列表
where ....
-
准备sql
# 创建部门表 create table dept( id int primary key auto_increment, name varchar(20) ) insert into dept (name) values ('开发部'),('市场部'),('财务部'); # 创建员工表 create table emp ( id int primary key auto_increment, name varchar(10), gender char(1), -- 性别 salary double, -- 工资 join_date date, -- 入职日期 dept_id int, foreign key (dept_id) references dept(id) -- 外键,关联部门表(部门表的主键) ) insert into emp(name,gender,salary,join_date,dept_id) values('孙悟空','男 ',7200,'2013-02-24',1); insert into emp(name,gender,salary,join_date,dept_id) values('猪八戒','男 ',3600,'2010-12-02',2); insert into emp(name,gender,salary,join_date,dept_id) values('唐僧','男',9000,'2008- 08-08',2); insert into emp(name,gender,salary,join_date,dept_id) values('白骨精','女 ',5000,'2015-10-07',3); insert into emp(name,gender,salary,join_date,dept_id) values('蜘蛛精','女 ',4500,'2011-03-14',1)
-
笛卡尔积:
- 有两个集合A,B取这两个集合的所有组成情况
- 要完成多表查询,需要消除无用的数据
-
多表查询的分类:
-
内连接查询:
-
隐式内连接:使用where条件消除无用数据
-- 查询所有员工信息和对应的部门信息 select * from emp,dept where emp.dept_id = dept.id; -- 查询员工的名称,性别。部门表的名称 select emp.name,emp.gender,dept.name from emp,dept where emp.dept_id = dept.id; select t1.name,-- 员工表的姓名 t1.gender,-- 员工表的性别 t2.name -- 部门表的名称 from emp t1, dept t2 where t1.dept_id = t2.id;
-
显式内连接:
语法:select 字段列表 from 表名1 [inner] join 表名2 on 条件 例如: select * from emp inner join dept on emp.dept_id = dept.id; select * from emp join dept on emp.dept_id = dept.id;
-
内连接查询:
- 从哪些表中查询数据
- 条件是什么
- 查询哪些字段
-
-
外连接查询:
-
左外连接:
-
语法:
select 字段列表 from 表1 left [outer] join 表2 on 条件;
-
查询的是左表所有数据以及其交集部分
-- 查询所有员工信息,如果员工有部门,则查询部门名称,没有部门,则不显示部门名称 select t1.*,t2.name from emp t1 left join deft t2 on t1.dept_id = t2.id
-
-
右外连接:
-
语法:
select 字段列表 from 表1 rigth [outer] join 表2 on 条件;
-
查询的是右表所有数据以及其交集部分
select * from dept t2 right join emp t1 on t1.dept_id = t2.id;
-
-
-
子查询:
-
概述:查询中嵌套查询,称嵌套查询为子查询
-- 1 查询最高的工资是多少 9000 select max(salary) from emp; -- 2 查询员工信息,并且工资等于9000的 select * from emp where emp.salary = 9000; -- 写成一条sql就完成这个操作。子查询 select * from emp.salary = (select * from emp where emp.salary = 9000);
-
子查询不同情况
-
子查询的结果是单行单列的:
- 子查询可以作为条件,使用运算符去判断。运算符:
> >= < <= =
-- 查询员工工资下雨平均工资的人 select * from emp where emp.salary < (select avg(salary) from emp);
- 子查询可以作为条件,使用运算符去判断。运算符:
-
子查询的结果是多行单列的:
- 子查询可以作为条件,使用运算符in来判断
-- 查询‘财务部’和‘市场部’所有的员工信息 select id from dept where name in ('财务部','市场部'); select * from emp where dept_id in (2,3); -- 子查询 select * from emp where dept_id in (select id from dept where name in ('财务部','市场部'));
-
子查询的结果是多行多列的:
- 子查询可以作为一张虚拟表参与查询
-- 查询员工入职日期是2011-11-11日之后的员工信息和部门信息 -- 子查询 select * from dept t1,(select * from emp where emp.join_date > '2011-11-11') t2 where t1.id = t2.dept_id; -- 普通内连接 select * from emp t1,dept t2 where t1.dept_id = t2.id and t1.join_date > '2011-11-11'
-
-
-
4.8、事务
4.8.1、事务的基本介绍
-
概念:
-
如果一个包含多个步骤的业务操作,被事务管理,那么这些操作要么同时成功,要么同时失败。
-
-
操作:
- 开启事务:
statrt transaction;
- 回滚:
rollback;
- 提交:
commit;
create table account( id int primary key auto_increment, name varchar(10), balabce double ); -- 添加数据 insert into account (name,balance) values ('zhangsan',1000),('lisi',1000); select * from account; update account set balance = 1000; -- 张三给李四转账500元 -- 0.开启事务 start transaction; -- 1.张三账户 -500 update account set balance = balance - 500 where name = 'zhangsan'; -- 2.李四账户 +500 -- 出错了.... update account set balance = balance + 500 where name = 'lisi'; -- 发现执行没有问题,提交事务 commit; -- 发现出问题了,回滚事务 rollback;
- 开启事务:
-
MySQL数据库中事务默认自动提交
- 事务提交的两种方式:
- 自动提交:
- MySQL就是自动提交的
- 一条DML(增删改)语句会自动提交一次事务
- 手动提交
- Oracle数据库默认是手动提交事务
- 需要先开启事务,再提交
- 自动提交:
- 修改事务的默认提交方式
- 查看事务的默认提交方式:
select @@autocommit; -- 1代表自动提交 0代表手动提交
- 修改默认提交方式:
set @@autocommit = 0;
- 查看事务的默认提交方式:
- 事务提交的两种方式:
4.8.2、事务的四大特征
- 原子性:是不可分割的最小操作单位,要么同时成功,要么同时失败。
- 持久性:当事务提交或回滚后,数据库会持久化的保存数据
- 隔离性:多个事务之间,相互独立
- 一致性:事务操作前后,数据总量不变
4.8.3、事务的隔离级别(了解)
-
概念:多个事务之间隔离的,相互独立的。但是如果多个事务操作同一批数据,则会引发一些问题,设置不同的隔离级别就可以解决这些问题
-
存在问题:
- 脏读:一个事务,读取到另一个事务中没有提交的数据
- 不可重复读(虚读):在同一个事务中,两次读取到的数据不一样
- 幻读:一个事务操作(DML)数据表中所有记录,另一个事务添加了一条数据,则第一个事务查询不到自己的修改
-
隔离级别:
read uncommitted:读未提交
- 产生的问题:脏读、不可重复读、幻读
read committed:读已提交(Oracle)
- 产生的问题:不可重复读、幻读
repeatable read:可重复读(MySQL默认)
- 产生的问题:幻读
serializable:串行化
- 可以解决所有的问题
-
注意:隔离级别从小到大安全性越来越高,但是效率越来越低
-
数据库查询隔离级别:
select @@tx_isolation;
-
数据库设置隔离级别:
set global transaction isolation level 级别字符串;
-
设置事务隔离级别,需要退出 MySQL 再重新登录才能看到隔离级别的变化
-
脏读的演示
将数据进行恢复:UPDATE account SET balance = 1000;
-
打开 A 窗口登录 MySQL,设置全局的隔离级别为最低
mysql -uroot -proot set global transaction isolation level read uncommitted;
-
打开B窗口,AB窗口都开启事务
use day23; start transaction;
-
A窗口更新两个人的账户数据,未提交
update account set balance=balance-500 where id=1; update account set balance=balance+500 where id=2;
-
B窗口查询账户
select * from account;
-
A窗口回滚
rollback;
-
B窗口查询账户,钱没了
脏读非常危险的,比如张三向李四购买商品,张三开启事务,向李四账号转入500块,然后打电话给李四说钱已经转了。李四一查询钱到账了,发货给张三。张三收到货后回滚事务,李四再查看钱没了
解决脏读的问题:将全局的隔离级别进行提高
将数据进行恢复:
UPDATE account SET balance = 1000;
-
在A窗口设置全局的隔离级别为
read committed
set global transaction isolation level read committed;
B窗口退出MySQL,B窗口再进入MySQL
AB窗口有同时开启事务
-
A更新2个人的账户,未提交
update account set balance=balance-500 where id=1; update account set balance=balance+500 where id=2
-
B窗口查询账户
A窗口commit提交事务
-
B窗口查看账户
结论:read committed 的方式可以避免脏读的发生
-
-
不可重复读的演示
将数据进行恢复:UPDATE account SET balance = 1000;
-
开启A窗口
set global transaction isolation level read committed;
-
开启B窗口,在B窗口开启事务
start transaction; select * from account
-
在A窗口开启事务,并更新数据
start transaction; update account set balance=balance+500 where id=1; commit;
-
B窗口查询
select * from account
两次查询输出的结果不同,到底哪次是对的?不知道以哪次为准。很多人认为这种情况就对了,无须困惑,
当然是后面为准。我们可以考虑这样一种情况,比如银行程序需要将查询结果分别输出到电脑屏幕和发短信给客
户,结果在一个事务中针对不同的输出目的地进行的两次查询不一致,导致文件和屏幕中的结果不一致,银行工作
人员就不知道以哪个为准了。
解决不可重复读的问题:
将全局的隔离级别进行提升为:
repeatable read
将数据进行恢复:
UPDATE account SET balance = 1000;
-
A 窗口设置隔离级别为:
repeatable read
set global transaction isolation level repeatable read;
-
B 窗口退出 MySQL,B 窗口再进入 MySQL
start transaction; select * from account;
-
A 窗口更新数据
start transaction; update account set balance=balance+500 where id=1; commit
-
B 窗口查询
select * from account
结论:同一个事务中为了保证多次查询数据一致,必须使用
repeatable read
隔离级别
-
-
幻读的演示
在 MySQL 中无法看到幻读的效果
但我们可以将事务隔离级别设置到最高,以挡住幻读的发生 将数据进行恢复:
UPDATE account SET balance = 1000;
-
开启A窗口
set global transaction isolation level serializable; -- 设置隔离级别为最高
-
A 窗口退出 MySQL,A 窗口重新登录 MySQL
start transaction; select count(*) from account
-
再开启 B 窗口,登录 MySQL
-
在 B 窗口中开启事务,添加一条记录
start transaction; -- 开启事务 insert into account (name,balance) values ('LaoWang', 500);
-
在 A 窗口中 commit 提交事务,B 窗口中 insert 语句会在 A 窗口事务提交后立马运行
-
在 A 窗口中接着查询,发现数据不变
select count(*) from account
-
B 窗口中 commit 提交当前事务
-
A 窗口就能看到最新的数据
结论:使用 serializable 隔离级别,一个事务没有执行完,其他事务的 SQL 执行不了,可以挡住幻读
-
4.9、DCL
-
SQL分类
-
DDL:操作数据库和表
-
DML:增删改表中的数据
-
DQL:查询表中数据
-
DCL:管理用户,授权
-
-
DBA:数据库管理员
-
DCL:管理用户,授权
-
管理用户
-
添加用户:
语法: create user '用户名'@'主机名' identified by '密码';
-
删除用户:
语法:drop user '用户名'@'主机名';
-
修改用户密码:
update user set password = password('新密码') where user = '用户名'; update user set password = password('abc') where user = 'lisi'; set password for '用户名'@'主机名' = password('新密码'); set password for 'root'@'localhost' = password('123');
- mysql中忘记了root用户的密码?
cmd --> net stop mysql 停止mysql服务
- 需要管理员运行该cmd
- 使用无验证方式启动mysql服务
- 打开新的cmd窗口,直接输入mysql命令,敲回车。就可以登录成功
use mysql;
update user set password = password ('你的新密码') where user = 'root';
- 关闭两个窗口
- 打开任何管理器,手动结束
mysqld.exe
的进程 - 启动mysql服务
- 使用新密码登录。
- mysql中忘记了root用户的密码?
-
查询用户:
-- 1.切换到MySQL数据库 use mysql; -- 2.查询user表 select * from user;
- 通配符:% 表示可以在任意主机使用用户登录数据库
-
-
权限管理:
-
查询权限:
-- 查询权限 show grants for '用户名'@'主机名'; show grants for 'lisi'@'%';
-
授予权限:
-- 授予权限 grant 权限列表 on 数据库名.表名 to '用户名'@'主机名'; -- 给张三用户授予所有权限,在任意数据库任意表上 grant all on *.* to 'zhangsan'@'localhost';
-
撤销权限:
-- 撤销权限: revoke 权限列表 on 数据库名.表名 from '用户名'@'主机名'; revoke update on db3.account from 'lisi'@'%';
-
-
5、JDBC
5.1、JDBC基本概念
- 概念:Java DataBase Connectivity Java 数据库连接,Java语言操作数据库
- JDBC本质:其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类
5.2、JDBC快速入门
- 步骤:
- 导入驱动jar包
mysql - connector-java-5.1.37-bin.jar
- 组织
mysql - connector-java-5.1.37-bin.jar
到项目的libs目录下 - 右键–>
Add As Library
- 组织
- 注册驱动
- 获取数据库连接对象
Connection
- 定义sql
- 获取执行sql语句的对象
Statement
- 执行sql,接受返回结果
- 处理结果
- 释放资源
- 导入驱动jar包
public class JDBCDome {
public static void main(String[] args) throws Exception {
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
//创建连接对象
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/bd_2", "root", "root");
//创建执行sql语句对象
Statement statement = connection.createStatement();
//创建sql语句
String sql = "insert into student values(null,'王五',1)";
//执行sql语句
int execute = statement.executeUpdate(sql);
System.out.println(execute);
//释放资源
statement.close();
connection.close();
}
}
5.3、详解各个对象
-
DriverManager
:驱动管理对象-
功能:
-
注册驱动:告诉程序该使用哪一个数据库驱动jar
static void registerDriver(Driver driver) //注册与给定的驱动程序DriverManager //写代码使用:Class.forName("com.mysql.jdbc.Driver"); //通过查看源码发现:在com.mysql.jdbc.Driver类中存在静态代码块 static { try{ java.sql.DriverManager.registerDriver(new Driver()); } catch(SQLException E){ throw new RuntimeException("Can't register driver!"); } }
注意:mysql5之后的驱动jar包可以省略注册驱动的步骤。
-
获取数据库连接
- 方法:
static Connection getConnection(String url,String user,String password)
- 参数:
- url:指定链接的路径
- 语法:
jdbc:mysql://IP地址(域名):端口号/数据库名称
- 例子:
jdbc:mysql://localhost:3306/db3
- 细节:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:
jdbc:mysql:///数据库名称
- 语法:
- user:用户名
- password:密码
- url:指定链接的路径
- 方法:
-
-
-
Connection
:数据库连接对象- 功能:
- 获取执行sql的对象
Statement createStatement()
PreparedStatement prepareStatement(String sql)
- 管理事务
- 开启事务:
setAutoCommit(boolean autoCommit)
:调用该方法设置参数为false,即开启事务 - 提交事务:
commit()
- 回滚事务:
rollback()
- 开启事务:
- 获取执行sql的对象
- 功能:
-
Statement
:执行sql的对象-
执行sql
-
boolean execute(String sql)
:可以执行任意的sql(了解) -
int executeUpdate(String sql)
:执行DML(insert,update、delete)语句、DDL(create、alter、drop)语句- 返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功 返回值>0的则执行成功,反之,则失败
-
ResultSet executeQuery(String sql)
:执行DQL(select)语句 -
练习:
package com.xh.JDBC; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class JDBCDome2 { public static void main(String[] args) { Connection connection = null; Statement statement = null; try { //注册驱动 Class.forName("com.mysql.jdbc.Driver"); //创建连接 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/bd_2", "root", "root"); //创建执行sql对象 statement = connection.createStatement(); //定义sql语句 String sql = "insert into student values(2,'李四',1)"; //执行sql int i = statement.executeUpdate(sql); System.out.println(i); } catch (ClassNotFoundException e) { e.printStackTrace(); }catch (SQLException throwables) { throwables.printStackTrace(); }finally { //释放资源 if (statement!=null){ try { statement.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (connection!=null){ try { connection.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } } }
-
-
-
ResultSet
:结果集对象,封装查询结果-
next()
:游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回true -
getXxx(参数)
:获取数据Xxx
:代表数据类型 如:int getInt()
,String getString()
- 参数:
int
:代表列的编号,从1开始 如:getString(1)
String
:代表列名称。如:getDouble("balance")
-
注意:
-
使用步骤:
- 游标向下移动一行
- 判断是否有数据
- 获取数据
//循环判断游标是否是最后一行 while(rs.next()){ //获取数据 //6.2获取数据 int id = rs.getInt(1); String name = rs.getString("name"); double balance = rs.getDouble(3); System.out.println(id + "---" + name + "---" + balance) }
-
练习:
- 定义一个方法,查询emp表的数据将其封装为对象,然后装载集合,返回
- 定义Emp类
- 定义方法
public List<Emp> findAll(){}
- 实现方法
select * from emp;
- 定义一个方法,查询emp表的数据将其封装为对象,然后装载集合,返回
-
-
-
PreparedStatement
:执行sql的对象- SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题
- 输入用户随便,输入密码:
a' or 'a' = 'a
sql:select * from user where username = "faijd" and password = 'a' or 'a' = 'a'
- 输入用户随便,输入密码:
- 解决sql注入问题:使用PreparedStatement对象来解决
- 预编译的SQL:参数使用?作为占位符
- 步骤:
- 导入驱动jar包
mysql - connector - java - 5.1.37 - bin.jar
- 注册驱动
- 获取数据库连接对象
Connection
- 定义sql
- 注意:sql的参数使用?作为占位符。如:
select * from user where username = ? and password = ?;
- 注意:sql的参数使用?作为占位符。如:
- 获取执行sql语句的对象
PreparedStatement Connectin.prepareStatement(String sql)
- 给?赋值:
- 方法:
setXxx(参数1,参数2)
参数1: ?的位置编号 从1 开始
参数2: ?的值
- 方法:
- 执行sql,接受返回结果,不需要传递sql语句
- 处理结果
- 释放资源
- 导入驱动jar包
- 注意:后期都会使用
PreparedStatement
来完成增删改查的所有操作- 可以防止SQL注入
- 效率更高
- SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题
5.4、抽取JDBC工具类:JDBCUtils
-
目的:简化书写
-
分析:
-
注册驱动也抽取
-
抽取一个方法获取连接对象
-
需求:不想传递参数(麻烦),还得保证工具类的通用性
-
解决:配置文件
url = jdbc:mysql://localhost:3306/bd_2 user = root password = root driver = com.mysql.jdbc.Driver
-
-
抽取一个方法释放资源
-
package com.xh.Utils;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/*
JDBC工具类
*/
public class JdbcUtils {
//定义变量
private static String url;
private static String user;
private static String password;
private static String driver;
//静态代码块初始
static {
//创建Properties对象
Properties pro = new Properties();
//读取配置文件
ClassLoader classLoader = JdbcUtils.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("jdbc.properties");
try {
pro.load(is);
//获取集合中的值
url = pro.getProperty("url");
user = pro.getProperty("user");
password = pro.getProperty("password");
driver = pro.getProperty("driver");
System.out.println(url);
//注册驱动
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//创建方法获取连接对象
public static Connection getConnection(){
Connection connection = null;
try {
connection = DriverManager.getConnection(url, user, password);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return connection;
}
//创建方法释放资源
public static void close(Statement statement,Connection connection){
if (statement!=null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
//释放资源方法
public static void close(ResultSet resultSet,Statement statement, Connection connection){
if (resultSet!=null){
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (statement!=null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
-
练习:
-
需求:
-
通过键盘录入用户名和密码
-
判断用户是否登录成功
select * from user where username = "" and password = ""; -- 如果这个sql有查询结果,则成功,反之,则失败
-
-
步骤:
-
创建数据库表 user
create table user( id int primary key auto_increment, username varchar(32), password varchar(32) ); insert into user values(null,'zhangsan','123'); insert into user values(null,'lisi','234');
-
-
5.5、JDBC控制事务
- 事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。
- 操作:
- 开启事务
- 提交事务
- 回滚事务
- 使用Connection对象来管理事务
- 开启事务:
setAutoCommit(boolean autoCommit)
:调用该方法设置参数为false,即开启事务- 在执行sql之前开启事务
- 提交事务:
commit()
- 当所有sql都执行完提交事务
- 回滚事务:
rollback()
- 在catch(出现异常)中回滚事务
- 开启事务:
6、数据库连接池
6.1、数据库连接池概述
-
概念:其实就是一个容器(集合),存放数据库连接的容器。
当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。
-
好吃:
- 节约资源
- 用户访问高效
6.2、连接池实现
实现:
- 标准接口:
DataSource javax.sql包下的
- 方法:
- 获取连接:
getConnection()
- 归还连接:
Connection.close()。
如果连接对象Connection
是从连接池中获取的,那么调用Connection.close()
方法,则不会再关闭连接了。而是归还连接
- 归还连接:
- 获取连接:
- 一般我们不去实现它,有数据库厂商来实现
- C3P0:数据库连接池技术
- Druid:数据库连接池实现技术,由阿里巴巴提供的
- 方法:
6.3、C3P0连接实现
- 步骤
- 导入jar包(两个)
c3p0-0.9.5.2.jar mchange-commons-java-0.2.12.jar
- 不要忘记导入数据库驱动jar包
- 定义配置文件
- 名称:
c3p0.properties 或者 c3po-config.xml
- 路径:直接将文件放在src目录下即可
- 名称:
- 创建核心对象 数据库连接池对象
ComboPooledDataSource
- 获取连接:
getConnection
- 导入jar包(两个)
6.4、Druid实现
Druid:数据库连接池实现技术,由阿里巴巴提供的
-
步骤:
-
导入jar包
druid-1.0.9.jar
-
定义配置文件:
-
是properties形式的
-
可以叫任意名称,可以放在任意目录下
-
-
加载配置文件。Properties
-
获取数据库连接池对象:通过工厂来来获取
DruidDataSourceFactory
-
获取连接:
getConnection
-
-
定义工具类
- 定义一个类
JDBCUtils
- 提供静态代码块加载配置文件,初始化连接池对象
- 提供方法
- 获取连接方法:通过数据库连接池获取连接
- 释放资源
- 获取连接池的方法
- 定义一个类
driverClassName=com.mysql.jdbc.Driver
#URL连接数据库的URL,其中travel(以下面例子来说)为连接的数据库,后面的参数可不改但不删
url=jdbc:mysql://localhost:3306/bd_1
characterEncoding=utf-8
#安装mysql时候设置的用户与密码
username=root
password=root
#初始化物理连接的个数
initialSize=5
#最大连接池数量
maxActive=10
#获取连接时最大等待时间
maxWait=3000
package com.xh.druid;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class DruidUtils {
private static DataSource ds;
static {
//创建集合对象
Properties properties = new Properties();
//读取配置文件
InputStream resourceAsStream = DruidUtils.class.getClassLoader().getResourceAsStream("Druid.properties");
try {
properties.load(resourceAsStream);
try {
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//获取连接方法
public static Connection getConnection() {
Connection connection = null;
try {
connection = ds.getConnection();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return connection;
}
//释放资源方法
public static void close(Statement statement,Connection connection){
if (statement!=null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
public static void close(ResultSet rs,Statement statement, Connection connection){
if (rs!=null){
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (statement!=null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
//获取连接池对象
public static DataSource getDataSource(){
return ds;
}
}
7、Spring JDBC:JDBC Template
7.1、JDBC Template介绍
- Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC的开发
- 步骤:
- 导入jar包
- 创建JdbcTemplate对象。依赖于数据源DataSource
JdbcTemplate template = new JdbcTemplate(ds);
- 调用JdbcTemplate的方法来完成CRUD的操作
update()
:执行DML语句。增、删、改语句queryForMap()
:查询结果将结果集封装为map集合,将列名作为key,将值作为value 将这条记录封装为一个map集合- 注意:这个方法查询的结果集长度只能是1
queryForList()
:查询结果将结果集封装为list集合- 注意:将每一条记录封装为一个Map集合,再将Map集合装载到List集合中
query()
:查询结果将结果集封装为JavaBean对象- query的参数:RowMapper
- 一般我们使用
BeanPropertyRowMapper
实现类。可以完成数据到JavaBean的自动封装 new BeanPropertyRowMapper<类型>(类型.class)
- 一般我们使用
- query的参数:RowMapper
queryForObject()
:查询结果,将结果封装为对象- 一般用于聚合函数的查询
JdbcTemplate jdbcTemplate = new JdbcTemplate(DruidUtils.getDataSource());
String sql = "select * from user where username = ? and password = ?";
User user1 = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), user.getUsername(), user.getPassword());
System.out.println(user1);