Lambda表达式的简单使用

lambda 表达式三要素:形式参数,箭头,代码块

Lambda 表达式的标准格式

格式:

( 形式参数 ) -> { 代码块 }

1
2
3
4
new Thread(() -> {
System.out.println("多线程启动了!");
}
).start();
  • 形式参数:如果有多个参数,参数之间用逗号隔开。如果没有参数,留空即可
  • ->:固定写法
  • 代码块:具体要做的内容

Lambda 表达式注意事项

  • 有一个接口,并且接口中有且仅有一个抽象方法
  • 必须要有上下文环境,才能推导出 Lambda 对应的接口(也就是说Lambda表达式不能单独使用)

Lambda 表达式实例

既没有参数也没有返回值的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
public class lambda01 {
public static void main(String[] args) {
myPrint(() -> {
System.out.println("既没有参数,也没有返回值");
});
}

private static void myPrint(Print a) { a.UserPrint(); }
}

interface Print {
void UserPrint();
}

有参数,无返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
public class lambda02 {
public static void main(String[] args) {
myFly((String s) -> {
System.out.println(s);
});
}

private static void myFly(Fly cat) { cat.AnimalFly("The cat is flying!"); }
}

interface Fly {
void AnimalFly(String s);
}

有参数,有返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Lambda03 {
public static void main(String[] args) {
MyOperate((int a, int b) -> {
return a + b;
});
}

private static void MyOperate(Operate operate) {
int sum = operate.UserOperate(1, 1);
System.out.println(sum);
}
}

interface Operate {
int UserOperate(int a, int b);
}

Lambda 表达式的省略模式

省略规则:

  1. 参数类型可以省略,但是又多个参数的情况下,不能只省略一个
  2. 如果参数有且仅有一个,那么小括号可以省略
  3. 如果代码块的语句只有一条,那么可以省略大括号和分号
  4. 如果代码块的语句只有一条,并且这条语句是 return 语句,那么可以省略大括号和分号和return
1
2
3
4
5
MyOperate((int a, int b) -> {
return a + b;
});
//============================
MyOperate((a, b) -> a + b);

Lambda 表达式和匿名内部类的区别

  1. 所需类型不同
    • 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
    • Lambda表达式:只能是接口
  2. 使用限制不同
    • 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
    • 如果接口中有多于一个的抽象方法,则只能使用匿名内部类
  3. 实现原理不同
    • 匿名内部类:编译之后,产生一个单独的.class 字节码文件
    • Lambda 表达式:编译之后,没有一个单独的.class 字节码文件,对应的字节码会在运行的时候动态生成

Java中的IO流

概述:

img

IO流原理及流的分类

img

img

流的分类

  • 按操作数据单位不同分为:字节流(8bit)(二进制文件),字符流(按字符)(文本文件)
  • 按数据流的流向不同分为:输入流,输出流
  • 按流的角色的不同分为:节点流,处理流/包装流

Java操作文件方式

创建文件

1
2
3
1. new File(String pathName);             //根据路径创建一个File对象
2. new File(File parent, String child); //根据父目录文件+子路径构建
3. new File(String parent, String child); //根据父目录+子路径构建

在创建文件对象后,对象只是存储在内存中,并没有实际在硬盘中创建,需要调用文件对象的 createNewFile() 方法才可真正创建。

1
2
3
4
5
6
7
8
public static void main(String[] args) {
File file = new File("e:\\test.txt");
try {
file.createNewFile();
} catch (Exception e) {
e.printStackTrace();
}
}

获取文件信息方法

  • getName //获取文件名
  • getAbsolutePath //获取绝对路径
  • getParent //获取文件父级目录
  • length //获取文件字节数
  • exists //判断文件是否存在,返回bool类型
  • isFile //判断是否是文件,bool类型
  • isDirectory //判断是否是目录,bool类型

创建目录

用mkdir方法,如果要创建多级目录,用mkdirs方法。

删除文件或者目录

用Delete方法,当用delete方法删除目录时,只能用来删除空目录。

字节流(二进制文件)

  • InputStream 字节输入流
  • OutputStream 字节输出流

img

如:拷贝图片文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    //将 1.jpg 文件中的内容读取到 2.jpg 中
static void FileCopy() {
String oldFile = "F:\\Programs\\JetBrains\\JavaSEprogram\\BiliBili\\src\\com\\pushihao\\File_\\File\\1.jpg";
String newFile = "F:\\Programs\\JetBrains\\JavaSEprogram\\BiliBili\\src\\com\\pushihao\\File_\\File\\2.jpg";
FileInputStream fileInputStream = null;
FileOutputStream fileOutputStream = null;
byte[] buf = new byte[1024];
int readLen;

try {
fileInputStream = new FileInputStream(oldFile);
fileOutputStream = new FileOutputStream(newFile, true);
while ((readLen = fileInputStream.read(buf)) != -1) {
fileOutputStream.write(buf, 0, readLen);
}

} catch (IOException e) {
e.printStackTrace();
} finally {
try {
fileInputStream.close();
fileOutputStream.close();

System.out.println("文件拷贝完毕!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

字符流(文本文件)

  • Reader 字符输入流
  • Writer 字符输出流

img

对应方法有:

img

img

处理流

img

字节流和处理流

注意:字节是最基本的单位,字节流既可以处理二进制文件,也可以处理字符文件

处理流 BufferedReader 和 BufferedWriter

  • BufferedReader 和 BufferedWriter 属于字符流,是按照字符来读取数据的,不能操作二进制文件
  • 关闭处理流时,只需要关闭外层流即可(外层流指高级的那层流)
1
BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));

关闭时,只需要关闭最外层即可

1
bufferedReader.close();

常用方法:

  1. BufferedReader
  • readLine 读取一行,但并不会读取换行符
  1. BufferedWriter
  • newLine 插入一个与系统相关的换行
  • write 多种重载,写入文件

处理流 BufferedInputStream 和 BufferedOutputStream

  • BufferedInputStream 和 BufferedOutputStream 是字节流,处理二进制文件

对象流 ObjectInputStream 和 ObjectOutputStream

  • 顾名思义,对象流就是可以把一个对象保存到文件(序列化)或者把对象从文件中读取出来(反序列化)
  • 序列化和反序列化
    1. 序列化就是在保存数据时,保存数据的值和数据类型
    2. 反序列化就是在恢复数据时,恢复数据的值和数据类型
    3. 需要让某个对象支持序列化机制,则必须让其类是可序列化的,而为了让某个类是可序列化的,该类必须实现如下两个接口之一
      • Serializable //这时一个标记接口
      • Externalizable
1
2
3
4
5
6
7
8
9
10
11
//序列化示例
public static void WriteFile() throws Exception {
Dog dog = new Dog("pillage", 18);
String filePath = "F:\\Programs\\JetBrains\\JavaSEprogram\\BiliBili\\src\\com\\pushihao\\File_\\File\\obj01.os";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
oos.writeObject(dog);
oos.close();
System.out.println("保存完毕!");
}

//class Dog implements Serializable

注意事项:

  1. 读写顺序要一致

  2. 序列化和反序列化所使用的类一定要是同一个类,因为在序列化的过程中,会把包信息也加入在内

  3. serialVersionUID 是序列化的版本号,可以提高兼容性(在类中新增属性时,只要版本号相同,他也会认为是同一个类。例如:private static final long serialVersionUID = 1L;

  4. 序列化对象时,默认将里面所有的属性都进行序列化,但除了static 和 transient 修饰的成员

  5. 序列化对象时,要求里面属性的类型也需要实现序列化接口

  6. 序列化具备可继承性,也就是说如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化

  7. 要求序列化或反序列化对象,需要实现 Serializable 接口

标准输入输出流

System.in 和 System.out

  • System 类的 public final static InputStream in = null

  • System.in 的编译类型 InputStream, 运行类型 BufferedInputStream(对应键盘)

  • System.out 的编译类型 PrintStream,运行类型 PrintStream(对应屏幕/显示器)

转换流

InputStreamReader 和 OutputStreamWriter

作用:字节流转成字符流,可以解决字符乱码问题

使用:

  1. InputStreamReader 是 Reader 的子类,可以将 InputStream(字节流)包装/转换成 Reader(字符流)
  2. OutputStreamWriter 是 Writer 的子类,可以将 OutputStream(字节流)包装/转换成 Writer(字符流)
  3. 当处理纯文本的数据时,使用字符流效率往往会更高,而且可以解决中文乱码的问题
  4. 可以在使用时指定文件编码格式(比如 utf-8,gbk 等)
1
2
3
4
5
6
7
//将a.txt用字节流方式打开并转成utf-8字符流形式,然后再包装成处理流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("E:\\a.txt"), "utf-8"));

//OutputStreamWriter
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("E:\\a.txt"), "gbk");
outputStreamWriter.write("psh");
outputStreamWriter.close();

打印流

PrintStream 和 PrintWriter

打印流只有输出流,没有输入流,打印并不一定打印到屏幕上,也可以打印到文件里,网页上等等( 用System.setOut(); 方法 )。

  • PrintStream 是字节流,PrintWriter 是字符流

Properties类

  • 专门用于读写配置文件的集合类

    • 配置文件格式:

      键1=值1

      键2=值2

  • 注意:键值对不需要有空格,值不需要用引号引起来,默认是 String 类型

  • Properties常用方法

    • load 加载配置文件的键值对到 Properties 对象
    • list 将数据显示到指定设备
    • getProperty(key) 根据键获取值
    • setProperty(key, value) 设置键值对到 Properties 对象(底层还是哈希表)
    • store 将 Properties 中的键值对存储到配置文件,在 idea 中,保存信息到配置文件,如果含有中文,会存储为 unicode 码

Java使用Stream流快速编程

概述

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用 StreamAPI 对集合数据进行操作,就类似于使用 SQL 执行的数据路查询

官方文档对它的介绍如下:

1
A sequence of elements supporting sequential and parallel aggregate operations.

大概意思为:

1
支持顺序和并行聚合操作的元素序列。

可以看出 Stream

  • 是一个元素序列
  • 支持对元素的串行和并行操作
  • 所有对流的操作并不会改变原集合的值

那么串行操作和并行操作有什么区别呢?

我的理解是:

  • 串行操作类似于单线程,处理数据时是一条一条处理的,所以处理的数据自然也是有序的
  • 并行操作类似于多线程,多条数据一起处理,虽然处理时效率较高,那么处理时数据自然也是无序的

总体来说,使用 Stream 的基本步骤为:

  1. 生成 Stream 操作(创建操作)
  2. 中间操作(转换操作)
  3. 终结操作(聚合操作)

这里我以 Student 类来举例具体操作(getter、setter、constructor、toString方法略)

1
2
3
4
5
6
public class Student {
private String name;
private Integer age;
private Character sex;
private String teacher;
}

定义返回数据的工具类

1
2
3
4
5
6
7
8
9
10
11
public class StudentUtils {
public static List<Student> getStudentList() {
Student student1 = new Student("张三", 18, '男', "马云");
Student student2 = new Student("李四", 20, '女', "马云");
Student student3 = new Student("王五", 20, '女', "马化腾");
Student student4 = new Student("赵六", 40, '女', "马云");
Student student5 = new Student("钱七", 30, '女', "王健林");

return Arrays.asList(student1, student2, student3, student4, student5);
}
}

Stream 的创建

通过集合创建 Stream

函数原型:

1
2
3
4
5
6
7
8
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}


default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}

第一个为返回一个串行的 Stream 流,第二个返回一个并行的 Stream 流

使用举例:

1
2
3
4
5
6
public static void main(String[] args) {
List<Student> studentList = StudentUtils.getStudentList();

Stream<Student> stream = studentList.stream();//返回串行Stream流
Stream<Student> studentStream = studentList.parallelStream();//返回并行Stream流
}

通过数组创建 Stream

通过 Arrays 类的 Stream 方法,函数原型为:

1
2
3
public static <T> Stream<T> stream(T[] array) {
return stream(array, 0, array.length);
}

使用举例:

1
2
3
4
5
6
public static void main(String[] args) {
List<Student> studentList = StudentUtils.getStudentList();
Student[] students = studentList.toArray(new Student[0]);

Stream<Student> stream = Arrays.stream(students);
}

通过 Stream 类的 of 方法来创建

函数原型:

1
2
3
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}

使用举例:

1
2
3
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
}

此处 Stream 自动把 1,2,3 这些数字封装成了对应的包装类 Integer

创建 Stream 无限流

通过 Stream 类的 iterate 方法和 generate 方法来创建无限流

iterate函数定义(它包含一个重载的方法,两参数的和三参数的):

1
2
3
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)

public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)

iterate使用举例:

  • 两参数的
1
2
3
public static void main(String[] args) {
Stream<Integer> stream1 = Stream.iterate(0, x -> x + 1);
}

第一个参数为seed(种子值),根据种子生成第一个x的值并放到流中。第二个参数是一个lambda表达式,本式的意思是每次让x加1并把结果放入流中

  • 三参数的
1
2
3
4
5
public static void main(String[] args) {
Stream<Integer> stream2 = Stream.iterate(0, x -> {
return x < 100;
} ,x -> x + 1);
}

第一个参数为seed(种子值),根据种子生成第一个x的值并放到流中。第二个参数为一个lambda表达式,返回boolean类型值,若值为真,则根据第三个参数继续生成流;若值为假,则从此刻停止生成流

generate函数定义:

1
public static<T> Stream<T> generate(Supplier<? extends T> s)

generate使用举例:

1
2
3
4
public static void main(String[] args) {
Stream<Integer> integerStream = Stream.generate(() -> new Random().nextInt());
Stream<Boolean> booleanStream = Stream.generate(() -> new Random().nextBoolean());
}

创建 Stream 并行流

创建并行流有两种方式,一是直接在集合的基础上创建并行流,二是在串行流的基础上创建并行流

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
List<Student> list = StudentUtils.getStudentList();

//方式一:直接使用Collection接口的ParallelStream()创建并行流
Stream<Student> parallelStream = list.parallelStream();
System.out.println(parallelStream.isParallel());

//方式二:使用BaseStream接口的Parallel()方法将串行流转变为并行流
Stream<Student> stream = list.stream();
Stream<Student> parallelStream2 = stream.parallel();
System.out.println(parallelStream2.isParallel());
}

Stream 的中间操作

Stream常见的中间操作

方法名 参数类型 方法说明
filter Predicate 用于对流中的数据进行过滤
concat Stream, Stream 合并两个流为一个流
distinct 通过流中元素的 hashcode 去除重复元素
sorted 将流中元素自然排序
sorted Comparator 将流中元素根据排序器进行排序
map Function 可以得到流中每一个元素,将流中元素进行映射,返回映射结果组成的流
peek Consumer 可以得到流中每一个元素,然后对元素进行操作,无返回值
limit long 截取前n个元素
skip long 与limit相反,跳过前n个元素,返回后面的元素

这几种常见的中间操作大致也可归为几类:

过滤操作

  • filter:用于对流中的数据进行过滤
  • distinct:通过流中元素的 hashcode 去除重复元素
1
2
3
4
5
6
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 2, 6, 5, 4, 3);
Stream<Integer> result = stream
.filter(n -> n > 1) //2, 2, 6, 5, 4, 3
.distinct(); // 2, 6, 5, 4, 3
}

截取操作

  • limit:获取前n个元素
  • skip:跳过前n个元素,获取后面的元素
1
2
3
4
5
6
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 2, 6, 5, 4, 3);
Stream<Integer> result = stream
.limit(4) // 1, 2, 2, 6
.skip(2); // 2, 6
}

排序操作

  • sorted:进行自然排序
  • sorted(Comparator):按照比较器进行排序
1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
Stream<Integer> stream = Stream.of(1, 2, 2, 6, 5, 4, 3);
Stream<Integer> result = stream
.sorted() // 1, 2, 2, 3, 4, 5, 6
.sorted(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
}); // 6, 5, 4, 3, 2, 2, 1
}

映射操作

  • map:可以得到流中每一个元素,将流中元素进行映射,返回映射结果组成的流
1
2
3
4
5
6
public static void main(String[] args) {
List<Student> list = StudentUtils.getStudentList();
list.stream()
.map(Student::getName) //得到所有的学生的姓名
.forEach(System.out::println);
}

消费操作

  • peek:可以得到流中每一个元素,然后对元素进行操作,无返回值
1
2
3
4
5
6
public static void main(String[] args) {
List<Student> list = StudentUtils.getStudentList();
list.stream()
.peek(student -> student.setAge(10)) //将所有同学的年龄设成10岁
.forEach(System.out::println);
}

Stream 终结操作

匹配、聚合操作

  • allMatch:对流中所有元素进行判断,如果都满足条件返回true,否则返回false
1
2
3
4
5
6
public static void main(String[] args) {
List<Student> list = StudentUtils.getStudentList();
System.out.println(list.stream().allMatch(student -> student.getAge() > 10));
}

//输出true
  • noneMatch:对流中所有元素进行判断,如果都不满足条件返回true,否则返回false
1
2
3
4
5
6
public static void main(String[] args) {
List<Student> list = StudentUtils.getStudentList();
System.out.println(list.stream().noneMatch(student -> student.getAge() > 19));
}

//输出false
  • anyMatch:对流中所有元素进行判断,只要有一个满足条件的就返回true,否则返回false
1
2
3
4
5
6
public static void main(String[] args) {
List<Student> list = StudentUtils.getStudentList();
System.out.println(list.stream().anyMatch(student -> student.getAge() > 30));
}

//输出true
  • findFirst:函数返回值为 Optional 类型。如果流为空,返回一个空 Optional,否则返回第一个元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
List<Student> list = StudentUtils.getStudentList();

System.out.println("=======串行流");
Stream<Student> stream = list.stream();
System.out.println(stream.findFirst());

System.out.println("=======并行流");
Stream<Student> parallel = list.parallelStream();
System.out.println(parallel.findFirst());
}


//输出结果:
=======串行流
Optional[Student{name='张三', age=18, sex=男, teacher='马云'}]
=======并行流
Optional[Student{name='张三', age=18, sex=男, teacher='马云'}]
  • findAny:如果数据较少,并且串行的情况下一般还是会返回第一个元素,但是当并行的情况下一般会返回任何一个元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
List<Student> list = StudentUtils.getStudentList();

System.out.println("=======串行流");
Stream<Student> stream = list.stream();
System.out.println(stream.findAny());

System.out.println("=======并行流");
Stream<Student> parallel = list.parallelStream();
System.out.println(parallel.findAny());
}


//输出结果:
=======串行流
Optional[Student{name='张三', age=18, sex=男, teacher='马云'}]
=======并行流
Optional[Student{name='王五', age=20, sex=女, teacher='马化腾'}]

规约操作

  • count:返回流中元素个数
1
2
3
4
5
6
public static void main(String[] args) {
List<Student> list = StudentUtils.getStudentList();

Stream<Student> stream = list.stream();
System.out.println(stream.count());
}
  • max:参数为 Comparator 类型的比较器,返回值为 Optional 类型。返回比较器比较结果的最后一个元素的 Optional 类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
List<Student> list = StudentUtils.getStudentList();

Stream<Student> stream = list.stream();
System.out.println(stream.max(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o2.getAge() - o1.getAge();
}
}));
}

//输出:
Optional[Student{name='张三', age=18, sex=男, teacher='马云'}]
  • min:参数为 Comparator 类型的比较器,返回值为 Optional 类型。返回比较器比较结果的第一个元素的 Optional 类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
List<Student> list = StudentUtils.getStudentList();

Stream<Student> stream = list.stream();
System.out.println(stream.min(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o2.getAge() - o1.getAge();
}
}));
}

//输出:
Optional[Student{name='赵六', age=40, sex=女, teacher='马云'}]

收集操作

这个就比较简单了,主要借助 Collectors

Collectors.toList:把 Stream 流转化为 List 集合

Collectors.toSet:把 Stream 流转化为 Set 集合

Collectors.toMap:把 Stream 流转化为 Map 集合


JDBC编程

JDBC 编程六部

  1. 注册驱动
  2. 获取数据库连接
  3. 获取数据库操作对象
  4. 执行 SQL 语句
  5. 处理查询结果集
  6. 释放资源

注意:连接数据库时的URL:

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public static void main(String[] args) {
Statement stmt = null;
Connection conn = null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");

//2.获取数据库连接
String url = "jdbc:mysql://localhost:3306/Study";
String user = "root";
String password = "Pu1949..";
conn = DriverManager.getConnection(url, user, password);
//输出连接对象的内存地址
//System.out.println(conn);
//com.mysql.cj.jdbc.ConnectionImpl@6f1de4c7

//3.获取数据库操作对象
stmt = conn.createStatement();
//通过一个连接对象可以创建多个 Statement 对象

//4.执行 SQL 语句
String sql = "INSERT INTO book(id, bname, price, authorId, pubDate)" +
"VALUES(4, '概率论与数理统计', 20.00, 4, NOW());";
int result = stmt.executeUpdate(sql);

//5.输出执行结果
System.out.println(result);

} catch (SQLException e) {
e.printStackTrace();
} finally {
//6.释放资源
//先释放 Statement,再释放 Connection
//分别进行 try-catch 处理
if (stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

功能类详解

DriverManager

DriverManager 驱动管理对象

注册驱动

方法一:注册给定地驱动程序

1
2
Driver driver = new com.mysql.jdbc.Driver();
DriverManager.registerDriver(driver);

方法二:直接使用com.mysql.cj.jdbc.Driver类中的静态代码块

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

源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.mysql.cj.jdbc;

import java.sql.DriverManager;
import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}

static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}

注意:

  • 一般情况下不需要调用DriverManager的静态方法registerDriver(),因为只要静态类被使用,就会自动调用其中的静态代码块完成注册驱动
  • mysql5 之后可以省略注册驱动的步骤
获取数据库连接

获取数据库连接对象:

1
static Connection getConnection(String url, String username, String password);

Connection

Connection 数据库连接对象

获取执行者对象
  1. 获取普通执行者对象:
1
Statement createStatement();
  1. 获取预编译执行者对象
1
PreparedStatement prepareStatement(String sql);
管理事务

手动管理事务:

1
setAutoCommit(boolean autoCommit);

jdbc 默认是自动提交事务,通过将参数设置为 false 可以开启手动提交事务

提交事务:

1
commit();

回滚事务:

1
rollback();
释放资源

立即将数据库连接对象释放:

1
void close();

Statement

Statement 执行 sql 语句

执行 DML 语句(增、删、改 操作)
1
int executeUpdate(String sql);

返回值:int 类型,指影响的行数

参数:insert、update、delete 语句

执行 DQL 语句(查询操作)
1
ResultSet executeQuery(String sql);

返回值:ResultSet 类型,封装查询结果

参数:select 语句

释放资源
1
void close();

ResultSet

ResultSet 结果集对象

判断结果集中是否还有数据
1
boolean next();

返回值:有数据则返回 true,并将索引向下移动一行。没有数据则返回 false

获取结果集中的数据
1
XXX getXxx("数据库列名");

XXX:java 数据类型

如:

1
2
String getString("name");
int getInt("age");
释放资源
1
void close();

PrepareStatement

PrepareStatement 预编译执行者对象

原理:

  • 在执行 sql 语句之前,将 sql 语句进行提前编译。明确 sql 语句的格式后,就不会改变了,剩下的内容都只会被认为是参数
  • sql 语句中的参数使用 ?做占位符
为 ?占位符赋值的方法
1
setXxx(参数1, 参数2);

Xxx:数据类型

参数1:?的位置编号(编号从1开始)

参数2:?的实际参数

1
2
3
String sql = "select * from book where id=?";
statement = connection.prepareStatement(sql);
statement.setInt(1, 1);
执行 sql 语句
  1. 执行 insert、update、delete 语句
1
int executeUpdate();
  1. 执行 select 语句
1
ResultSet executeQuery();
释放资源
1
void close();

数据库连接池

自定义数据库连接池

实现步骤
  1. 定义一个类,实现 DataSource 接口
  2. 定义一个容器,用于保存多个 Connection 连接对象
  3. 定义静态代码块,通过 JDBC 工具类获取 10 个连接对象并保存到容器中
  4. 重写 getConnection 方法,从容器中获取一个连接并返回
  5. 定义 getSize 方法,用于获取容器的大小并返回
归还连接

归还数据库连接的方式:

  1. 继承方式

思想:

  • 通过打印连接对象,发现 DriverManager 获取的连接实现类是 JDBC4Connection
  • 那么就可以自己定义一个类,继承 JDBC4Connection这个类,重写close() 方法,完成连接对象的归还

问题:

  • 我们虽然定义了一个子类完成了连接归还的操作,但是DriverManager获取的还是 JDBC4Connection这个对象,并不是我们自己定义的子类对象。多态允许把一个子类对象指向父类引用,但是这里强转相当于把一个父类对象指向子类引用,会报语法错误。所以此方法行不通
  1. 装饰设计模式

思想:

  • 可以自己定义一个类,实现 Connection 接口。这样就具备了和 JDBC4Connection相同的行为了
  • 重写close() 方法,完成连接的归还,其余的功能还调用 mysql 驱动包实现类原有的方法即可

问题:

  • 实现 Connection 接口后,有大量的方法需要在自定义类中进行重写,非常麻烦
  1. 适配器设计模式

思想:

  • 我们可以自己定义一个适配器类,实现 Connection 接口,将除 close 外的所有方法进行实现
  • 自定义连接类只需要继承这个适配器类,重写需要改进的 close 方法即可

问题:

  • 虽然自定义连接类比较简洁,但是适配器类和上个方法一样麻烦
  1. 动态代理方式

思想:

  • 通过 Proxy 来完成对 Connection 实现类对象的代理
  • 代理过程中判断如果执行的是 close 方法,就将连接归还到连接池中,如果是其他的方法就继续调用原来的功能即可

第三方连接池

c3p0 连接池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) throws Exception {
//1.创建c3p0连接池对象
DataSource dataSource = new ComboPooledDataSource();

//2.通过连接池对象获取数据库连接
Connection connection = dataSource.getConnection();

//3.执行操作
String sql = "select * from book";
PreparedStatement stmt = connection.prepareStatement(sql);
ResultSet set = stmt.executeQuery();
while (set.next()) {
System.out.printf("id:%d name:%s price:%.2f\n", set.getInt("id"), set.getString("bookName"), set.getDouble("price"));
}
set.close();
stmt.close();
connection.close();
}
Druid 连接池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void main(String[] args) throws Exception {
//1.通过Properties集合加载配置文件
InputStream in = testDruid.class.getClassLoader().getResourceAsStream("druid.properties");
Properties druid = new Properties();
druid.load(in);

//2.通过Druid连接池工厂类获取数据库连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(druid);

//3.通过连接池对象获取数据库连接进行使用
Connection connection = dataSource.getConnection();
String sql = "select * from book";
PreparedStatement stmt = connection.prepareStatement(sql);
ResultSet set = stmt.executeQuery();
while (set.next()) {
System.out.printf("id:%d name:%s price:%.2f\n",
set.getInt("id"),
set.getString("bookName"),
set.getDouble("price"));
}
set.close();
stmt.close();
connection.close();
}