JDBC 技术( mysql 8.0.26 JDK 21 尚硅谷最新版 4h)
基础篇
:以解决现有问题的角度,快速学习 JDBC 的基础应用,让学员以最低的学习成本上手使用 JDBC
一,引言
1.1 数据的存储
我们在开发Java程序时,数据都是存储在内存中,属于临时存储,当程序停止或重启时,内存中的数据就丢失了!我们为了解决数据的长期存储问题,有如下解决方案:
- 数据通过 I/O 流技术,存储在本地文件中,解决了持久化问题,但是没有结构和逻辑,不方便管理和维护。
- 通过关系型数据库,将数据按照特定的格式交由数据库管理系统维护。关系型数据库是通过库和表分隔不用的数据,表中数据存储的方式是行和列,区分相同格式不同值的数据
1.2 数据的操作
数据存储在数据库,仅仅解决了我们数据存储的问题,但当我们程序运行时,需要读取数据,以及对数据做增删改查的操作,那么我们如何通过Java程序对数据库中的数据做增删改查呢?
二,JDBC
2.1概念
- JDBC: Java Database Connectivity, 意为 Java 数据库连接
- JDBC 是 Java 提供的一组独立于任何数据库管理系统的 API.
- Java 提供接口规范,由各个数据库厂商提供接口的实现,厂商提供的实现类封装成 jar 文件,也就是我们俗称的数据库驱动 jar 包。
- 学习 JDBC, 充分体现了面向接口编程的好处,程序员只关心标准和规范,而无需关注实现过程。
2.2 JDBC的核心组成
- 接口规范:
-
为了项目代码的可移植性,可维护性,SUN 公司从最初就制定了Java程序连接各种数据库的统一接口规
范。这样的话,不管是连接哪一种 DBMS 软件,Java 代码可以保持一致性。 -
接口存储在 java.sql 和 javax.sql 包下。
- 实现规范:
-
因为各个数据库厂商的 DBMS 软件各有不同,那么各自的内部如何通过 SQL 实现增、删、改、查等操作管理
数据,只有这个数据库厂商自己更清楚,因此把接口规范的实现交给各个数据库厂商自己实现。 -
厂商将实现内容和过程封装成 jar 文件,我们程序员只需要将 jar 文件引l入到项目中集成即可,就可以开发调
用实现过程操作数据库了。 (一般第三方 jar 放在 lib 文件下)
三,JDBC 快速入门
连接数据库, 创建学习项目
四,核心API理解
4.1 注册驱动
-
Class.forName("com.mysql.cj.jdbc.Driver"); // 不带.cj的是5.几版本的
-
在Java中,**当使用 JDBC(Java Database Connectivity) 连接数据库时,需要加载数据库特定的驱动程序,以便于数据库进行通信。**加载驱动程序的目的时为了注册驱动程序,使得 JDBCAPI 能够识别并与特定的数据库进行交互
-
从JDK6 开始,不再需要显式地调用 Class.forName() 来加载 JDBC 驱动程序,只要在类路径中集成了对应地jar文件,会自动在初始化时注册驱动程序
4.2 Connection
-
Connection 接口是 JDBCAPI 的重要接口,用于建立与数据库的通信通道。换而言之,Connection 对象不为
则代表一次数据库连接。 -
在建立连接时,需要指定数据库URL、用户名、密码参数。
- URL: jdbc:mysql://localhost:3306/atguigu
- jdbc:mysql://IP地址:端口号/数据库名称?参数键值对1&参数键值对2
- URL: jdbc:mysql://localhost:3306/atguigu
-
Connection 接口还负责管理事务,Connection 接口提供了 commit 和 rollback 方法,用于提交
和回滚事务。 -
可以创建 Statement 对象,用于执行 SQL 语句并与数据库进行交互。
-
在使用 JDBC 技术时,必须要先获取 Connection 对象,在使用完毕后,要释放资源,避免资源占用浪费及泄露
4.3 Statement
-
**Statement接口用于执行SQL 语句并与数据库进行交互。**它是 JDBCAPI 中的一个重要接口。通过
Statement 对象,可以向数据库发送 SQL 语句并获取执行结果。 -
结果可以是一个或多个结果。
- 增删改:受影响行数单个结果。
- 查询:单行单列、多行多列、单行多列等结果。
-
但是 Statement 接口在执行 sQL 语句时,会产生 SQL 注入攻击问题:
- 当使用Statement执行动态构建的 SQL 查询时,往往需要将查询条件与 SQL 语句拼接在一起,直接将
参数和 SQL 语句一并生成,让 SQL 的查询条件始终为true得到结果。
- 当使用Statement执行动态构建的 SQL 查询时,往往需要将查询条件与 SQL 语句拼接在一起,直接将
4.4 PreparedStatement
-
PreparedStatement 是 Statement 接口的子接口,用于执行预编译的 SQL 查询,作用如下:
-
**预编译SQL语句:**在创建 PreparedStatement 时,就会预编译 SQL 语句,也就是 SQL 语句已经固定。
-
**防止sQL注入:**PreparedStatement 支持参数化查询,将数据作为参数传递到 sQL 语句中,采用?占位符
的方式,将传入的参数用一对单引号包裹起来",无论传递什么都作为值。有效防止传入关键字或值导致SQL 注入问题。 -
**性能提升:**PreparedStatement 是预编译 sQL 语句,同一 SQL 语句多次执行的情况下,可以复用,不必每次重新编译和解析。
-
-
后续的学习我们都是基于 PreparedStatement 进行实现,更安全、效率更高
4.5 ResultSet
- ResultSet 是 JDBC API 中的一个接口,用于表示从数据库中执行查询语句所返回的结果集。它提供了一种月
于遍历和访问查询结果的方式。 - **遍历结果:**ResultSet 可以使用 next() 方法将游标移动到结果集的下一行,逐行遍历数据库查询的结果,返
回值为 boolean 类型,true 代表有下一行结果,false 则代表没有。 - **获取单列结果:**可以通过 getXxx 的方法获取单列的数据,该方法为重载方法,支持索引和列名进行获取。
五,基于PrepareStatement实现CRUD
5.1 查询单行单列
@Test
public void querySingleRowAndColumn()throws SQLException{
//1.注册驱动
// Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取数据库连接
Connection connection =
DriverManager.getConnection("jdbc:mysql://localhost:3306/atguigu","root","atguigu");
//3.创建PreparedStatement对象,并预编译SQL语句
PreparedStatement preparedStatement = connection.prepareStatement("select count(*)acount from t_emp");
//4.执行SQL语句,获取结果
ResultSet resultSet = preparedStatement.executeQuery();
// 5.处理结果(一次) 建议使用while(resultSet.next())
if(resultSet.next()){
int count = resultSet.getInt("count");
System.out.println("count="+count);
}
//6.释放资源(先开后关原则)
resultSet.close();
preparedStatement.close();
connection.close();
}
5.2 查询单行多列
@Test
public void testQuerySingleRow()throws Exception {
//1.注册驱动
//2.获取连接
Connection connection = DriverManager.getConnection(url:"jdbc:mysql:///atguigu",user:"root", password:"atguigu");
//3.预编译SQL语句获得PreparedStatement对象
PreparedStatement preparedStatement = connection.prepareStatement( sql: "SELECT emp-id,emp_name,emp_salary,emp-age FROM t-emp WHERE emp_id = ?");
//4.为占位符赋值,然后执行,并接受结果
preparedStatement.setInt(parameterIndex: 1, x: 5);
ResultSet resultSet = preparedStatement.executeQuery();
//5.处理结果
while(resultSet.next()){
int empId = resultSet.getInt( columnLabel:"emp_id");
String empName = resultSet.getString( columnLabel:"emp_name");
double empSalary = resultSet.getDouble(columnLabel:"emp_salary");
int empAge = resultSet.getInt( columnLabel: "emp_age");
System.out.println(empId+"\t"+empName+"\t"+empSalary+"\t"+empAge);
}
//6.资源释放
resultSet.close();
preparedStatement.close();
connection.close()
}
5.3 查询多行多列
@Test
public void testQueryMoreRow() throws Exception {
//2.获取连接
Connection connection = DriverManager.getConnection(url:"jdbc:mysql:///atguigu", user:"root",password:"atguigu");
//3.预编译SQL语句获得PreparedStatement对象
PreparedStatement preparedStatement = connection.prepareStatement( sql: "SELECT emp_id,emp_name,emp_salary,emp_age FROM t_emp WHERE emp_age > ?")
//4.为占位符赋值,执行SQL语句,接受结果
preparedStatement.setInt( parameterlndex: 1, x: 25);
ResultSet resultSet = preparedStatement.executeQuery();
//5.处理逻辑
while(resultset.next()) {
int empId = resultSet.getInt( columnLabel: "emp_id");
String empName = resultSet.getString( columnLabel: "emp_name");
double empSalary = resultSet.getDouble( columnLabel:"emp_salary");
int empAge = resultSet.getInt( columnLabel:"emp-age");
System.out.println(empId+"\t"+empName+"\t"+empSalary+"\t"+empAge);
}
//6.资源释放
resultSet.close();
preparedStatement.close();
connection.close();
5.4 新增
@Test
public void testInsert() throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "atguigu");
PreparedStatement preparedStatement = connection.prepareStatement("insert into t_emp(emp_name, emp_salary, emp_age) values (?, ?, ?)");
// 为占位符赋值,执行sql语句,接受结果
preparedStatement.setString(1, "rose");
preparedStatement.setDouble(2, 345.67);
preparedStatement.setInt(parameterIndex: 3, x: 28);
// 根据受影响行数,做判断,得到成功或失败
int result = preparedStatement.executeUpdate();
if (result > 0) {
System.out.println("成功!");
} else {
System.out.println("失败!");
}
preparedStatement.close();
connection.close();
}
5.5 修改
@Test
public void testUpdate() throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "atguigu");
PreparedStatement preparedStatement = connection.prepareStatement("update t_emp emp_salary = ? where emp_id = ?");
preparedStatement.setDouble(1, 888.88);
preparedStatement.setInt(2, 6);
int result = prepareStatement.executeUpdate();
if (result > 0) {
System.out.println("成功!");
} else {
System.out.println("失败!");
}
preparedStatement.close();
connection.close();
}
5.6 删除
@Test
public void testDelete() throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:mysql:///atguigu", "root", "atguigu");
PreparedStatement preparedStatement = connection.prepareStatement("delete from t_emp where emp_id = ?");
// preparedStatement.setDouble(1, 888.88);
// preparedStatement.setInt(2, 6);
preparedStatement.setDouble(1, 6);
int result = prepareStatement.executeUpdate();
if (result > 0) {
System.out.println("成功!");
} else {
System.out.println("失败!");
}
preparedStatement.close();
connection.close();
}
六,常见问题
6.1 资源的管理
在使用JDBC的相关资源时,比如Connection, PreparedStatement, ResultSet, 使用完毕后, 要及时关闭这些资源以释放数据库服务器资源和避免内存泄漏是很重要的。
6.2 SQL语句问题
java.sql.SQLSyntaxErrorException: SQL语句错误异常,一般有几种可能:
**SQL语句有错误,检查SQL语句!**建议SQL语句在SQL工具中测试后再复制到Java程序中!
连接数据库的URL中,数据库名称编写错误,也会报该异常!
java.sql.SQLSyntaxErrorExeption:
6.3 SQL语句未设置参数问题
java.sql.SQLException: No value specified for parameter 1
在使用预编译SQL语句时,如果有?占位符,要为每一个占位符赋值,否则报该错误
java.sql.SQLSyntaxErrorException:
6.4 用户名或密码错误问题
连接数据库时,如果用户名或密码输入错误,也会报SQLException, 容易混淆,所以一定要看清楚异常后面的原因描述
java.sql.SQLException: Access denied for user ‘root’@‘localhost’ (using password: YES)
6.5 通信异常
在连接数据库的URL中,如果IP或端口写错了,会报如下异常。
com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
com.mysql.cj.jdbc.exceptions.CommunicatonsException: Communication link failure
进阶篇
:理解ORM思想,在基础篇内容上做进一步的空间,掌握多种连接池优化程序的能力,以优化程序的能力,以优化角度学习。
七,JDBC扩展
7.1 实体类和ROM
- 在使用JDBC操作数据库时,我们会发现数据都是零散的,明明在数据库中是一行完整的数据,到了Java
中变成了一个一个的变量,不利于维护和管理。而我们Java是面向对象的,一个表对应的是一个类,一行
数据就对应的是Java中的一个对象,一个列对应的是对象的属性,所以我们要把数据存储在一个载体里,
这个载体就是实体类!- ORM(ObjectRelationalMapping)思想,对象到关系数据库的映射,作用是在编程中,把面向对象的概
念跟数据库中表的概念对应起来,以面向对象的角度操作数据库中的数据,即一张表对应一个类,一行数
据对应一个对象,一个列对应一个属性!- 当下JDBC中这种过程我们称其为手动ORM。后续我们也会学习ORM框架,比如MyBatis、JPA等。
方式一
@Test
public void testORM() throws Exception {
Connection connection =DriverManager.getConnection(url:"jdbc:mysql:///atguigu",user:"root",password:"atguigu");
PreparedStatement preparedStatement = connection.prepareStatement( sql: "select emp_id,emp_name,emp_salary,emp_age from t_emp where emp_id = ?");
preparedStatement.setInt( parameterlndex: 1, x: 1);
ResultSet resultSet = preparedStatement.executeQuery();
Employee employee = null;
if(resultSet.next()) {
employee = new Employee();
int empId = resultSet.getInt( columnlabel: "emp_id");
String empName = resultSet.getString( columnLabel: "emp_name");
double empSalary =resultSet.getDouble(columnLabel:"emp_salary");
int empAge = resultSet.getInt( columnLabel:"emp_age");
//为对象的属性赋值!
employee.setEmpId(empId);
employee.setEmpName(empName);
employee.setEmpSalary(empSalary);
employee.setEmpAge(empAge);
}
System.out.println(employee);
resultSet.close();
preparedStatement.close();
connection.close();
}
方式二
@Test
public void testORMList() throws Exception {
Connection connection =DriverManager.getConnection(url:"jdbc:mysql:///atguigu",user:"root",password:"atguigu");
PreparedStatement preparedStatement = connection.prepareStatement( sql: "select emp_id,emp_name,emp_salary,emp_age from t_emp");
// preparedStatement.setInt( parameterlndex: 1, x: 1); 无占位符,无法赋值
ResultSet resultSet = preparedStatement.executeQuery();
Employee employee = null;
List<Employee> employeeList = new ArrayList<>();
if(resultSet.next()) {
employee = new Employee();
int empId = resultSet.getInt( columnlabel: "emp_id");
String empName = resultSet.getString( columnLabel: "emp_name");
double empSalary =resultSet.getDouble(columnLabel:"emp_salary");
int empAge = resultSet.getInt( columnLabel:"emp_age");
//为对象的属性赋值!
employee.setEmpId(empId);
employee.setEmpName(empName);
employee.setEmpSalary(empSalary);
employee.setEmpAge(empAge);
// 将每次循环封装的一行数据的对象存储在集合里
employeeList.add(employee);
}
//处理结果,遍历集合
for (Employee emp : employeeList) {
System.out.println(employee);
}
resultSet.close();
preparedStatement.close();
connection.close();
}
7.2 主键回显
-
在数据中,执行新增操作时,主键列为自动增长,可以在表中直观的看到,但是在Java程序中,我们执行完新
增后,只能得到受影响行数,无法得知当前新增数据的主键值。在Java程序中获取数据库中插入新数据后的主
键值,并赋值给Java对象,此操作为主键回显。-
eg:
-
-
代码实现:
-
小小语法优化
7.3 批量操作
- 插入多条语句,一条一条发送给数据库执行,效率低下!
- 通过批量操作,可以提升多次操作效率
@Test
public void testMoreInsert() throws Exception {
//1.注册驱动
// Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
// Connection connection = DriverManager.getConnection(url:"jdbc:mysql:///atguigu",user:"root", password: "atguigu");
Connection connection = DriverManager.getConnection(url:"jdbc:mysql:///atguigu?atguigu?rewriteBatchedStatements=true",user:"root", password: "atguigu");
/***
注意: 1.必须在连接数据库的URL后面追加?rewriteBatchedStatements=true 允许批量操作
2. 新增SQL必须用values.且语句最后不要追加;结束
3. 调用oddBatch()方法,将SQL语句进行批量添加操作
4. 统一执行批量操作,调用executeBatch()
***/
//3.编写SQL语句
String sql = "insert into t_emp (emp_name,emp_salary,emp_age) values (?,?,?)";
//4.创建预编译的PreparedStatement,传入SQL语句
PreparedStatementpreparedStatement = connection.prepareStatement(sql);
//获取当前行代码执行的时间。毫秒值
Long s = System.currentTimeMillis();
for(int i = 0; i < 100000; i++) {
//为占位符赋值
preparedStatement.setString(1, "marry" + i);
prepareustatement.setDouble(parameterIndex:2, x:100.0+i);
preparedStatement.setInt(parameterlndex:3,x:20+i);
// preparedStatement.executeUpdate();
preparedStatement.addBatch();
}
preparedStatement.executeBatch();
long end = System.currentTimeMillis();
System.out.println("消耗时间"+(end - start));
preparedStatement.close();
connection.close();
}
八,连接池
8.1 现有问题
- 每次操作数据库都要获取新连接,使用完毕后就close释放,频繁的创建和销毁造成资源浪费
- 连接的数量无法把控,对服务器来说压力巨大
8.2 连接池
连接池就是数据库连接对象的缓冲区,通过配置,由连接池负责创建连接、管理连接、释放连接等操作。
预先创建数据库连接放入连接池,用户在请求时,通过池直接获取连接,使用完毕后,将连接放回池中,避
免了频繁的创建和销毁,同时解决了创建的效率。当池中无连接可用,且未达到上限时,连接池会新建连接。
池中连接达到上限,用户请求会等待,可以设置超时时间。
8.3 常见连接池
JDBC的数据库连接池使用javax.sql.DataSource接口进行规范,所有的第三方连接池都实现此接口,自行添加具体实现!也就是说,所有连接池获取连接的和回收连接方法都一样,不同的只有性能和扩展功能!
- DBCP是Apache提供的数据库连接池,速度相对C3Po较快,但自身存在一些BUG。
- C3P0是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。
- Proxool是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3po差一
- Druid是阿里提供的数据库连接池,是集DBCP、C3PO、Proxool优点于一身的数据库连接池,性能、扩展
性、易用性都更好,功能丰富。 - Hikari(U办[shigalij)取自日语,是光的意思,是SpringBoot2.x之后内置的一款连接池,基于BoneCP
(已经放弃维护,推荐该连接池)做了不少的改进和优化,口号是快速、简单、可靠。
Druid与Hikari对比
8.4 Druid连接池使用
-
使用步骤
- 引入jar包
- 编码
-
代码实现
-
硬编码方式(了解)
-
@Test public void druidHard() throws SQLException{ /* 硬编码:将连接池的配置信息和Java代码耦合在一起 1, 创建DruidDataSource连接池对象 2, 设置连接池的配置信息[必须 | 非必须] 3, 设置连接池获取连接对象 4, 回收连接[不是释放连接,而是将连接归还给连接池,给其他线程进行复用] */ //1.连接池对象 DruidDataSource dataSource = new DruidDataSource(); //2.设置四个必须参数 dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUsername("root"); dataSource.setPassword("atguigu"); dataSource.setUr1("jdbc:mysql:///atguigu"); //非必须 dataSource.setInitialSize(10); //初始化数量 dataSource.setMaxActive(20); //最大数量 //3.通过连接池获取连接 Connection connection = dataSource.getConnection(); //JDBC的步骤正常curd //4.回收连接,此处不是释放,而是将连接放回池中。 connection.close(); }
-
-
软编码方式(推荐):
-
在项目目录下创建resources文件夹, 标识该文件夹为资源目录, 创建db.properties配置文件,将连接信息定义在该文件中.
-
# druid连接池需要的配置参数,key固定命名 driverClassName=com.mysql.cj.jdbc.Driver username=root password=atguigu url=jdbc:mysql:///atguigu initialSize=10 maxActive=20
-
-
Java代码
-
@Test //druid.properties直接放在src目录下 public void druidSoft() throws Exception { //创建Properties集合,存储文件中的key=value Properties properties = new Properties(); //借助类加载器获取文件的字节数入流 InputStream ips = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties"); //将流中的数据存储到集合中 properties.load(ips); //读取Properties集合中的数据创建DruidDataSource连接池 DataSource dataSource= DruidDataSourceFactory.createDataSource(properties); // 通过连接池获取连接对象 Connection connection = dataSource.getConnecion(); //开发CRUD //回收连接 connection.close(); }
-
-
-
8.5 Druid其它配置[了解]
视频未提及!!!。
8.6 HikariCP连接池使用
-
使用步骤
-
引入jar包
-
硬编码方式:
-
public class HikariTest { @Test public void testHardCodeHikari() throws SQLException { /* 硬编码:将连接池的配置信息和JaVa代码耦合在一起 1、创建HikariDataSource连接池对象 2、设置连接池的配置信息【必须|非必须】 3、通过连接池获取连接对象 4、回收连接 */ //1.创建HikariDataSource连接池对象 HikariDataSource hikariDataSource = newHikariDataSource(); //2.设置连接池的配置信息【必须|非必须】 //2.1必须设置的配置 hikariDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); hikariDataSource.setJdbcUrl("jdbc:mysql:///atguigu"); hikariDataSource.setUsername("root"); hikariDataSource.setPassword("atguigu"); //2.2非必须设置的配置 hikariDataSource.setMinimumIdle(10); hikariDataSource.setMaximumPoolSize(20); //3.通过连接池获取连接对象 Connection connection = hikarDataSource.getConnection(); System.out.println(connection); // 回收连接 } }
-
-
软编码方式
-
driverClassName=com.mysql.cj.jdbc.Driver jdbcUrl=jdbc:mysql:///atguigu username=root password=atguigu minimumIdle=10 maximumPoolSize=20
-
public class HikariTest { @Test public void testHardCodeHikari() throws Exception { no usages @Test public void testResourcesHikari() throws Exception { // k1.创建Properties集合,用于存储外部配置文件的key和value值。 Properties properties = new Properties(); //2.读取外部配置文件,获取输入流,加载到Properties集合里。 InputStream inputStream = HikariTest.class.getClassLoader().getResourceAsStream(name:"hikari.properties"); properties.load(inputStream); //3.创建HikariConfig连接池配置对象,将Properties集合传进去。 HikariConfig hikariConfig = new HikariConfig(properties); //4.基于HikariConfig连接池配置对象,构建HikariDataSource HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig); //5.获取连接 Connection connection = hikariDataSource.getConnection(); System.out.println(connection); //6.回收连接 connection.close(); } }
-
-
8.7 HikariCP其他配置[了解]
视频尚未提及!!!。
高级篇
:在掌握进阶篇内容后,融入实战经验的封装,优化,功能流程的把控,强化开发项目思想,优化代码逻辑。
九,JDBC优化及工具类封装
9.1 现有问题
我们在使用JDBC的过程中,发现部分代码存在冗余的问题
- 创建连接池。
- 获取连接。
- 连接的回收。
9.2 JDBC工具类封装V1.0
-
resources / db.properties 配置文件
-
# druid连接池需要的配置参数,key固定命名 driverClassName=com.mysql.cj.jdbc.Driver username=root password=atguigu url=jdbc:mysql:///atguigu
-
-
工具类代码:
-
/ ** JDBC 工具类(v1.0) 1、维护一个连接池对象。 2、对外提供在连接池中获取连接的方法 3、对外提供回收连接的方法 注意:工具类仅对外提供共性的功能代码,所以方法均为静态方法! */ public class JDBCUtil { // 创建连接池引用,因为要提供给当前项目的全局使用,所以创建为静态的。 private static DataSource dataSource; //在项目启动时,即创建连接池对象,赋值给dataSource static { try{ Properties properties = new Properties(); InputStream inputStream = JDBcutil.class.getClassLoader().getResourceAsStream(name:"db.properties"); properties.load(inputStream); dataSource = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { throw new RuntimeException(e) } } // 对外提供在连接池中获取连接的方法 public static Connection getConnection() { try { return dataSource.getConnection(); } catch (SQLException e) { threw new RuntimeException(e); } } //对外提供回收连接的方法 public static void release(Connection connection) { try { connection.close(); } catch (SELException e) { throw new RuntimeException(e); } } }
-
9.3 ThreadLocal
**JDK1.2的版本中就提供java.lang.ThreadLocal,**为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。通常用来在在多线程中管理共享数据库连接、Session等。
ThreadLocal用于保存某个线程共享变量,原因是在Java中,每一个线程对象中都有一个
ThreadLocalMap<ThreadLocal,Object>,其key就是一个ThreadLocal,而object即为该线程的共享变量。而这个map是通过ThreadLocal的set和get方法操作的。对于同一个staticThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
- 在进行对象跨层传递的时候**,使用ThreadLocal可以避免多次传递,打破层次间的约束。**
- 线程间数据隔离。
- 进行事务操作,用于存储线程事务信息。
- 数据库连接,Session会话管理。
- 1、ThreadLocal对象.get: 获取ThreadLocal中当前线程共享变量的值。
- 2、ThreadLocal对象.set: 设置ThreadLocal中当前线程共享变量的值。
- 3、ThreadLocal对象.remove:移除ThreadLocal中当前线程共享变量的值。
9.4 JDBC工具类封装V2.0
/**
JDBC工具类(V2.0):
1、维护一个连接池对象、维护了一个线程绑定变量的ThreadLocal对象
2、对外提供在ThreadLocal中获取连接的方法
3、对外提供回收连接的方法,回收过程中,将要回收的连接从ThreadLocal中移除!
注意:工具类仅对外提供共性的功能代码,所以方法均为静态方法!
注意: 使用ThreadLocal就是为了一个线程在多次数据库操纵过程中,使用的是同一个连接!
*/
public class JDBCUtilV2 {
//创建连接池引用,因为要提供给当前项目的全局使用,所以创建为静态的。
private static DataSource odataSource;
private static ThreadLocal<Connection>threadLocal = new ThreadLocal<>();
//在项目启动时,即创建连接池对象,赋值给dataSource
static{
try{
Properties properties = new Properties();
InputStream inputStream = JDBcUtil.class.getClassLoader().getResourceAsStream( name:"db.properties");
properties.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 对外提供在连接池中获取连接的方法
public static Connection qetConnection() {
try{
//在ThreadLocal中获取Connection、
Connection connection = threadLocal.get();
//threadLocal里没有存储Connection,也就是第一次获取
if (connection == null) {
//在连接池中获取一个连接,存储在threadLocal里。
connection=dataSource.getConnection();
threadLocal.set(connection);
}
return connection;
} catch(SQLException e) {
throw new RuntimeException(e);
}
public staticvoid release(){
try{
connection connection = threadLocal.get();
if (connection != null) {
threadLocal.remove();
connection.close();
}
} catch(SQLException e) {
throw new RuntimeException(e);
}
}
}
十,DAO封装及BaseDAO工具类
10.1 DAO概念
DAO: DataAccessObject,数据访问对象。
Java是面向对象语言,数据在Java中通常以对象的形式存在。一张表对应一个实体类,一张表的操作对应一个
DAO对象!在Java操作数据库时,我们会将对同一张表的增删改查操作统一维护起来,维护的这个类就是DAO层。
DAO层只关注对数据库的操作,供业务层Service调用,将职责划分清楚!
10.1 BaseDAO概念
基本上每一个数据表都应该有一个对应的DAO接口及其实现类,发现对所有表的操作(增、删、改、查)代码重复度很高,所以可以抽取公共代码,给这些DAO的实现类可以抽取一个公共的父类,复用增删改查的基本操作,我们称为BaseDAO。
10.2 BaseDAO搭建
搭建增删改查(注意目录结构也值得学习)
搭建通用查询
/** 通用的查询:多行多列、单行多列、单行单列
多行多列:List<EmpLoyee>
单行多列:EmpLoyee
单行单列:封装的是一个结果。DoubLe、Integer、
封装过程:
1、返回的类型:泛型:类型不确定,调用者知道,调用时,将此次查询的结果类型告知BaSeDAO就可以了。
2、返回的结果:通用,List可以存储多个结果,也可以存储一个结果get(θ)
3、结果的封装:反射,要求调用者告知BaseDAO要封装对象的类对象。class
**/
public <T> Lis t<T> executeQuery (Class<T> clazz,String sql, object...params) throws Exception {
//获取连接
Connection connection = JDBcUtilV2.getConnection();
//预编译SQL语句
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//设置占位符的值
if(params != null && params.length > θ){
for(int i = θ;i < params.length;i++) {
preparedStatement.setobject(parameterlndex:i+1,params[i]);
}
}
//执行SQL,并接受返回的结果集
ResultSetresultSet=preparedStatement.executeQuery();
// 获取结果集中的元数据对象
// 包含了:列的数量,每个列的名称
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
List<T> list = new ArrayList<>();
// 处理结果
while (resultSet.next()) {
// 循环一次,代表有一行数据,通过反射创建一个对象
T t = class.newInstance();
for (int i = 0; i <= columnCount; i++) {
// 通过下表获取列的值
Object value = resultSet.getObject(i);
// 获取到的列的value值,这个值就是t这个对像中的某一个属性
// 获取当前拿到的列的名字 = 对象的属性名
// String columnLabel = metaData.getColumnLabel(i);
// 通过类对象获取对象的属性
String fieldName = metaData.getColumnLabel(i);
// 通过类对象和fieldName获取要封装的对象的属性
Field field = clazz.getDeclaredFirst(fieldName);
// 突破封装的private
field.setAccessible(true);
field.set(t, value);
}
list.add(t);
}
resultSet.close();
preparedStatement.close();
JDBCUtiV2.release();
return list;
}
搭建查询单个结果
public <T> List<T> executeQueryBean (Class<T> clazz, String sql, Object...params) throws Exception {
List<T> list = this.executeQuery(clazz, sql, params);
if (list == null || list.size() == 0) {
return null;
}
return list.get(0);
}
10.3 BaseDAO的应用
十一,事务
11.1 事务回顾
-
数据库事务就是一种SQL语句执行的缓存机制,不会单条执行完毕就更新数据库数据,最终根据缓存内的多条
语句执行结果统一判定!一个事务内所有语句都成功及事务成功,我们可以触发commit提交事务来结束事
务,更新数据!一个事务内任意一条语句失败,即为事务失败,我们可以触发rollback回滚结束事务,数据回
到事务之前状态! -
一个业务涉及多条修改数据库语句!例如:
- 经典的转账案例,转账业务(A账户减钱和B账户加钱,要一起成功)
- 批量删除(涉及多个删除)
- 批量添加(涉及多个插入)
-
事务的特性:
-
原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不
发生。 -
一致性(Consistency)事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
-
隔离性(Isolation)事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及
使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 -
持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来
的其他操作和数据库故障不应该对其有任何影响
-
-
事务的提交方式:
- 自动提交:每条语句自动存储一个事务中,执行成功自动提交,执行失败自动回滚!
- 手动提交:手动开启事务,添加语句,手动提交或者手动回滚即可
11.2 JDBC中事务实现
-
关键代码
-
try { connection.setAutoCommit(false); // 关闭自动提交了 // connection.setAutoCommit(false) 也就是类型于 set autocommit = off // 注意, 只要当前connection对象,进行数据库操作,都不会自动提交事务 // 数据库动作! // prepareStatement -单一的数据库动作 c r u d // connection -操作事务 // 所有操作执行正确, 提交事务 connection.commit(); } catch (Execption e) { connection.rollback(); }
-
11.3 JDBC事务代码实现
-
准备数据库表
-
-- 继续在atguigu的库中创建银行表 create table t_bank { id int primary key auto_increment comment '账号主键', account varchar(20) not null unique comment '账号', money int usingend comment '金额, 不能为负值'; } insert into t_bank (account, money) values ('zhangsan', 1000), ('list', 1000); // 变动的小地方 resultSet.close(); preparedStatement.close(); if (connection.getAutoCommit()) { JDBCUtilV2,release(); }
-
-
DAO接口代码
-
public interface BankDao { int addMoney (String account, Integer money); int subMoney(String account, Integer money); }
-
-
DAO实现类代码
-
public class BankDaoImp1 extends BaseDao implements BankDao { @Override public int addMoney (String account, Integer money) { try { String sql = "update t_bank set money = money + ? where account = ?"; return update(sql.money.account); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public int subMoney (String account, Integer money) { try { String sql = "update t_bank set money = money - ? where id = ?"; return executeUpdate(sql, money, id); } catch (Exception e) { throw new RuntimeException(e); } } }
()) {
JDBCUtilV2,release();
}
-
-
DAO接口代码
-
public interface BankDao { int addMoney (String account, Integer money); int subMoney(String account, Integer money); }
-
-
DAO实现类代码
-
public class BankDaoImp1 extends BaseDao implements BankDao { @Override public int addMoney (String account, Integer money) { try { String sql = "update t_bank set money = money + ? where account = ?"; return update(sql.money.account); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public int subMoney (String account, Integer money) { try { String sql = "update t_bank set money = money - ? where id = ?"; return executeUpdate(sql, money, id); } catch (Exception e) { throw new RuntimeException(e); } } }
大总结
果然用Typora写的笔记应该直接导入md编辑器中,这样格式好看不少,哈哈。感谢大家观看
-