一文搞懂JDBC全流程(含MySQL安装和JDK下载)
前言:
1.了解
问题1:什么是JDBC?
- JDBC全称: Java Database Connectivity,即Java数据库连接
- JDBC是Java提供的一组独立于任何数据库管理系统的API。
- Java提供接口规范,由各个数据库厂商提供接口的实现,厂商提供的实现类封装成jar文件,也就是数据库驱动jar包。
- 学习JDBC,充分体现了面向接口编程的好处,程序员只关心标准和规范,而无需关注实现过程。
问题2:为什么需要使用JDBC?
-
数据库连接:JDBC提供了一个标准接口,使Java应用程序能够与各种关系型数据库(如MySQL、Oracle、PostgreSQL等)进行连接和交互。通过JDBC,开发者可以轻松地执行SQL查询和更新操作。
-
跨平台性:由于Java是一个跨平台的编程语言,JDBC也继承了这一特性。无论是开发在Windows、Linux还是macOS上的应用程序,JDBC都能提供一致的数据库访问体验。
-
标准化:JDBC为不同的数据库提供了一个统一的访问方式。无论使用什么样的数据库,只需通过JDBC接口,开发者就可以使用相同的代码逻辑与之交互,降低了学习成本。
-
性能优化:JDBC支持批处理操作,使得多个SQL语句可以在一个请求中发送到数据库,减少网络延迟,提高性能。
JDBC 的简单执行流程图:
2.准备
开始前的准备工作
1.准备数据库
- MySQL8.x或者MySQL5.x都可以
- 下载网址:https://www.mysql.com/downloads/
进入网址下滑找到并点击:
跳转后点击:
进入下载界面,请选择本地下载:
后续安装操作:https://blog.csdn.net/weixin_39289696/article/details/128850498
2.官网下载数据库连接驱动jar包。
网址:https://downloads.mysql.com/archives/c-j/
提示:8.0.25以后的驱动不需要设置时区(即后面都可)
3.创建Java项目(JDK21)
JDK的下载:
4.在项目下创建lib文件夹,将下载的驱动jar包复制到文件夹里
5.选中lib文件夹右键->Add as Library(创建库),与项目集成。
6.简单感受编写的场景
package com.atguigu.base;
import java.sql.*;
public class JDBCQuick {
public static void main(String[] args) throws Exception {
//注册驱动
//带有cj是sql8.x的,不使用的是sql5.x
Class.forName("com.mysql.cj.jdbc.Driver");
//获取驱动信息
//company:为创建的数据库的名称
String url = "jdbc:mysql://localhost:3306/company";
String username = "root";
String password = "123456";
Connection connection = DriverManager.getConnection(url, username, password);
//连接执行SQL语句的对象,使用Statement的场景
Statement statement = connection.createStatement();
//编写SQL语句,并执行,接收返回的结果集
String sql = "SELECT emp_id,emp_name,emp_salary,emp_age FROM t_emp;";
ResultSet resultSet = statement.executeQuery(sql);
//处理结果,遍历resultSet结果集
while (resultSet.next()) {
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
Double empSalary = resultSet.getDouble("emp_salary");
int empAge = resultSet.getInt("emp_age");
System.out.println(empId + "\t" + empName + "\t" + empSalary + "\t" + empAge);
}
//关闭资源,抬头法:从下往上
resultSet.close();
statement.close();
connection.close();
}
}
一、基础篇
1.五大核心API
1.1 注册驱动
格式:
Class.forName("com.mysql.cj.jdbc.Driver");
或
DriverManager.registerDriver(new Dirver());
- 在Java中,使用JDBC连接数据库时,需要加载特定的数据库驱动程序以便与之通信。加载驱动程序是为了注册驱动程序,使JDBC API能识别并与特定数据库交互。
- 从JDK6开始,只要将对应的JAR文件放在类路径中,就无需显式调用
class.forName()
来加载驱动程序,驱动程序会在初始化时自动注册。
1.2 Connection
Connection接口是JDBC API的重要接口,用于建立与数据库的通信通道。换而言之,connection对象不为空,则代表一次数据库连接。
在建立连接时,需要指定数据库URL、用户名、密码参数。
- URL:jdbc:mysql://localhost:3306/company
- "jdbc:mysq!://IP地址:端口号/数据库名称?参数键值对1&参数键值对2
Connection 接口还负责
- 管理事务,Connection,接口提供了 commit 和 rollback 方法,用于提交事务和回滚事务。
- 可以创建 statement 对象,用于执行 SQL语句并与数据库进行交互,
- 在使用JDBC技术时,必须要先获取Connection对象,在使用完毕后,要释放资源,避免资源占用浪费及泄漏。
1.3 Statement
Statement 接口用于执行 SQL语句并与数据库进行交互。
通过Statement 对象,可以向数据库发送 SQL语句并获取执行结果。结果可以是一个或多个结果。
- 增删改:受影响行数单个结果。
- 查询:单行单列、多行多列、单行多列等结果。
注意:
- statement 接口在执行SQL语句时,会产生 SQL注入攻击问题:
原因:当使用 Statement 执行动态构建的 SQL查询时,往往需要将查询条件与 SQL语句拼接在一起,直接将参数和SQL语句一并生成,让SQL的查询条件始终为true得到结果。
例子:
输入的是abc' or '1' = '1,却返回表中全部数据
1.4 PreparedStatement
PreparedStatement是 statement 接口的子接口,用于执行 预编译 的 SQL 查询
作用如下
- 预编译SQL语句:在创建PreparedStatement时,就会预编译SQL语句,也就是SQL语句已经固定。
- 防止SQL注入: PreparedStatement 支持参数化查询,通过使用 ? 占位符将数据作为参数传递到SQL语句中。传入的参数会自动用单引号包裹,从而有效防止SQL注入问题。
- 性能提升:Preparedstatement是预编译SQL语句,同一SQL语句多次执行的情况下,可以复用,不必重新编译和解析。
例子:
相同的情况使用preparedStatement的结果,并不会返回所有值
- 因为 ?占位符会给写进去的‘ ’ 当成值传递 即 abc' or '1' = '1会变成: 'abc\' or \'1\' = \'1'
1.5 ResultSet
ResultSet 是 JDBCAPI 中的一个接口,用于表示从数据库中 执行査询语句所返回的结果集。它提供了一种用于遍历和访问查询结果的方式。
- 遍历结果:Resultset可以使用 next()方法将游标移动到结果集的下一行,逐行遍历数据库查询的结果,返回值为boolean类型,true代表有下一行结果,false则代表没有。
- 获取单列结果:可以通过getxxx的方法获取单列的数据,该方法为重载方法,支持索引和列名进行获取
代码使用实例:
package com.atguigu.base;
import com.mysql.cj.jdbc.Driver;
import java.sql.*;
import java.util.Scanner;
public class JDBCQuick1 {
public static void main(String[] args) throws Exception {
Connection connection = DriverManager.getConnection("jdbc:mysql:///company","root","123456");
//连接执行SQL语句的对象,使用Statement的场景,由于存在sql注入的问题,开发中不适用
/*Statement statement = connection.createStatement();
String sql = "SELECT emp_id,emp_name,emp_salary,emp_age FROM t_emp WHERE emp_name = '"+name+"'";
ResultSet resultSet = statement.executeQuery(sql);*/
//使用PreparedStatement的场景,由于是预编译的语句,后续不用在创建sql执行的语句,而是直接执行并返回结果即可
PreparedStatement preparedStatement = connection.prepareStatement("SELECT emp_id,emp_name,emp_salary,emp_age FROM t_emp WHERE emp_name = ?");
//使用手动输入,对比Statement和prepareStatement
System.out.println("请输入员工姓名");
Scanner scanner = new Scanner(System.in);
String name = scanner.nextLine();
//给?占位符赋值并执行,接收返回的结果集
//这里的输入的1,我们使用的?是第一个参数,如果有多个问号,就用2,3来表示
preparedStatement.setString(1,name);
ResultSet resultSet = preparedStatement.executeQuery();
//处理结果,遍历resultSet结果集
while(resultSet.next()){
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
Double empSalary = resultSet.getDouble("emp_salary");
int empAge = resultSet.getInt("emp_age");
System.out.println(empId+"\t"+empName+"\t"+empSalary+"\t"+empAge);
}
//关闭资源,抬头法:从下往上
resultSet.close();
// statement.close();
preparedStatement.close();
connection.close();
}
}
2.PreparedStatement实现CRUD
2.1 单行单列
package com.atguigu.base;
import org.junit.Test;
import java.sql.*;
public class JDBCOperation {
//查询单行单列
@Test
public void test() throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/company", "root", "123456");
PreparedStatement preparedStatement = connection.prepareStatement("SELECT COUNT(*) as count FROM t_emp");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
int count = resultSet.getInt("count");
System.out.println(count);
}
resultSet.close();
preparedStatement.close();
connection.close();
}
}
2.2 单行多列
//查询单行多列
@Test
public void testQuerySingleRow() throws SQLException{
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/company","root","123456");
PreparedStatement preparedStatement = connection.prepareStatement("SELECT emp_id,emp_name,emp_salary,emp_age FROM t_emp WHERE emp_id = ?");
//查找id为4的
preparedStatement.setInt(1,4);
ResultSet resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
double empSalary = resultSet.getDouble("emp_salary");
int empAge = resultSet.getInt("emp_age");
System.out.println(empId + "\t" + empName + "\t" + empSalary + "\t" + empAge);
}
resultSet.close();
preparedStatement.close();
connection.close();
}
2.3 多行多列
//查找多行多列
@Test
public void testQueryMoreRow() throws SQLException{
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/company","root","123456");
PreparedStatement preparedStatement = connection.prepareStatement("SELECT emp_id,emp_name,emp_salary,emp_age FROM t_emp WHERE emp_age > ?");
//为占位符赋值,并返回结果集
preparedStatement.setInt(1,25);
ResultSet resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
double empSalary = resultSet.getDouble("emp_salary");
int empAge = resultSet.getInt("emp_age");
System.out.println(empId+"\t"+empName+"\t"+empSalary+"\t"+empAge);
}
resultSet.close();
preparedStatement.close();
connection.close();
}
2.4 增加,修改,删除
//增加
@Test
public void testInsert() throws SQLException{
Connection connection = DriverManager.getConnection("jdbc:mysql:///company","root","123456");
PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO t_emp (emp_name,emp_salary,emp_age) VALUES (?,?,?)");
preparedStatement.setString(1,"孙悟空");
preparedStatement.setDouble(2,150.66);
preparedStatement.setInt(3,500);
int result = preparedStatement.executeUpdate();
if(result > 0){
System.out.println("插入成功!");
}else{
System.out.println("插入失败!");
}
preparedStatement.close();
connection.close();
}
//删除
@Test
public void test5Deleter() throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:mysql:///company","root","123456");
PreparedStatement preparedStatement = connection.prepareStatement("UPDATE t_emp SET emp_salary = ? , emp_age = ? WHERE emp_id = ?");
preparedStatement.setDouble(1,123.78);
preparedStatement.setInt(2,120);
preparedStatement.setInt(3,7);
int result = preparedStatement.executeUpdate();
if(result >0 ){
System.out.println("修改成功!");
}else{
System.out.println("修改失败!");
}
preparedStatement.close();
connection.close();
}
//删除
@Test
public void testDelete() throws SQLException {
Connection connection = DriverManager.getConnection("jdbc:mysql:///company", "root", "123456");
PreparedStatement preparedStatement = connection.prepareStatement("DELETE FROM t_emp WHERE emp_id = ?");
preparedStatement.setInt(1,8);
int result = preparedStatement.executeUpdate();
if(result > 0){
System.out.println("删除成功!");
}else{
System.out.println("删除失败!");
}
preparedStatement.close();
connection.close();
}
3.常见错误
1.SQL语法错误异常:(写错SQL语句,可先在数据库中写好并测试,无误后使用)
2.通信异常:(写错连接的IP地址或端口号)
3.资源泄露:(一定要记得xxx.close(),从下自上,先开后关)
4. 用户名或密码写错:(睁大眼睛看仔细!!!)
二、进阶篇
1.JDBC扩展
1.1 实体类和ORM
实体类:
package com.atguigu.advanced.pojo;
//实体类名就是创建的表的全称
public class Employee {
private Integer empId;//emp_id
private String empName;//emp_name
private Double empSalary;//emp_salary
private Integer empAge;//emp_age
public Employee() {
}
public Employee(Integer empId, String empName, Double empSalary, Integer empAge) {
this.empId = empId;
this.empName = empName;
this.empSalary = empSalary;
this.empAge = empAge;
}
public Integer getEmpId() {
return empId;
}
public void setEmpId(Integer empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public Double getEmpSalary() {
return empSalary;
}
public void setEmpSalary(Double empSalary) {
this.empSalary = empSalary;
}
public Integer getEmpAge() {
return empAge;
}
public void setEmpAge(Integer empAge) {
this.empAge = empAge;
}
@Override
public String toString() {
return "Employee{" +
"empId=" + empId +
", empName='" + empName + '\'' +
", empSalary=" + empSalary +
", empAge=" + empAge +
'}';
}
}
ORM封装单个和多个:
//ORM思想封装单个对象
@Test
public void testORM() throws Exception{
Connection connection = DriverManager.getConnection("jdbc:mysql:///company", "root", "123456");
PreparedStatement preparedStatement = connection.prepareStatement("SELECT emp_id,emp_name,emp_salary,emp_age FROM t_emp WHERE emp_id = ?");
preparedStatement.setInt(1,6);
ResultSet resultSet = preparedStatement.executeQuery();
Employee employee = null;
while(resultSet.next()){
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
double empSalary = resultSet.getDouble("emp_salary");
int empAge = resultSet.getInt("emp_age");
System.out.println("转换成下面的写法就是ORM中M的方法");
employee = new Employee();
employee.setEmpId(empId);
employee.setEmpName(empName);
employee.setEmpSalary(empSalary);
employee.setEmpAge(empAge);
}
System.out.println(employee);
resultSet.close();
preparedStatement.close();
connection.close();
}
//ORM思想封装集合
@Test
public void testORMList() throws Exception{
Connection connection = DriverManager.getConnection("jdbc:mysql:///company", "root", "123456");
PreparedStatement preparedStatement = connection.prepareStatement("SELECT emp_id,emp_name,emp_salary,emp_age FROM t_emp");
ResultSet resultSet = preparedStatement.executeQuery();
Employee employee = null;
List<Employee> employeeslist = new ArrayList<>();
while(resultSet.next()){
employee = new Employee();
int empId = resultSet.getInt("emp_id");
String empName = resultSet.getString("emp_name");
double empSalary = resultSet.getDouble("emp_salary");
int empAge = resultSet.getInt("emp_age");
employee.setEmpId(empId);
employee.setEmpName(empName);
employee.setEmpSalary(empSalary);
employee.setEmpAge(empAge);
employeeslist.add(employee);
}
for (Employee emp : employeeslist) {
System.out.println(emp);
}
resultSet.close();
preparedStatement.close();
connection.close();
}
1.2 主键回显
//主键回显且优化,pk就是primary key
@Test
public void testReturnPK() throws Exception{
Connection connection = DriverManager.getConnection("jdbc:mysql:///company","root","123456");
String sql = "INSERT INTO t_emp (emp_name,emp_salary,emp_age) VALUES (?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);//Statement.RETURN_GENERATED_KEYS指返回生成的值
Employee employee = new Employee(null,"Emma",520.13,27);
preparedStatement.setString(1, employee.getEmpName());
preparedStatement.setDouble(2,employee.getEmpSalary());
preparedStatement.setInt(3,employee.getEmpAge());
int result = preparedStatement.executeUpdate();
ResultSet resultSet = null;
if(result >0){
resultSet = preparedStatement.getGeneratedKeys();
if(resultSet.next()){
int empId = resultSet.getInt(1);
}
System.out.println(employee);
}else{
System.out.println("创建失败!");
}
if (resultSet != null) {
resultSet.close();
}
preparedStatement.close();
connection.close();
}
1.3 批量操作
//批量操作
@Test
public void testMoreInsert() throws Exception{
Connection connection = DriverManager.getConnection("jdbc:mysql:///company?rewriteBatchedStatements=true", "root", "123456");
/*
* 1.?rewriteBatchedStatement=true
* 2.只能使用values
* 3.在其中调用addBatch(),去进行批操作
* 4.使用的是executeBatch()
* */
String sql = "insert into t_emp (emp_name,emp_salary,emp_age) values (?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
preparedStatement.setString(1,"jerry"+i);
preparedStatement.setDouble(2,901.12+i);
preparedStatement.setInt(3,21+i);
preparedStatement.addBatch();
}
long end = System.currentTimeMillis();
System.out.println( "花费的时间:" + (end-start) );
preparedStatement.executeBatch();
preparedStatement.close();
connection.close();
}
2.连接池
2.1 连接池引入
连接池:数据库连接对象的缓冲区,通过配置,由连接池负责创建连接、管理连接、释放连接等操作。
预先创建数据库连接放入连接池,用户在请求时,通过池直接获取连接,使用完毕后,将连接放回池中,避免了频繁的创建和销毁,同时解决了创建的效率。
- 当池中无连接可用,且未达到上限时,连接池会新建连接。
- 池中连接达到上限,用户请求会等待,可以设置超时时间。
2.1.1 常见连接池
JDBC的数据库连接池使用 javax.sql.DataSource接口 进行规范,所有的第三方连接池都实现此接口,自行添加具体实现!
即,所有连接池:
- 相同:获取连接的和回收连接方法
- 不同:性能和扩展功能
常见连接池:
- DBCP:是Apache提供的数据库连接池,速度相对C3P0较快,但自身存在一些BUG。
- C3P0: 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。
- Proxool:是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3po差一点
其中后续开发使用:
- Druid: 是阿里提供的数据库连接池,是集DBCP、C3P0、Proxool优点于一身的数据库连接池,性能、扩展性、易用性都更好,功能丰富。
- Hikari:是SpringBoot2.x之后内置的一款连接池,基于 BoneCP(已经放弃维护,推荐该连接池)做了不少的改进和优化,口号是快速、简单、可靠。
2.2 Druid连接池使用
2.2.1 导入jar包
2.2.2 创建资源根目录,配置文件
2.2.3 创建连接
Druid硬连接
import java.sql.*;
public class DruidTest {
//硬编码实现
@Test
public void test() throws SQLException{
//1.连接池对象
DruidDataSource dataSource = new DruidDataSource();
//2.设置必要参数(必须)
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setUrl("jdbc:mysql:///company");
//非必须
dataSource.setInitialSize(10);//初始化数量
dataSource.setMaxActive(15);//最大数量
//3。通过连接池获取连接
Connection connection = dataSource.getConnection();
/*中间插入增删改查的内容*/
//4.回收连接
connection.close();
}
}
结果:
Druid软连接
在db.properties配置文件中写好
代码实现:
//Druid软编码
@Test
public void testResourcesDruid() throws Exception{
//创建 properties实例对象
Properties properties = new Properties();
//转换成流
InputStream inputStream = DruidTest.class.getClassLoader().getResourceAsStream("db.properties");
properties.load(inputStream);
//基于properties集合创建连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
System.out.println(connection);
//回收资源
connection.close();
}
结果:
2.3 HikariCP
2.3.1 导入jar包
2.3.2 创建配置文件
2.3.3 创建连接
HikariCP硬连接
//hikari硬连接
@Test
public void testHashcodeHikari() throws SQLException {
//创建Hikari对象
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
hikariDataSource.setJdbcUrl("jdbc:mysql:///company");
hikariDataSource.setUsername("root");
hikariDataSource.setPassword("123456");
hikariDataSource.setMinimumIdle(10);
hikariDataSource.setMaximumPoolSize(20);
Connection connection = hikariDataSource.getConnection();
System.out.println(connection);
connection.close();
}
结果:(没有配日志所以报这个错误,不用管)
HikariCP软连接
配置文件中的信息
代码实现:
//hikari软连接
@Test
public void testResourcesHikari() throws Exception{
//创建properties对象,存储外部配置文件中key-value的值
Properties properties = new Properties();
//将配置文件的信息转换成流传递
InputStream inputStream = HikariTest.class.getClassLoader().getResourceAsStream("hikari.properties");
properties.load(inputStream);
//创建hikariConfig对象,接收properties中的配置信息
HikariConfig hikariConfig = new HikariConfig(properties);
//基于hikariConfig对象,创建hikariDataSource对象
HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig);
//获取连接
Connection connection = hikariDataSource.getConnection();
System.out.println(connection);
connection.close();
}
结果;
三、高级篇
1.工具类的搭建
先在senior的包下创建util包
代码实现:
package com.atguigu.senior.util;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtilV2 {
private static DataSource dataSource;
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
static {
try {
Properties properties = new Properties();
InputStream inputStream = JDBCUtilV2.class.getClassLoader().getResourceAsStream("db.properties");
properties.load(inputStream);
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Connection getConnection(){
try {
Connection connection = threadLocal.get();
if (connection == null) {
connection = dataSource.getConnection();
threadLocal.set(connection);
}
return connection;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void release(){
try {
Connection connection = threadLocal.get();
if (connection != null) {
threadLocal.remove();
connection.setAutoCommit(true);
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
测试:
@Test
public void testJDBCv2(){
/*Connection connection1 = JDBCUtilV1.getConnection();
Connection connection2 = JDBCUtilV1.getConnection();
Connection connection3 = JDBCUtilV1.getConnection();
System.out.println(connection1);
System.out.println(connection2);
System.out.println(connection3);*/
System.out.println("\n对比使用ThreadLocal\n");
Connection connection1 = JDBCUtilV2.getConnection();
Connection connection2 = JDBCUtilV2.getConnection();
Connection connection3 = JDBCUtilV2.getConnection();
System.out.println(connection1);
System.out.println(connection2);
System.out.println(connection3);
}
2.DAO和BaseDAO
在senior包下创建dao包,在dao包里面创建EmployeeDao接口还有BaseDao接口
其中Base接口中主要是通用的增删改查操作:
package com.atguigu.senior.dao;
import com.atguigu.senior.util.JDBCUtilV2;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;
public class BaseDAO {
/**
* 通用的增删改操作
* @param sql 操作者输入的sql操作语句
* @param params sql语句中占位符赋值
* @return 受影响的行数
*/
public int executeUpdate(String sql, Object... params) throws Exception{
Connection connection = JDBCUtilV2.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
if (params != null && params.length > 0) {
for (int i = 0; i < params.length; i++) {
preparedStatement.setObject(i+1,params[i]);
}
}
int row = preparedStatement.executeUpdate();
//释放资源
preparedStatement.close();
if (connection.getAutoCommit()) {
JDBCUtilV2.release();
}
//返回结果
return row;
}
/**
* 操作返回的结果的类型
* 单行单列:封装的是一个结果,Double,String,Integer...
* 单行多列:Employee
* 多行多列:Lise<Employee>
* 封装的过程:
* 返回的类型:泛型,不确定返回值的类型,但调用者知道吗,后续使用时,要告知BaseDAO
* 返回的结果:List,返回多个就是集合,返回一个就是get(0)
* 结果的封装:反射,要求调用者告知BaseDAO类对象,class
*/
public <T> List<T> executeQuery(Class<T> clazz,String sql, Object... params) throws Exception{
Connection connection = JDBCUtilV2.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
if (params != null && params.length>0) {
for (int i = 0; i < params.length; i++) {
preparedStatement.setObject(i+1,params[i]);
}
}
//执行sql语句,结果返回结果集
ResultSet resultSet = preparedStatement.executeQuery();
//获取结果集中的元数据对象
//包含:列的数量,每个列的名称
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
List<T> list = new ArrayList<>();
while(resultSet.next()){
//通过循环,创建一个对象
Constructor<T> constructor = clazz.getConstructor();
T t = constructor.newInstance();
for (int i = 1; i <= columnCount; i++) {
Object value = resultSet.getObject(i);
//获取列的value值,这个值就是t这个对象中的某一个属性
//获取当列的名称 = 对象的属性名
String fieldName = metaData.getColumnLabel(i);
//通过类对象和fieldName获取封装的对象的属性
Field field = clazz.getDeclaredField(fieldName);
//反射
field.setAccessible(true);
field.set(t,value);
}
list.add(t);
}
//回收资源
resultSet.close();
preparedStatement.close();
if (connection.getAutoCommit()) {
JDBCUtilV2.release();
}
//返回结果
return list;
}
/**
* 通用查询:获取上面的集合结果中获取第一个结果,简化了单行多列和单行单列的获取
*/
public <T> 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);
}
}
EmployeeDao包中是要实现增删改查方法
package com.atguigu.senior.dao;
import com.atguigu.senior.pojo.Employee;
import java.util.List;
public interface EmployeeDao {
/**
* 查询所有员工的所有信息
* @return 所有员工的所有信息
*/
List<Employee> selectAll();
/**
* 在数据库中使用员工的id查询单个信息
* @param empId 主键对
* @return 一个员工的所有信心
*/
Employee selectByEmpId(Integer empId);
/**
* 新增一条员工
* @param employee ORM思想中的一个员工对象
* @return 受影响的行数
*/
int insert(Employee employee);
/**
* 修改一条员工信息
* @param employee ORM思想中的一个员工对象
* @return 受影响行数
*/
int update(Employee employee);
/**
* 在数据库中通过员工id删除一条员工信息
* @param empId 主键对
* @return 受影响的行数
*/
int delete(Integer empId);
}
EmployeeDaoImpl 继承BaseDao,并重写EmployeeDao接口中的
package com.atguigu.senior.dao.impl;
import com.atguigu.senior.dao.BaseDAO;
import com.atguigu.senior.dao.EmployeeDao;
import com.atguigu.senior.pojo.Employee;
import java.util.List;
public class EmployeeDaoImpl extends BaseDAO implements EmployeeDao {
@Override
public List<Employee> selectAll() {
try {
String sql = "select emp_id empId ,emp_name empName,emp_salary empSalary,emp_age empAge from t_emp";
return executeQuery(Employee.class,sql,null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Employee selectByEmpId(Integer empId) {
try {
String sql = "select emp_id empId,emp_name empName, emp_salary empSalary,emp_age empAge from t_emp where emp_id = ?";
return executeQueryBean(Employee.class,sql,empId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int insert(Employee employee) {
try {
String sql = "insert into t_emp(emp_name, emp_salary,emp_age) values (?,?,?)";
return executeUpdate(sql,employee.getEmpName(),employee.getEmpSalary(),employee.getEmpAge());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int update(Employee employee) {
try {
String sql = "update t_emp set emp_salary = ? where emp_id = ?";
return executeUpdate(sql,employee.getEmpSalary(),employee.getEmpId());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int delete(Integer empId) {
try {
String sql = "delete from t_emp where emp_id = ?";
return executeUpdate(sql,empId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
测试:
@Test
public void testEmployeeDAO(){
EmployeeDao employeeDao = new EmployeeDaoImpl();
List<Employee> employeeList = employeeDao.selectAll();
for (Employee employee : employeeList) {
System.out.println("员工" + employee);
}
Employee employee = employeeDao.selectByEmpId(1);
System.out.println("员工信息:"+employee);
Employee employee1 = new Employee(null,"猪八戒",345.23,34);
int insert1 = employeeDao.insert(employee1);
System.out.println(insert1);
Employee employee2 = new Employee(11,"猪八戒",666.99,34);
int update1 = employeeDao.update(employee2);
System.out.println(update1);
int delete = employeeDao.delete(11);
System.out.println(delete);
}
3.事务
事务的特点:
-
原子性(Atomicity): 事务是一个不可分割的操作单元,事务内的所有操作要么全部成功执行,要么在发生错误时全部回滚。
-
一致性(Consistency): 事务的执行应该使数据库从一个一致性状态转变到另一个一致性状态。即在事务开始和结束时,数据库的完整性约束不会被破坏。
-
隔离性(Isolation): 并发执行的事务之间是相互独立的,一个事务的执行不应受到其他事务的影响。即使多个事务同时执行,它们的效果也应该与它们顺序执行的效果相同。
-
持久性(Durability): 一旦事务提交,其对数据库的修改是永久性的,即使系统崩溃或出现故障,这些修改也会被保存。
事务的基本操作:
- 开始事务(Begin Transaction)
- 执行操作(如插入、更新、删除数据)
- 提交事务(Commit),将所有更改保存到数据库
- 回滚事务(Rollback),撤销所有未提交的更改
在mysql中创建t_bank表,并插入图中的数据
创建BankDao接口
package com.atguigu.senior.dao;
import com.atguigu.senior.pojo.Bank;
import java.util.List;
public interface BankDao {
int addmoney(Integer id,Integer money);
int submoney(Integer id,Integer money);
}
在BankDaoImpl类中重写BankDao类中的接口
package com.atguigu.senior.dao.impl;
import com.atguigu.senior.dao.BankDao;
import com.atguigu.senior.dao.BaseDAO;
public class BankDaoImpl extends BaseDAO implements BankDao {
@Override
public int addmoney(Integer id, Integer money) {
try {
String sql = "update t_bank set money = money + ? where id = ?";
return executeUpdate(sql,id,money);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int submoney(Integer id, Integer money) {
try {
String sql = "update t_bank set money = money - ? where id = ?";
return executeUpdate(sql,id,money);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
测试:
@Test
public void testBankDAO(){
BankDao bankDao = new BankDaoImpl();
Connection connection = null;
//获取连接
try {
connection = JDBCUtilV2.getConnection();
connection.setAutoCommit(false);
//转账操作
bankDao.submoney(1,100);
//用以验证异常,事务是否会回滚
//int i = 10/0;
//接收操作
bankDao.addmoney(2,100);
//提交事务
connection.commit();
} catch (Exception e) {
e.printStackTrace();
try {
connection.rollback();
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
} finally {
JDBCUtilV2.release();
}
}
结果:修改后