1.JDBS简介

JDBC(Java DataBase Connectivity), 即java数据库连接,是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。

Java 具有坚固、安全、易于使用、易于理解和可从网络上自动下载等特性,是编写数据库应用程序的杰出语言。所需要的只是 Java应用程序与各种不同数据库之间进行对话的方法。

JDBC可以在各种平台上使用Java,如Windows,Mac OS和各种版本的UNIX。JDBC库包括通常与数据库使用相关的下面提到的每个任务的API:

  • 连接数据库。
  • 创建SQL或MySQL语句。
  • 在数据库中执行SQL或MySQL查询。
  • 查看和修改生成的记录。

2.JDBC体系结构

JDBC API支持用于数据库访问的两层和三层处理模型,但通常,JDBC体系结构由两层组成:

  • JDBC API:这提供了应用程序到JDBC管理器连接。
  • JDBC驱动程序API:这支持JDBC管理器到驱动程序连接。

JDBC API使用驱动程序管理器和特定于数据库的驱动程序来提供与异构数据库的透明连接。

java 项目 oceanbase数据库用什么驱动 java database_数据库

3 JDBC核心组件

DriverManager: 此类管理数据库驱动程序列表。使用通信子协议将来自java应用程序的连接请求与适当的数据库驱动程序匹配。

Driver:此接口处理与数据库服务器的通信,我们很少会直接与Driver对象进行交互。而是使用DriverManager对象来管理这种类型的对象。

Connection:该界面具有用于联系数据库的所有方法。连接对象表示通信上下文,即,与数据库的所有通信仅通过连接对象。

Statement:使用从此接口创建的对象将SQL语句提交到数据库。除了执行存储过程之外,一些派生接口还接受参数。

ResultSet:在使用Statement对象执行SQL查询后,这些对象保存从数据库检索的数据。它作为一个迭代器,允许我们移动其数据。

SQLException:此类处理数据库应用程序中发生的任何错误

4 JDBC使用

构建JDBC应用程序涉及以下六个步骤:

  • 导入包:需要包含包含数据库编程所需的JDBC类的包。大多数情况下,使用import java.sql.*就足够了。
  • 注册JDBC驱动程序:要求您初始化驱动程序,以便您可以打开与数据库的通信通道。
  • 打开连接:需要使用DriverManager.getConnection()方法创建一个Connection对象,该对象表示与数据库的物理连接。
  • 执行查询:需要使用类型为Statement的对象来构建和提交SQL语句到数据库。
  • 从结果集中提取数据:需要使用相应的 ResultSet.getXXX()方法从结果集中检索数据。
  • 释放资源:需要明确地关闭所有数据库资源,而不依赖于JVM的垃圾收集。

4.1 JDBC的连接

4.1.1 导入JDBC包

使用 import 导入Java代码中所需的类

4.1.2 注册JDBC驱动程序

使JVM将所需的驱动程序实现加载到内存中,以便它可以满足JDBC请求。

1. Class.forName();

注册驱动程序最常见的方法是使用Java的 Class.forName() 方法,将驱动程序的类文件动态加载到内存
中,并将其自动注册.

try {
	Class.forName("com.mysql.cj.jdbc.Driver");
}catch(ClassNotFoundException ex) {
	System.out.println("Error: unable to load driver class!");
	System.exit(1);
}
2. DriverManager.registerDriver();

第二种方法是使用静态 DriverManager.registerDriver() 方法。

try {
	Driver myDriver = new com.mysql.cj.jdbc.Driver();
	DriverManager.registerDriver( myDriver );
}catch(ClassNotFoundException ex) {
	System.out.println("Error: unable to load driver class!");
	System.exit(1);
}

4.1.3 数据库URL配置

创建一个格式正确的地址,指向要连接到的数据库。

加载驱动程序后,可以使用 DriverManager.getConnection() 方法建立连接。以下列出三个重载的DriverManager.getConnection()方法:

  • getConnection(String url)
  • getConnection(String url,Properties prop)
  • getConnection(String url,String user,String password)

RDBMS

JDBC驱动程序名称

网址格式

MYSQL8

com.mysql.cj.jdbc.Driver

jdbc:mysql://hostname:3306/databaseName?serverTimezone=UTC

MySQL

com.mysql.jdbc.Driver

jdbc:mysql://hostname:3306/databaseName

ORACLE

oracle.jdbc.driver.OracleDriver

jdbc:oracle:thin:@hostname:port Number:databaseName

DB2

COM.ibm.db2.jdbc.net.DB2Driver

jdbc:db2:hostname:port Number / databaseName

SYBASE

com.sybase.jdbc.SybDriver

jdbc:sybase:Tds:hostname:port Number / databaseName

4.1.4 创建数据库连接对象

调用DriverManager对象的 getConnection() 方法来建立实际的数据库连接。

String URL = "jdbc:mysql://localhost:3306/db3?serverTimezone=UTC";
String USER = "root";
String PASS = "123456"
Connection conn = DriverManager.getConnection(URL, USER, PASS);

DriverManager.getConnection() 方法的另一种形式需要一个 数据库URL 和一个 Properties 对象

DriverManager.getConnection(String url, Properties info);
import java.util.*;
String URL = "jdbc:mysql://localhost:3306/yhp2?serverTimezone=UTC";
//为properties添加属性值
Properties info = new Properties( );
info.put( "user", "root" );
info.put( "password", "123456" );
Connection conn = DriverManager.getConnection(URL, info);

4.1.5 关闭数据库连接

为确保连接关闭,您可以在代码中提供一个“finally”块。一个finally块总是执行,不管是否发生异常。要关闭上面打开的连接,你应该调用 close() 方法如下:

conn.close();

4.2 JDBC执行SQL语句

一旦获得了连接,我们可以与数据库进行交互。JDBC Statement 和 PreparedStatement 接口定义了能够发送SQL命令并从数据库接收数据的方法和属性。

接口

推荐使用

Statement

用于对数据库进行通用访问。在运行时使用静态SQL语句时很有用。Statement 接口不能接受参数。

PreparedStatement

当您计划多次使用SQL语句时使用。PreparedStatement 接口在运行时接受输入参数。

4.2.1 Statement(状态通道)

创建语句对象
在使用 Statement 对象执行SQL语句之前,需要使用 Connection 对象的 createStatement() 方法创建一个。

创建Statement对象后,可以使用它来执行一个SQL语句,其中有三个执行方法:

  • boolean execute(String SQL):如果可以检索到 ResultSet 对象,则返回一个布尔值 true ; 否则返回 false。在执行SQL DDL语句或需要使用真正的动态SQL时使用此方法。
  • int executeUpdate(String SQL):返回受SQL语句执行影响的行数。使用此方法执行预期会影响多个行的SQL语句,例如 INSERT,UPDATE 或 DELETE 语句。
  • ResultSet executeQuery(String SQL)返回一个 ResultSet 对象。希望获得结果集时,请使用此方法,例如 SELECT 语句。
关闭Statement对象

就像我们关闭一个Connection对象以保存数据库资源一样,由于同样的原因,还应该关闭Statement对象。调用close()方法将执行该作业。如果先关闭 Connection对象,它也会关闭 Statement对象。但是,应始终显式关闭 Statement对象,以确保正确清理。

Statement stmt = null;
try {
	stmt = conn.createStatement( );
	ResultSet resultset = stmt. executeQuery("select * from student");
	· · ·
} catch (SQLException e) {
	. . .
} finally {
	stmt.close();
}

4.2.2 PreparedStatement(预状态通道)

该 PreparedStatement接口 扩展了 Statement接口,它提供了一个通用的 Statement对象 没有的附加功能。此语句可以动态地提供参数。

JDBC中的所有参数都由 ?符号,这被称为参数标记在执行SQL语句之前,必须为每个参数提供值。使用 setXXX() 方法将值绑定到所述参数,其中 XXX 代表要绑定到输入参数的值的Java数据类型。如果忘记提供值,将收到一个 SQLException。每个参数标记由其顺序位置引用。第一个标记表示位置1,下一个位置2, ······

关闭 PreparedStatement 对象

就像关闭 Statement对象 一样,如果先关闭 Connection对象,PreparedStatement对象 也会关闭。但是,应始终显式关闭 PreparedStatement对象,以确保正确清理。

PreparedStatement pstmt = null;
try {
	String SQL = "Update Employees SET age = ? WHERE id = ?";
	pstmt = conn.prepareStatement(SQL);
	
	String uname="admin";
	String pword="123";
	//给占位符赋值 SetXXX(下标,内容)
	pstmt.setString(1, uname);
	pstmt.setString(2, pword);
	
	resultSet = pstmt.executeQuery();
. . .
} catch (SQLException e) {
. . .
} finally {
pstmt.close();
}

4.2.3 对比 statement 和 PreparedStatement;

(1) statement 属于状态通道,PreparedStatement 属于预状态通道
(2) 预状态通道 会先编译sql语句,再去执行,比statement执行效率高
(3) 预状态通道支持占位符 ? ,给占位符赋值的时候,位置从1开始
(4) 预状态通道可以防止sql注入,原因:预状态通道在处理值的时候以字符串的方式处理

4.3 SQL注入

就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击。

以下代码为 通过用户名和密码验证登录:

String username ="admin";
String password=" 'abc'";
String sql="select * from users where username= '"+username+"' and password="+password;
resultset = statement.executeQuery(sql);
if(resultSet.next()){
	System.out.println("登陆成功");
}else{
	System.out.println("登陆失败");
}

如以下代码所示,password中包含了 “or 1=1”, 其中 “1=1” 代表恒成立,意思是无论用户名或密码在数据库中是否存在,该sql语句的执行结果一定不为空

String username ="aaa";
String password=" '' or 1=1 ";
String sql="select * from users where username= '"+username+"' and password="+password;
resultset = statement.executeQuery(sql);
if(resultSet.next()){
	System.out.println("登陆成功");
}else{
	System.out.println("登陆失败");
}

SQL注入的解决方法就是上面的 PreparedStatement,因为 PreparedStatement 对值的处理都是以字符串的形式,“or 1=1”不再使等式恒成立

4.4 ResultSet

SELECT语句是从数据库中选择行并在结果集中查看行的标准方法。该java.sql.ResultSet中的接口表示结果集数据库查询“结果集”是指包含在ResultSet对象中的行和列数据

如果没有指定任何 ResultSet类型,您将自动获得一个 TYPE_FORWARD_ONLY。

类型

描述

ResultSet.TYPE_SCROLL_INSENSITIVE

光标可以向前和向后滚动,结果集对创建结果集后发生的数据库的其他更改不敏感。

ResultSet.TYPE_SCROLL_SENSITIVE

光标可以向前和向后滚动,结果集对创建结果集之后发生的其他数据库所做的更改敏感。

ResultSet.TYPE_FORWARD_ONLY

光标只能在结果集中向前移动。

try {
	Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
}catch(Exception ex) {
	...
}finally {
	...
}

4.5 应用举例

//0.导入 sql 包
import java.sql.*;
//...

public static void main(String[] args) {
    Connection connection = null;
    Statement statement = null;
    ResultSet resultset = null;

    try {
        //1.加载驱动
        Class.forName("com.mysql.cj.jdbc.Driver");
        //2.获得链接
        String username = "root";
        String password = "123456";
        String url = "jdbc:mysql://localhost:3306/mydb1?serverTimezone=UTC";
        connection = DriverManager.getConnection(url, username, password);
        //3.定义sql,创建状态通道(进行sql 语句的发送)
        statement = connection.createStatement();
        resultset = statement.executeQuery("select * from emp1");//executeQuery(sql)
        //4.取出结果集信息
        while(resultset.next()){
            //取出数据 resultset.getString("列名");
            System.out.println("姓名"+resultset.getString("ename")+", 工资:"+resultset.getDouble("sal")+
                    ", 雇佣日期:"+resultset.getDate("hiredate"));
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    } finally{
        try {
            if (resultset != null) {
                resultset.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

4.6 JDBC步骤总结

时间

功能

语句

第一步

加载驱动

Class.forName("驱动信息")

第二步

创建连接

Connection conn = DriverManager.getConnection(url, username, password)

第三步

创建(预)状态通道

Statement stmt = conn.createStatement(); / PreparedStatement pstmt = conn.prepareStatement(String SQL)

第四步

获取结果集()

ResultSet rs = stmt.executeQuery(String SQL); / rs = pstmt.executeQuery();

第五步

取出结果集信息

使用迭代器:while(rs.next()){...}

4.7 JDBC的增删改操作

将上面的获取结果集时用到的 executeQuery(String SQL) 换成 executeUpdate(String SQL) ,注意它的返回值为被影响的行数(int类型)

String sql = "insert into emp1(ename,hiredate,sal) values('aa','2020-1-1',2000)";
String sql1 = "update emp1 set sal=888;";
String sql2 = "delete from emp1 where ename='aa')";
//返回受影响的行数
int result = statement.executeUpdate(sql);

5 Java 操作两表关系

5.1 一对多

1 创建数据表

创建 student表 和 teacher表:

CREATE TABLE `student` (
`stuid` int(11) NOT NULL AUTO_INCREMENT,
`stuname` varchar(255) DEFAULT NULL,
`teacherid` int(11) DEFAULT NULL,
PRIMARY KEY (`stuid`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
INSERT INTO `student` VALUES ('1', 'aaa', '3');
INSERT INTO `student` VALUES ('2', 'bb', '1');
INSERT INTO `student` VALUES ('3', 'cc', '3');
INSERT INTO `student` VALUES ('4', 'dd', '1');
INSERT INTO `student` VALUES ('5', 'ee', '1');
INSERT INTO `student` VALUES ('6', 'ff', '2');
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (
`tid` int(11) NOT NULL AUTO_INCREMENT,
`tname` varchar(255) DEFAULT NULL,
PRIMARY KEY (`tid`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
INSERT INTO `teacher` VALUES ('1', '张三老师');
INSERT INTO `teacher` VALUES ('2', '李四老师');
INSERT INTO `teacher` VALUES ('3', '王五');
2 创建实体类

在一对多的情况下,应在一方设置一个列表类型的属性,用来存放多方的数据

public class Teacher {
    private int tid;
    private String tname;
    //在一方创建存储多方数据的集合
    private List<Student> studentList;
    
    public int getTid() {return tid;}
    public void setTid(int tid) {this.tid = tid;}
    public String getTname() {return tname;}
    public void setTname(String tname) {this.tname = tname;}
    public List<Student> getStudentList() {return studentList;}
    public void setStudentList(List<Student> studentList) {this.studentList = studentList;}
}
public class Student {
    private int stuid;
    private String stuName;
    private int teacherId;

    public int getStuid() {return stuid;}
    public void setStuid(int stuid) {this.stuid = stuid;}
    public String getStuName() {return stuName;}
    public void setStuName(String stuName) {this.stuName = stuName;}
    public int getTeacherId() {return teacherId;}
    public void setTeacherId(int teacherId) {this.teacherId = teacherId;}
}
3 定义dao接口
public interface TeacherDao {
    //定义操作方法
    //1.定义一个根据老师的id查询老师的信息(学生的信息)
    public Teacher getById(int tid);
}
4 定义实现类

与上面的JDBC的操作类似,加载驱动 -> 创建连接 -> 创建状态通道 -> 获取并处理数据

public class TeacherDaoImpl implements TeacherDao {
    @Override
    public Teacher getById(int tid) {
        Connection connection = null;
        PreparedStatement pstmt = null;
        ResultSet resultSet = null;
        try {
        	//加载驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //创建连接
            String username = "root";
            String password = "123456";
            String url = "jdbc:mysql://localhost:3306/yhp3?serverTimezone=UTC";
            connection = DriverManager.getConnection(url, username, password);
            //建立状态通道
            String sql = "SELECT * from student s,teacher t where s.teacherid=t.tid and t.tid=?";
            pstmt = connection.prepareStatement(sql);
            pstmt.setInt(1, tid);
            resultSet = pstmt.executeQuery();
            //处理数据
            Teacher teacher = new Teacher();
            List<Student> students = new ArrayList<>();
            while(resultSet.next()){
                teacher.setTid(resultSet.getInt("tid"));
                teacher.setTname(resultSet.getString("tname"));

                Student student = new Student();
                student.setStuid(resultSet.getInt("stuid"));
                student.setStuName(resultSet.getString("stuname"));
                students.add(student);
            }
            //师生建立联系
            teacher.setStudentList(students);
            return teacher;
            
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally{
            try {
                if (resultSet != null) {resultSet.close();}
                if (pstmt != null) {pstmt.close();}
                if (connection != null) {connection.close();}
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        return null;
    }
}
5 定义测试类
public static void main(String[] args) {
	//创建实现类接口的对象
    TeacherDao dao = new TeacherDaoImpl();
    Teacher teacher = dao.getById(1);
    System.out.println("老师姓名:"+teacher.getTname());
    List<Student> studentList = teacher.getStudentList();
    for (Student s: studentList) {
        System.out.println("\t"+"学生名:"+s.getStuName()+"学号:"+s.getStuid());
    }
}

5.2 多对一

1 创建数据表

数据表的创建与之前相同

2 实体类建立

注意:此时多方要存储属性值为** 一方的数据** 的对象

public class Teacher {
    private int tid;
    private String tname;
    
    public int getTid() {return tid;}
    public void setTid(int tid) {this.tid = tid;}
    public String getTname() {return tname;}
    public void setTname(String tname) {this.tname = tname;}
}
public class Student {
    private int stuid;
    private String stuName;
    private int teacherId;
    private Teacher teacher;

    public Teacher getTeacher() {return teacher;}
    public void setTeacher(Teacher teacher) {this.teacher = teacher;}
    public int getStuid() {return stuid;}
    public void setStuid(int stuid) {this.stuid = stuid;}
    public String getStuName() {return stuName;}
    public void setStuName(String stuName) {this.stuName = stuName;}
    public int getTeacherId() {return teacherId;}
    public void setTeacherId(int teacherId) {this.teacherId = teacherId;}
}
3 定义dao接口
public interface TeacherDao {
    public Teacher getById(int tid);
    public List<Student> getAll();
}
4 定义实现类
public List<Student> getAll() {
    //操作数据库
    Connection connection = null;
    PreparedStatement pstmt = null;
    ResultSet resultSet = null;
    try {
        Class.forName("com.mysql.cj.jdbc.Driver");
        String username = "root";
        String password = "123456";
        String url = "jdbc:mysql://localhost:3306/yhp3?serverTimezone=UTC";
        connection = DriverManager.getConnection(url, username, password);
        String sql = "SELECT * from student s,teacher t where s.teacherid=t.tid";
        pstmt = connection.prepareStatement(sql);
        resultSet = pstmt.executeQuery();
        List<Student> students = new ArrayList<>();
        while(resultSet.next()){
            Student student = new Student();
            student.setStuid(resultSet.getInt("stuid"));
            student.setStuName(resultSet.getString("stuname"));
            
            Teacher teacher = new Teacher();
            teacher.setTid(resultSet.getInt("tid"));
            teacher.setTname(resultSet.getString("tname"));
            
            student.setTeacher(teacher);
            students.add(student);
        }
        return students;
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    } finally{
        try {
            if (resultSet != null) {resultSet.close();}
            if (pstmt != null) {pstmt.close();}
            if (connection != null) {connection.close();}
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
    return null;
}
5 定义测试类
TeacherDao dao = new TeacherDaoImpl();
List<Student> students = dao.getAll();
for (Student student : students) {
    System.out.println(student.getStuName()+","+student.getTeacher().getTname());
}

5.3 一对一

实体类建立

在两方都创建存储属性值为** 另一方的数据** 的对象

5.4 多对多

实体类建立

两方互相存入列表

6 数据库事务

6.1 事务概述

数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。

事务开始于:

  • 连接到数据库上,并执行一条DML语句 insert、update 或 delete
  • 前一个事务结束后,又输入了另一条DML语句

事务结束于:

  • 执行 commit 或 rollback 语句。
  • 执行一条 DDL 语句,例如 create table 语句,在这种情况下,会自动执行 commit 语句。
  • 执行一条 DCL 语句,例如 grant 语句,在这种情况下,会自动执行 commit。
  • 断开与数据库的连接
  • 执行了一条 DML 语句,该语句却失败了,在这种情况中,会为这个无效的DML语句执行 rollback 语
    句。

6.2 事务的四大特点(ACID)

  • actomicity(原子性)
    表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败
  • consistency(一致性)
    表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前状态
  • isolation(隔离性)
    事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修
    改它之后的状态,事务不会查看中间状态的数据
  • durability(持久性)
    持久性事务完成之后,它对于系统的影响是永久性的

6.3 JDBC中事务应用

如果JDBC连接处于自动提交模式,默认情况下,则每个SQL语句在完成后都会提交到数据库。要启用手动事务支持,而不是JDBC驱动程序默认使用的自动提交模式,请使用 Connection对象 的setAutoCommit() 方法。如果将boolean false传递给 setAutoCommit(),则关闭自动提交。我们可以传递一个布尔值true来重新打开它。

6.3.1 事物的提交和回滚

完成更改后,我们要提交更改,然后在连接对象上调用commit()方法,否则,要使用数据库回滚更新:

try {
       Class.forName("com.mysql.cj.jdbc.Driver");
       ...
       connection = DriverManager.getConnection(url, username, password);
       //注意:这里关闭了自动提交
       connection.setAutoCommit(false);
       statement = connection.createStatement();
       int result = statement.executeUpdate("SQL语句");
       ...
       //这里手动提交事务
       connection.commit();
       ...
   } catch (Exception e) {
       e.printStackTrace();
       //出现异常,回滚到初始状态
       try {
           connection.rollback();
       } catch (SQLException throwables) {
           throwables.printStackTrace();
       }
   }
   ...

6.3.2 Savepoints

设置保存点时,可以在事务中定义逻辑回滚点。如果通过保存点发生错误,则可以使用回滚方法来撤消
所有更改或仅保存在保存点之后所做的更改。
Connection对象有两种新的方法来帮助您管理保存点

  • setSavepoint(String savepointName):定义新的保存点。它还返回一个Savepoint对象。
  • releaseSavepoint(Savepoint savepointName):删除保存点。请注意,它需要一个 Savepoint 对象作为参数。此对象通常是由 setSavepoint()方法生成的保存点。
try{
	...
	int result = statement.executeUpdate("SQL语句");
	//加入保存点abc
	Savepoint abc = connection.setSavepoint("abc");
	int result2 = statement.executeUpdate("SQL语句1");
	System.out.println(5/0);
	connection.commit();
	...
} catch (Exception e) {
     e.printStackTrace();
     try {
     	//回滚到保存点abc的位置
         connection.rollback(abc);
         //重新提交保存点之前的操作
         connection.commit();
     } catch (SQLException throwables) {
         throwables.printStackTrace();
     }
}
...

6.3.3 案例:转账

编写的程序可能含有一定的逻辑,如果功能失败,整个事务就需要回滚。

try{
	...
	connection.setAutoCommit(false);
	statement = connection.createStatement();
	int result = statement.executeUpdate("update money set yue=yue-100 where userid=1");
	int result2 = statement.executeUpdate("update money set yue=yue+100 where userid=2");
	connection.commit();
	...
} catch (Exception e) {
     e.printStackTrace();
     try {
         connection.rollback();
     } catch (SQLException throwables) {
         throwables.printStackTrace();
     }
}
...

7 JDBC批处理

批量处理允许您将相关的SQL语句分组到批处理中,并通过对数据库的一次调用提交它们。
当您一次向数据库发送多个SQL语句时,可以减少连接数据库的开销,从而提高性能。

7.1 Statement批处理

以下是使用语句对象的批处理的典型步骤序列

  • 使用createStatement()方法创建Statement对象。
  • 使用setAutoCommit()将auto-commit设置为false 。
  • 使用addBatch()方法在创建的语句对象上添加您喜欢的SQL语句到批处理中。
  • 在创建的语句对象上使用executeBatch()方法执行所有SQL语句。
  • 最后,使用commit()方法提交所有更改。
try {
	...
    connection.setAutoCommit(false);
    statement = connection.createStatement();
    //定义sql
    String sql1 = "insert into teacher(tname) values('aa1')";
    statement.addBatch(sql1);
    String sql2 = "insert into teacher(tname) values('aa2')";
    statement.addBatch(sql2);
    String sql3 = "insert into teacher(tname) values('aa3')";
    statement.addBatch(sql3);
    String sql4 = "insert into teacher(tname) values('aa4')";
    statement.addBatch(sql4);
    int[] ints = statement.executeBatch();
    connection.commit();
    for (int anInt : ints) {
        System.out.println(anInt);
    }
} catch (Exception e) {
    e.printStackTrace();
    try {
        connection.rollback();
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    }
}

7.2 PreparedStatement批处理

  1. 使用占位符创建SQL语句。
  2. 使用prepareStatement() 方法创建PrepareStatement对象。
  3. 使用setAutoCommit()将auto-commit设置为false 。
  4. 使用addBatch()方法在创建的语句对象上添加您喜欢的SQL语句到批处理中。
  5. 在创建的语句对象上使用executeBatch()方法执行所有SQL语句。
  6. 最后,使用commit()方法提交所有更改。
try {
    ...
    connection.setAutoCommit(false);
    pps = connection.prepareStatement("insert into teacher(tname) values(?)");
    //定义sql
    pps.setString(1,"LISI");
    pps.addBatch();
    pps.setString(1,"LISI1");
    pps.addBatch();
    pps.setString(1,"LISI2");
    pps.addBatch();
    pps.setString(1,"LISI3");
    pps.addBatch();
    int[] ints = pps.executeBatch();
    connection.commit();
    for (int anInt : ints) {
        System.out.println(anInt);
    }
} catch (Exception e) {
    e.printStackTrace();
    try {
        connection.rollback();
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    }
}

8 反射处理结果集

每次通过set方法获取对象的属性值非常麻烦,因此可以使用反射的方法,直接将数据库的列名反射到对应的属性中。因此在定义类的属性名时,要与数据表中对应的列名一致(可以忽略大小写)。

...
String sql = "SELECT * from student";
pstmt = connection.prepareStatement(sql);
resultSet = pstmt.executeQuery();
List students = new ArrayList();
ResultSetMetaData metaData = resultSet.getMetaData();//存储结果集信息
int columnCount = metaData.getColumnCount();//得到列数
String[] columnNames = new String[columnCount];
for (int i=0; i < columnCount; i++){
    columnNames[i] = metaData.getColumnLabel(i+1);
    System.out.println("column"+columnNames[i]);
}
//得到类中所有的方法
Method[] declaredMethods = cla.getDeclaredMethods();
while(resultSet.next()){
    try {
        Object stu = cla.newInstance();
        for (String columnName : columnNames) {
            String methodName = "set"+columnName;
            for (Method declaredMethod : declaredMethods) {
            	//当需要 set 的属性名与数据库结果集属性名相同,激活对应的setter方法将该属性值加到对用的对象中
                if(declaredMethod.getName().equalsIgnoreCase(methodName)){
                    declaredMethod.invoke(stu,resultSet.getObject(columnName));
                    break;
                }
            }
        }
        students.add(stu);
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}
return students;
TeacherDao dao = new TeacherDaoImpl();
List<Student> allStudent = dao.getAllStudent(Student.class);
for (Student student : allStudent) {
         	System.out.println(student.getStuid()+","+
         	student.getStuName()+","+student.getTeacherId();
}

9 封装工具类

简化重复代码,很有用。
工具类的定义,使用protected是为了仅仅让继承他的子类使用里面的方法:

public class DBUtils {
    private Connection connection;
    private PreparedStatement pps;
    private ResultSet resultSet;
    private int count;

    private String username = "root";
    private String password = "123456";
    private String url = "jdbc:mysql://localhost:3306/yhp3?serverTimezone=UTC";

    //加载驱动
    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    //创建连接
    protected Connection getConnection(){
        try {
            connection = DriverManager.getConnection(url,username,password);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return connection;
    }
    //创建状态通道
    protected PreparedStatement getPps(String sql){
        try {
            pps = getConnection().prepareStatement(sql);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return pps;
    }
    //list保存的是被占位符覆盖的值,为预状态通道赋值(传参)
    protected void param(List list){
        if(list != null && list.size()>0){
            for (int i=0; i<list.size(); i++) {
                try {
                    pps.setObject(i+1,list.get(i));
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
    //DML
    protected int update(String sql, List list){
        getPps(sql);
        param(list);
        try {
            count = pps.executeUpdate();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return count;
    }
    //DQL
    protected ResultSet query(String sql, List list){
        getPps(sql);
        param(list);
        ResultSet resultSet = null;
        try {
            resultSet = pps.executeQuery();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return resultSet;
    }
	//关闭连接
    protected void closeAll(){
        try {
            if (connection != null) {
                connection.close();
            }
            if (pps != null) {
                pps.close();
            }
            if (resultSet != null) {
                resultSet.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}
public class TeacherDaoImpl extends DBUtils implements TeacherDao {
    @Override
    public Student getByStuId(int id) {
        Student student = new Student();
        try {
            String sql = "select * from student where stuid=?";
            List list = new ArrayList();
            list.add(id);
            ResultSet rs = query(sql,list);

            while(rs.next()){
                student.setStuid(rs.getInt("stuid"));
                student.setStuName(rs.getString("stuname"));
            }
            return student;
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            closeAll();
        }
        return null;
    }
}

9.1 properties文件保存数据库信息

  • 特点:key-value存储方式

将需要用到的值存放在 db.properties 文件中

driver=com.mysql.cj.jdbc.Driver
username=root
password=123456
url=jdbc:mysql://localhost:3306/yhp3?serverTimezone=UTC

用来加载驱动的静态代码块:

private static String username;
private static String password;
private static String url;
private static String dirverName;
    
static {
    try {
        InputStream inputStream = DBUtils.class.getClassLoader()
                .getResourceAsStream("db.properties");
        Properties properties = new Properties();
        properties.load(inputStream);
        dirverName = properties.getProperty("driver");
        url = properties.getProperty("url");
        username = properties.getProperty("username");
        password = properties.getProperty("password");
        Class.forName(dirverName);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

static{
	//参数只写属性文件名即可,不需要写后缀
	ResourceBundle bundle = ResourceBundle.getBundle("db");
	driver = bundle.getString("driver");
	url = bundle.getString("url");
	username = bundle.getString("user");
	password = bundle.getString("password");
}

10 连接池

1.自定义连接池

数据连接池原理

连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等,也可以通过其自身的管理机制来监视数据库连接的
数量、使用情况等。

1.1 自定义连接池

我们可以通过自定义的方式实现连接池!分析连接池类应该包含特定的属性和方法!
属性: 集合 放置Connection
方法: 获取连接方法
回收连接方法
具体实现代码: