基本数据类型(4类8种) :直接传具体的值i使用 引用数据类型 ;在调用时,应给它相应光电对象
NoNameDemo nnd = new NoNameDemo();普通对象 nnd.Test(new Teacher());//匿名对象
概述 :隐藏对象的属性和实现的细节,仅对外提供公共的访问方式。 原则:将不需要用户的操作的内容隐藏起来。根据需求,提供公共的访问需求。 优点 :
1.它是一个权限修饰符 2.可以修饰成员的变量和方法 3.被修饰的成员只能在本类中被访问,如果其他类想访问,需要经过公共的访问方法。
1.私有化成员变量 2.提供getxxx/setxxx方法
1.表示当前类的对象
概述:创建对象,并对对象的数据进行初始化 格式: public 与类名相同的方法
注意事项:如果我们给出构造方法,则系统将不会在提供一个默认的无参构造方法 不给 ,系统提供
私有成员变量赋值 1.setxxx 2.有参构造函数
1.成员变量 2.成员方法 3.构造方法
1.打开API 2.左上角“显示”----“索引”--------搜索要学习 的类 3.除了java.lang包不需要导包,其他所有的包在使用前都需要导包import 4.类的组成 成员变量----字段 的摘要 成员方法-----方法 构造方法------构造方法 5.学习构造方法: a.邮构造方法:按照API的格式创建对象 b没有构造方法:可能它的成员变量和成员方法是静态的,直接可以通过类名进行调用
6.学习成员变量 了解每个成员变量的含义 7.学习成员方法
左边:
a.是否为静态
b。是否又返回值
右边:
a.方法名
b。参数列表
/**
概述:是把多个类相同的成员提取出来,定义到一个独立的类中,然后让这多个类与这个独立的类 产生一个关系,多个类就具备了这个独立类的属性和功能,这个关系就叫做继承 格式:extends 关键字 class 子类extends 父类() 优点:1.提高了代码的复用性 2.提高了代码的维护性 3.多态的前程 缺点:1.蹭强了类与类之间的耦合性,不符合“高内聚。低耦合”的设计 2.打破了封装性 注意事项:1.子类不能继承父类的私有成员方法和变量 2.子类不能继承父类的构造方法,但是可以通过super关键字去访问 3.不因为一个类的某几种功能而去选择继承 继承成员的关系: 1.成员变量 a 变量名相同 1.在子类局部位置寻找 2.在子类的成员变量位置寻找 3.在父类的成员位置寻找】 b. 变量名不同 2.构造方法 a。子类所有的构造方法都会默认访问父类的无参构造方法 原因:子类对象如果能够访问的属性活方法,则必须对父类进行初始化 b。如果父类没有无参构造 1.通过super关键字指定访问父类的有参构造方法,完成完成初始化 2.通过this关键字访问本类的其他构造方法,但是被访问的这个构造方法必须已经完成父类构造方法的访问 3.手动给出父类的无参构造 3.成员方法 a。方法名相同 1.在子类中找 2.在父类中找 3.报错 b。方法名不同
概述:同一操作,作用于不同对象,可以产生不同的结果。 前提: 1.有继承关系
2.有方法的重写
3.父类引用指向子类的对象
成员访问特点: .
1.成员变量
编译看父类(左边),执行看父类(左边)
2.构造方法
子类的构造默认访问父类的无参构造
3.成员方法
编译看父类(左边),执行看子类(右边)
4.静态方法
编译看父类(左边),执行石父类(左边)
多态的优点:
1.提高了代码的维护性
2.提高了代码的扩展性
多态的缺点:
不能访问子类特有的方法。
对象间的转型:
向上转型(由子到父)
Fu f = new zi();
向下转型 (由父到子)
Zi z = (Zi)f;
●被关键字abstract修饰,可以修饰类和方法
格式:
abstract class 类名{}抽象类
public abstract void方法名(参数列表) C抽象方法
●抽象类不一定有抽象方法,但是有抽象方法的类- -定是抽象类
●抽象类只能被子类实例化(多态的一 -种)
●抽象类的子类:①抽象类②标准类:必须重写抽象类中的所有抽象方法
概述: Java接口时一系列方法的声明, 是一-些方法特征的集合,接口中只有方法的特征,没有方法的 实现,因此这些方法可以在不同地方被不同类实现,而这些实现可以具有不同的行为 特点: 1.关键字interface interface接口名{ }
2.类实现接口使用关键字implements
class类名implements 接口名1,接口..... }
3.接口不能被创建对象,但是可以通过多态去实例化
4.接口的实现类
a.可以是标准类,但是标准类必须重写接口中所有的抽象方法。
b.可以是抽象类,不需要重写抽象方法的。
成员特点:1.成员变量
只能有常量,因为变量默认的被public static final修饰
2.构造方法
没有构造方法
3.成员方法
只能是抽象方法,默认被public abstract修饰
类,接口之间的关系
1.类与类
继承关系,只能单继承,不能多继承,但可以多层继承。
2.类与接口
实现关系,单实现或多实现
3.接口与接口
继承关系,并且是多继承。
面试题: Java中的继承是单继承? 错
Java中的类是单继承? 对
接口与抽象类的区别
1.组成区别
抽象类: .
成员变量:可以是变量,可以是常量
构造方法:有构造方法
成员方法:可以是抽象方法,也可以是非抽象方法
接口:
成员变量:没有成员变量,只能是常量
构造方法:没有构造方法
成员方法:只能是抽象方法
2.关系的区别
类与类
类与接口
接口与接口
3.应用的区别
抽象类中定义的是继承体系中相同的属性和方法
接口中定义的是类的扩展功能。
15.包 概述:其实就是文件夹 作用:对类的分类管理(图书馆对于书籍的分类,这个分类就相当于包) 关键字: package 格式: package包名; (多级包用 ”. "隔开) 注意事项: 1.package语句必须是java文件的第一 条语句(注释除外) 2.-个java文件中, 只能有一条package语句 3.没有package语句,默认就是没有包 导包:关键字import 1.import包名类名(推荐格式) 2.import包名.*
16.权限修饰符 权限修饰符 本类 同一包 不同包子类 不同包其他类 private Y 默认 Y Y protected Y Y Y public Y Y γ Y
**常见修饰符:**
1.权限修饰符: private , 默认 ,protected ,public
2.状态修饰符:final , static
3.抽象修饰符:abstract
修饰符
1.权限修饰符: private , 默认 ,protected ,public
2.状态修饰符:final , static 3.抽象修饰符:abstract
类:
权限修饰符: 默认 ,protected ,public
状态修饰符:final
抽象修饰符:abstract
常用修饰符:public 默认的
成员变量:
权限修饰符:private , 默认 ,protected ,public
状态修饰符:final , static
常用修饰符: private
构造方法:
权限修饰符:private , 默认 ,protected ,public
常用修饰符:public
成员方法:
权限修饰符:private , 默认 ,protected ,public
状态修饰符:final , static
抽象修饰符:abstract
常用修饰符:public
常见修饰符组合:
成员变量
public static final
成员方法
public static
public abstract
public final
**概述:**用于设备间数据传输的操作
分类:
1.流向
输入流:读取操作
输出流:写出操作
2.数据类型
字节流
字节输入流
字节输出流
字符流
字符输入流
字符输出流
注意:
1.如果文件用记事本打开,内容能够读懂,就用字符流,读不懂就用字节流。实在不知道用什么流的话就用字节流。
操作步骤:
1. 创建字节流对象
FileOutputStream fos = new FileOutputStream(“fos.txt”);
2.调用write()
fos.write(“Hello”.getBytes());
3.释放资源
fos.close
注意:
1.如何换行,如何在文件中追加内容
FileInputStream fis = new FileInputStream(“fos.txt”);
//方式1:一次读取一个字节
int content = 0 ;
while((content = fis.read())!=-1){
System.out.println((char)content)
}
//方式2:一次读取一个字节数组
byte[] bytes = new byte[1024];
int len = 0 ;
while((len=fis.read(bytes))!=-1){
System.out.println(new String(bytes,0,len))
}
3.释放资源
fis.close
BufferedOutputStream
BufferedInputStream
OutputStreamWriter(OutputStream os):没有指定编码集,就采用默认编码集:UTF-8
OutputStreamWriter(OutputStream os , String charsetName )指定编码集
InputStreamWriter(OutputStream os):没有指定编码集,就采用默认编码集:UTF-8
InputStreamWriter(OutputStream os , String charsetName )指定编码集
Writer
FileWriter
Reader
FileReader
in:标准输入流—inputStream
out:标准输出流—PrintStream
字节打印流–PrintStream
字符打印流–PrintWriter
特点:
1.只负责输出数据,不负责读取数据 2.它有自己特有的方法
**序列化流:**把对象按照流的方式存入到文件中或在网络中传输。 对象----》流数据-ObjectOutputStream
**反序列化流:**把文件中的流数据或网络传输中的流数据还原成对象。 流数据----》对象-ObjectInputStream
如何实现序列化:
让被序列化的对象所属的类实现序列化接口。这个接口是一个标记接口。
注意
UID:如果你不固定UID
,你每次修改类文件时,都会相应的生成新的UID
,这样你就需要重新执行对象的序列化和反序列化操作。
**transient:**不想被序列化的属性可以用它来修饰
1.它是一个集合类,HashTable的一个子类。它的数据存储类似于Map集合,并且它的key和value都是String.
2.特有方法
public Object setProperty(String key,String value):添加元素
public String getProperty(String key):根据键获取值
public Set stringPropertyNames():获取键集合
3.与IO流结合使用 的方法
public void load(Reader reader):将文件中的数据写入到集合中----指定数据源
public void store(Writer writer,String comments):将集合中的数据写入到文件中---指定目的地
1.了解IP地址与InetAddress类的关系。
2.了解如何使用URL定位网络资源。
3.了解编码和解码的操作。
4.了解ServerSocket类与Socket类的关系以及客户端与服务器端的通信模式。
5.了解如何将多线程机制应用在服务器开发上。
6.了解UDP程序与TCP程序的实现区别。
扩展:TCP和UDP协议
TCP:可靠的传输协议,传输前会采用“三方握手”的方式建立连接,以保证传输的可靠性;
UDP:不可靠的传输协议,即发送的数据不一定接受得到,网上的聊天工具一般采用此协议。
IP地址使用32为长度二进制表示,一般实际中看到的大部分IP地址都是以十进制的数据形式表示,如同192.168.1.1。
IP地址=网络地址+主机地址
127.x.x.x的为保留地址,用作循环测试使用。
public static InetAddress getByAddress(byte[] addr)throws UnknownHostException
在给定原始 IP 地址的情况下,返回 InetAddress 对象。
参数按网络字节顺序:地址的高位字节位于 getAddress()[0] 中。
此方法不会阻塞,即不执行任何反向名称服务查找操作。
IPv4 地址 byte 数组的长度必须为 4 个字节,IPv6 byte 数组的长度必须为 16 个字节
public String getHostName()
//常用来输出IP地址
public String getHostAddress()
public static InetAddress getLocalHost()
throws UnknownHostException
1.UDP开发中使用DatagramPacket类包装一条要发送的信息,之后使用DatagramPacket类用于完成信息的发送操作,这两个类的常用方法如下:
包装发送的信息
public DatagramPacket(byte[] buf,int offset,int length,InetAddress address,int port)
//实例化DatagramPacket对象时指定发送的数据、数据的长度、目标地址及端口
public DatagramPacket(byte[] buf,int length)
//实例化DatagramPacket对象时指定发送数据的长度。
public byte[] getData()
//返回数据缓冲区。接收到的或将要发送的数据从缓冲区中的偏移量 offset 处开始,持续 length 长度。
public int getLength()
//返回将要发送或接收到的数据的长度。
完成信息的发送操作
//构造方法
public DatagramSocket(int port) throws SocketException
//普通方法
public void receive(DatagramPacket p) throws IOException
//从此套接字接收数据报包。当此方法返回时,DatagramPacket 的缓冲区填充了接收的数据。数据报包也包含发送方的 IP 地址和发送方机器上的端口号。
此方法在接收到数据报前一直阻塞。数据报包对象的 length 字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。
如果存在安全管理器,而安全管理器的 checkAccept 方法不允许接收操作,则包不能被接收。
public void send(DatagramPacket p) throws IOException
//从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号
任务:
2.接受用户端想要监听的端口
3.用户发送数据 ,服务器接受消息 ,反馈。
4.完成用户登录注册方式 ,服务器自动回复登录成功等。
//1.int[] numbers = {23,5,28,29,4.65} 冒泡排序
//2.上传文件
2017年9月发布
R:read
E:evaluate
P:print
L:loop
产生背景:
Python ,Scala语言在很早就提出了交互式编程环境
常用命令/help
1./list :显示已经成功的代码
2./vars(method):显示已定义的变量(方法)
3./edit:调用编辑框,修改代码
4./open:运行JAVA文件中的代码
特点:
1.可以声明同名变量,但是原来的值会被覆盖。
2.可以声明同名方法,原方法体被覆盖
3.tab可以提示和补全代码
4.没有编译时异常
特点:
1.接口中可以有默认方法,静态方法,私有方法
2.实现接口的类必须实现抽象方法,但是不强制实现默认方法
3.接口中的静态方法,通过接口名调用
4.接口中的私有方法,只能被默认方法使用,不能被静态方法以及实现类中被使用。
在JDK8中,匿名内部类如果使用泛型会发生错误;
错误格式:
//JDK9
Comparator
**修改动机:**一般我们的String型数据占用两个字节的空间,但是对于许多应用使用的数据手机,我们发现,String是堆内存中主要的使用部分,而且String的主要表现形式都是使用拉丁文来表示的,我们知道拉丁文只占1个字节,所以有一半的空间都是被浪费的。
**更新:**JDK更改了String内部的存储形式,从 utf-16的char型数组 修改为byte[]字节数组,并增加了编码集识别字段。这样的更新会根据数据动态的去指定使用哪一种格式,比如拉丁文,就是用 ISO-8859,如果是中文这类需要使用两个字节表示的数据,就是用utf-16编码集,这样就可以减少系统资源的消耗。
该方法可以直接将输入流的数据传输给输出流,不需要我们再去手动的操作:
public class InputStreamDemo {
public static void main(String[] args) {
try {
//输入流
FileInputStream fis = new FileInputStream("D:\\a.txt");
//输出流
FileOutputStream fos = new FileOutputStream("a.txt");
fis.transferTo(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
1.lambda
格式1:无参数,无返回值
/*
格式1: 无参数,无返回值
*/
@Test
public void test() {
//内部类
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("内部类实现");
}
} ;
runnable.run();
//Lambda表达式
Runnable runnable2 = () -> System.out.println("Lambda表达式实现");
runnable2.run();
}
格式2:有一个参数,没有返回值
@Test
public void test2() {
//内部类写法
Consumer consumer = new Consumer() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
consumer.accept("普通内部类");
//Lambda表达式
Consumer consumer2 = s-> System.out.println(s);
consumer2.accept("Lambda表达式");
}
格式3:参数数据类型可以省略,因为可以由编译器进行类型推断
格式4:Lambda表达式若只有一个参数时,参数的小括号可以省略
格式5:Lambda表达式需要两个或两个以上的参数,多条执行语句,并且有返回值
@Test
public void test3() {
//内部类实现
Comparator comparator = new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};
//Lambda表达式
Comparator comparator2 = (o1,o2)->{
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
};
int compare = comparator2.compare(10, 20);
System.out.println(compare);
}
格式6:当Lambda表达式语句只有一条语句时,花括号和return都可以省略。
@Test
public void test3() {
//内部类实现
Comparator comparator = new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};
//Lambda表达式
Comparator comparator2 = (o1,o2)-> o1.compareTo(o2);
int compare = comparator2.compare(10, 20);
System.out.println(compare);
}
总结:
-> 右边:Lambda形参列表的参数类型可以省略,如果形参只有一个,小括号也可以省略。
->左边:Lambda体应该有一对花括号包裹,如果只有一条执行语句,花括号可以省略,return也可以省略。
条件:这个接口必须只能有一个抽象方法,这样我们实现时,才能省去它的方法名以及new接口名这两部分。
这样只有一个抽象方法的接口,这就叫函数式接口
@Functionallnterface : 注解
JAVA注解:又称为java标注,是JDK5.0引入 的一种注释机制,java中的类,方法,变量,参数和包都可以注0释
@FunctionalInterface
public interface MyInterface {
void method();
//void method2();//注释会报错,不符合函数式接口规则
}
概述:1.函数式接口只能有一个抽象方法
2.函数式接口上使用@Functionallnterface注释,这样可以校验它是否符合函数式接口的规则,并且,注解也会被声明到javadoc中
3.在java.util.function包下定义了丰富的函数式接口,供我们调用
\4. Java支持 (OOP)面向对象编程 (FOP)面向函数编程
只要一个对象是函数式接口的实例,我们就可以用Lambda表示它。
函数式接口 | 参数类型 | 返回值类型 | 用途 |
---|---|---|---|
Consumer 消费型接口 | T | void | 表示接受单个输入参数并且不返回任何结果的操作,包含操作:void accpt(T t) |
Supplier 供给型接口 | void | T | 代表结果的供应商。包含操作:T get() |
Function |
T | R | 对类型为T的对象进行操作,并返回结果,结果为R型的对象。包含操作:R apply(T t) |
Predicate断定型接口 | T | boolean | 确实类型为T的对象是否满足某种约束,并返回boolean test(T t) |
**Consumer 消费型接口:**就好像消费者,你给我什么,我消费什么
**Supplier 供给型接口:**和消费者正好相反,它不要参数,还会给你返回结果
**Function函数式接口:**主要做数据转换,将参数T的数据类型转换为R类型
**Predicate断定型接口:**主要做判断的
情况1:对象名::非静态方法
案例1:
@Test
public void test() {
//Lambda表达式
Consumer consumer = s-> System.out.println(s);
consumer.accept("HelloWorld!");
System.out.println("-------------------------");
//方法引用
PrintStream out = System.out;
Consumer consumer2 =out::println;
consumer2.accept("HelloJava!");
}
案例2:
/*
供给型接口 Supplier T get()
Student String getName()
*/
@Test
public void test2() {
//Lambda表达式
Student student = new Student("王总", 18);
Supplier supplier =() -> student.getName();
System.out.println(supplier.get());
//方法引用
Supplier supplier2 = student::getName;
System.out.println(supplier2.get());
}
使用要求:
接口中抽象方法的形参列表和返回值类型与方法引用的方法的形参列表还有返回值类型相同时,可以使用的方法引用。
技巧:无论是方法引用还是Lambda表达式,在编写时,我们只需要了解抽象方法的声明格式即可。
案例:
/*
*/
//lambda
Function function = aDouble- >Math.round(aDouble);
System.out.println(function.apply(1.4));
//方法引用
Function function2 = Math::round;
System.out.println(function2.apply(1.4));
//lambda表达式
BiPredicate predicate = (s,s2) -> s.equals(s2);
System.out.println(predicate.test("abc", "abc"));
//方法引用
BiPredicate predicate2 = String::equals;
System.out.println(predicate2.test("abc", "abc"));
案例2:
//lambda表达式
Comparator comparator = (String o1, String o2)-> o1.compareTo(o2);
System.out.println(comparator.compare("abc","abd"));
//方法引用
Comparator comparator2 =String::compareTo;
System.out.println(comparator2.compare("abc","abd"));
通过案例:当抽象方法的两个形参中,一个参数作为已实现方法的调用者,而另一个参数不变时,可以考虑方法引用
案例:
/*
Supplier T get()
Student Student()
*/
@Test
public void test() {
//lambda表达式
Supplier supplier = () -> new Student();
Student student = supplier.get();
System.out.println(student);
//构造器引用
Supplier supplier2 = Student::new;
Student student1 = supplier2.get();
System.out.println(student1);
}
/*
Function R apply(T t)
*/
@Test
public void test2() {
//Lambda表达式
Function function = s -> new Student(s);
Student student = function.apply("终南山");
System.out.println(student);
//构造器引用
Function function2 = Student::new;
Student student1 = function2.apply("袁隆平");
System.out.println(student1);
}
**规律:**把数组看作一个特殊的类,则写法与构造器引用一致。
案例
@Test
public void test3() {
//Lambda表达式
Function function = integer -> new String[integer];
String[] strings = function.apply(10);
System.out.println(Arrays.toString(strings));
//数组引用
Function function2 = String[]::new;
String[] strings2 = function.apply(5);
System.out.println(Arrays.toString(strings2));
}
规律:我们可以把数组看作一个特殊的类,则写法与构造器引用一致
java 8中最重大的两个改变,第一个就是Lambda表达式,另一个及时Stream API
Stream API 是真正的把函数式编程引入到java中,它是目前为止对java类库最好的补充。
Stream 可以对集合数据进行非常复杂的查找,过滤,映射数据等操作。使用Stream API对集合数据进行操作,类似于使用SQL语句进行数据操作。Stream API还可以使用并行操作。
Stream API提供一种高效且易于处理数据的方式。
1.可以进行高效聚合操作
2.多核时代影响的产物
1.Stream 本身不存数据
2.Stream 不会改变源数据,但是它会返回一个持有结果的新Stream
3.Stream操作是延迟的
1.第一步:创建Stream
一个数据源,获取一个Stream
2.第二步:中间操作
一个中间操作链,对数据进行处理
3.第三步:终止操作
一旦执行终止操作,就会执行中间操作链,并返回结果。
default Stream stream()
default Stream parallelStream()
List list = Arrays.asList("aaa", "bbb", "ccc");
Stream stream = list.stream();
Stream stringStream = list.parallelStream();
static Stream stream(T[] arr)
1.JVM前言
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ZkddKwp-1629445551942)(C:\Users\86151\Desktop\1.PNG)]
官网下载地址:https://dev.mysql.com/downloads/mysql/5.7.html
下载完成后,双击运行,按照下列步骤操作即可。
第一步:选默认安装
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HwzYmBZK-1629445551944)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\image-20201126111812055.png)]
第二步:查看是否有支持库没有安装。若有,则需要提前安装支持库后,再继续MySql的安装。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UTw7NN2O-1629445551946)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\image-20201126112502364.png)]
第三步:开始安装
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YAgq5OaO-1629445551949)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\image-20201126114105949.png)]
第四步:到这一步表示你的MySql已经安装成功。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d61jvKdx-1629445551950)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\image-20201126114522491.png)]
第五步:安装SQLyog
因为该软件安装比较简单,所以此处省略,如果不会刻意百度教程,网上有很多相关博客文章。
数据库:DataBase,简写DB
概述:
用于存储和管理数据的仓库。
特点:
用于持久化存储数据的。其实数据库就是一个文件系统。
1.只要是持久化存储,肯定都是以文件的形式存储在硬盘上
2.数据库的文件系统和windows文件系统的虽然都可以存储数据,但是数据库不光要求能存数据,还要求对于数据的操作更简单,管理更方便。
方便存储和管理数据
使用统一的方式操作数据库—SQL
常见数据库软件:
启动与关闭
MySql安装完成后,需要去启动服务。什么是服务:服务就是没有界面的应用程序
启动、关闭服务
方式1: cmd ----> services.msc 打开服务窗口
方式2:管理员身份cmd—>net start MySQL 或 net stop MySQL
注意:MySQL 一定是与你的服务名称一致
MySql登录
mysql -u用户名 -p密码
mysql -h目标主机IP地址 -u用户名 -p密码
mysql --host=目标主机IP地址 --user=用户名 --password=密码
MySql退出
exit
quit
Structured Query Language: 结构化查询语言。只要是关系型数据库,都可以通过SQL语言去操作。 不同的数据库间的SQL操作略有差异,但是主体部分还是一样的。对于数据库之间的差异,我们称之为:“方言”。
SQL语句可以单行也可以是多行,多行以分号结尾
->show database
->;
使用空格和缩进来增强语句的可读性。
MySql 数据库的SQL语句不区分大写,关键字建议使用大写
->shOw DaTabase
->;
三种注释
--
注释内容 或 #注释内容(MySql 特有)DDL(Data Definition Language):数据库定义语言
用来定义数据库对象:数据库,表,列表等。关键字create , drop , alter 等
DML(Data Manipulation Language)数据库操作语言
用来对数据库中表的数据进行增删改。关键字:insert , delete , updata等
DQL(Data Query Language)数据库查询语言
用来查询表中的记录。关键字:select , where 等
DCL(Data Control Language)数据库控制语言
用来定义数据库的访问权限和安全级别,及创建用户。关键字 GRANT,REVOKE
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JGggss6N-1629445551951)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\SQL分类.png)]
概述:
C:Create 创建
CREATE DATABASE test;--创建test数据库
SHOW CREATE DATABASE test;--查看test数据库
注意:
数据库已经存在的话用上面语句会报错
CREATE DATABASE IF NOT EXISTS test;--如果数据库不存在再创建。
CREATE DATABASE test2 CHAR SET utf8;--指定test2 的编码集UTF-8
**练习:**创建一个数据库test3 ,判断是否存在,并指定字符集为gbk.
CREATE DATABASE IF NOT EXISTS test3 CHAR SET gbk;
R:Retrieve 查询
查询所有数据的名称
show databases;
2. 查询指定数据库的创建语句 和 该数据库的编码集
SHOW CREATE DATABASE 数据库名;
U:Update 修改
修改数据库字符集
ALTER DATABASE 数据库 CHAR SET 字符集;
D:Drop 删除
DROP DATABASE IF EXISTS 数据库名;
使用数据库
USE 数据库名;
C:Create 创建
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9XVuXgAw-1629445551951)(C:\Users\30526\AppData\Roaming\Typora\typora-user-images\image-20201215161607761.png)]
语法:
CREATE TABLE 表名(
列名1 数据类型1,
列名2 数据类型2,
列名3 数据类型3,
……
列名n 数据类型n
)
注意:
最后一列,不需要加 ,
常见数据类型:
int:整数类型
例: age int ,
float ,double:小数类型
例:score float( 4, 2 ) 共4个数,小数位为2位
date:日期类型,只包含年月日,yyyy-MM-dd
datetime日期类型,包含年月日时分秒 yyyy-MM-dd HH-mm-ss
timestamp时间戳类型,包含年月日时分秒 yyyy-MM-dd HH-mm-ss
注意:该类型若在插入数据时,不赋值或赋值为null,则默认使用当前系统时间,来自动复制。
char ,varchar
char(n):定长度数据类型若存入少于n个字符,则用空格补齐,查询之时再去掉空格,所以 char型数据后面不能有空格. 并且当数据 varchar(n):变长度数据类型,该类型比较常用。
案例:
CREATE TABLE student(
id INT,
NAME VARCHAR(32),
age INT,
score DOUBLE(5,2),
brithday DATE,
insert_time TIMESTAMP
);
R:Retrieve 查询
SHOW TABLES;
DESC 表名;
U:Update 修改
修改表名
alter table 表名 rename to 新的表名;
ALTER TABLE student RENAME TO stu;
修改表字符集
alter table 表名 char set 字符集;
ALTER TABLE stu CHAR SET utf8;
添加一列
alter table 表名 add 列名 数据类型;
ALTER TABLE stu ADD sex CHAR(2);
修改列名称,数据类型
方式1:alter table 表名 change 列名 新列名 新数据类型;
ALTER TABLE stu CHANGE sex gender VARCHAR(10);
方式2:alter table 表名 modify 列名 新数据类型;
ALTER TABLE stu MODIFY gender VARCHAR(20);
删除列
alter table 表名 drop 列名;
ALTER TABLE stu DROP gender;
D:Drop 删除
drop table 表名;
drop table if exists 表名;--方式2
语法:
insert into 表名(列名1,列名2,列名3) values (value1 , value2 , value3) ;
INSERT INTO stu(id,NAME,age) VALUES(1,"周杰伦",18);
注意:
列名 和 值 需要一一对应。
如果表名后不加列名,则表示默认给所有列添加值
除了数字类型,其他数据类型都需要用引号(单双都可以)括起来
INSERT INTO stu VALUES(2,'林俊杰',19,98.50,NULL,NULL);--正确
INSERT INTO stu VALUES(3,'刘德华',20,97.50,'1988-02-04',NULL);--正确
INSERT INTO stu VALUES(4,'刘德华',20,97.50,'1988-0204',NULL);--错误,date格式不符
INSERT INTO stu VALUES(5,'刘德华',20,97.50,NULL);--错误,缺少字段
语法:
delete from 表名 [where 条件]
注意:
如果不带条件,则会把表中所有数据都删除
如果要删除表中所有记录
a. delete from 表名,不推荐使用,因为效率比较低,有多少条记录,它就会执行多少次。
b. truncate table 表名,推荐使用,先删除表,然后再创建一个一模一样的表。
DELETE FROM stu WHERE id=3;--删除id=3的数据
TRUNCATE TABLE stu;--删除stu表,然后创建一个一模一样的新表
语法:
update 表名 set 列名1 = 值1,列名2 = 值2 ,… [where 条件]
注意:
UPDATE stu SET age = 20;--修改表中所有数据
UPDATE stu SET age = 19 WHERE id = 1;--将id=1这条数据的年龄设置为19岁
UPDATE stu SET age =21,score=100 WHERE NAME='林俊杰';--将name='林俊杰'的年龄修改为21,成绩修改为100
select 字段列表 from 表名列表 where 条件列表 grop by 分组字段 having 分组之后的条件 order by 排序 limit 分页限定
测试表:
USE test;
CREATE TABLE IF NOT EXISTS student(
id INT,#学号
NAME VARCHAR(20),#姓名
age INT,#年龄
sex VARCHAR(5),#性别
address VARCHAR(100),#地址
math INT,#数学
english INT#英语
);
DROP TABLE IF EXISTS student;
INSERT INTO student (id,NAME,age,sex,address,math,english) VALUES
(1,'马云',18,'男','杭州',80,80),
(2,'马化腾',19,'男','深圳',75,60),
(3,'埃隆马斯克',31,'男','美国',76,93),
(4,'扎克伯格',27,'男','美国',65,NULL),
(5,'郎平',16,'女','上海',90,98),
(6,'姚明',32,'男','上海',80,81);
--查询 姓名 和 年龄
SELECT NAME,age FROM student;
--一些公司会要求查询语句不能出现*,必须写字段名加注释
-- 查询姓名 和 年龄
SELECT
NAME,#姓名
age #年龄
FROM
student;#学生表
SELECT address FROM student;--有重复地名
SELECT DISTINCT address FROM student;
SELECT NAME,math,english,(math + english) FROM student;
注意:
1. 当字段中有null值参与运算时,结果也为null,可以使用ifnull()
SELECT NAME,math,english,(math + IFNULL(english,0)) FROM student;
SELECT NAME math,english,(math + IFNULL(english,0)) '总成绩' FROM student;
或
SELECT NAME math,english,(math + IFNULL(english,0)) AS '总成绩' FROM student;
>, < , >= , <= , = , <>
-- 查询年龄大于20的学生信息
SELECT * FROM student WHERE age >20;
-- 查询年龄大于20的学生信息,包括20岁
SELECT * FROM student WHERE age>=20;
-- 查询年龄等于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;#简化写法
-- 查询年龄 16 ,18 ,19 的学生信息
SELECT * FROM student WHERE age = 16 OR age = 18 OR age = 19;
SELECT * FROM student WHERE age = 16 || age = 18 || age = 19;
SELECT * FROM student WHERE age IN (16,18,19);#简化写法
-- 查询没有英语成绩的学生信息
SELECT * FROM student WHERE english = NULL;-- 该条语句不正确,因为null不能用 = ,!= 判断
SELECT * FROM student WHERE english IS NULL;
-- 查询有英语成绩的学生信息
SELECT * FROM student WHERE english IS NOT NULL;
模糊查询在项目中用的比较多,因为大多数搜索框都是模糊查询
# like 查询
-- 查询 姓马的学生信息
SELECT * FROM student WHERE NAME LIKE '马%';
-- 查询第二字为克的学生信息
SELECT * FROM student WHERE NAME LIKE '_克%';
-- 查询名字中包含马字的学生信息
SELECT * FROM student WHERE NAME LIKE '%马%';
-- 查询名字是3个字的学生信息
SELECT * FROM student WHERE NAME LIKE '___';
order by 子句
order by 排序字段1,排序方式1 ,排序字段2,排序方式2
-- 查询学生信息,并按照数学成绩升序排列
SELECT * FROM student ORDER BY math;
SELECT * FROM student ORDER BY math ASC;
-- 查询学生信息,并按照数学成绩降序排列
SELECT * FROM student ORDER BY math DESC;
-- 查询学生信息,并按照数学成绩升序排列,如果数学成绩相同就按英语成绩升序排列
SELECT * FROM student ORDER BY math ASC ,english ASC ;
SELECT * FROM student WHERE english IS NOT NULL ORDER BY math ASC ,english ASC ;
如果有多个排序条件时,只有前一个条件值相同时,才会判断第二条件。
将一列数据作为一个整体,进行纵向计算
select 聚合函数(列名)[别名] from 表名
-- 求学生中数学成绩最高分
SELECT MAX(math) FROM student;
-- 求学生中英语成绩最低分
SELECT MIN(IFNULL(english,0)) FROM student;
-- 求学生的学生数学总成绩
SELECT SUM(math) FROM student;
-- 求学生的学生数学平均分
SELECT AVG(math) FROM student;
聚合函数会自动排除null值。
1.选择非空列进行计算
2.ifnull函数
SELECT COUNT(english) FROM student;--5条记录,因为排除了null
SELECT COUNT(id) FROM student;--6条记录,因为它是非空列
SELECT COUNT(IFNULL(english ,0)) FROM student;-- 6条记录,使用函数将为null的数据替换成0
group by 分组字段
SELECT * FROM student GROUP BY sex;-- 没有任何意义
结果:查出局部数据
a. where 在分组前进行限定,如果不满足条件,则不参与分组 。having 是对结果进行限定,如果不满 足则不会被查询出来
b. where后不能跟聚合函数,having后可以跟聚合函数
-- 按照性别分组,分别查询男,女同学的数学平均分
SELECT * FROM student GROUP BY sex;-- 没有任何意义
SELECT sex,AVG(math) FROM student GROUP BY sex;
-- 按照性别分组,分别查询男,女同学的数学平均分,及相应性别对应的人数
SELECT sex,AVG(math),COUNT(id) FROM student GROUP BY sex;
-- 分组前添加一些限定条件
-- 按照性别分组,分别查询男,女同学的英语平均分,及相应性别对应的人数,分组要求:分数小于60的不参与统计
SELECT sex,AVG(IFNULL(english,0)),COUNT(id) FROM student WHERE english >60 GROUP BY sex;
SELECT sex,AVG(IFNULL(english,0)),COUNT(id) FROM student GROUP BY sex HAVING english >60;-- 错误,因为条件是分组前的条件,having只对分组后的结果进行条件添加
-- 按照性别分组,分别查询男,女同学的英语平均分,及相应性别对应的人数,分组要求:分数小于60的不参与统计,并且只查看人数大于2的分组数据
SELECT sex,AVG(IFNULL(english,0)),COUNT(id) FROM student WHERE english >60 GROUP BY sex HAVING COUNT(id)>2;
-- 另一种写法
SELECT sex,AVG(IFNULL(english,0)),COUNT(id) 人数 FROM student WHERE english >60 GROUP BY sex HAVING 人数>2;
limit 开始的索引,每页显示的条数;
开始的索引 = (当前页码-1)* 每页显示的条数
推导过程:
-- 每页显示2条数据
SELECT * FROM student LIMIT 0,2;-- 第1页
SELECT * FROM student LIMIT 2,2;-- 第2页
SELECT * FROM student LIMIT 4,2;-- 第3页
SELECT * FROM student LIMIT 6,2;-- 第4页
-- 后面在做网站实现分页效果的时候,我们只需要确定每页起始的索引就可以实现分页效果
-- 分页公式:开始的索引 = (当前页码-1)* 每页显示的条数
分页操作limit语法是MySql的“方言”
对表中的数据进行限定,保证数据的正确性,有效性和完整性。
对于我们一直操作的学生表,我们可以尝试添加一些非法数据,比如名字=null,它是能被正常保存进去的,这样对于的数据是无效,不完整,且没有任何意义的。所以我们在设计表的时候可以给表中的字段加一些约束,比如名字字段对应的值不能为null,这样就可以有效的保证数据的正确性,有效性,和完整性。
1. 创建表时
2. 修改表时
1. 主键约束:primary key
CREATE TABLE people(
NAME VARCHAR(20) NOT NULL,
age INT
);
SELECT * FROM people;
INSERT INTO people (NAME,age) VALUES('马化腾',25);
INSERT INTO people (NAME,age) VALUES(NULL,25);-- 报错:Column 'name' cannot be null
-- 修改表来添加或删除约束
ALTER TABLE people MODIFY NAME VARCHAR(20);
INSERT INTO people (NAME,age) VALUES(NULL,25);-- 不报错:我们将非空约束删除了
ALTER TABLE people MODIFY NAME VARCHAR(20) NOT NULL;
INSERT INTO people (NAME,age) VALUES(NULL,25);-- 报错:Column 'name' cannot be null
多个null值不算重复值。
DROP TABLE IF EXISTS people;
CREATE TABLE IF NOT EXISTS people(
NAME VARCHAR(20),
p_id BIGINT(18) UNIQUE
);
INSERT INTO people (NAME,p_id) VALUES('马云',610503197702021125);
INSERT INTO people (NAME,p_id) VALUES('马云',610503197702021125);-- 报错:Duplicate entry '610503197702021125' for key 'p_id'
INSERT INTO people (NAME,p_id) VALUES('马化腾',NULL);-- 不报错
INSERT INTO people (NAME,p_id) VALUES('马化腾',NULL);-- 不报错
SELECT * FROM people;
ALTER TABLE people MODIFY p_id BIGINT(18);
INSERT INTO people (NAME,p_id) VALUES('马云',610503197702021125);-- 报错,因为唯一约束的删除有特殊语法
ALTER TABLE people DROP INDEX p_id;
INSERT INTO people (NAME,p_id) VALUES('马云',610503197702021125);-- 不报错
ALTER TABLE people MODIFY p_id BIGINT(18) UNIQUE;-- 报错,因为表中有不唯一数据,删除之后就不会报错了
SELECT * FROM people;
1.主键 = 非空且唯一
2.一张表中只能有一个字段为主键
3.主键就是表中记录的唯一标识
DROP TABLE IF EXISTS people;
CREATE TABLE IF NOT EXISTS people(
id INT PRIMARY KEY,-- 主键
NAME VARCHAR(20)
);
INSERT INTO people (id,NAME) VALUES(1,'马云');
INSERT INTO people (id,NAME) VALUES(2,'刘德华');
INSERT INTO people (id,NAME) VALUES(2,'张惠妹');-- 报错:Duplicate entry '2' for key 'PRIMARY'
INSERT INTO people (id,NAME) VALUES(NULL,'张惠妹');-- 报错:Column 'id' cannot be null
-- 删除主键
ALTER TABLE people DROP PRIMARY KEY;
-- 添加 主键
ALTER TABLE people MODIFY id INT PRIMARY KEY;
如果某一列是数值类型的,并且连续非空且连续的时候,我们就可以使用 auto_increment 来完成自动增长
DROP TABLE IF EXISTS people;
CREATE TABLE IF NOT EXISTS people(
id INT PRIMARY KEY AUTO_INCREMENT ,-- 主键
NAME VARCHAR(20)
);
-- 自增序列我们可以不给值,它会自己给
INSERT INTO people (id,NAME) VALUES(NULL,'马云');
INSERT INTO people (id,NAME) VALUES(NULL,'刘德华');
-- 我们也可以手动的指定值,并且下一次添加的值只与最后一条数据有关
INSERT INTO people (id,NAME) VALUES(100,'马云');
INSERT INTO people (id,NAME) VALUES(NULL,'刘德华');--id=101
SELECT * FROM people;
ALTER TABLE people MODIFY id INT;
INSERT INTO people (id,NAME) VALUES(NULL,'刘德华');-- 报错:Column 'id' cannot be null
ALTER TABLE people MODIFY id INT AUTO_INCREMENT;
首先我们先建一个测试表:emp
CREATE TABLE emp(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT,
dep_name VARCHAR(30),-- 部门名称
dep_location VARCHAR(30)-- 部门地址
)
INSERT INTO emp (NAME,age,dep_name,dep_location) VALUES('马化腾',20,'研发部','深圳');
INSERT INTO emp (NAME,age,dep_name,dep_location) VALUES('马云',26,'研发部','深圳');
INSERT INTO emp (NAME,age,dep_name,dep_location) VALUES('周杰伦',18,'销售部','北京');
INSERT INTO emp (NAME,age,dep_name,dep_location) VALUES('蔡依林',19,'销售部','北京');
INSERT INTO emp (NAME,age,dep_name,dep_location) VALUES('求伯君',22,'研发部','深圳');
INSERT INTO emp (NAME,age,dep_name,dep_location) VALUES('詹姆斯高斯林',30,'研发部','深圳');
INSERT INTO emp (NAME,age,dep_name,dep_location) VALUES('汤姆克鲁斯',20,'销售部','北京');
INSERT INTO emp (NAME,age,dep_name,dep_location) VALUES('丁磊',27,'研发部','深圳');
通过查询数据,我们发现 研发部 对应 深圳,销售部 对应 北京 这样相同部门的员工信息中 部门地址就会出现很多重复数据,并且在修改部门地址时非常不方便,需要每条数据单独操作。
为了解决上述问题,我们可以将表进行拆分,将员工表拆分成 员工表 和 部门信息表,让这两个表产生关联。
-- 解决数据冗余问题
-- 拆分表
DROP TABLE emp;
CREATE TABLE employee( -- 员工信息表
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT,
dep_id INT
);
CREATE TABLE department(-- 部门信息表
id INT PRIMARY KEY AUTO_INCREMENT,
dep_name VARCHAR(30),
dep_location VARCHAR(30)
);
-- 添加数据
-- 添加部门
INSERT INTO department (dep_name,dep_location) VALUES ('研发部','深圳');
INSERT INTO department (dep_name,dep_location) VALUES ('销售部','北京');
-- 添加员工信息
INSERT INTO employee (NAME,age,dep_id) VALUES('马化腾',20,1);
INSERT INTO employee (NAME,age,dep_id) VALUES('马云',26,1);
INSERT INTO employee (NAME,age,dep_id) VALUES('周杰伦',18,2);
INSERT INTO employee (NAME,age,dep_id) VALUES('蔡依林',19,2);
INSERT INTO employee (NAME,age,dep_id) VALUES('求伯君',22,1);
INSERT INTO employee (NAME,age,dep_id) VALUES('詹姆斯高斯林',30,1);
INSERT INTO employee (NAME,age,dep_id) VALUES('汤姆克鲁斯',20,2);
INSERT INTO employee (NAME,age,dep_id) VALUES('丁磊',27,1);
这样做看似解决了之前所说的数据冗余,操作不方便等问题,但是又有了新问题,比如我现在直接将部门表中一个部门删掉,并且是可以删除成功的,但是我员工表中还有几个员工在被删除的这个部门,这样的话就会出现逻辑问题,所以应该有一个约束:只有当前部门中没有员工时,才能删除该部门。
语法:
create table 表名(
……
外键列
constraint 外键名称 foreign key (外键列名称) references 关联表名称(主表列名称)
);
案例:
-- 添加外键
DROP TABLE employee;
DROP TABLE department;
CREATE TABLE department(-- 部门信息表
id INT PRIMARY KEY AUTO_INCREMENT,
dep_name VARCHAR(30),
dep_location VARCHAR(30)
);
CREATE TABLE employee( -- 员工信息表
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT,
dep_id INT ,-- 外键列
CONSTRAINT emp_dep_fk FOREIGN KEY (dep_id) REFERENCES department(id)
);
-- 添加数据
-- 添加部门
INSERT INTO department (dep_name,dep_location) VALUES ('研发部','深圳');
INSERT INTO department (dep_name,dep_location) VALUES ('销售部','北京');
-- 添加员工信息
INSERT INTO employee (NAME,age,dep_id) VALUES('马化腾',20,1);
INSERT INTO employee (NAME,age,dep_id) VALUES('马云',26,1);
INSERT INTO employee (NAME,age,dep_id) VALUES('周杰伦',18,2);
INSERT INTO employee (NAME,age,dep_id) VALUES('蔡依林',19,2);
INSERT INTO employee (NAME,age,dep_id) VALUES('求伯君',22,1);
INSERT INTO employee (NAME,age,dep_id) VALUES('詹姆斯高斯林',30,1);
INSERT INTO employee (NAME,age,dep_id) VALUES('汤姆克鲁斯',20,2);
INSERT INTO employee (NAME,age,dep_id) VALUES('丁磊',27,1);
INSERT INTO employee (NAME,age,dep_id) VALUES('丁磊',27,3);-- 报错:Cannot add or update a child row
DELETE FROM department WHERE id = 1;-- 报错:annot delete or update a parent row:
-- 删除外键
ALTER TABLE employee DROP FOREIGN KEY emp_dep_fk;
-- 添加外键
ALTER TABLE employee ADD CONSTRAINT emp_dep_fk FOREIGN KEY (dep_id) REFERENCES department(id);
当我们设置了外键之后,我想修改我的部门id,可以操作,但是比较麻烦
-- 1. 将员工表中关联的数据设置为null
UPDATE employee SET dep_id=NULL WHERE dep_id = 1;
-- 2. 修改部门id
UPDATE department SET id = 10 WHERE id=1;
-- 3. 将外键为null的值再设置成10
UPDATE employee SET dep_id=10 WHERE dep_id IS NULL;
设置级联更新:ON UPDATE CASCADE
-- 设置级联更新
ALTER TABLE employee DROP FOREIGN KEY emp_dep_fk;-- 先删除外键
ALTER TABLE employee ADD CONSTRAINT emp_dep_fk FOREIGN KEY (dep_id) REFERENCES department(id) ON UPDATE CASCADE;
UPDATE department SET id = 1 WHERE id=10;
设置级联删除:ON DELETE CASCADE
-- 设置级联删除
ALTER TABLE employee DROP FOREIGN KEY emp_dep_fk;-- 先删除外键
ALTER TABLE employee ADD CONSTRAINT emp_dep_fk FOREIGN KEY (dep_id) REFERENCES department(id) ON UPDATE CASCADE ON DELETE CASCADE;--可以同时设置更新和删除,也可以分开设置
DELETE FROM department WHERE id=1;
什么是主键、外键 关系型数据库中的一条记录中有若干个属性,若其中某一个属性组(注意是组)能唯一标识一条记录,该属性组就可以成为一个主键。
比如:
学生表(学号,姓名,性别,班级) 其中每个学生的学号是唯一的,学号就是一个主键
课程表(课程编号,课程名,学分) 其中课程编号是唯一的,课程编号就是一个主键
成绩表(学号,课程号,成绩) 成绩表中单一一个属性无法唯一标识一条记录,学号和课程号的组合才可以唯一标识一条记录,所以学号和课程号的属性组是一个主键
成绩表中的学号不是成绩表的主键,但它和学生表中的学号相对应,并且学生表中的学号是学生表的主键,则称成绩表中的学号是学生表的外键。
同理:成绩表中的课程号是课程表的外键。
定义主键和外键主要是为了维护关系数据库的完整性,总结一下:
1.主键是能确定一条记录的唯一标识,比如,一条记录包括身份正号,姓名,年龄。身份证号是唯一能确定你这个人的,其他都可能有重复,所以,身份证号是主键。
2.外键用于与另一张表的关联。是能确定另一张表记录的字段,用于保持数据的一致性。比如,A表中的一个字段,是B表的主键,那他就可以是A表的外键。
主键、外键和索引的区别 主键 外键 索引 定义 唯一标识一条记录,不能有重复的,不允许为NULL 表的外键是另一表的主键, 外键可以有重复的, 可以是NULL 没有重复值,可以为NULL(会使索引无效) 作用 用来保证数据完整性 用来和其他表建立联系用的 提高查询排序的速度 个数 主键只能有一个 一个表可以有多个外键 一个表可以有多个惟一索引 外键约束 在上面“什么是主键、外键” 一小节中,我给大家灌输的思维是,学生表使用学号作为主键,课程表使用课程ID作为主键,成绩表使用学号、课程ID作为联合主键(联合主键(使用组合索引进行替代)以后压根就别用,主键的设计原则就是字段数目越少越好),这样就产成了一个问题,外键的参考键必须是另一个表的主键吗?
答案当然不是,但是参考键必须是唯一性索引。主键约束和唯一性约束都是唯一性索引。
错误的设计方式—[1215] Cannot add foreign key constraint 出现这种问题的原因一般有两个:
1.两张表里要设主键和外键的字段的数据类型或者数据长度不一样。 2.某个表里已经有记录了。
我当时属于第一个。
如何设计良好的数据库主键 摘抄一位知乎用户的回答:知乎链接—纪路
主键的话我的建议是自增整形,不要使用与业务相关的名字,仅用id即可,而效率问题都可以用索引来解决。因为主键的不可变的特性,如果选择不慎,会在未来产生难以预期的问题。比如你用int型做文章的id,但是如果在未来某一天文章数超过了无符号整形的最大值,你将没法将主键修改成bigint。或者为了给用户起一个唯一id用了自增主键,但是如果未来有其他的项目用户要合并进来,他也是这么做的。这时候为了区分不同的项目可能要在这个用户id前加一个前缀,这时候也没法修改主键的值。主键之所以叫做主键就是到什么时候都不能改,所以最好的方案就是使用自增数字id做主键,并且不要给这个主键赋予一个业务相关的意义。
总结上面前辈的一句话就是,不要将表中与业务相关的字段设置为主键,即使它可以唯一标识这一行,比如身份证号,学号等等,主键越没有意义,说明主键设置的越好。
主键、外键的使用 创建表 就按照我们上面的例子来建立三张表吧:student、course、score表。
创建student表:
[](javascript:void(0)
create table student(
pk_id bigint unsigned not null auto_increment primary key,
uk_sno int(10) unsigned not null,
name char(60) not null,
sex char(10) not null,
class char(60) not null,
constraint uk_sno unique (sno)
)enige = InnoDB, charset = utf8;
[](javascript:void(0)
创建course表:
[](javascript:void(0)
create table course(
pk_id bigint unsigned not null auto_increment primary key,
uk_course_id int(10) unsigned not null,
course_name char(30) not null,
credit int not null,
constraint uk_course_id unique (course_id)
)enige = InnoDB, charset=utf8;
[](javascript:void(0)
创建score表:
[](javascript:void(0)
create table score(
pk_id bigint not null auto_increment primary key,
fk_sno int(10) unsigned not null,
fk_course_id int(10) unsigned not null,
result int not null,
constraint fk_sno foreign key (fk_sno) references .student (sno),
constraint fk_course_id foreign key (fk_course_id) references .course (course_id)
)enige = InnoDB, charset=utf8;
[](javascript:void(0)
值得一说的是,创建外键的时候也会自动创建普通索引,所以fk_sno、fk_course_id其实是两个普通索引的名称。
对于使用IDEA的同学,我们会发现在设置外键的时候还有Update rule 和 Delete rule规则,对于这两个选项的解释,我们下面再说。
外键的使用–更新与删除 表已经建立成功,现在我们插入数据: student表:
INSERT INTO student(uk_sno, name, sex, class) VALUES(123456, “spider_hgyi”, “male”, “cs”); crouse表:
INSERT INTO course(uk_course_id, course_name, credit) VALUES(1, “csapp”, 10); score表:
INSERT INTO score(fk_sno, fk_course_id, result) VALUES(123456, 1, 100); 好了,现在三个表里都已经有了数据,现在我们尝试更新学生表中学号的信息:
UPDATE student SET uk_sno=12345678 WHERE uk_sno=123456; MySQL报错:
(1451, ‘Cannot delete or update a parent row: a foreign key constraint fails (bookmanager
.score
, CONSTRAINT fk_sno
FOREIGN KEY (fk_sno
) REFERENCES student
(uk_sno
))’) 看看错误告诉我们什么:不能删除或更新这一行,存在外键约束,score表中的fk_sno列是当前要更新的uk_sno的外键,也就是说,你要更新学生表中的学号,但是成绩表中的学号是你的外键,你不能不管它呀,删除也是同理。
要怎么解决?
还记得刚才我贴的那张IDEA的图片吗?那两个规则就可以帮助我们解决这个问题。
级联删除与更新 我们在更新与删除时遇到的外键约束解决方案分别对应设置Update rule与Delete rule。有如下四个选项:
1.CASCADE:从父表删除或更新且自动删除或更新子表中匹配的行。 2.SET NULL:从父表删除或更新行,并设置子表中的外键列为NULL。如果使用该选项,必须保证子表列没有指定NOT NULL。 3.RESTRICT:拒绝对父表的删除或更新操作。 4.NO ACTION:标准SQL的关键字,在MySQL中与RESTRICT相同。
可以看到我在创建外键的时候选择的是NO ACTION,也就是第四个选项。我们只需要选择CASCADE就可以啦。具体效果就不进行演示了。
如果你不用IDEA也没关系,接下来我给出SQL语句的实现(重新创建score表):
[](javascript:void(0)
create table score(
pk_id bigint not null auto_increment primary key,
fk_sno int(10) unsigned not null,
fk_course_id int(10) unsigned not null,
result int not null,
constraint fk_sno foreign key (fk_sno) references .student (sno) on update cascade on delete cascade,
constraint fk_course_id foreign key (fk_course_id) references .course (course_id) on update cascade on delete cascade
)enige = InnoDB, charset=utf8;
[](javascript:void(0)
补充 博主在学习阿里的Java开发手册时,他们对于外键与级联是这样描述的:
【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
说明:以学生和成绩的关系为例,学生表中的 student _ id 是主键,那么成绩表中的 student _ id则为外键。如果更新学生表中的 student _ id ,同时触发成绩表中的 student _ id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群 ; 级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
本来我是打算以后在脑海中抛弃外键与级联这部分知识的,但经过学长的敲打,不得不说我对阿里的盲目崇拜。
外键约束、级联更新与删除对于开发者是非常有用的,它确保了数据删除与更新的完整性。至于阿里所说的影响性能,学长反问我:“你的应用有多少人在用?阿里的应用有多少人在用?”
多表之间的关系其实也是我们数据库设计所要研究的重点。
**例子:**人 和 身份证,一个人对应一个身份证,一个身份证对应一个人,无论从那个角度都成立。这种关系就是一对一的关系
实现:一对一关系实现,可以在任意一方添加唯一外键
**例子:**班级和学生,一个班级对应多个学生,一个学生只能对应一个班级。这种关系就是一对多(多对一)的关系。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M49KVniy-1629445551956)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\一对多.jpg)]
处理:设置外键,多的一方关联到少的一方
**例子:**学生和课程,一个学生可以选择多门课,一门课可以被多个学生选择,这种关系就是多对多的关系。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-60lN2CUM-1629445551957)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\多对多.jpg)]
实现:多对多关系实现需要借助第三张中间表。中间表至少包含两个字段,这两个字段作为第三张表的外键,分别指向两张表的主键。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x4g9XmHG-1629445551957)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\多表关系分析图.jpg)]
SQL:
-- 创建书籍分类表book_category
CREATE TABLE IF NOT EXISTS book_category(
c_id INT AUTO_INCREMENT PRIMARY KEY,-- 分类id,主键,自增长
c_name VARCHAR(100) NOT NULL UNIQUE -- 分类名,非空,唯一
);
-- 创建 书籍表 book
/*
b_id: 书籍id , 主键 , 自增长
b_name:书籍名称 , 非空 ,唯一
b_date:上架时间,日期型
c_id: 外键列 , 所属分类
*/
CREATE TABLE IF NOT EXISTS book(
b_id INT PRIMARY KEY AUTO_INCREMENT,
b_name VARCHAR(100) NOT NULL UNIQUE,
b_date DATE,
c_id INT,
CONSTRAINT cid_bid_fk FOREIGN KEY (c_id) REFERENCES book_category(c_id)
);
-- 创建用户表 b_user
/*
u_id: 用户id, 主键 , 自增长
u_name:用户名称 , 非空 ,唯一
u_pwd:用户密码,非空
*/
CREATE TABLE IF NOT EXISTS b_user(
u_id INT PRIMARY KEY AUTO_INCREMENT,
u_name VARCHAR(50) NOT NULL UNIQUE,
u_pwd VARCHAR(100) NOT NULL
);
-- 创建中间表
CREATE TABLE IF NOT EXISTS user_book(
b_id INT,
u_id INT,
borrow_date TIMESTAMP,
PRIMARY KEY(b_id,u_id),-- 联合主键
FOREIGN KEY (b_id) REFERENCES book(b_id),
FOREIGN KEY (u_id) REFERENCES b_user(u_id)
);
设计数据库时,需要遵循的一些规范。
设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小。
目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。
—— 转自百度百科
简单来说,后一个范式都是在前一个范式的基础上添加了额外的要求,就好比我给你们布置作业:
第一范式:提交学习笔记,
第二范式:提交学习笔记,提交课堂演示代码
第三范式:提交学习笔记,提交课堂演示代码,提交课后练习代码
我们用表格来讲解:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NTeYuZ8f-1629445551958)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\范式用表.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cVuoTZ5x-1629445551958)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\第一范式.png)]
第二范式:在1NF的基础上,非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖)
了解第二范式首先需要了解几个概念
例如:学号(属性) --> 姓名。 (学号,课程名称) --> 分数
例如:(学号,课程名称) --> 分数
例如:(学号,课程名称) —> 姓名
例如:学号 —>系名,系名—> 系主任
例如:该表中码为:(学号,课程名称)。
接下来我来看一看怎么用第二范式来解决第一范式存在的问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FwLJ21bm-1629445551959)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\image-20201225092923879.png)]
学号 —>姓名,年龄,系名 , 年级
课程名称 —> 分数
所以说(学号,课程名称)为该表的码,为了消除部分依赖,我们需要把该表拆分一下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aXXubrdm-1629445551959)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\第二范式.png)]
拆分之后我们对比一下第一范式中存在的问题,哪些问题被解决了?只有第一个问题被有效解决了,其他问题还是存在。
3. **第三范式:在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)**
第三范式主要是为了解决传递依赖,所以我们继续拆分表。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YGLjQWss-1629445551960)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\第三范式.png)]
另外三个范式,要求更严格,我们目前不用去研究,只要掌握前三个范式,在工作中,就可以通过这三个范式的应用去检验我们设计的表是否合理和规范。
该操作一般是由我们数据库管理员(DBA)来完成的。为什么要进行数据库的备份与还原,就是为了防止在数据库出现不正常状况时,出现数据丢失等问题,为了保证数据的安全性,我们每一天 都要对数据库进行备份。
我们可以通过两种方式来备份数据库:
备份:
mysqldump -u用户名 -p密码 数据库名 > 保存的路径
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oTTLNCw8-1629445551961)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\image-20201225104540849.png)]
还原:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8r7w1gM6-1629445551961)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\image-20201225105150969.png)]
备份:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iLUHB8qm-1629445551962)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\图形化界面备份.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3b4wQgho-1629445551962)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\图形化界面备份2.png)]
还原:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lSijXDip-1629445551963)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\图形化界面还原.png)]
选择刚才备份的sql脚本 执行即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TDqQEtMP-1629445551963)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\图形化界面还原2.png)]
多表查询 与 单表查询大致相同,我们先来回顾一下查询的语法
复习:查询的语法
select 字段列表 from 表名列表 where 条件列表
grop by 分组字段 having 分组之后的条件 order by 排序 limit 分页限定
案例SQL:
USE test;
-- 创建部门表
CREATE TABLE dept (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);
/*
解决插入数据乱码问题
*/
SHOW FULL COLUMNS FROM dept;-- 查看所有字段的编码集
ALTER TABLE dept CONVERT TO CHARACTER SET utf8; -- 将所有字段的编码集统一修改为:utf8
INSERT INTO dept (NAME) VALUES ('财务部'),('市场部'),('开发部');
-- 创建员工表
CREATE TABLE emp(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20) NOT NULL, -- 员工编号
gender CHAR(1),-- 性别
salary DOUBLE,-- 工资
join_date DATE,-- 入职日期
dept_id INT,-- 部门id
CONSTRAINT deptid_dept_fk FOREIGN KEY (dept_id) REFERENCES dept(id)-- 外键,关联部门表id
);
INSERT INTO emp (NAME,gender,salary,join_date,dept_id) VALUES
('周杰伦','男',20000,'2015-5-28',2),
('林俊杰','男',16500,'2016-8-13',2),
('马化腾','男',25000,'2014-3-02',3),
('蔡依林','女',9800,'2016-12-1',1),
('马云','男',22000,'2014-5-8',3),
('埃隆马斯克','男',10000,'2016-1-17',2),
('蔡健雅','女',9000,'2016-7-1',1);
SELECT * FROM dept;
SELECT * FROM emp;
简单的多表查询:select * from emp,dept;
查询所得结果为笛卡尔积(有两个集合A,B,取A集合 和 B集合中所有元素的组合数 ),共21条数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MykXfj9B-1629445551965)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\简单多表查询结果.png)]
通过观察结果,我们发现,多表查询中出现了许多无用的数据,比如周杰伦的部门信息,即出现了 2,还数显了 1和 3,这种情况很显然不是我们想要的。
-- 隐式内连接
-- 查询所有员工信息及对应的部门信息
SELECT * FROM emp,dept WHERE emp.`dept_id` = dept.`id`;
-- 查询员工表的名称,性别。部门表的名称
SELECT emp.`id`,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 emp.`id`,emp.`name`,emp.`dept_id`,dept.`name` FROM emp INNER JOIN dept ON emp.`dept_id` = dept.`id`;
SELECT emp.`id`,emp.`name`,emp.`dept_id`,dept.`name` FROM emp JOIN dept ON emp.`dept_id` = dept.`id`;
内连接查询注意事项
外连接查询
语法:select 字段列表 from 表1 left [ outer ] join 表2 on 条件
-- 左外连接
-- 我们先插入一条数据,部门id为null
INSERT INTO emp (NAME,gender,salary) VALUES ('邓紫棋','女',8500);
-- 使用内连接先查看
SELECT
t1.`name`,t1.`gender`,t2.`name`
FROM
emp t1 , dept t2
WHERE
t1.`dept_id` = t2.`id`;
-- 没有邓紫棋这名员工信息,因为内连接会将数据为null的情况排除
-- 接下来我们使用左外连接
SELECT
t1.`id`,
t1.`name`,
t1.`gender`,
t2.`id`,
t2.`name`
FROM
emp t1
LEFT OUTER JOIN
dept t2
ON
t1.`dept_id` = t2.`id`;
-- 接下来我们使用右外连接
SELECT
t2.`id`,
t2.`name`,
t2.`gender`,
t1.`id`,
t1.`name`
FROM
dept t1
RIGHT OUTER JOIN
emp t2
ON
t2.`dept_id` = t1.`id`;
左外连接 和 右外连接 是一个相对概念,关键字左右的表位置调换一下 效果是一样的。
**概述:**查询中嵌套查询,其中嵌套查询成为子查询。
-- 查询工资最高的员工信息
-- 1. 查询最高工资
SELECT MAX(emp.`salary`) FROM emp;
-- 2. 查询员工信息,其工资=最高工资
SELECT * FROM emp WHERE emp.`salary`=25000;
-- 子查询(嵌套查询)
SELECT * FROM emp WHERE emp.`salary` =(SELECT MAX(emp.`salary`) FROM emp);
分类:
子查询结果是单行单列的
-- 查询工资低于平均工资的员工
SELECT * FROM emp WHERE emp.`salary` <(SELECT AVG(emp.`salary`) FROM emp);
子查询结果是多行单列的
查询可以作为判断条件,与运算符 in
一起使用
-- 2. 查询部门编号是2 或 3的员工信息
SELECT * FROM emp WHERE emp.`dept_id`=2 OR emp.`dept_id`=3;
-- 简化
SELECT * FROM emp WHERE emp.`dept_id` IN (2,3);
-- 子查询简化
SELECT * FROM emp WHERE emp.`dept_id` IN (SELECT id FROM dept WHERE NAME ='市场部' OR NAME ='开发部');
子查询结果是多行多列的
-- 查询 入职日期在 2015-5-28 之后入职的员工信息和部门信息
-- 1. 先查询日期在2015-5-28 之后入职的员工信息
SELECT * FROM emp WHERE emp.`join_date`>'2015-05-28';-- 多行多列查出来的结果相当于一张表,
-- 2. 子查询
SELECT * FROM (SELECT * FROM emp WHERE emp.`join_date`>'2015-05-28') t2 ,dept t1 WHERE t1.`id` = t2.dept_id;
概述:
如果一个包含多个步骤的业务操作,被事务管理,那么这些操作要么同时成功,要么同时失败。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Og0kmjx8-1629445551970)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\事务图解.jpg)]
根据上图,我们使用事务的三个操作
事务代码演示:
CREATE TABLE test(
NAME VARCHAR(10),
money DOUBLE(7,2)
);
INSERT INTO test (NAME,money) VALUES ('张三',1000),('李四',1000);
SELECT * FROM test;
UPDATE test SET test.`money` = 1000 - 500 WHERE NAME='张三';
UPDATE test SET test.`money` = 1000 + 500 WHERE NAME='李四';
SELECT * FROM test;
UPDATE test SET test.`money` = money - 500 WHERE NAME='张三';
出错了。。。
UPDATE test SET test.`money` = money + 500 WHERE NAME='李四';
SELECT * FROM test;
这样张三的钱被减了500,但是李四账户金额并没有加
START TRANSACTION;-- 开启事务
UPDATE test SET test.`money` = money - 500 WHERE NAME='张三';
出错了。。。
UPDATE test SET test.`money` = money + 500 WHERE NAME='李四';
SELECT * FROM test;
我们分别开启两个sqlyog进行该表的查询,一个数据是(500,1000) 一个是 (1000,1000),(500,1000)这个数据是临时数据,当你把连接断开时,该临时数据会消失。
5. 我们 添加回滚
-- 还原数据
UPDATE test SET test.`money` = money WHERE NAME='张三';
UPDATE test SET test.`money` = money WHERE NAME='李四';
START TRANSACTION;-- 开启事务
UPDATE test SET test.`money` = money - 500 WHERE NAME='张三';
出错了。。。
UPDATE test SET test.`money` = money + 500 WHERE NAME='李四';
-- 有问题,数据回滚,回滚到开启事务时的状态
ROLLBACK;
添加回滚后数据又回到了开启时的状态,并且两个窗口查询结果都为(1000,1000)
START TRANSACTION;-- 开启事务
UPDATE test SET test.`money` = 1000 - 500 WHERE NAME='张三';
-- 出错了。。。
UPDATE test SET test.`money` = 1000 + 500 WHERE NAME='李四';
-- 没有问题,提交事务
COMMIT;
commit之前,一个窗口显示(500,1500),另一个窗口显示(1000,1000),是因为此时(500,1500)还是临时数据,commit之后,两个窗口数据就统一了,因为此时会把这真正的数据进行一个修改,这样就保证了数据的安全性。
注意事项:
MySQL数据库中事务默认自动提交
事务提交方式:
自动提交:
手动提交
需要先手动开启事务,再提交
3. 修改事务的默认提交方式:
-- 查看事务默认提交方式
SELECT @@autocommit; -- 1:自动提交 0:手动提交
SET @@autocommit=0;-- 设置为手动提交方式
UPDATE test SET test.`money`=100; -- 修改完查看,因为有临时数据,所以效果是正常被修改了
SELECT * FROM test;-- 断开连接重新打开sqlyog,因为事务没有提交,所以数据又会恢复到之前的1000
1. 原子性:是不可分割的最小操作单位,要么同时成功,要么同时失败
概述:
多个事务之间是相互隔离的,相互独立的。但是如果多个事务操作同一批数据,则会引发一些问题,设置不同的隔离级别就可以解决这些问题
存在问题:
1. 脏读:一个事务,读取到另一个数据没有提交的数据
2. 不可重复度(虚读):在同一个事务中,两次读取到的数据不一致
3. 幻读:一个事务1操作(DML)数据表中的所有数据,另一个事务2此时插入数据,事务1查询数据发现与自己修改的数据不一致,就好像产生了幻觉一样。
隔离级别:
1. read uncommitted: 读未提交
- 问题:脏读,不可重复读,幻读
2. read committed:读已提交(Oracle 默认)
- 问题:不可重复读,幻读
3. repeatable read: 可重复读(MySQL默认)
- 问题:幻读
4. serializable: 串行化
- 可以解决所有问题
注意:
隔离级别从1-5安全性越来越高,但是效率越来越低。我们在选择隔离级别时需要兼顾安全和效率。
查询隔离级别:
-- 查询隔离级别
SELECT @@tx_isolation;
设置隔离级别:(需重新连接,才可生效。)
-- 设置隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
问题演示:
1. read uncommitted: 读未提交
窗口1:
-- 设置隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- 查看隔离级别
SELECT @@tx_isolation;
USE test;
-- 开启事务(两窗口同时开启事务)
START TRANSACTION;
-- 更新完数据后,在窗口二种查看该表中的数据
UPDATE test SET test.`money` = test.`money`- 500 WHERE NAME='张三';
UPDATE test SET test.`money` = test.`money` + 500 WHERE NAME='李四';
ROLLBACK;
窗口2:
-- 查看隔离级别
SELECT @@tx_isolation;
USE test;
-- 开启事务
START TRANSACTION ;
-- 查询两次
-- 回滚前:500,1500 (脏读)
-- 回滚后:1000,1000 (不可重复读)
SELECT * FROM test;
窗口1:
-- 2. read committed: 读已提交
-- 设置隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 查看隔离级别
SELECT @@tx_isolation;
USE test;
-- 开启事务(两窗口同时开启事务)
START TRANSACTION;
UPDATE test SET test.`money` = test.`money`- 500 WHERE NAME='张三';
UPDATE test SET test.`money` = test.`money` + 500 WHERE NAME='李四';
SELECT * FROM test;
COMMIT
窗口2:
-- 查看隔离级别
SELECT @@tx_isolation;
USE test;
-- 开启事务
START TRANSACTION ;
-- 查询两次
-- 提交前:1000,1000
-- 提交后:500,1500 不可重复读,因为两次结果不同
SELECT * FROM test;
窗口1:
-- 1.repeatable read: 可重复读
-- 设置隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 查看隔离级别
SELECT @@tx_isolation;
USE test;
-- 开启事务(两窗口同时开启事务)
START TRANSACTION;
UPDATE test SET test.`money` = test.`money`- 500 WHERE NAME='张三';
UPDATE test SET test.`money` = test.`money` + 500 WHERE NAME='李四';
SELECT * FROM test;
COMMIT;
窗口2:
-- 查看隔离级别
SELECT @@tx_isolation;
USE test;
-- 开启事务
START TRANSACTION ;
-- 查询三次
-- 提交前:1000,1000
-- 提交后:1000,1000 不可重复读,因为两次结果不同
commit;-- 本窗口再次提交,再次查询:500,1500 不可重复度问题解决。
SELECT * FROM test;
serializable: 串行化
该级别类似于java中的锁,一个事务1在操作表时,另一个事务2进行查询,该查询会被阻塞,直到事务1结束操作,事务2的查询动作才能继续向下进行。
回顾:
DDL(Data Definition Language):数据库定义语言
用来定义数据库对象:数据库,表,列表等。关键字create , drop , alter 等
DML(Data Manipulation Language)数据库操作语言
用来对数据库中表的数据进行增删改。关键字:insert , delete , updata等
DQL(Data Query Language)数据库查询语言
用来查询表中的记录。关键字:select , where 等
DCL(Data Control Language)数据库控制语言
用来定义数据库的访问权限和安全级别,及创建用户。关键字 GRANT,REVOKE
DCL的使用一般是公司DBA-数据库管理员使用的。
1. 查询用户
-- 切换数据库
USE mysql;
-- 查询user表
SELECT * FROM USER;
2. 创建用户
create user ‘用户名’@‘主机名’ IDENTIFIED BY ‘密码’;
-- 创建用户
CREATE USER 'zhangsan'@'localhost' IDENTIFIED BY '123456';
-- % 通配符 任意主机都可以登录
CREATE USER 'lisi'@'%' IDENTIFIED BY '123456';
3. 删除用户
DROP USER ‘用户名’@‘主机名’;
-- 删除用户
DROP USER 'zhangsan'@'localhost';
4. 修改密码
SET PASSWORD FOR ‘用户名’@‘主机’ = PASSWORD(‘新密码’);
-- 修改密码
SET PASSWORD FOR 'lisi'@'%'=PASSWORD('aaa');
1. 查询权限
SHOW GRANTS FOR ‘用户名’@‘主机名’;
-- 查看用户权限
SHOW GRANTS FOR 'lisi'@'%';
SHOW GRANTS FOR 'root'@'localhost';
2. 授予权限
用户在没有赋权时,可以登录mysql,但是无法操作数据库
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CTgzqTGI-1629445551972)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\image-20201226064818314.png)]
GRANT 权限列表 ON 数据库.表名 to ‘用户名’@‘主机名’;
GRANT SELECT ON test.`test` TO 'lisi'@'%';-- 该操作只能查询,其他任何操作都会被拒绝
GRANT SELECT, DELETE,UPDATE ON test.`test` TO 'lisi'@'%';
-- 给zhangsan 用户赋予所有权限,在任意数据库和任意表上
GRANT ALL ON *.* TO 'zhangsan'@'%';
3. 撤销权限
revoke 权限列表 on 数据库名.表名 from ‘用户名’@‘主机名’ ;
REVOKE ALL ON *.* FROM 'lisi'@'%';
索引(index)是帮助MySQL进行高效获取数据的数据结构(有序)。在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8vLgSDgK-1629445551975)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\索引示意图.jpg)]
优势:
劣势:
索引是在MySQL的存储引擎层中实现的,而不是在服务器层实现的,所以每种存储引擎的索引都不一定完全相同,也不是所有存储引擎都支持所有的索引类型。
MySQL 目前提供了4中索引:
BTREE 索引 : 最常见的索引类型,大部分索引都支持 B 树索引。
HASH 索引:只有Memory引擎支持 , 使用场景简单 。
R-tree 索引(空间索引):空间索引是MyISAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少,不做特别介绍。
Full-text (全文索引) :全文索引也是MyISAM的一个特殊索引类型,主要用于全文索引,InnoDB从Mysql5.6版本开始支持全文索引。
MyISAM、InnoDB、Memory三种存储引擎对各种索引类型的支持
索引 | InnoDB引擎 (默认) | MyISAM引擎 | Memory引擎 |
---|---|---|---|
BTREE索引 | 支持 | 支持 | 支持 |
HASH 索引 | 不支持 | 不支持 | 支持 |
R-tree 索引 | 不支持 | 支持 | 不支持 |
Full-text | 5.6版本之后支持 | 支持 | 不支持 |
Normal 普通索引 :表示普通索引,大多数情况下都可以使用
Unique 唯一索引 :表示唯一的,不允许重复的索引,如果该字段信息保证不会重复例如身份证号用作索引时,可设置为unique
其中 primary key 是一种特殊的唯一索引
FullText 全文索引 :表示全文搜索,在检索长文本的时候,效果最好,短文本建议使用Index。如果在检索的时候数据量比较大,可以先将数据放入一个没有全局索引的表中,然后在用Create Index创建的Full Text索引,要比先为一张表建立Full Text然后在写入数据要快的很多。
SPATIAL 空间索引:空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。MYSQL使用SPATIAL关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MYISAM的表中创建(了解)
空间数据又称几何数据,它用来表示物体的位置、形态、大小分布等各方面的信息,是对现世界中存在的具有定位意义的事物和现象的定量描述。根据在计算机系统中对地图是对现实教想的存储组织、处理方法的不同,以及空间数据本身的几何特征,空间数据又可分为图形数据和图像数据。
——百度百科
索引在创建表的时候,可以同时创建,也可以alter命令随时增加新的索引。
CREATE DATABASE demo_01 DEFAULT CHARSET=utf8;
USE demo_01;
CREATE TABLE `city` (
`city_id` INT(11) NOT NULL AUTO_INCREMENT,
`city_name` VARCHAR(50) NOT NULL,
`country_id` INT(11) NOT NULL,
PRIMARY KEY (`city_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
CREATE TABLE `country` (
`country_id` INT(11) NOT NULL AUTO_INCREMENT,
`country_name` VARCHAR(100) NOT NULL,
PRIMARY KEY (`country_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `city` (`city_id`, `city_name`, `country_id`) VALUES(1,'西安',1);
INSERT INTO `city` (`city_id`, `city_name`, `country_id`) VALUES(2,'NewYork',2);
INSERT INTO `city` (`city_id`, `city_name`, `country_id`) VALUES(3,'北京',1);
INSERT INTO `city` (`city_id`, `city_name`, `country_id`) VALUES(4,'上海',1);
INSERT INTO `country` (`country_id`, `country_name`) VALUES(1,'China');
INSERT INTO `country` (`country_id`, `country_name`) VALUES(2,'America');
INSERT INTO `country` (`country_id`, `country_name`) VALUES(3,'Japan');
INSERT INTO `country` (`country_id`, `country_name`) VALUES(4,'UK');
CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX 索引名称 -- 不指定索引类型默认为普通索引
[using 索引数据结构类型] -- 不指定默认 BTree结构
ON 表名(列名1,列名2...)
针对测试表建立索引
注意:在MySQL数据库中,针对主键都会自动创建一个 主键索引。
-- 针对city_name创建索引
CREATE INDEX idx_city_name ON city(city_name);
语法:
show index from 表名
-- 查看索引
SHOW INDEX FROM city;
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W3wt1Krj-1629445551976)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\image-20201226214112543.png)]
语法:
drop index 索引名 on 表名
-- 删除索引
DROP INDEX idx_city_name ON city;
alter table tb_name add primary key(column_list);
该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL
alter table tb_name add unique index_name(column_list);
这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)
alter table tb_name add index index_name(column_list);
添加普通索引, 索引值可以出现多次。
alter table tb_name add fulltext index_name(column_list);
该语句指定了索引为FULLTEXT, 用于全文索引
演示:
-- 添加唯一索引
ALTER TABLE city ADD UNIQUE idx_city_name(city_name);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pLfvKhoZ-1629445551976)(D:\工作文件夹\录课\JavaEE 二阶段\1.MySql\img\image-20201226215110957.png)]
合理的设计索引在最理想的情况下提高数据查询的效率
1. 我们需要针对什么样的表建立索引?
对查询频次较高,且数据量比较大的表建立索引。索引只是针对查询效率优化的数据结构,如果一张表只做insert,update操作我还有必要给它添加索引吗?当然没有必要。
2. 我们应该针对什么样的字段建立索引?
索引字段的选择,最佳的候选列应该从where子句中的条件中提取,如果where子句的组合比较多,那么应当挑选最常用,过滤效果最好的列的组合建立索引
3. 建立索引尽量使用唯一索引
因为唯一索引的区分度高,这样可以是检索效率提高。
4. 索引是不是越多越好?
当然不是越多越好。索引越多,维护索引的代价自然也就越高。对于插入、更新、删除等DML操作比较频繁的表来说,索引过多,会引入相当高的维护代价,降低DML操作的效率,增加相应操作的时间消耗。
5. 使用短索引
如何理解短索引:段索引就是指建立索引的列的数据的长度,你不能在一个对应数据类型是text的列上面建立一个索引,而是选择简单的数据类型比如int,长度比较短的varchar,这样一次IO读取的索引会更多,越容易命中对应的值。
6. 使用最左前缀
N个列组合创建组合索引,那么相当于创建了N个索引,如果查询时where子句中使用了组成该索引的几个字段,那么查询SQL的操作就可以利用组合索引来提高查询效率
例子:
-- 创建组合索引
CREATE INDEX idx_id_name_gender ON emp(id,NAME,gender);
-- 等价于
-- 对id创建了索引
-- 对id,name 创建了索引
-- 对id,name,gender 创建了索引
视图(View)是一种虚拟存在的表。视图并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。通俗的讲,视图就是一条SELECT语句执行后返回的结果集。所以我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上。
创建视图的语法:
CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
VIEW 视图名称 [(column_list)]
AS select 语句
[WITH [CASCADED | LOCAL] CHECK OPTION]
https://blog.csdn.net/luyaran/article/details/81018763
--[WITH [CASCADED | LOCAL] CHECK OPTION]详解
案例:
-- 显示城市和对应的国家信息
SELECT
t1.* ,t2.country_name
FROM
city t1,country t2
WHERE
t1.country_id = t2.country_id;
-- 将该查询结果封装到视图中
CREATE VIEW view_city_country
AS
SELECT
t1.* ,t2.country_name
FROM
city t1,country t2
WHERE
t1.country_id = t2.country_id;
-- 查询视图
SELECT * FROM view_city_country;
-- 更新视图
UPDATE view_city_country SET city_name='西安市' WHERE city_id=1;-- 更新视图实际是更新它所封装的基表中的数据
SELECT * FROM city;-- 基表中的数据已经被修改
修改视图的语法:
ALTER [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}]
VIEW 视图名称 [(column_list)]
AS 查询语句
[WITH [CASCADED | LOCAL] CHECK OPTION]
查看视图:
方式1:show tables
方式2:图形化界面
删除视图:
drop view 视图名称
存储过程和函数是 事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程和函数可以简化数数据库设计与开发的工作,减少数据在数据库和应用服务器之间的传输,因为应用服务器访问数据库的次数越多,数据传输效率越低,有了存储过程,应用服务器只需要访问一次数据库,就可以执行存储过程中封装的所有sql语句,对于提高数据处理的效率是有好处的。
存储过程和函数的区别在于函数必须有返回值,而存储过程没有。
语法:
CREATE PROCEDURE 存储过程名([存储过程需要的参数列表])
begin
-- 多条SQL语句
end;
案例:
-- 创建一个存储过程
-- 因为在命令行模式下end后要跟;表示存储过程结束,它前面的sql语句也要跟;表示该条语句结束,那么存储过程怎么写呢?
-- 我们可以用一条语句该别结束标记
DELIMITER $ -- 将结束标记修改为$
CREATE PROCEDURE pro_test()
BEGIN
SELECT 'Hello MySQL';
END$
语法:
call 存储过程名()
CALL pro_test();
语法:
-- 查询指定数据库中的所有的存储过程
select name from mysql.proc where db='数据库名';
-- 查询存储过程的状态信息
show procedure status;
-- 查询某个存储过程的定义
show create procedure 存储过程名;
案例:
-- 查询指定数据库中的所有的存储过程
SELECT NAME FROM mysql.`proc` WHERE db='demo_01';
-- 查询存储过程的状态信息
SHOW PROCEDURE STATUS;
-- 查询某个存储过程的定义
SHOW CREATE PROCEDURE pro_test;
语法:
drop procedure 存储结构名
-- 删除存储过程
DROP PROCEDURE pro_test;
DECLARE
通过 DECLARE 关键字可以定义一个变量,该变量的作用范围只能在BEGIN - END 块中
语法
DECLARE 变量名[变量名2,变量名2...] 数据类型 [DEFAULT 默认值]
案例:
DELIMITER $
CREATE PROCEDURE pro_test2()
BEGIN
DECLARE num INT DEFAULT 10;-- 声明变量
SELECT CONCAT('num的值为:',num);-- concat()函数,将字符串进行拼接
END$
-- 执行存储过程
CALL pro_test2();
SET
直接赋值使用 SET,可以赋常量或者赋表达式。
语法
SET 变量名 = 值或表达式
案例:
-- 变量赋值
DELIMITER $
CREATE PROCEDURE pro_test3()
BEGIN
DECLARE num INT DEFAULT 0;
SET num = num + 10;
SELECT CONCAT('num的值为:',num);
END$
CALL pro_test3();
也可以通过select … into 进行变量赋值
案例:
-- select...into 赋值
DELIMITER $
CREATE PROCEDURE pro_test4()
BEGIN
DECLARE num INT;
SELECT COUNT(*) INTO num FROM city;
SELECT CONCAT('共有',num,'个城市');
END$
-- 调用之后,我们给city表添加一条数据,再次执行存储过程,其值会相应的改变
CALL pro_test4();
if
判断条件
then
-- 如果成立则执行此处的sql语句
sql语句
[elseif 判断条件 then sql语句] ...
[else sql语句]
end if;
案例:
-- if 选择结构
/*
需求:根据成绩判断其所属的阶段:优秀,一般,良好
socre>=90 优秀
90>socre>=80 一般
socre<80 良好
*/
DELIMITER $
CREATE PROCEDURE pro_test5()
BEGIN
DECLARE score INT DEFAULT 95; -- 成绩变量
DECLARE description VARCHAR(10) DEFAULT ''; -- 所属分段的描述信息
IF
score>=90
THEN
SET
description='优秀';
ELSEIF
score>=80 AND score<90
THEN
SET
description='一般';
ELSEIF
score<80
THEN
SET
description='良好';
END IF;
SELECT CONCAT('该同学的成绩为:',score,',属于',description);
END$
CALL pro_test5();
上一个案例,成绩是写死在存储过程中的,这样显然是不合理的,所以接下来我们学习传递参数,让我们在调用存储过程时可以给它传递一个成绩的值,然后让他根据传递的值显示对应的分段信息
create procedure 存储过程名([in/out/inout] 参数名 参数类型)
...
IN : 该参数可以作为输入,也就是需要调用方传入值 , 默认
OUT: 该参数作为输出,也就是该参数可以作为返回值
INOUT: 既可以作为输入参数,也可以作为输出参数
案例:
-- 传递参数
-- in-输入参数
DELIMITER $
CREATE PROCEDURE pro_test6(IN score INT)
BEGIN
DECLARE description VARCHAR(10) DEFAULT ''; -- 所属分段的描述信息
IF
score>=90
THEN
SET
description='优秀';
ELSEIF
score>=80 AND score<90
THEN
SET
description='一般';
ELSEIF
score<80
THEN
SET
description='良好';
END IF;
SELECT CONCAT('该同学的成绩为:',score,',属于',description);
END$
CALL pro_test6(70);
案例:
/*
需求:根据出入的成绩变量,获取当前成绩所属的分段(返回值)
*/
-- out-输入参数
DELIMITER $
CREATE PROCEDURE pro_test7(IN score INT,OUT description VARCHAR(10))
BEGIN
IF
score>=90
THEN
SET
description='优秀';
ELSEIF
score>=80 AND score<90
THEN
SET
description='一般';
ELSEIF
score<80
THEN
SET
description='良好';
END IF;
-- SELECT CONCAT('该同学的成绩为:',score,',属于',description);
END$
CALL pro_test7(90,@description); -- @description 用户会话变量
SELECT @description;
注意:
**@变量名:**这种变量叫做用户会话变量,代表这个会话过程中,它都是有效的,一旦断开连接,或会话窗口关闭,则@变量会被全部释放。
@@变量名:系统变量
方式一 :
CASE 所要判断的值
WHEN 是否匹配的值 THEN sql语句 -- 是否匹配的值 就相当于java中 case后面跟的值
[WHEN 是否匹配的值 THEN sql语句]
...
[ELSE sql语句] -- 相当于java中的default
END CASE;
方式二 :
CASE
WHEN 条件表达式 THEN sql语句 -- 条件表达式为真,则执行sql语句
[WHEN 条件表达式 THEN sql语句] ...
[ELSE sql语句]
END CASE;
案例:
/*
给定一个月份,计算该月所在的季度
*/
DELIMITER $
CREATE PROCEDURE pro_test8(IN mon INT)
BEGIN
DECLARE result VARCHAR(10);
CASE
WHEN mon>=1 AND mon<=3 THEN
SET result='第一季度';
WHEN mon>=4 AND mon<=6 THEN
SET result='第二季度';
WHEN mon>=7 AND mon<=9 THEN
SET result='第三季度';
WHEN mon>=10 AND mon<=12 THEN
SET result='第四季度';
END CASE;
SELECT CONCAT(mon,'属于',result);
END$
CALL pro_test8(2);
当满足条件时,则执行循环
while 条件 do
sql语句
end while;
-- 条件成立,执行do后面的语句
案例:
/*
计算从1 加到 n 的和
*/
DELIMITER $
CREATE PROCEDURE pro_test9(n INT) -- 默认是in 所以in可以省略
BEGIN
DECLARE total INT DEFAULT 0;
DECLARE num INT DEFAULT 1;
WHILE num<=n DO
SET total = total+num;
SET num=num+1;
END WHILE;
SELECT CONCAT('从1加到',n,'的和为',total);
END$
CALL pro_test9(100);
当满足条件时,则退出循环
REPEAT
sql语句
UNTIL 条件
END REPEAT;
-- 条件成立 结束循环
案例
-- repeat循环
/*
计算从1 加到 n 的和
*/
DELIMITER $
CREATE PROCEDURE pro_test10(n INT) -- 默认是in 所以in可以省略
BEGIN
DECLARE total INT DEFAULT 0;
REPEAT
SET total = total+n;
SET n = n -1;
UNTIL n=0 -- 这里没有;号 一定要注意
END REPEAT;
SELECT CONCAT('从1加到',n,'的和为',total);
END$
CALL pro_test10(100);
LOOP 实现简单的循环,退出循环的条件需要使用其他的语句定义,通常可以使用 LEAVE 语句实现
[别名:] LOOP
sql语句
END LOOP [别名]
-- 如果不在 sql语句 中增加退出循环的语句,那么 LOOP 语句可以用来实现简单的死循环。
案例:
-- loop循环
/*
计算从1 加到 n 的和
*/
DELIMITER $
CREATE PROCEDURE pro_test11(n INT) -- 默认是in 所以in可以省略
BEGIN
DECLARE total INT DEFAULT 0;
s:LOOP
SET total = total+n;
SET n=n-1;
IF n<0 THEN
LEAVE s;-- 退出循环
END IF;
END LOOP s;
SELECT total;
END$
CALL pro_test11(99);
概述:
游标是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用游标对结果集进行循环的处理。
我们使用select语句查询出来的结果通过展示界面我们可以看到结果中的数据,但是我还想对查询出的数据进行进一步的处理,这样的话我们就需要一个容器来存储这个结果集,这个容器就是游标。
游标的使用包括游戏】标的声明、OPEN、FETCH 和 CLOSE。
声明光标:
DECLARE 游标名 CURSOR FOR select语句 ;
OPEN 游标:
OPEN 游标名 ;
FETCH 光标:
FETCH 游标名 INTO var_name [, var_name] ...-- 相当于java中的迭代器
-- fetch 游标名 相当于 iterator.next()
CLOSE 光标:
CLOSE 游标名 ;
案例:
-- 准备一张测试表和几条数据
CREATE TABLE IF NOT EXISTS student(
id INT,#学号
NAME VARCHAR(20),#姓名
age INT,#年龄
sex VARCHAR(5),#性别
address VARCHAR(100),#地址
math INT,#数学
english INT#英语
);
INSERT INTO student (id,NAME,age,sex,address,math,english) VALUES
(1,'马云',18,'男','杭州',80,80),
(2,'马化腾',19,'男','深圳',75,60),
(3,'埃隆马斯克',31,'男','美国',76,93),
(4,'扎克伯格',27,'男','美国',65,NULL),
(5,'郎平',16,'女','上海',90,98),
(6,'姚明',32,'男','上海',80,81);
-- 查询学生表中的数据,并进行逐行展示
DELIMITER $
CREATE PROCEDURE pro_test12()
BEGIN
DECLARE id INT;#学号
DECLARE NAME VARCHAR(20);#姓名
DECLARE age INT;#年龄
DECLARE sex VARCHAR(5);#性别
DECLARE address VARCHAR(100);#地址
DECLARE math INT;#数学
DECLARE english INT;#英语
DECLARE student_result CURSOR FOR SELECT * FROM student;-- 声明游标
OPEN student_result;-- 开启游标
FETCH student_result INTO id,NAME,age,sex,address,math,english; -- fetch一次,取一行记录
SELECT CONCAT('id:',id,',name:',NAME,',age',age);
CLOSE student_result;-- 关闭游标
END$
CALL pro_test12();
游标结合循环
思路1:
1. 获取num = count(*)
-- 循环改进
/*
1. 获取num = count(*)
2. 每循环一次,num-1
3. 当num=0时,退出
*/
DELIMITER $
CREATE PROCEDURE pro_test13()
BEGIN
DECLARE id INT;#学号
DECLARE NAME VARCHAR(20);#姓名
DECLARE age INT;#年龄
DECLARE sex VARCHAR(5);#性别
DECLARE address VARCHAR(100);#地址
DECLARE math INT;#数学
DECLARE english INT;#英语
DECLARE student_result CURSOR FOR SELECT * FROM student;-- 声明游标
OPEN student_result;-- 开启游标
SET @num = (SELECT COUNT(*)FROM student);
REPEAT
SET @num=@num-1;
FETCH student_result INTO id,NAME,age,sex,address,math,english; -- fetch一次,取一行记录
SELECT CONCAT('id:',id,',name:',NAME,',age',age);
UNTIL @num=0
END REPEAT;
CLOSE student_result;-- 关闭游标
END$
CALL pro_test13();
思路2:句柄机制
-- 方式2:句柄机制
DELIMITER $
CREATE PROCEDURE pro_test14()
BEGIN
DECLARE id INT;#学号
DECLARE NAME VARCHAR(20);#姓名
DECLARE age INT;#年龄
DECLARE sex VARCHAR(5);#性别
DECLARE address VARCHAR(100);#地址
DECLARE math INT;#数学
DECLARE english INT;#英语
DECLARE has_data INT DEFAULT 1; -- 状态变量,1代表有数据
DECLARE student_result CURSOR FOR SELECT * FROM student;-- 声明游标
-- 设置一个句柄机制,当抓取不到数据时触发句柄机制,执行set后操作
DECLARE EXIT HANDLER FOR NOT FOUND SET has_data=0;
OPEN student_result;-- 开启游标
REPEAT
FETCH student_result INTO id,NAME,age,sex,address,math,english; -- fetch一次,取一行记录
SELECT CONCAT('id:',id,',name:',NAME,',age',age);
UNTIL has_data = 0
END REPEAT;
CLOSE student_result;-- 关闭游标
END$
CALL pro_test14();
存储过程 与 函数非常相似,存储过程虽然没有返回值,但是我们可以指定out来返回结果,所以存储函数能做的事情,存储过程也可以做。
CREATE FUNCTION 函数名([参数名 数据类型 ... ])
RETURNS 返回值类型
BEGIN
...
END;
案例:
-- 存储函数
-- 定义一个函数,通过国家id查询city表中对应有多少个城市
DELIMITER $
CREATE FUNCTION fun1(countryID INT)
RETURNS INT
BEGIN
DECLARE cnum INT;
SELECT COUNT(*) INTO cnum FROM city WHERE country_id = countryID;
RETURN cnum;
END$
SELECT fun1(1);
触发器是与表有关的数据库对象,指在 insert/update/delete 之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性 , 日志记录 , 数据校验等操作 。
使用别名 OLD 和 NEW 来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句级触发。
触发器类型 | NEW 和 OLD的使用 |
---|---|
INSERT 型触发器 | NEW 表示将要或者已经新增的数据 |
UPDATE 型触发器 | OLD 表示修改之前的数据 , NEW 表示将要或已经修改后的数据 |
DELETE 型触发器 | OLD 表示将要或者已经删除的数据 |
本章重点
1.获取Class 实例
2.创建运行时类的对象
3.调用运行时类的指定结构
4.代理模式-静态代理和动态代理
Reflection(反射) 他是动态语言的关键,反射机制允许程序在执行期间借助反射API取得任何类的内部信息,并能直接操作任意对象的内部属性和方法。
动态性:java程序执行有两个过程:编译和执行。动态性说的就是程序编译器并不能确定我们需要使用那些对象,只有在实际执行时才能确定。
理解:
加载完类之后,在堆内存的方法区产生一个Class类型的对象,这个对象包含了完整类的结构信息,我们可以通过它的获取类的结构。
1.动态语言
1**.动态语言**:是一类在运行时可以改变其结构的语言,通俗的解释:就是在运行时可以根据某些条件改变自身结构。代表:C# PHP IS Python
2.静态语言:运行时接口不可变的就是静态语言。代表 JAVA C++ C
表示类的结构信息,Class类的实例对应着加载到内存中的一个运行时类。
平时我们使用类名调用静态方法所使用的类名就是Class的一个实例。通过这个实例我们可以获取一些类的结构:比如属性,方法,构造器,实现的接口,所在的包,它的父类。。。
// 工具类
public class Person{
/*
Person 类
*/
@Override
//重写toString
//私有方法
}
1.通通反射方式创建对象
Class c = Person.class;
//通读Class对象 获取对应类的构造器
Constructor constructor = c.getConstructor(String.class,int.class);
//通过构造器创建对象
Object o = constructor.newInstance(”参数“); Person person = (Person) constructor.newInsrance(“参数"")
2..通过反射的方式调用对象的指定属性和方法
Field name = c.getDeeclaredField("name");
name.setAccessible(true)//设置允许不允许访问
name.set(Person,"100");
1.通过反射的方式创建对象
2.通过反射的方式调用对象的指定属性和方法
类的加载 类的连接 类的初始化
1.new 的方式 和反射的方式都可以创建对象,开发中到底用那个?
代码量和难易程度,建议使用new关键字,那么什么时候使用反射的方式呢?
实现动态性需要使用反射
2.反射机制和面向对象的封装 二者是不是相互矛盾?
不矛盾表面看二者是有一些矛盾,其实二者的应用领域不同,作用不同。
首先:封装是用来解决编码层面。在面向对象我们讲解封装的时候,隐藏对象属性和功能实现细节,仅对外提供对外的访问形式,作用是提高安全性,独立性,复用性。
私有方法都会被本类中的公共方法调用。
而反射:为了解决应用层面。当我们需要使用某一个的对象时,但 又不清楚对象内部结构时,就需要借助反射机制,动态的产生用户需求的对象。这才是反射现象的基本存在的意义。
类的加载过程
程序经过java.exe命令编译以后,会生成一个或多个字节码文件().class,接着我们使用java.exe命名对某个字节码文件解释运行。相当于把某个字节码文件加载到内存中,加载的过程称为类的加载。加载到内存中的类我们称为class的实例对象。
但是该类的对象我们不能直接用类名去表示,所以我们就在类名后添加一个属性,.class是Class的一个对象。
//1.通过.class获取
Class oneClass = one.class;
System.out.println(oneClass);
//2.通过运行时类的对象调用
one one1 = new one();
Class aClass = one1.getClass();
System.out.println(aClass);
//3.调用Class的静态方法forname
Class aClass1 = Class.forName("org.xiyou.util.Practice.one");
System.out.println(aClass1);
//4.类加载器
ClassLoader classLoader = Practice0.class.getClassLoader();
Class aClass2 = classLoader.loadClass("org.xiyou.util.Practice.one");
System.out.println(aClass2);
第三种是最常用的,第三种运行时才知道有没有错误,它更能体现动态性,反射主要关注的就是动态性,动态性强调的是运行时的状态。
Class c = Object.class;
Class se = Serializable.class;
//只要数据类型和维度不同就是不同的实例类
Class aClass = int[].class;
Class aClass1 = int[][].class;
//枚举
Class elementTypeClass = ElementType.class;
//注解
Class overrideClass = Override.class;
//返回值int void class 皆有对象
接口 父类 数组 枚举 注解 返回值
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过三步来对类进行初始化。
类的加载器
加载器的分类
//方式1:
Properties properties = new Properties();
FileInputStream fis = new FileInputStream("text.properties");
properties.load(fis);
String username = properties.getProperty("username");
String userpassword = properties.getProperty("userpassword");
System.out.println(username +"-----"+userpassword);
//方式2 读取配置文件
ReadProperties.class.getClassLoader();
public class Dome {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class personClass = Person.class;
Person.class.getConstructor().newInstance();
}
}
通过输入流
注意:
1.反射方式获取类的对象,其实它的底层也是调用的该类的无参构造,则会报错
2.无参构造的访问权限必须
1.获取父亲的泛型
2.获取类实现的接口:动态代理中使用
3.获取运行时类声明的讲解
两大核心技术:ioc容器和AOP切面编程(动态代理技术)
概述:代理模式是JAVA开发中使用较多的一种设计模式,代理模式就是为其他对象提供一种代理以控制对这个对象的访问,使用代理对象包装起来,然后代理对象取代原始对象的
9.2动态代理
概述:
xml:可扩展标记语言
标签都是自定义的
配置文件
在网络中传输
1.xml文档后缀:.xml
2.xml文档第一行必须是文档的声明:
3.xml文档中有且仅有一个标签
4.属性值必须使用引号引起来
5.标签必须关闭
6.xml标签区分大小写
案例:
比尔盖茨
29
Male
nake
22
Male
格式:
属性列表
version:版本号
encoding 编码格式,告知解析器解析引擎当前文档使用的字符集 默认:iso-8859-1.
standalone 是否独立
yes不依赖其他文件
no依赖其他文件
与css结合使用控制xml样式的,现在已经不用了
自定义标签
规则
1.名称就可以包含字母,数字以及其他字符
2.名称不能以其他数字或者标点符号开始
3.名称不能包含字母组合:xml
4.名字不能包含空格
规则 :
1.以键值对形式存在
2.属性必须用引号引起来
3.id属性值必须唯一
注意事项
1.文本中包含<,>,&必须使用转义字符
< < & &:amp > >
2.原样输出 CDATA区
格式:
3.1概述
规定xml文档的书写规范
1.1软件架构
B/S 浏览器端到/服务器端
C/S 客户端/服务器端
1.2资源分类:
静态资源:所有用户访问,得到的结构都是静态资源,静态资源可以直接被浏览器解析。 HTML CSS
动态资源:每个用户访问相同资源后可能不同,称为动态资源。
动态资源被访问后需要先转化为静态资源,在返回给浏览器。
如:jsp+Servlet+php
1.3网络通信三要素
IP:电子设备在网络中的唯一标识
端口:应用程序在计算机中 的唯一标识。0-65536
传输协议:规定了数据传输的规则
1.TCP : 可靠,建立连接,三次握手,数据大小没有限制
2.UDP:不可靠,不连接,传输速度快,数据大小有限制
1.4服务器:
服务器: 安装了服务器软件的计算机
服务器软件:接受用户请求,处理请求,做出响应软件
web服务器软件:接受用户请求,处理请求,做出响应的软件
它里面可以部署web项目,让用户通过浏览器来访问这些项目
动态资源必须依赖于web服务器才能运行,所以也被称为web容器
常见的java相关的web服务器的软件
Tomcat:Apache 基金组织 中小型javaEE服务器,仅支持少量javaEE规范,开源/免费,性能优异,支持集群,所有深受广大企业喜爱。
weblogic:oracle公司 大型的javaEE服务器,支持所有javaEE规范,收费
webSphere:IBM公司 大型的javaEE服务器,支持所有javaEE规范,收费
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rLAHm8Ej-1629445551977)(C:\Users\86151\Desktop\99-9.PNG)]
删除文件即可
bin/startup.bat
启动报错
解决方案L:logs–>catalina.当天日期。log—>在日志文件中查找错误信息–>百度
启动窗口闪退
解决方案:java_HOME配置问题,正确配置文件
错误日志信息中出现端口号被占用
解决方案1:cmd–》命令,netstat -ano–>找到对应端口PID–>任务管理器的选项卡—>找到对应PID进程–》右键关闭
解决方案2:修改Tomcat端口号–>conf–>server.xml修改端口号即可
正常关闭
bin/shutdown,bat
启动窗口:ctrl+c
方式1:直接将项目复制到webapps下
MyWeb:项目访问路径,其实他是一个虚拟路径,没有配置前,默认是实际路径。
方式2:将项目打包成war复制到webapps下
服务器会为我们自动部署项目,删除项目只需删除war包,对应的项目也会被自动删除
方式3:通过配置server.xml
1.在配置文件中找到Host标签中
2.在Host标签中加入配置
context:自闭和标签
docBase:项目的存放路径
path:访问的虚拟路径
缺点 server.xml是针对服务器的配置文件,一般我们不会修改这个文件,
方式4:在conf/Catalina/localhost/文件夹中创建 虚拟路径.xml
在该xml中添加标签
1.创建一个JAVAEE项目
2.定义一个类,实现Servlet接口
3.实现接口中的抽象方法
4.配置servlet
创建Servlet无法实现接口的解决方法
执行原理:
1.服务器接受到浏览器的请求后,会解析URL路径,获取访问的Servlet的资源路径(/demo)
2.查找web.xml文件中是否有对应的
3.如果有,就会找到对应的,拿到
对应的servlet的全限定类名。
4.tomcat将对应的字节码文件加载到内存中,并创建改类的对象。
5.调用servlet()方法
3.3Servlet方法和生命周期
1.生命周期
被创建:执行Init方法,只执行一次,
提供服务:执行service()方法
1.请求消息:客户端发服务器的数据
请求行
请求头
请求空行
请求体:
Post:有请求体
Get:以参数的方式,在请求行中
2.响应消息:服务器回应给客户端的数据
响应行:
响应头:
响应空行:
响应体:
1.组成
协议/版本 响应状态码 响应码描述
2.状态码
分类
1.1XX-服务器接受客户端信息,但是没有接受完成,等待一段时间后,发送1xx代码,询问客户端是否继续发送消息
2.2xx-成功代表:200
3.3xx-重定向。资源跳转的方式。代表码:302-重定向 304-访问缓存
4.4xx-客户端错误。比如访问路径有问题,没有对应资源404
5.5xx-服务端错误 。服务器代码出现异常:500.
JDBC:java DataBase Connectivty :java 数据库连接,简单说:java语音操作数据库
JDBC:所有关系型数据库的规则,即接口,各个数据库厂商实现这套接口,提供数据库驱动jar包,我们可以使用这套接口进行编程,真正执行代码的是数据库驱动jar包中的实现类。
步骤:
1.导入驱动jar包
2.注册驱动,让程序知道我们用的是那个版本的驱动
3.获取数据库连接对象,Connection
4.定义sql
5.获取执行sql语句的对象
6.执行sql,接收返回结果
7.处理结果
8.释放资源
1.1清理:将之前编译得到的旧class文件删除,为下次编译做准备,
1.2编译:将java文件编译成字节码文件
1.3测试:自动测试,自动调用junit
1.4报告:测试执行结果
1.5打包:动态web工程打包成war包,普通java工程是jar包。
1.6安装:将打包得到的文件复制到仓库中的指定位置
1.7部署:将war包复制到服务器指定目录下。
1.官网下载安装包,找一个没有中文的目录解压
2.配置环境变量
1.检查JAVA_HOME
2.配置Maven
MAVEN_HOME 或者 M2_HOME
path
3.cmd --> mvn - v
1.约定的目录结构
2.POM
3.坐标
4.依赖
5.仓库
6.生命周期,插件,目标
7.继承
8.集合
1.mvn compile :编译主程序
2.**mvn clean**: 清理
3.mvn test-compile: 编译测试程序
4.**mvn test**: 执行测试
5.mvn package:打包
注意:
执行Maven命令 ,必须进入pom.xml所在目录中
Maven工程所依赖 插件,并不包含在Maven核心程序中
当我们执行Maven命令,如果需要某些插件,Maven主程序首先会到本地仓库中寻找
pom: Project Object Model 项目对象模型
Pom.xml:对于Maven工程,它是核心配置文件,与构建相关的所有配置都在这个文件中配置,后期我们也是围绕这个文件。
在Maven中的坐标
1. : 公司或组织的域名倒序 + 项目名
com.xiyou
2.:模块名
login
3.:版本号
1.0-SNAPSHOT
注意:SNAPSHOT -不稳定版本 或者 快照版本,表示改项目出去开发状态,随时肯发生变化。
RELEASE- 代表稳定版本或者发布版本,一般项目上线后都会改为RELEASE版本
1.分类
本地仓库:当前电脑上部署的Maven仓库,它是为当前电脑上所有的Maven工程服务的
远程仓库:
私服:搭建在局域网中,为局域网中的Maven工程服务的
中央仓库:架设在互联网上,为全球所有的 Maven工程服务
中央仓库镜像:为中央仓库分担访问压力,提高用户体验性
2.仓库里面保存的内容
Maven自身所需要的插件
第三方框架或者jar包
我们自己开发的Maven项目
概念:
通过快捷键:Alt + Ctrl +shift +u调用项目的依赖关系
通过图示,我们可以看到maven_02虽然没有依赖hamcrest-core,但是他依赖了junit,所有它也就对hamcrest-core有了依赖,这就是依赖的传递性。
我们依赖Spring-core ,但是我们不想依赖spring-jcl,我们就可以
org.springframework
login
就近原则 先到先得原则
• 5.3.7.RELEASE
•
org.springframework
1.生命周期的各个阶段仅仅定义了该阶段的任务是什么
2.各个阶段和插件的目标是对应的
3.相似的目标由特定的插件来完成
4.插件的目标也可以看作是调用插件的命令
比如我们有三个模块
模块1:junit4.1
模块2:junit5.1
模块3:junit5.3
1.配置Maven插件
Maven home directory:Maven的安装目录
user settings file : maven 安装目录下 conf/settings.xml
Local repository: Maven的本地仓库
2.配置Runner
3.配置新建工程(与步骤一配置一致)
file–>new project settings —>setting for new Projects
MyBatis是一款优秀的持久成框架,它支持自定义SQL,存储过程以及高级映射,MyBatis免去了几乎所有的JDBC代码
复习JDBC代码
//1注册驱动
Class.forname("com.mysql.jdbc.Driver");
//2.获取连接
Connection connection = Drivermanager.getConnection(url)
//3.定义sql
String sql = "select * from student where id=?";
//4.预编译
PreparedStatement statement = connection.preparedStatement(sql);
//5.设置参数
statement.setInteger(1,2001)
//6.执行sql,封装结果
ResultSet result = statement.executeQuery();
Hibernate 框架:全自动映射ORM框架
JDBC :sql编写在代码中,耦合度比较高
实际开发中SQL会经常被更新,维护不易
Hibernate
内部自动生成SQL,不方便特殊优化
长难复杂的sql,对我们的Hibernate来说处理很不容易
基于全映射的全自动框架,进行字段部分映射比较困难,并且会导致数据库性能下降。
MyBatis
SQL和java编码分离 ,功能划分清楚,一个专注数据,一个专注业务。核心SQL可以自己编写。优化比较方便。
1.创建Maven工程,并导入相关依赖
2.创建实体类
3.创建接口
4.创建MyBatis主配置文件:SqlMapConfig.xml
5.创建映射配置文件:UserDao.xml
注意事项:
1.userdao usermap命名不同 是一个
1.1创建directory 和package的区别
directory :com.xiyou.dao 创建的是一级目录
package: com.xiyou.dao创建的是三级目录
1.2MyBatis的映射配置文件所在目录层级要与对应的dao层接口目录层级相同
1.3映射配置文件 mapper标签namespace属性值必须对应接口的全限定类名
1.4映射配置文件标签 id属性必须与对应接口 中的方法名一致,不能随便写。
1.5只要遵循2 3 4的规定,我们就不用去写接口的实现类。
步骤:
MyBatis在使用代理模式实现增删改查都做了什么?
1.创建代理对象
2.在代理对象中创建方法
@Test
public void findAllTest() throws IOException {
//1.读取配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建sqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
//3.用工厂生产SqlSession对象
SqlSession sqlSession = factory.openSession();
//4.使用SqlSession对象创建dao层的代理对象
UserDao userDao =sqlSession.getMapper(UserDao.class);
//5.使用代理对象执行方法
//6.释放资源
sqlSession.close();
}
public class UserDaoTest {
InputStream is;
SqlSessionFactoryBuilder builder;
SqlSessionFactory factory;
SqlSession sqlSession;
UserDao userDao;
@Before
public void init() throws IOException{
//1.读取配置文件
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
builder = new SqlSessionFactoryBuilder();
factory = builder.build(is);
//3.用工厂生产SqlSession对象
sqlSession = factory.openSession();
//4.使用SqlSession对象创建dao层的代理对象
userDao = sqlSession.getMapper(UserDao.class);
}
@After
public void destroy()throws IOException{
sqlSession.close();
is.close();
}
@Test
public void findAllTest() throws IOException{
//5.使用代理对象执行方法
List users = userDao.findAll();
//遍历
for(User user : users){
System.out.println(user);
}
}
}
别名
/*
ven工程服务的
远程仓库:
私服:搭建在局域网中,为局域网中的Maven工程服务的
中央仓库:架设在互联网上,为全球所有的 Maven工程服务
中央仓库镜像:为中央仓库分担访问压力,提高用户体验性
2.仓库里面保存的内容
Maven自身所需要的插件
第三方框架或者jar包
我们自己开发的Maven项目
概念:
通过快捷键:Alt + Ctrl +shift +u调用项目的依赖关系
通过图示,我们可以看到maven_02虽然没有依赖hamcrest-core,但是他依赖了junit,所有它也就对hamcrest-core有了依赖,这就是依赖的传递性。
我们依赖Spring-core ,但是我们不想依赖spring-jcl,我们就可以
org.springframework
login
就近原则 先到先得原则
• 5.3.7.RELEASE
•
org.springframework
1.生命周期的各个阶段仅仅定义了该阶段的任务是什么
2.各个阶段和插件的目标是对应的
3.相似的目标由特定的插件来完成
4.插件的目标也可以看作是调用插件的命令
比如我们有三个模块
模块1:junit4.1
模块2:junit5.1
模块3:junit5.3
1.配置Maven插件
Maven home directory:Maven的安装目录
user settings file : maven 安装目录下 conf/settings.xml
Local repository: Maven的本地仓库
2.配置Runner
3.配置新建工程(与步骤一配置一致)
file–>new project settings —>setting for new Projects
MyBatis是一款优秀的持久成框架,它支持自定义SQL,存储过程以及高级映射,MyBatis免去了几乎所有的JDBC代码
复习JDBC代码
//1注册驱动
Class.forname("com.mysql.jdbc.Driver");
//2.获取连接
Connection connection = Drivermanager.getConnection(url)
//3.定义sql
String sql = "select * from student where id=?";
//4.预编译
PreparedStatement statement = connection.preparedStatement(sql);
//5.设置参数
statement.setInteger(1,2001)
//6.执行sql,封装结果
ResultSet result = statement.executeQuery();
Hibernate 框架:全自动映射ORM框架
JDBC :sql编写在代码中,耦合度比较高
实际开发中SQL会经常被更新,维护不易
Hibernate
内部自动生成SQL,不方便特殊优化
长难复杂的sql,对我们的Hibernate来说处理很不容易
基于全映射的全自动框架,进行字段部分映射比较困难,并且会导致数据库性能下降。
MyBatis
SQL和java编码分离 ,功能划分清楚,一个专注数据,一个专注业务。核心SQL可以自己编写。优化比较方便。
1.创建Maven工程,并导入相关依赖
2.创建实体类
3.创建接口
4.创建MyBatis主配置文件:SqlMapConfig.xml
5.创建映射配置文件:UserDao.xml
注意事项:
1.userdao usermap命名不同 是一个
1.1创建directory 和package的区别
directory :com.xiyou.dao 创建的是一级目录
package: com.xiyou.dao创建的是三级目录
1.2MyBatis的映射配置文件所在目录层级要与对应的dao层接口目录层级相同
1.3映射配置文件 mapper标签namespace属性值必须对应接口的全限定类名
1.4映射配置文件标签 id属性必须与对应接口 中的方法名一致,不能随便写。
1.5只要遵循2 3 4的规定,我们就不用去写接口的实现类。
步骤:
MyBatis在使用代理模式实现增删改查都做了什么?
1.创建代理对象
2.在代理对象中创建方法
@Test
public void findAllTest() throws IOException {
//1.读取配置文件
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建sqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
//3.用工厂生产SqlSession对象
SqlSession sqlSession = factory.openSession();
//4.使用SqlSession对象创建dao层的代理对象
UserDao userDao =sqlSession.getMapper(UserDao.class);
//5.使用代理对象执行方法
//6.释放资源
sqlSession.close();
}
public class UserDaoTest {
InputStream is;
SqlSessionFactoryBuilder builder;
SqlSessionFactory factory;
SqlSession sqlSession;
UserDao userDao;
@Before
public void init() throws IOException{
//1.读取配置文件
is = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
builder = new SqlSessionFactoryBuilder();
factory = builder.build(is);
//3.用工厂生产SqlSession对象
sqlSession = factory.openSession();
//4.使用SqlSession对象创建dao层的代理对象
userDao = sqlSession.getMapper(UserDao.class);
}
@After
public void destroy()throws IOException{
sqlSession.close();
is.close();
}
@Test
public void findAllTest() throws IOException{
//5.使用代理对象执行方法
List users = userDao.findAll();
//遍历
for(User user : users){
System.out.println(user);
}
}
}
别名
/*