THIS IS B3c0me

记录生活中的点点滴滴

0%

JDBC数据库编程

一、JDBC介绍

JDBC(Java DataBase Contectivity),Java与数据库的连接—数据库编程

JDBC是Java语言(JDK)为完成数据库的访问操作提供的一套标准

二、JDBC步骤

  1. 加载驱动,添加数据库驱动jar包,加载
  2. 创建连接
  3. 编写SQL指令
  4. 编译SQL指令
  5. 执行SQL指令
  6. 处理结果
  7. 关闭连接

三、JDBC入门案例

JDBC是用Java代码完成数据库访问的规范

3.1 加载驱动

3.1.1 下载对应数据库匹配版本的驱动jar包

  • MySQL数据库版本为5.x的推荐驱动版本5.1.47
  • MySQL为8.x的推荐使用8.0.x

3.1.2 将驱动jar文件添加到java应用

  • 在Java应用中创建lib文件夹
  • 将下载好的jar文件复制到lib
  • 将驱动文件设置为java库—驱动文件(add as library)

3.1.3注册驱动

通过反射机制将驱动jar文件中提供的驱动类载入到JVM中

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

3.2 创建连接

1
2
3
4
5
6
7
8
9
10
11

//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");

//2.创建连接:通过数据库驱动管理器获取连接
//java.sql.Connection接口, 一个对象就表示一个连接
//URL 表示数据库的统一资源定位器,
//URL 的参数:设置编码 ,使用SSL通信, 设置客户端和服务端时区转换
String url = "jdbc:mysql://localhost:3308/test1?characterEncoding=utf8&useSSL=true&useTimezone=true";
Connection connection = DriverManager.getConnection(url,"root","JBJHHZSL@0");
System.out.println(connection);

3.3编写SQL指令

1
2
3
//3.编写要执行的SQL指令   sql语句中需要的语句可以使用字符串拼接的形式(但是可能会导致SQL注入问题)
String sql = "insert into books(book_isbn,book_name,book_author) values " +
"(‘"+book_isbn+"’,‘"+book_name+"’,‘"+book_author+"’)";

3.4 加载SQL指令

1
2
3
4
//4.加载SQL指令:获取SQL指令的加载器
//Java.sql.statement 对象可以理解为SQL指令的加载器
//Java.sql.preparedStatement :SQL指令的预编译加载器
Statement statement = connection.createStatement();

3.5 执行SQL指令

1
2
3
4
//5.加载执行sql,获取执行结果
//a.如果我们的SQL指令为查询语句,则 ResultSet rs = statement.executeQuery(sql); rs 就是查询结果
//b.如果是DML指令, 则 int i = statement.executeUpdate(sql); i 表示DML操作影响的数据行数
int i = statement.executeUpdate(sql); // 如果 i>0,则表示DML操作成功 如果=0说明对数据表中的数据没有影响

3.6 处理结果

1
2
3
4
5
6
//6.处理结果
//添加操作:如果返回值大于0则添加成功
//修改操作:如果返回值I>0,表示修改成功,如果i<=0,表示对数据库没有影响
//删除操作:i>0表示成功
//查询操作:从ResultSet rs 中取出查询结果,封装到Java对象中;
System.out.println(i>0?"添加成功":"添加失败");

3.7 关闭连接

1
2
3
4
5
6
//6.处理结果
//添加操作:如果返回值大于0则添加成功
//修改操作:如果返回值I>0,表示修改成功,如果i<=0,表示对数据库没有影响
//删除操作:i>0表示成功
//查询操作:从ResultSet rs 中取出查询结果,封装到Java对象中;
System.out.println(i>0?"添加成功":"添加失败");

四、JDBC增删查改实例练习

使用JDBC完成数据库的CRUD访问

4.1 insert操作

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
public class TestInsertBook {
public static void main(String[] args) throws ClassNotFoundException, SQLException {

int book_id = 1004;
String book_name = "name3";
String book_author = "au3";

//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");

//2.创建连接:通过数据库驱动管理器获取连接

String url = "jdbc:mysql://localhost:3308/test1?characterEncoding=utf8&useSSL=true&useTimezone=true";
Connection connection = DriverManager.getConnection(url,"root","JBJHHZSTSL@0");
System.out.println(connection);

//3.编写要执行的SQL指令 sql语句中需要的语句可以使用字符串拼接的形式(但是可能会导致SQL注入问题)
String sql = "insert into books(book_isbn,book_name,book_author) values ('"+book_id+"','"+book_name+"','"+book_author+"')";

//4.加载SQL指令:获取SQL指令的加载器

Statement statement = connection.createStatement();

//5.加载执行sql,获取执行结果

int i = statement.executeUpdate(sql); // 如果 i>0,则表示DML操作成功 如果=0说明对数据表中的数据没有影响

//6.处理结果

System.out.println(i>0?"添加成功":"添加失败");

//7.断开连接

if(!statement.isClosed()) {
statement.close();
}
if(!connection.isClosed()) {
connection.close();
}

4.2 delete 操作

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
public class TestDeleteBook {

public static void main(String args[]) throws ClassNotFoundException, SQLException {

int bid = 1001;

//使用JDBC 根据图书编号删除图书信息

//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");

//2.创建连接
String url = "jdbc:mysql://localhost:3308/test1";
Connection connection = DriverManager.getConnection(url,"root","JBJHHZSTSL@0");

//3.编写SQL
String sql = "delete from books where book_isbn=" +bid;

//4.加载SQL
Statement statement = connection.createStatement();

//5.执行SQL
int i = statement.executeUpdate(sql);

//6.处理结果
System.out.println(i>0?"删除成功":"删除失败");

//7.关闭连接
if(statement != null && !statement.isClosed())
{
statement.close();
}
if(connection != null && !connection.isClosed()) {
connection.close();
}
}
}

4.3.update 操作

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
public class TestUpdateBook {

public static void main(String args[]) throws ClassNotFoundException, SQLException {

//根据主键修改图书信息

int bid = 1002;

//注册驱动 创建连接
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3308/test1";
Connection connection = DriverManager.getConnection(url,"root","JBJHHZSTSL@0");

//编写SQL语句
String sql = "update books set book_name='java' where book_isbn=" + bid;

//加载SQL
Statement statement = connection.createStatement();

//执行SQL语句
int i = statement.executeUpdate(sql);

//处理结果
System.out.println(i>0?"更新成功":"更新失败");

//关闭连接
if(statement != null && !statement.isClosed()){
statement.close();
}
if(connection != null && !connection.isClosed()){
connection.close();
}
}
}

4.4.select 操作

重点在结果集rs 的处理

1
2
3
4
5
6
7
8
9
//key code:
//通过executeQuery 查询,并将查询的结果集存放在一个ResultSet 对象中;
ResultSet rs = statement.executeQuery(sql);
while(rs.next()){
int book_id = rs.getInt("book_isbn");
String book_name = rs.getString("book_name");
String book_author = rs.getString("book_author");
System.out.println(book_id +"-"+book_name+"-"+book_author);
}

五、JDBC的核心类与接口

java.sql.DriverManager类 驱动管理器

java.sql.Connection接口 数据库连接

java.sql.Statement接口 SQL指令的加载器

java.sql.ResultSet接口 结果集

5.1 DriverManager 类

  • 注册驱动
  • 获取数据库连接
  • 注册驱动

    1
    2
    3
    4
    Class.forName("com.mysql.cj.jdbc.Driver");
    //在Driver类中的静态初始化块中,注册驱动
    //在我们的应用程序中手动注册驱动的代码也可以省略(Class.forNane()可以不写)
    //如果我们没有手动注册驱动,驱动管理器在获取连接时会自动读取驱动jar META-info 中的路径进行注册
  • 获取数据库连接

    1
    Connection connection = DriverManager.getConnection(url,username,paswd);

5.2 Connection 接口

Connection对象表示java应用于数据库之间的连接

  • 通过Connection接口对象,获取执行SQL语句的Statement对象
  • 完成数据库的事务管理

5.2.1 获取Statement对象

  • Statement接口:编译执行静态SQL指令

    1
    Statement statement = connection.createStatement();
  • PreparedStatement接口:继承了Statement接口,可以预编译动态SQL指令(解决SQL注入问题)

    1
    PreparedStatement preparedStatement = connection.prepareStatement(sql);
  • CallableStatement接口,继承了PreparedStatement接口,可以调用存储过程

    1
    CallableStatement callablestatement = connection.prepareCall(sql);

5.2.2 事务管理

1
2
3
4
5
6
7
8
//开启事务(关闭事务自动提交)
connection.setAutoCommit(false);

//事务回滚
connection.rollback();

//提交事务
connerction.commit();

5.3 Statement接口

用于编译、执行静态SQL指令

  • 静态SQL指令:可以直接执行的完整SQL指令
  • 动态SQL指令:带有占位符的SQL指令,不能直接执行
1
2
3
4
5
//执行DML操作的SQL指令
int i = statement.executeUpdate(sql);

//执行DQL的SQL指令
ResultSet rs = statement.executeQuery(sql);

5.4 ResultSet 接口

ResultSet 接口对象用于封装查询操作的结果集

1
2
3
4
5
6
//判断结果集中是否还有数据
while(rs.next()){...};

//获取结果集中的数据
rs.getInt("columnname");
rs.getDate(""); //获取日期类型的数据

六、SQL注入问题

6.1SQL注入介绍

在JDBC的SQL指令编写过程中,如果SQL指令中需要数据,可以通过字符串拼接的形式将参数拼接到SQL指令中,参数变化可能导致SQL原意变化,导致对数据的非法操作

6.2 如何解决SQL注入问题

使用PreparedStatement 进行SQL指令的预编译

  • 在编写SQL指令时,如果SQL指令中需要参数,一律使用 ? 占位符
  • 如果SQL指令中有占位符?,则使用preraredstatement对象预编译SQL指令
  • 预编译完成之后,通过PreparedStatement对象给预编译后的SQL语句赋值
  • 在所有SQL指令中的?被赋值之后,通过PreparedStatement来执行SQL,执行SQL时不再加载SQL
    • int i = preparedStatement.executeUpdate(); 括号内不传参

tip:

  • SQL中没有参数优先使用statement对象

七、工具类

7.1代码的复用性

在我们的程序中,如果需要完成相同的操作,相同的代码无需重复编写,我们只需要一次编写即可

7.2 工具类DBHelper封装

JDBC数据库编程有一个固定的步骤

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
package utils;

import java.sql.*;

public class DBHelper {
//优化1:将创建数据库连接所需的字符串定义为常量 ,集中管理
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String URL = "jdbc:mysql://localhost:3308/test1";
private static final String USER = "root";
private static final String PASSWORD = "JBJHHZSTSL@0";

//优化2:注册驱动只需完成一次,因此放在帮助类的静态初始化块中完成
static {
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
System.out.println("---------注册驱动失败");
}
}
public static Connection getConnection(){
Connection connection = null;
try {
connection = DriverManager.getConnection(URL,USER,PASSWORD);
} catch (SQLException e) {
System.out.println("---------创建连接失败");
}
return connection;
}
//优化3:使用statement接口,既可以接受statement对象也可以接受PrepareStetement对象 (多态的应用)

public static void closeConnection(Statement statement,Connection connection) {
//优化4:合并删除方法
closeConnection(null,statement,connection);
}


public static void closeConnection(ResultSet resultSet,Statement statement,Connection connection) {
try {
if (resultSet != null && !resultSet.isClosed()) {
resultSet.close();
}
if (statement != null && !statement.isClosed()) {
statement.close();
}
if (connection != null && !connection.isClosed()) {
connection.close();
}
} catch (SQLException e) {
System.out.println("------关闭连接失败");
}
}
}

八、DAO 与 DTO的封装

DAO(Data Access Object) 数据访问对象

DTO(Data Transfer Object) 数据传输对象

8.1 CRUD方法的封装

面向对象编程的特征之一——封装

即要将完成某个CRUD操作的代码单独定义成一个方法

添加insert

1
2
3
4
5
6
7
8
9
10
11
public boolean insertBook(int book_id,String book_name,String book_author) throws SQLException {

boolean flag = false;
Connection connection = DBHelper.getConnection();
String sql = "insert into books(book_isbn,book_name,book_author) values ('"+book_id+"','"+book_name+"','"+book_author+"')";
Statement statement = connection.createStatement();
int i = statement.executeUpdate(sql); // 如果 i>0,则表示DML操作成功 如果=0说明对数据表中的数据没有影响
flag = i > 1;
DBHelper.closeConnection(statement,connection);
return flag;
}

8.2 DTO实体类封装

处理:在Java程序中创建一个属性与数据库表匹配的类,通过此类的对象来封装查询到的数据,我们把用于传递JDBC增删查改操作的数据的对象称为数据传输对象(DTO)

实体类的创建规则

1
2
3
4
5
6
7
//1.类中属性的个数与查询到的数据库记录列数相同
//2.类中属性的类型与表中字段类型匹配
//3.提供所有属性的get 和 set方法 Alt + insert 快捷键
//4.提供全参构造器
//5.提供无参构造器
//6.重写toString 方法
//7.暂略 :重写jashcode 和 equals 实现序列化接口

实体类实例

省略get和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
public class Book {

private int bookId;
private String bookName;
private String bookAuthor;

public Book(int bookId, String bookName, String bookAuthor) {
this.bookId = bookId;
this.bookName = bookName;
this.bookAuthor = bookAuthor;
}
//无参构造器
public Book() {
}
//重写toString方法
@Override
public String toString() {
return "Book{" +
"bookId=" + bookId +
", bookName='" + bookName + '\'' +
", bookAuthor='" + bookAuthor + '\'' +
'}';
}

8.3 使用DTO封装多条记录

每条查询的结果分别存放到一个实体对象中,再将多个DTO对象存放到一个List集合中,返回list集合

8.4 实体类传递参数

8.5 DAO封装

我们将对数据库中同一张表的JDBC操作方法封装到同一个Java类中,这个类就是访问此数据表的数据访问对象

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
package dao;

import dto.Book;
import utils.DBHelper;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;

public class BookDao {

public boolean insertBook(int book_id,String book_name,String book_author) throws SQLException {

boolean flag = false;
Connection connection = DBHelper.getConnection();
String sql = "insert into books(book_isbn,book_name,book_author) values ('"+book_id+"','"+book_name+"','"+book_author+"')";
Statement statement = connection.createStatement();
int i = statement.executeUpdate(sql); // 如果 i>0,则表示DML操作成功 如果=0说明对数据表中的数据没有影响
flag = i > 1;
DBHelper.closeConnection(statement,connection);
return flag;
}

public boolean deleteBook(Book book) throws SQLException {
boolean flag = false;
Connection connection = DBHelper.getConnection();
//3.编写SQL
String sql = "delete from books where book_isbn=?";

//4.加载SQL
//Statement statement = connection.createStatement();
PreparedStatement prepareStatement = connection.prepareStatement(sql);
prepareStatement.setInt(1,book.getBookId());
//5.执行SQL
int i = prepareStatement.executeUpdate();
flag = i>0;
DBHelper.closeConnection(prepareStatement,connection);
return flag;
}
}

8.6 DAO类的优化

1.在应用程序开发中,如果方法中抛出异常且自己可以处理,则直接通过try catch代码块进行捕获处理

2.JDBC操作方法的连接需要放在finally中进行关闭

3.将数据库连接connection 和 statement 、resultset等对象定义在try语句之前,在try语句中直接赋值

4.因为所有的JDBC操作都需要Connection,statement,resultset等对象,因此在DAO中可以将这些对象定义为成员变量

九、JDBC的综合案例

完成学生信息的CRUD操作

9.1 JDBC 数据库编程的流程

  • 创建数据库,数据表
  • 创建新的Java工程
  • 创建JDBC的工具类DBHelper
  • 创建DTO类(用于封装数据对象)
  • 创建DAO类(用于完成对数据的操作)

9.2 创建JDBC工具类

  • 在Java工程中创建package:com.jdbc.utils
  • 在com.jdbc.utils里面创建工具类HBHelper
  • 编写DBHelper工具类
    • 添加驱动jar文件
    • 编写代码

9.3 创建DTO类

  • 新建一个package dto
  • 在dto包中创建数据表对应的实体类
  • 编写实体类代码

9.4 创建DAO类

  • 新建package dao
  • 在dao包中创建类
  • 编写dao类的代码

9.5 测试DAO类中的方法

使用Junit对DAO中定义的JDBC方法进行单元测试

9.5.1 下载导入junit依赖到项目中

  • 下载 mvnrepository.com-
    • junit
    • hamcrest-core
  • 添加为Lib

9.5.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
39
40
41
package com.jdbc.test;

import com.jdbc.dao.StudentDao;
import com.jdbc.dto.Student;
import org.junit.Test;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

public class StudentDaoTest {

//1.在测试类中定义成员变量:创建被测试类的对象;
private StudentDao studentDao = new StudentDao();


//2、测试insertStudent方法
//a.测试方法名=test+被测试方法名
//b.测试方法无参数无返回值
@Test
public void testInsertStudent(){
//准备被测试方法所需的参数
Student stu = new Student("20211021","钱倒一","女",18,1);
//调用被测试方法,获取结果
boolean b = studentDao.insertStudent(stu);
//断言返回结果(成立或者不成立)
assertTrue(b);
}
@Test
public void testQueryStudent(){
String snum = "20211021";
Student student = studentDao.queryStudent(snum);
assertEquals("钱一",student.getStuName());

}
@Test
public void testListStudent(){
List<Student> students = studentDao.listStudents();
assertEquals(8,students.size());
}
}

十、JDBC事务管理

  • 事务的四大特性:ACID
  • 事务的隔离级别

10.1 JDBC实现借书操作

1.向records表添加借书记录

2.修改books表中的库存

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.jdbc.dao;

import com.jdbc.utils.DBHelper;

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

public class BookDAO {

public boolean borrowBook(String stuNum,String bookId,int num){
boolean flag = false;
try {
//1.向借书记录表添加借书记录
Connection connection = DBHelper.getConnection();
String sql1 = "insert into records(snum,bid,borrow_num,is_return,borrow_data) values(?,?,?,0,sysdate())";
PreparedStatement preparedStatement = connection.prepareStatement(sql1);
preparedStatement.setString(1,stuNum);
preparedStatement.setString(2,bookId);
preparedStatement.setInt(3,num);
int i = preparedStatement.executeUpdate();

//2.修改图书库存
Connection connection2 = DBHelper.getConnection();
String sql2 = "update books set book_stock=book_stock-? where book_id=?";
PreparedStatement preparedStatement2 = connection2.prepareStatement(sql2);
preparedStatement2.setInt(1,num);
preparedStatement2.setString(2,bookId);
int j = preparedStatement2.executeUpdate();

flag = i >0 && j>0;
} catch (SQLException e){
e.printStackTrace();
} finally {
//关闭连接
}
return flag;

}
}

分析

  • 借书业务由两个数据库操作完成,这两个操作要么同时成功要么同时失败,构成一个数据库事务
  • JDBC的DML操作默认是自动提交的,因此当第一个DLM操作(添加借书记录)完成后,无论第二个操作是否成功,借书记录都会永久添加到数据库
  • JDBC如何做事务管理呢?

10.2 JDBC事务管理

1.一个事务中的多个DML操作需要基于同一个数据库连接

2.创建连接之后设置事务手动提交

3.在当前事务中的所有操作完成之后手动提交

4.当事务中的任何一个步骤失败后执行事务回滚

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

import com.jdbc.utils.DBHelper;

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

public class BookDAO {

public boolean borrowBook(String stuNum,String bookId,int num){
boolean flag = false;
Connection connection = null;
PreparedStatement preparedStatement = null;
PreparedStatement preparedStatement2 = null;
try {
//1.向借书记录表添加借书记录
connection = DBHelper.getConnection();
//建立连接之后关闭自动提交
connection.setAutoCommit(false);
String sql1 = "insert into records(snum,bid,borrow_num,is_return,borrow_data) values(?,?,?,0,sysdate())";
preparedStatement = connection.prepareStatement(sql1);
preparedStatement.setString(1,stuNum);
preparedStatement.setString(2,bookId);
preparedStatement.setInt(3,num);
int i = preparedStatement.executeUpdate();

//设置测试异常
int k = 10/0;

//2.修改图书库存
String sql2 = "update books set book_stock=book_stock-? where book_id=?";
preparedStatement2 = connection.prepareStatement(sql2);
preparedStatement2.setInt(1,num);
preparedStatement2.setString(2,bookId);
int j = preparedStatement2.executeUpdate();
connection.commit();
flag = i >0 && j>0;

} catch (Exception e){
e.printStackTrace();
try {
connection.rollback();
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
} finally {
DBHelper.close(preparedStatement,null);
DBHelper.close(preparedStatement2,connection);
}
return flag;

}
}

10.3 service层的事务管理

DAO只负责数据库的操作,业务由service层进行管理

10.3.1 Service 分层介绍
  • DAO只负责特定的数据库操作
  • Service进行业务处理
10.3.2 Service 分层实现
  • RecordDAO
  • BookDAO
  • 创建Bookservice
10.3.3 Service 事务管理

service层事务中多个数据的DML操作是相互独立的,如何保证所有DML操作要么同时成功要么同时失败呢?

  • 多个DML操作需要基于同一个数据库连接
  • 第一个DML操作之前设置事务手动提交

如何实现多个DAO基于同一个数据库连接?

  • Service 层通过传递Connection连接对象的方式实现多个DML操作使用同一个连接
    • 分析:DAO类中的Connection对象需要通过Service传递给进来,但是当我们通过面向接口开发时,容易造成接口冗余(接口污染)
  • 使用ThreadLocal容器,实现多个DML操作的同一连接

存储Connection对象可以使用List容器,但是在多线程并发编程中会出现资源竞争问题

为了解决并发编程的连接对象共享问题,我们可以使用ThreadLocl作为数据库连接对象的容器

**ThreadLocal:**内部维护一个Map集合,可以为每一个线程存储一个连接对象

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

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

public class DBHelper {

//定义数据库连接信息
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String URL = "jdbc:mysql://localhost:3308/db_test1?characterEncoding=utf8";
private static final String USER = "root";
private static final String PASSWORD = "JBJHHZSS0";

private static final ThreadLocal<Connection> container = new ThreadLocal<>();

//静态初始化块注册驱动
static{
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}

//创建数据库连接
public static Connection getConnection() {
//从容器中获取连接
Connection connection = container.get();
try {
//如果容器中没有连接,则创建连接
if(connection == null) {
connection = DriverManager.getConnection(URL, USER, PASSWORD);
container.set(connection);
}
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}

//关闭连接

//如果使用ThreadLocal存储数据库连接对象,关闭连接时同时要将Connection对象从ThreadLocal容器中移除
public static void closeConnection(){
Connection connection = container.get();//获取到当前线程使用的数据库连接对象
try {
if(connection != null && !connection.isClosed()){
connection.close();
}
container.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void closeStatement(ResultSet resultSet, Statement statement) {
try {
if (resultSet != null && !resultSet.isClosed()) {
resultSet.close();
}
if (statement != null && !statement.isClosed()) {
statement.close();
}

} catch (SQLException e) {
e.printStackTrace();
}
}

public static void closeStatement(Statement statement){
closeStatement(null,statement);
}

//测试用
// public static void main(String[] args) {
// System.out.println(getConnection());
// }
}

十一、数据库连接池

11.1概念

如果每个JDBC操作都创建一个连接,使用完成之后销毁,JVM会因为频繁地创建、销毁连接而占用额外的系统资源

数据库连接本质上是可以被重用的资源

因此创建一个存放连接的容器,用于存放数据库连接,当我们进行JDBC操作室,直接从这个容器中获取连接,如果容器中没有数据库连接或者没有空闲的连接,才重新建立连接使用(新建立的连接也被放入连接池)

数据库连接池是有容量的,如果连接池中没有闲置连接对象或者容量已满,则新的DML操作就会进行等待

连接池:存放数据库连接对象的容器

作用:对数据库连接进行管理

11.2 常用的连接池

我们可以编程实现创建一个数组或者集合来存放数据库连接

目前市面上已有多种实现的数据库连接池,我们无需再手动实现

  • 基于连接池的性能,使用的便捷性以及连接池的性能监控多方面综合,druid是目前企业中应用最广泛的连接池
  • HikariCp诸多竞品中最好的

11.3 使用数据库连接池Druid

11.3.1 创建应用
  • 创建Java工程
  • 添加数据库连接驱动jar文件
11.3.2 创建连接池属性配置文件
  1. 创建druid.properties文件

  2. 配置druid连接池的属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #数据库连接信息
    driverClassName=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3308/db_test1
    username=root
    password=JBJHHZSTSL@0

    #连接池属性

    #连接池的最大容量
    maxActive=50

    #连接池的初始化连接数
    initialSize=10

    #最小空闲连接数(当数据库连接使用率很低时,连接池中的连接会被释放一部分)
    minIdle=5

    #超时等待时间/ms
    maxWait=30000

11.3.3 创建连接池工具类
  1. 下载并导入Druid的jar文件

  2. 创建DruidUtils工具类(与属性文件在同一个包下)

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

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

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

    //创建数据库连接池的工具类
    public class DruidUtils {

    //DruidDataSource对象 就表示数据库连接池
    private static DruidDataSource druidDataSource;

    //静态代码块初始化druid
    static {

    try {
    //读取配置属性
    InputStream is = DruidUtils.class.getResourceAsStream("druid.properties");
    Properties properties = new Properties();
    properties.load(is);
    //System.out.println(properties);
    //使用属性文件初始化DruidDataSoure对象
    druidDataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);
    } catch (IOException e) {
    e.printStackTrace();
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    //创建静态方法,从连接池对象中获取连接
    public static Connection getConnection(){
    Connection connection = null;
    try {
    connection = druidDataSource.getConnection();
    } catch (SQLException e) {
    e.printStackTrace();
    }
    return connection;
    }
    public static DruidDataSource getDruidDataSource(){
    return druidDataSource;
    }

    }

十二、通用JDBC操作封装

在DAO层的JDBC操作中,对数据表的增删改查操作存在代码的冗余

12.1 DML操作封装

  • 参数不同
  • SQL不同
  • 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
package com.jdbc.dao;
import com.jdbc.utils.DruidUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
//用于封装公共的JDBC操作
public class CommonDAO {
//传递动态参数
public boolean update(String asql,Object... args){
boolean flag = false;
try{
Connection connection = DruidUtils.getConnection();
String sql = asql;
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//动态赋值
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i+1,args[i]);
}
int i = preparedStatement.executeUpdate();
flag = i>0;
} catch (Exception e){
e.printStackTrace();
}
return flag;
}
}

12.2 DQL操作封装

  • 泛型的使用
  • 自定义接口
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
//3.查询多个学生信息
public List<Student> listStudents(){

String sql = "select stu_num,stu_name,stu_gender,stu_age,cid from students";
CommonDAO<Student> commonDAO = new CommonDAO<Student>();
RowMapper<Student> rowMapper = new RowMapper<Student>() {
@Override
public Student getRow(ResultSet resultSet) {
Student student = null;
try {
String stuNum = resultSet.getString("stu_num");
String stuName = resultSet.getString("stu_name");
String stuGender = resultSet.getString("stu_gender");
int stuAge = resultSet.getInt("stu_age");
int stuCid = resultSet.getInt("cid");

student = new Student(stuNum,stuName,stuGender,stuAge,stuCid);

} catch (SQLException e) {
e.printStackTrace();
}
return student;
}
};

return commonDAO.select(sql,rowMapper);
}

十三、Apache DBUtils

13.1 DBUtils介绍

Commons DBUtils 是Apache 组织提供的一个针对JDBC进行简单封装的开源工具类, 使用DBUtils可以极大简化JDBC应用开发程序开发,同时不会影响数据库访问的性能

  • 提供对数据表通用的DML操作
  • 提供通用的DQL操作
  • 核心类:
    • QueryRunner,用于执行SQL指令:update操作和query操作
    • ResultSetHandler接口,结果集处理器

13.2 DBUtils使用准备

  1. 新建Java工程
  2. 添加依赖:
    • jdbc mysql的驱动jar
    • Druid的jar包
    • DBUtils 的jar包
  3. 配置druid的属性文件
    • 新建一个utils文件夹
    • 文件夹中新建文件druid.properties
      • 编写配置文件
  4. 创建连接池工具类
    • 在工具包中创建DruidUtils工具类
    • 编写工具类

13.3 DBUtils使用

完成图书信息的数据库操作

13.3.1 添加操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//添加操作
public int insertBook(Book book){
//1.编写SQL
int i = 0;
try {
String sql = "insert into books(book_id,book_name,book_stock) values(?,?,?)";
//2.准备参数
Object[] params = {book.getBookId(),book.getBookName(),book.getBookStock()};
//3.调用commons-dbutils中的QueryRunner

QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
i = queryRunner.update(sql, params);
} catch (SQLException e) {
e.printStackTrace();
}
return i;
}
13.3.2 删除操作
1
2
3
4
5
6
7
8
9
10
11
12
//删除操作
public int deleteBook(String bid){
int i = 0;
String sql = "delete from books where book_id=?";
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
try {
i = queryRunner.update(sql,bid);
} catch (SQLException e) {
e.printStackTrace();
}
return i;
}
13.3.3 修改操作
1
2
3
4
5
6
7
8
9
10
11
12
13
//修改操作
public int updateBook(Book book){
int i = 0;
try {
String sql = "update books set book_name=?,book_stock=? where book_id=?";
Object[] params = {book.getBookName(),book.getBookStock(),book.getBookId()};
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
i = queryRunner.update(sql,params);
} catch (SQLException e) {
e.printStackTrace();
}
return i;
}
13.3.4 查询操作
  • 查询一条记录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //查询一条信息
    public Book queryBook(String bookId){
    Book book = null;
    String sql = "select book_id bookId,book_name bookName,book_stock bookStock from books where book_id=?";
    QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
    //对于查询操作,需要通过queryRuner对象调用query对象来执行,所有的query对象都需要ResultSetHandler参数
    //如果SQL执行之后返回的是一行记录,我们要给一个实现类BeanHandler
    //要求:查询结果集的字段名必须与指定的实体类的属性名匹配
    //解决办法:1.创建实体类的时候属性名与数据表中的列名相同
    // 2.查询语句字段名取别名
    // 3.自定义结果集处理
    BeanHandler<Book> beanHandler = new BeanHandler<Book>(Book.class);
    try {
    book = queryRunner.query(sql, beanHandler, bookId);
    } catch (SQLException e) {
    e.printStackTrace();
    }
    return book;
    }
  • 查询多条记录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //查询多条记录
    public List<Book> listBooks(){
    List<Book> bookList = null;
    String sql = "select book_id bookId,book_name bookName,book_stock bookStock from books";
    QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
    //如果SQL返回的是多条记录,用BeanListHandler封装查询结果
    BeanListHandler<Book> beanListHandler = new BeanListHandler<Book>(Book.class);

    try {
    bookList = queryRunner.query(sql, beanListHandler);
    } catch (SQLException e) {
    e.printStackTrace();
    }
    return bookList;
    }
13.3.5 查询一个值
1
2
3
4
5
6
7
8
9
10
11
12
13
public int getCount(){
int count = 0;
String sql = "select count(1) from books";
//如果返回的是一个值,通过ScalarHandler指定返回类型
ScalarHandler<Integer> scalarHandler = new ScalarHandler<Integer>();
QueryRunner queryRunner = new QueryRunner(DruidUtils.getDataSource());
try {
count = queryRunner.query(sql, scalarHandler);
} catch (SQLException e) {
e.printStackTrace();
}
return count;
}

2022-10-19完结

欢迎关注我的其它发布渠道