jdbc学习笔记

1 简介和基础使用

1.1 简介

JDBC:是sun发布的一个java程序和数据库之间通信的规范(接口)

各大数据库厂商去实现JDBC规范(实现类),将这些实现类打成压缩包,就是所谓的jar包

比如:

image-20220621124839866

1.2 创建连接

url:防止中文乱码,加上参数useUnicode=true&characterEncoding=utf-8。url的标准格式为:jdbc:mysql://ip:端口号/数据库名称?参数列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Demo01 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1.添加jar包
// 2.加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 3.通过驱动管理器获取连接对象
// 3-1 准备url
String url = "jdbc:mysql://localhost:3306/fruitdb";
// 3-2 准备用户名
String usr = "root";
// 3-3 准备密码
String pwd = "root";
Connection connection = DriverManager.getConnection(url,usr,pwd);
System.out.println("conn = "+connection);
}
}

1.3 添加数据

先建立连接之后,得到connection对象,这个对象相当于建立了一条连接数据库和java之间的马路,通过connection对象得到预处理对象PreparedStatement,这个对象相当于马路上的一辆车,用于运送真实的数据。

在SQL语句中,使用?当做占位符,再通过预处理对象的set方法为占位符填充内容

最后,记得关闭连接,先关闭预处理对象,再关闭连接对象

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
public class Demo02 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.通过驱动管理器获取连接对象
// 2-1 准备url
// url表示和数据库通信的地址,如果需要带参数,则需要使用?进行连接
// 如果使用多个参数,多个参数之间使用&连接
String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf-8";
// 2-2 准备用户名
String usr = "root";
// 2-3 准备密码
String pwd = "root";

Connection connection = DriverManager.getConnection(url,usr,pwd);

// 3. 编写SQL语句
// id,fname,price,fcount,remark
String sql = "insert into t_fruit value(0,?,?,?,?)";

// 4.创建预处理对象
PreparedStatement psmt = connection.prepareStatement(sql);
// 5.填充参数
psmt.setString(1,"榴莲");
psmt.setInt(2,15);
psmt.setInt(3,100);
psmt.setString(4,"榴莲是一种神奇的水果");
// 6.执行更新(增删改),返回影响行数
int i = psmt.executeUpdate();
System.out.println(i>0?"添加成功!":"添加失败");
// 7.释放资源
psmt.close();
connection.close();
}
}

1.4 更新和删除数据

和添加数据步骤都一样,最后都是预处理对象调用executeUpdate方法并返回影响行数。因此更新与修改和添加的不同就是SQL语句和预处理设置SQL中的占位符的值而已。

更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Fruit fruit = new Fruit(33, "猕猴桃", "猕猴桃啊猕猴桃");

Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf8";
Connection connection = DriverManager.getConnection(url, "root", "root");

String sql = "update t_fruit set fname=?,remark = ? where fid = ?";
PreparedStatement psmt = connection.prepareStatement(sql);
psmt.setString(1, fruit.getFname());
psmt.setString(2, fruit.getRemark());
psmt.setInt(3,fruit.getFid());
int i = psmt.executeUpdate();
System.out.println(i>0?"修改成功":"修改失败");

psmt.close();
connection.close();

删除:

1
2
3
4
5
6
7
8
9
10
11
12
13
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf8";
Connection connection = DriverManager.getConnection(url, "root", "root");

String sql = "delete from t_fruit where fid = ?";
PreparedStatement psmt = connection.prepareStatement(sql);
psmt.setInt(1,33);

int i = psmt.executeUpdate();
System.out.println(i>0?"删除成功":"删除失败");

psmt.close();
connection.close();

1.5 增删更新总结

1.5.1 步骤:

  1. 加载驱动
  2. 通过驱动获取连接对象Connection,连接对象相当于连接java与数据库之间的一条马路
  3. 编写SQL语句,语句中使用占位符
  4. 创建数据预处理对象PreparedStatement,数据预处理对象相当于马路上运送SQL语句的小车
  5. 填充预处理数据对象的参数
  6. 执行更新参数,对于增删查都是执行PreparedStatement对象的executeUpdate方法,返回影响行数
  7. 关闭资源,关闭PreparedStatement对象和Connection对象

1.6 查询

查询与增删改的不同之处在于:

  • 预处理对象执行的是executeQuery方法
  • 方法返回结果集ResultSet,可以遍历结果接进行数据的操作

例如:查询所有数据

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
//        1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf8";
// 2.获取连接对象
Connection connection = DriverManager.getConnection(url, "root", "root");

// 3.编写SQL语句
String sql = "select * from t_fruit";
// 4.创建预处理命令对象
PreparedStatement psmt = connection.prepareStatement(sql);
// 5.填充参数,这里没有,略
// 6.执行查询,返回结果集
ResultSet res = psmt.executeQuery();
List<Fruit> list = new ArrayList<>();
// 7.处理结果集
// 判断下一行是否有数据,并且指针指到下一行
while(res.next()){
// 表示当前行第一列的数据,因为这一列是int类型的数据,所以使用getInt
int fid = res.getInt(1);
// 也可以通过结果集的列名
String fname = res.getString("fname");
int price = res.getInt(3);
int fcount = res.getInt("fcount");
String remark = res.getString("remark");

Fruit fruit = new Fruit(fid,fname,price,fcount,remark);
list.add(fruit);
}
// 8.释放资源
res.close();
psmt.close();
connection.close();
for (Fruit fruit : list) {
System.out.println(fruit);
}

查询指定的一条记录:

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
		Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf8";
Connection connection = DriverManager.getConnection(url, "root", "root");

// 编写SQL语句
String sql = "select * from t_fruit where fid = ?";
// 创建预处理命令对象
PreparedStatement psmt = connection.prepareStatement(sql);
// 填充参数
psmt.setInt(1,2);
// 由于fid是主键,所以可以不使用列表,这里改一下
// 执行查询,返回结果集
ResultSet res = psmt.executeQuery();
// 判断下一行是否有数据,并且指针指到下一行
if(res.next()){
// 表示当前行第一列的数据,因为这一列是int类型的数据,所以使用getInt
int fid = res.getInt(1);
// 也可以通过结果集的列名
String fname = res.getString("fname");
int price = res.getInt(3);
int fcount = res.getInt("fcount");
String remark = res.getString("remark");
Fruit fruit = new Fruit(fid,fname,price,fcount,remark);
System.out.println(fruit);
}
// 释放资源
res.close();
psmt.close();
connection.close();

查询总记录条数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
        Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/fruitdb?useSSL=false&useUnicode=true&characterEncoding=utf8";
Connection connection = DriverManager.getConnection(url, "root", "root");

// 编写SQL语句
String sql = "select count(*) from t_fruit";
// 创建预处理命令对象
PreparedStatement psmt = connection.prepareStatement(sql);
// 执行查询,返回结果集
// 这里返回的是一个一行一列的数据
ResultSet res = psmt.executeQuery();
// 判断下一行是否有数据,并且指针指到下一行
if(res.next()){
int count = res.getInt(1);
System.out.println("总记录条数:"+count);
}
// 释放资源
res.close();
psmt.close();
connection.close();

1.7 查询总结

1.7.1 查询与增删改的不同之处在于:

  • 预处理对象执行的是executeQuery方法
  • 方法返回结果集ResultSet,可以遍历结果接进行数据的操作

1.7.2 步骤:

  1. 加载驱动
  2. 通过驱动获取连接对象Connection,连接对象相当于连接java与数据库之间的一条马路
  3. 编写SQL语句,语句中使用占位符
  4. 创建数据预处理对象PreparedStatement,数据预处理对象相当于马路上运送SQL语句的小车
  5. 填充预处理数据对象的参数
  6. 执行更新参数,执行executeQuery方法
  7. 返回结果集ResultSet
  8. 关闭资源,关闭ResultSet对象、PreparedStatement对象和Connection对象

1.8 批处理

如果一次性添加大量数据,若每一条数据都单独执行一次方法,那么效率很低下,所以可以采用批处理的方法来提高处理的效率。

批处理操作的步骤:

  1. 如果执行批处理任务,需要在添加一个参数: rewriteBatchedStatements=true
  2. 将数据加入一个批次:psmt.addBatch();
  3. 执行批处理:int[] batch = psmt.executeBatch();如果继续进行处理,需要进行clear:psmt.clear();
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
51
52
53
54
55
56
57
package com.zylai.jdbc01;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;

/**
* @Author: Zhao YunLai
* @Date: 2022/06/21/12:54
* @Description: 添加
*/
public class DemoAddBatch {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1.加载驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.通过驱动管理器获取连接对象
// 批处理操作一
// 如果执行批处理任务,需要添加一个参数: rewriteBatchedStatements=true
String url = "jdbc:mysql://localhost:3306/fruitdb?rewriteBatchedStatements=true&useSSL=false&useUnicode=true&characterEncoding=utf-8";
// 2-2 准备用户名
String usr = "root";
// 2-3 准备密码
String pwd = "root";
Connection connection = DriverManager.getConnection(url,usr,pwd);
// 3. 编写SQL语句
String sql = "insert into t_fruit value(0,?,?,?,?)";

// 4.创建预处理对象
PreparedStatement psmt = connection.prepareStatement(sql);
// 5.填充参数
for (int i = 0; i < 1000; i++) {
psmt.setString(1,"榴莲"+i);
psmt.setInt(2,15);
psmt.setInt(3,100);
psmt.setString(4,"榴莲是一种神奇的水果"+i);

// 批处理操作二:将数据加入一个批次
psmt.addBatch();
// 每一百个处理一次,分批次处理,每次执行完要记得清空一下队列
if(i%100==0){
psmt.executeBatch();
// 记得clear一下
psmt.clearBatch();
}
}
// 批处理操作三
int[] batch = psmt.executeBatch();
System.out.println(Arrays.toString(batch));

// 7.释放资源
psmt.close();
connection.close();
}
}

2 工程

2.1 需求概述和整体框架

以实现一个水果库存系统为例,这个系统需要提供水果的增删改查操作

1
2
3
4
5
6
7
8
=================欢迎使用水果库存系统=====================
1.查看水果库存列表
2.添加水果库存信息
3.查看特定水果库存信息
4.水果下架
5.退出
======================================================

整体框架如下图,在controller里面写调用DAO层的方法,在dao里面写持久层方法,提供接口及其实现类,pojo写实体类,view是客户端视图操作。

image-20220621232711636

2.2 最初版本

2.2.1 pojo

pojo包下的Fruit类

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package com.zylai.fruit01.pojo;

/**
* @Author: Zhao YunLai
* @Date: 2022/06/21/17:33
* @Description:
*/
public class Fruit {
private Integer fid;
private String fname;
private Integer price;
private Integer fcount;
private String remark;

public Fruit(){}

public Fruit(Integer fid, String fname, String remark) {
this.fid = fid;
this.fname = fname;
this.remark = remark;
}

public Fruit(Integer fid, String fname, Integer price, Integer fcount, String remark) {
this.fid = fid;
this.fname = fname;
this.price = price;
this.fcount = fcount;
this.remark = remark;
}

@Override
public String toString() {
return fid+"\t\t"+fname+"\t\t"+price+"\t\t"+fcount+"\t\t"+remark;
}

public Integer getFid() {
return fid;
}

public void setFid(Integer fid) {
this.fid = fid;
}

public String getFname() {
return fname;
}

public void setFname(String fname) {
this.fname = fname;
}

public Integer getPrice() {
return price;
}

public void setPrice(Integer price) {
this.price = price;
}

public Integer getFcount() {
return fcount;
}

public void setFcount(Integer fcount) {
this.fcount = fcount;
}

public String getRemark() {
return remark;
}

public void setRemark(String remark) {
this.remark = remark;
}
}

2.2.2 dao

FruitDAO接口

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
package com.zylai.fruit01.dao;

import com.zylai.fruit01.pojo.Fruit;

import java.util.List;

/**
* @Author: Zhao YunLai
* @Date: 2022/06/21/18:02
* @Description:
*/
public interface FruitDAO {
// 查询库存列表
List<Fruit> getFruitList();

// 根据水果名称查询库存
Fruit getFruitByName(String fname);
// 新增水果库存
boolean addFruit(Fruit fruit);
// 修改库存
boolean updateFruit(Fruit fruit);
// 根据水果名称删除记录
boolean deleteFruitByName(String fname);

}

FruitDAOImpl实现类

注意到这个实现类存在着大量的代码冗余之后的优化主要对这个类进行优化

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
package com.zylai.fruit01.dao.impl;

import com.zylai.fruit01.dao.FruitDAO;
import com.zylai.fruit01.pojo.Fruit;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
* @Author: Zhao YunLai
* @Date: 2022/06/21/18:05
* @Description:
*/
public class FruitDAOImpl implements FruitDAO {
private final String DRIVER = "com.mysql.jdbc.Driver";
private final String URL = "jdbc:mysql://localhost:3306/fruitdb?useUnicode=true&characterEncoding=utf-8&useSSL=false";
private final String USER = "root";
private final String PWD = "root";

// 查询所有的库存信息
@Override
public List<Fruit> getFruitList() {
List<Fruit> fruitList = new ArrayList<>();
Connection connection = null;
PreparedStatement psmt = null;
ResultSet rs = null;
try {
// 1.加载驱动
Class.forName(DRIVER);
// 2.通过驱动获取连接对象
connection= DriverManager.getConnection(URL, USER, PWD);
// 3.编写SQL语句
String sql = "select * from t_fruit";
// 4.创建预处理命令对象
psmt = connection.prepareStatement(sql);
// 5.执行查询
rs = psmt.executeQuery();
// 6.解析查询
while(rs.next()){
int fid = rs.getInt(1);
String fname = rs.getString(2);
int price = rs.getInt(3);
int fcount = rs.getInt(4);
String remark = rs.getString(5);

Fruit fruit = new Fruit(fid,fname,price,fcount,remark);
fruitList.add(fruit);
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
try {
if(rs!=null){
rs.close();
}
if(psmt!=null){
psmt.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return fruitList;
}

// 通过水果名称查询记录
@Override
public Fruit getFruitByName(String fname) {
Connection connection = null;
PreparedStatement psmt = null;
ResultSet rs = null;
try {
Class.forName(DRIVER);
connection = DriverManager.getConnection(URL,USER,PWD);

String sql = "select * from t_fruit where fname=?";
psmt = connection.prepareStatement(sql);
psmt.setString(1,fname);
rs= psmt.executeQuery();
if(rs.next()){
int fid = rs.getInt("fid");
int price = rs.getInt("price");
int fcount = rs.getInt("fcount");
String remark = rs.getString("remark");

return new Fruit(fid,fname,price,fcount,remark);
}

} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
try{
if(rs!=null){
rs.close();
}
if(psmt!=null){
psmt.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}

// 添加水果
@Override
public boolean addFruit(Fruit fruit) {
Connection connection = null;
PreparedStatement psmt = null;
try {
Class.forName(DRIVER);
connection = DriverManager.getConnection(URL,USER,PWD);
String sql = "insert into t_fruit values(0,?,?,?,?)";
psmt = connection.prepareStatement(sql);
psmt.setString(1,fruit.getFname());
psmt.setInt(2,fruit.getPrice());
psmt.setInt(3,fruit.getFcount());
psmt.setString(4,fruit.getRemark());

int i = psmt.executeUpdate();
return i>0;
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
try {
if(psmt!=null){
psmt.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return false;
}

// 更新水果库存信息
@Override
public boolean updateFruit(Fruit fruit) {
Connection connection = null;
PreparedStatement psmt = null;
try {
Class.forName(DRIVER);
connection = DriverManager.getConnection(URL,USER,PWD);
String sql = "update t_fruit set fname=?,price=?,fcount=?,remark=? where fid=?";
psmt = connection.prepareStatement(sql);
psmt.setString(1, fruit.getFname());
psmt.setInt(2,fruit.getPrice());
psmt.setInt(3,fruit.getFcount());
psmt.setString(4,fruit.getRemark());
psmt.setInt(5,fruit.getFid());

int i = psmt.executeUpdate();
return i>0;
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
try {
if(psmt!=null){
psmt.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return false;
}

// 删除水果
@Override
public boolean deleteFruitByName(String fname) {
Connection connection = null;
PreparedStatement psmt = null;

try {
Class.forName(DRIVER);
connection = DriverManager.getConnection(URL,USER,PWD);
String sql = "delete from t_fruit where fname=?";
psmt = connection.prepareStatement(sql);
psmt.setString(1,fname);
return psmt.executeUpdate()>0;
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
try {
if(psmt!=null){
psmt.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
return false;
}
}

2.2.3 controller

处理请求层,由于业务比较简单,所以一些业务都在controller里面进行

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package com.zylai.fruit01.controller;

import com.zylai.fruit01.dao.FruitDAO;
import com.zylai.fruit01.dao.impl.FruitDAOImpl;
import com.zylai.fruit01.pojo.Fruit;

import java.util.List;
import java.util.Scanner;

/**
* @Author: Zhao YunLai
* @Date: 2022/06/21/17:35
* @Description:
*/
public class Menu {

Scanner input = new Scanner(System.in);
private FruitDAO fruitDAO = new FruitDAOImpl();

public int showMainMenu(){
System.out.println("=================欢迎使用水果库存系统=====================");
System.out.println("1.查看水果库存列表");
System.out.println("2.添加水果库存信息");
System.out.println("3.查看特定水果库存信息");
System.out.println("4.水果下架");
System.out.println("5.退出");
System.out.println("======================================================");
System.out.print("请选择:");
int res = input.nextInt();
while(true){
if(res<1||res>5){
System.out.println("请在1~5当中进行选择!");
System.out.print("请选择:");
res = input.nextInt();
}else{
break;
}
}
return res;
}
// 显示所有水果库存
public void showFruitList(){
List<Fruit> fruitList = fruitDAO.getFruitList();
System.out.println("------------------------------------------------------");
System.out.println("编号\t\t名称\t\t单价\t\t库存\t\t备注\t\t");
if(fruitList==null||fruitList.size()<=0){
System.out.println("对不起,库存为空");
}else{
for (Fruit fruit : fruitList) {
System.out.println(fruit);
}
}
System.out.println("------------------------------------------------------");
}

// 添加水果库存信息 -- 业务方法
public void addFruit(){
System.out.print("请输入水果名称:");
String fname = input.next();
// 判断是添加库存还是添加一个新水果
Fruit fruitByName = fruitDAO.getFruitByName(fname);
if(fruitByName==null){//说明库存中没有这个水果,直接添加
System.out.print("请输入水果单价:");
int price = input.nextInt();
System.out.print("请输入水果库存量:");
int fcount = input.nextInt();
System.out.print("请输入水果备注:");
String remark = input.next();
fruitByName = new Fruit(0,fname,price,fcount,remark);
// 调用dao
fruitDAO.addFruit(fruitByName);
}else{
System.out.print("请输入追加的库存量:");
int fcount = input.nextInt();
fruitByName.setFcount(fruitByName.getFcount()+fcount);
fruitDAO.updateFruit(fruitByName);
}
}
// 查看特定水果库存信息
public void showFruitInfo(){
System.out.print("请输入水果名称:");
String fname = input.next();
Fruit fruit = fruitDAO.getFruitByName(fname);
if(fruit == null){
System.out.println("对不起,没有找到指定的水果库存记录");
}else{
System.out.println("------------------------------------------------------");
System.out.println("编号\t\t名称\t\t单价\t\t库存\t\t备注\t\t");
System.out.println(fruit);
System.out.println("------------------------------------------------------");
}
}

public void deleteFruit(){
System.out.print("请输入水果名称:");
String fname = input.next();
Fruit fruit = fruitDAO.getFruitByName(fname);
if(fruit==null){
System.out.println("对不起,没有找到要下架的水果信息");
}else{
System.out.print("是否下架?(Y/N)");
String str = input.next();
if("y".equalsIgnoreCase(str)){
boolean b = fruitDAO.deleteFruitByName(fname);
if(b){
System.out.println("下架成功!");
}
}
}
}
// 退出
public boolean exit(){
do{
System.out.print("是否确认退出?(Y/N):");
String res = input.next();
if("y".equalsIgnoreCase(res)){
return true;
}else if("n".equalsIgnoreCase(res)){
return false;
}
}while(true);

}
}

2.2.4 view

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
package com.zylai.fruit01.view;

import com.zylai.fruit01.controller.Menu;

/**
* @Author: Zhao YunLai
* @Date: 2022/06/21/17:36
* @Description:
*/
public class Client {
public static void main(String[] args) {
Menu menu = new Menu();
boolean flag = false;
while(!flag){
// 调用主菜单的方法
int slt = menu.showMainMenu();
switch(slt){
case 1:
menu.showFruitList();
break;
case 2:
menu.addFruit();
break;
case 3:
menu.showFruitInfo();
break;
case 4:
menu.deleteFruit();
break;
case 5:
flag = menu.exit();
break;
}
}
System.out.println("谢谢使用");
}
}

2.2.5 最初版总结

代码的冗余量很大,主要是dao层的实现类,每个方法都要重复建立驱动、获取连接、关闭资源等操作。

2.3 改进:对于FruitDAOImpl中获取连接操作以及释放资源操作做了提取

这次的改进就是对于FruitDAOImpl中获取连接操作以及释放资源操作做了提取

如下:将加载驱动获取连接封装在getConnection方法中,将关闭资源封装在close方法中

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
51
52
53
54
55
56
57
58
59
    private Connection getConnection(){
try {
// 1.加载驱动
Class.forName(DRIVER);
// 2.通过驱动获取连接对象
return DriverManager.getConnection(URL, USER, PWD);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return null;
}

private void close(Connection connection,PreparedStatement psmt, ResultSet rs){
try {
if(rs!=null){
rs.close();
}
if(psmt!=null){
psmt.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}

//实际中使用
// 通过水果名称查询记录
@Override
public Fruit getFruitByName(String fname) {
Connection connection = null;
PreparedStatement psmt = null;
ResultSet rs = null;
try {

connection = getConnection();

String sql = "select * from t_fruit where fname=?";
psmt = connection.prepareStatement(sql);
psmt.setString(1,fname);
rs= psmt.executeQuery();
if(rs.next()){
int fid = rs.getInt("fid");
int price = rs.getInt("price");
int fcount = rs.getInt("fcount");
String remark = rs.getString("remark");

return new Fruit(fid,fname,price,fcount,remark);
}

} catch ( SQLException e) {
e.printStackTrace();
}finally {
close(connection,psmt,rs);
}
return null;
}

2.4 改进:设置抽象类,并抽取增删改操作

可以看到下面这个三个操作的步骤完全一样,不一样的地方只是SQL语句和psmt设置的参数
因此可以将这些相同的操作抽取出来

  • addFruit:
    1.获取连接
    2.编写SQL
    3.psmt
    4.执行更新
    5.关闭资源

  • updateFruit:
    1.获取连接
    2.编写SQL
    3.psmt
    4.执行更新
    5.关闭资源

  • deleteFruit
    1.获取连接
    2.编写SQL
    3.psmt
    4.执行更新
    5.关闭连接

此外,这里只是涉及到一个表的操作,当有多个表时,就涉及到多个dao类,而这多个dao类都需要这共同的驱动地址,数据库url,用户名,密码,获取驱动方法,关闭资源的方法,抽取增删改操作方法。

因此,加入一个抽象类baseDAO,里面提供公共的常量和方法,并且把抽取的增删改方法加入

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package com.zylai.fruit.dao.base;

import java.sql.*;

/**
* @Author: Zhao YunLai
* @Date: 2022/06/21/21:45
* @Description:
*/
public abstract class BaseDAO {
protected final String DRIVER = "com.mysql.jdbc.Driver";
protected final String URL = "jdbc:mysql://localhost:3306/fruitdb?useUnicode=true&characterEncoding=utf-8&useSSL=false";
protected final String USER = "root";
protected final String PWD = "root";

protected Connection getConnection(){
try {
// 1.加载驱动
Class.forName(DRIVER);
// 2.通过驱动获取连接对象
return DriverManager.getConnection(URL, USER, PWD);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return null;
}

// 关闭资源
protected void close(Connection connection, PreparedStatement psmt, ResultSet rs){
try {
if(rs!=null){
rs.close();
}
if(psmt!=null){
psmt.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}

// 执行更新,返回影响行数
protected int executeUpdate(String sql,Object... params){
Connection connection = null;
PreparedStatement psmt = null;
try {
connection = getConnection();
psmt = connection.prepareStatement(sql);
if(params!=null && params.length>0){
for (int i = 0; i < params.length; i++) {
psmt.setObject(i+1,params[i]);
}
}
return psmt.executeUpdate();
} catch ( SQLException e) {
e.printStackTrace();
}finally {
close(connection,psmt,null);
}
return 0;
}

}

然后让daoImpl继承该抽象类

由于没有涉及到查询的改进,这里省去查询的方法。可以看到对于增删改方法,只需要调用父类的更新执行方法,传入sql语句和参数即可完成业务。

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
package com.zylai.fruit.dao.impl;

import com.zylai.fruit.dao.FruitDAO;
import com.zylai.fruit.dao.base.BaseDAO;
import com.zylai.fruit.pojo.Fruit;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
* @Author: Zhao YunLai
* @Date: 2022/06/21/18:05
* @Description:
*/
public class FruitDAOImpl extends BaseDAO implements FruitDAO {

// 添加水果
@Override
public boolean addFruit(Fruit fruit) {
String sql = "insert into t_fruit values(0,?,?,?,?)";
return super.executeUpdate(sql,fruit.getFname(),fruit.getPrice(),fruit.getFcount(),fruit.getRemark())>0;
}

// 更新水果库存信息
@Override
public boolean updateFruit(Fruit fruit) {
String sql = "update t_fruit set fname=?,price=?,fcount=?,remark=? where fid=?";
return super.executeUpdate(sql,fruit.getFname(),fruit.getPrice(),fruit.getFcount(),fruit.getRemark(),fruit.getFid())>0;
}

// 删除水果
@Override
public boolean deleteFruitByName(String fname) {
String sql = "delete from t_fruit where fname=?";
return super.executeUpdate(sql,fname)>0;
}
}

2.5 改进:抽取查询操作

2.5.1 使用泛型

通过执行查询操作返回查询的结果集,然后通过get方法得到指定列的值赋值给实体类比如Fruit,而对于baseDAO会有多个DAOImpl继承它,因此不能把实体类写死,所以采用泛型。

1
2
3
public abstract class BaseDAO<T> {}

public class FruitDAOImpl extends BaseDAO<Fruit> implements FruitDAO {}

2.5.2 通过反射得到真正的类

因为泛型只是一个符号,我们不能直接通过泛型创建实例,所以用到反射技术来确定真正的实体类。

首先在类中定义Class 属性

1
2
//    T的Class对象
private Class entityClass;

在BaseDAO的无参构造器中,确定真正的实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    public BaseDAO(){
// getClass获取Class对象,当前我们执行的是new FruitDAOImpl(),创建的是FruitDAOImpl实例
// 那么构造方法首先会调用父类(BaseDAO)的无参构造方法
// 因此此处的getClass方法执行,获取的是FruitDAOImpl的Class
// 所以使用getGenericSuperclass
// getGenericSuperclass是获取父类的泛型,即获取BaseDAO的泛型
Type genericSuperclass = getClass().getGenericSuperclass();
// ParameterizedType:参数化类型
// 获取<T>(注意,泛型可以传递多个如<T,A,B>,所以返回值是一个数组)中的T的真是类型
Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments();
Type actualType = actualTypeArguments[0];

try {
entityClass = Class.forName(actualType.getTypeName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

2.5.3 查询方法

在处理结果集时我们不知道T到底有几个参数,是什么类型。因此,我们需要获取结果集的元数据,所谓元数据就是描述结果集数据的数据,简单来讲,就是这个结果集有哪些列,什么类型等等。

之后通过Class对象创建一个T的实例进行操作即可

先定义一个工具函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//    通过反射,给obj对象的property属性赋propertyValue值
private void setValue(Object obj,String property,Object propertyValue){
Class clazz = obj.getClass();
// 获取property这个字符串对应的属性名,比如“fid”去找obj中的fid属性
try {
Field field = clazz.getDeclaredField(property);
if(field!=null){
// 强制访问
field.setAccessible(true);
//为obj对象设置filed属性的值
field.set(obj,propertyValue);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}

执行查询的函数:

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
//    执行查询
protected List<T> executeQuery(String sql,Object... params){
List<T> list = new ArrayList<>();
try {
// 获取连接
connection = getConnection();
// 设置预处理对象
psmt = connection.prepareStatement(sql);
// 设置参数
setParams(psmt,params);
// 执行查询
rs= psmt.executeQuery();
// 处理结果集
// 难点在于如何处理T,我们不知道T是什么类型并且不知道T有多少个参数
// 通过rs可以获取结果集的元数据
// 元数据:描述结果集数据的数据,简单讲,就是这个结果接有哪些列,什么类型等等
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
while(rs.next()){
T entity = (T)entityClass.newInstance();
for (int i = 0; i < columnCount; i++) {
// 获取列名
String columnName = metaData.getColumnName(i + 1);
// 获取列对应的值
Object columnValue = rs.getObject(i + 1);
// 这里获取了列名,就需要根据列名把值填充给entity对象
setValue(entity,columnName,columnValue);
}
list.add(entity);

}
} catch (SQLException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} finally {
close(connection,psmt,rs);
}

return list;
}

2.5.4 改进后的整个BaseDAO类为:

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package com.zylai.fruit.dao.base;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
* @Author: Zhao YunLai
* @Date: 2022/06/21/21:45
* @Description:
*/
public abstract class BaseDAO<T> {
protected final String DRIVER = "com.mysql.jdbc.Driver";
protected final String URL = "jdbc:mysql://localhost:3306/fruitdb?useUnicode=true&characterEncoding=utf-8&useSSL=false";
protected final String USER = "root";
protected final String PWD = "root";

// 老师是吧这几个放到这里的,我觉得可能会出现并发问题,不过目前不考虑这个
protected Connection connection = null;
protected PreparedStatement psmt = null;
protected ResultSet rs = null;

// T的Class对象
private Class entityClass;

public BaseDAO(){
// getClass获取Class对象,当前我们执行的是new FruitDAOImpl(),创建的是FruitDAOImpl实例
// 那么构造方法首先会调用父类(BaseDAO)的无参构造方法
// 因此此处的getClass方法执行,获取的是FruitDAOImpl的Class
// 所以使用getGenericSuperclass
// getGenericSuperclass是获取父类的泛型,即获取BaseDAO的泛型
Type genericSuperclass = getClass().getGenericSuperclass();
// ParameterizedType:参数化类型
// 获取<T>(注意,泛型可以传递多个如<T,A,B>,所以返回值是一个数组)中的T的真是类型
Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments();
Type actualType = actualTypeArguments[0];

try {
entityClass = Class.forName(actualType.getTypeName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

protected Connection getConnection(){
try {
// 1.加载驱动
Class.forName(DRIVER);
// 2.通过驱动获取连接对象
return DriverManager.getConnection(URL, USER, PWD);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return null;
}

// 关闭资源
protected void close(Connection connection, PreparedStatement psmt, ResultSet rs){
try {
if(rs!=null){
rs.close();
}
if(psmt!=null){
psmt.close();
}
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
// 给预处理对象设置参数
private void setParams(PreparedStatement psmt,Object... params) throws SQLException {
if(params!=null && params.length>0){
for (int i = 0; i < params.length; i++) {
psmt.setObject(i+1,params[i]);
}
}
}


// 通过反射,给obj对象的property属性赋propertyValue值
private void setValue(Object obj,String property,Object propertyValue){
Class clazz = obj.getClass();
// 获取property这个字符串对应的属性名,比如“fid”去找obj中的fid属性
try {
Field field = clazz.getDeclaredField(property);
if(field!=null){
// 强制访问
field.setAccessible(true);
//为obj对象设置filed属性的值
field.set(obj,propertyValue);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}

// 执行查询
protected List<T> executeQuery(String sql,Object... params){
List<T> list = new ArrayList<>();
try {
// 获取连接
connection = getConnection();
// 设置预处理对象
psmt = connection.prepareStatement(sql);
// 设置参数
setParams(psmt,params);
// 执行查询
rs= psmt.executeQuery();
// 处理结果集
// 难点在于如何处理T,我们不知道T是什么类型并且不知道T有多少个参数
// 通过rs可以获取结果集的元数据
// 元数据:描述结果集数据的数据,简单讲,就是这个结果接有哪些列,什么类型等等
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
while(rs.next()){
T entity = (T)entityClass.newInstance();
for (int i = 0; i < columnCount; i++) {
// 获取列名
String columnName = metaData.getColumnName(i + 1);
// 获取列对应的值
Object columnValue = rs.getObject(i + 1);
// 这里获取了列名,就需要根据列名把值填充给entity对象
setValue(entity,columnName,columnValue);
}
list.add(entity);

}
} catch (SQLException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} finally {
close(connection,psmt,rs);
}

return list;
}

// 通过查询返回一条记录
protected T load(String sql,Object... params){
try {
// 获取连接
connection = getConnection();
// 设置预处理对象
psmt = connection.prepareStatement(sql);
// 设置参数
setParams(psmt,params);
// 执行查询
rs= psmt.executeQuery();
// 处理结果集
// 难点在于如何处理T,我们不知道T是什么类型并且不知道T有多少个参数
// 通过rs可以获取结果集的元数据
// 元数据:描述结果集数据的数据,简单讲,就是这个结果接有哪些列,什么类型等等
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
if (rs.next()){
T entity = (T)entityClass.newInstance();
for (int i = 0; i < columnCount; i++) {
// 获取列名
String columnName = metaData.getColumnName(i + 1);
// 获取列对应的值
Object columnValue = rs.getObject(i + 1);
// 这里获取了列名,就需要根据列名把值填充给entity对象
setValue(entity,columnName,columnValue);
}
return entity;
}
} catch (SQLException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} finally {
close(connection,psmt,rs);
}
return null;
}

// 执行复杂的查询,返回统计结果,这个方法暂时没有用到
protected Object[] executeComplexQuery(String sql,Object... params){

try {
// 获取连接
connection = getConnection();
// 设置预处理对象
psmt = connection.prepareStatement(sql);
// 设置参数
setParams(psmt,params);
// 执行查询
rs= psmt.executeQuery();
// 处理结果集
// 难点在于如何处理T,我们不知道T是什么类型并且不知道T有多少个参数
// 通过rs可以获取结果集的元数据
// 元数据:描述结果集数据的数据,简单讲,就是这个结果接有哪些列,什么类型等等
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
if (rs.next()){
Object[] columnValueArr = new Object[columnCount];
for (int i = 0; i < columnCount; i++) {
// 获取列对应的值
Object columnValue = rs.getObject(i + 1);
columnValueArr[i]=columnValue;
}
return columnValueArr;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(connection,psmt,rs);
}
return null;
}

}

3 数据库连接池

3.1 概述和优势

之前的的连接都是用的时候才创建连接,当使用完成之后就销毁,这样效率很低,浪费资源。

使用数据库连接池就可以先创建多个连接对象放到连接池中,等到使用的时候直接从连接池中取出来,用完之后归还。

3.1.1 好处:

  1. 响应时间更快
  2. 连接对象的利用率更高

3.1.2详细配置参数:

不需要背,只是参考,记住主要用的几个就可以了

配置 缺省 说明
name 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
url 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username 连接数据库的用户名
password 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
driverClassName 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive 8 最大连接池数量
maxIdle 8 已经不再使用,配置了也没效果
minIdle 最小连接池数量
maxWait 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements false 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements -1 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrow true 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdle false 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis 有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun 不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls 物理连接初始化的时候执行的sql
exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

3.2 使用

3.2.1 基础使用

将对应的jar引入到项目中之后,创建DruidDataSource对象,进行连接即可。

注意:

  1. 被close的连接对象并没有真正关闭,而是将状态重新设置为空闲状态放回池子
  2. 没有被close的连接对象会被一直占用,那么下次继续获取连接对象,是不会获取到这个对象的
  3. 对于conn1对象指向别的东西,原始情况下jvm会回收conn1之前指向的对象,但是连接池中的连接默认有一个变量指着,所以不会被回收
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
package com.zylai.jdbc;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;

import java.sql.Connection;
import java.sql.SQLException;


/**
* @Author: Zhao YunLai
* @Date: 2022/06/22/14:13
* @Description:
*/
public class Demo02Druid {
public static void main(String[] args) throws SQLException {

DruidDataSource dataSource = new DruidDataSource();

dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/fruitdb?&useSSL=false&useUnicode=true&characterEncoding=utf-8");
dataSource.setUsername("root");
dataSource.setPassword("root");

// 证明两点:
// 1.被close的连接对象并没有真正关闭,而是将状态重新设置为空闲状态放回池子
// 2.没有被close的连接对象会被一直占用,那么下次继续获取连接对象,是不会获取到这个对象的
// 对于conn1对象指向别的东西,原始情况下jvm会回收conn1之前指向的对象,但是连接池中的连接默认有一个变量指着,所以不会被回收

for (int i = 0; i < 5; i++) {
Connection conn1 = dataSource.getConnection();
Connection conn2 = dataSource.getConnection();

System.out.println(conn1);
System.out.println(conn2);

if(i%3==0){
conn1.close();
conn2.close();
}
}
}

}

3.2.2 验证连接池的部分参数

这里以最大连接数和最长等待时间为例,当获取完所有的连接之后,再去获取连接超过指定的等待时间会抛出异常

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
package com.zylai.jdbc;

import com.alibaba.druid.pool.DruidDataSource;

import java.sql.Connection;
import java.sql.SQLException;


/**
* @Author: Zhao YunLai
* @Date: 2022/06/22/14:13
* @Description: 验证连接池的部分参数
*/
public class Demo03Druid {
public static void main(String[] args) throws SQLException {

DruidDataSource dataSource = new DruidDataSource();

dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/fruitdb?&useSSL=false&useUnicode=true&characterEncoding=utf-8");
dataSource.setUsername("root");
dataSource.setPassword("root");

dataSource.setInitialSize(2);
dataSource.setMaxActive(5);
dataSource.setMaxWait(5000);


for (int i = 0; i < 10; i++) {
Connection conn1 = dataSource.getConnection();

System.out.println(i+" "+conn1);

}
}

}

image-20220622185437122

3.2.3 读取配置文件信息创建连接池

在实际开发中,比如数据库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
package com.zylai.jdbc;

import com.alibaba.druid.pool.DruidDataSource;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
* @Author: Zhao YunLai
* @Date: 2022/06/22/14:13
* @Description: 从配置文件中读取信息
*/
public class Demo04Druid {
public static void main(String[] args) throws SQLException, IOException {

Properties properties = new Properties();
InputStream is = Demo04Druid.class.getClassLoader().getResourceAsStream("jdbc.properties");
properties.load(is);
DruidDataSource dataSource = new DruidDataSource();

dataSource.setDriverClassName(properties.getProperty("jdbc.driverClassName"));
dataSource.setUrl(properties.getProperty("jdbc.url"));
dataSource.setUsername(properties.getProperty("jdbc.username"));
dataSource.setPassword(properties.getProperty("jdbc.pwd"));

dataSource.setInitialSize(Integer.parseInt(properties.getProperty("druid.initialSize")));
dataSource.setMaxActive(Integer.parseInt(properties.getProperty("druid.maxActive")));
dataSource.setMaxWait(Long.parseLong(properties.getProperty("druid.maxWait")));

for (int i = 0; i < 10; i++) {
Connection conn1 = dataSource.getConnection();
System.out.println(i+" "+conn1);
}
}

}

配置文件jdbc.properties:

1
2
3
4
5
6
7
8
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/fruitdb?&useSSL=false&useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.pwd=root

druid.initialSize=2
druid.maxActive=5
druid.maxWait=5000

3.2.4 直接通过配置文件建立连接池

通过DruidDataSourceFactory.createDataSource(properties);返回DataSource接口,直接通过配置文件建立好了连接池。不过这里对于配置文件的格式有要求。

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
package com.zylai.jdbc;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
* @Author: Zhao YunLai
* @Date: 2022/06/22/14:13
* @Description: 直接通过配置文件创建连接池
*/
public class Demo05Druid {
public static void main(String[] args) throws Exception {

Properties properties = new Properties();
InputStream is = Demo05Druid.class.getClassLoader().getResourceAsStream("jdbc2.properties");
properties.load(is);

DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

for (int i = 0; i < 10; i++) {
Connection conn1 = dataSource.getConnection();
System.out.println(i+" "+conn1);
}
}
}

配置文件的格式应该一致

1
2
3
4
5
6
7
8
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/fruitdb?&useSSL=false&useUnicode=true&characterEncoding=utf-8
username=root
password=root

initialSize=2
maxActive=5
maxWait=5000

jdbc学习笔记
https://zhaoyunlai.github.io/posts/4bf3ee607b9f/
作者
赵运来
发布于
2022年6月22日
许可协议