一、简介
JDBC(Java Database Connectivity)Java数据库连接,是一种用于执行SQL语句的javaAPI,可以为多种关系的数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序
Java具有坚固、安全、易于使用、易于理解和可从网络上自动下载等特性,是编写数据库应用的杰出语言。所需要的只是java应用程序与各种不同数据库之间进行对话的方法。
JDBC可以在各种平台上使用java,如Windows、Mac os和各种版本的UNIX。
JDBC库包括通常与数据库使用相关的下面提到的每个任务的API。
- 连接数据库
- 创建SQL或MySQL语句
- 在数据库中执行SQL或MySQL查询
- 查看和修改生成的记录。
二、JDBC体系结构
JDBC API支持用于数据库访问的两层和三层处理模型,但通常,JDBC体系结构由两层组成:
- **JDBC API:**这提供了应用程序到DBC管理器连接
- **JDBC 驱动程序:**这支持JDBC管理器到驱动程序的连接
JDBC API使用驱动程序管理器和特定于数据库的驱动程序来提供与异构数据库的透明连接
三、JDBC核心组件(类库)
**DriverManager(驱动管理器):**此类管理数据库驱动程序列表,使用通信子协议将来自Java应用程序的连接请求与适当的数据库驱动程序匹配。
**Driver(驱动):**此接口处理与数据库服务的通信,我们很少会直接与Driver对象交互。而是使用DriverManager对象来管理这类型的对象。
**Connection(连接):**该类具有用于连接数据库的所有方法。连接对象表示通信上下文,即与数据库的所有通信仅通过连接对象
**Statement(状态通道):**使用从此接口创建的对象将SQL语句提交到数据库。除了执行存储过程之外,一些派生接口还接受参数。
**Resultset(结果集):**在使用Statement对象执行SQL查询后,由Resultset的对象保存从数据库检索到的数据,它作为一个迭代器,允许我们移动其数据。
**SQLException(数据库异常):**此类处理数据库应用程序中发生的任何错误
四、使用步骤
构建JDBC应用程序设计以下六个步骤:
- 导入包:需要包含包含数据库编程所需的JDBC类的包。大多数情况下,使用import.java.sql.*就足够了。
- 注册JDBC驱动程序:要求初始化驱动程序,以便可以打开与数据库的连接通道。
- 打开链接:需要使用DriverManager.getConnection() 方法创建一个Connection链接对象,该对象表示与数据库的物理连接
- 执行查询:需要使用类型为Statement的对象来构建和提交SQL语句到数据库。
- 从结果集中提取数据:需要使用相应的ResultSet.getXXX()方法从结果集中检索数据。
- 释放资源:需要明确地关闭所有数据库资源,而不依赖于JVM的垃圾收集。
五、JDBC连接步骤
建立JDBC连接所涉及的编程分以下四个步骤:
- 导入JDBC包:将Java语言的 import 语句添加到java代码中导入所需的类
- 注册JDBC驱动程序:此步骤将使JVM将所需的驱动程序实现加载到内存中,以便它可以满足我们的JDBC需求
- 数据库URL配置:这是为了创建一个格式正确的地址,指向想要连接的数据库
- 创建连接对象:最后,调用DriverManager对象的getConnection()方法来建立实际的数据库连接
注册驱动程序:
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);
}
DriverManager.registerDriver()
第二种方法是使用静态DriverManager.registerDriver() 方法
try{
Driver driver = new com.mysql.cj.jdbc.Driver();//创建驱动对象
DriverManager.registerDriver(driver);//调用注册方法传入驱动对象进行注册
}catch(ClassNotFoundException ex){
System.out.println("Error: unable to load driver class!");
System.exit(1);
}
数据库URL配置
加载驱动之后,可以使用DriverManager.getConnection()方法建立连接。getConnection(String url);
getConnection(String url,Properties prop);
getConnection(String url,String user,String password);
RDBMS | JDBC驱动程序名称 | URL |
---|---|---|
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 |
SYBASE | com.sybase.jdbc.SybDriver | jdbc:sybase:Tds:hostname:port Number / databaseName |
DB2 | com.ibm.db2.jdbc.net.DB2Driver | jdbc:db2:hostname:port Number / databaseName |
创建数据库连接对象
String url = "jdbc:mysql://hostname:3306/mydb?serverTimezone=UTC"; //获取数据库地址
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url,user,password); //传入地址,用户名,密码然后创建连接
或
String url = "jdbc:mysql://hostname:3306/mydb?serverTimezone=UTC"; //获取数据库地址
Properties info = new Properties; //创建一个属性对象
info.put("user","root");
info.put("password","root"); //将用户名和密码封装进对象里
Connection conn = DriverManager.getConnection(url,info); //传入地址和属性对象
关闭数据库连接
为确保连接关闭,您可以在代码中提供一个“finally”块,一个finally块总会执行,不管是否发生异常。对象名.close()
六、JDBC执行SQL语句
一旦获得了连接,我们可以与数据库进行交互。JDBC Statement 和 PreparedStatement 接口定义了使您能够发送SQL命令并从数据库接收数据的方法和属性
Statement
创建语句对象
在使用Statement对象执行SQL语句之前,需要使用Connection对象的createStatement()方法创建一个,示例如下:
Statement statement= null;
statement = conn.createStatement();
创建Statement对象后,可以用它来执行一个SQL语句,其中三个执行方法之一。
- boolean execute(String sql): 如果可以检索到ResultSet对象,也就是操作有结果,则返回一个boolean值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对象,以确保正确清理。
SQL注入
就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有的应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。比如先前的很多视频网站泄露VIP会员密码大多就是通过Web表单递交查询字符暴露出的,这类表单特别容易受到SQL注入式攻击。
例如:
String username = "admin";
String password = "'abc' or 1=1";
String sql = "select * from users where username="+username+" and password="+password;
PreparedStatement(预状态通道)
该PreparedStatement的接口扩展了Statement接口,它为您提供了一个通用的Statement对象有两个有点附加功能。
此语句使我们可以动态得提供参数。
PreparedStatement pstmt = null;
try{
String sql = "Update emp set age = ? where id = ?";
pstmt = conn.PreparStatement(sql);
...
}catch(SQLExeception e){
...
}finally(
...
}
JDBC中的所有参数都由 ? 符号,这被称为参数标记,在执行SQL语句前,必须为每个参数提供值。
所述的setXXX()方法将值绑定到所述参数,其中XXX代表要绑定到输入参数的值的Java数据类型。如果忘记提供值,将收到一个SQLException。
每一个参数标记由其顺序位置引用,第一个标记表示位置1,下一个是位置2 该方法与java数组索引不同,数组从0开始 而PreparedStatement从1开始。
关闭PreparedStatement对象
就像关闭Statement对象一样,由于同样的原因,还应该关闭PreparedStatement对象。
一个简单的调用close()方法将执行该作业。如果先关闭Connection对象,它也会关闭 PreparedStatement对象。但是,应始终显式关闭PreparedStatement对象,以确保正确清理
对比statement和PreparedStatement
- statement属于状态通道,preparedStatement属于预状态通道
- 预状态通道会先编译sql语句,再去执行,比statement执行效率高
- 预状态通道支持占位符?,给占位符赋值的时候,位置从1开始
- 预状态通道可以防止SQL注入攻击,原因:预状态通道在处理值的时候以字符串的方式处理
ResultSet
select语句是从数据库中选择并在结果集中查看行的标准方法。该java.sql.ResultSet中的接口表示结果集数据库查询。
ResultSet对象维护指向结果集中当前行的游标,术语“结果集”是指包含在ResultSet对象中的行和列数据。
如果没有指定任何ResultSet类型,您将自动获得一个TYPE_FORWORD_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{
...
}
七、Java操作两表关系
四种:一对多、多对一、双向一对一、多对多
多表关系处理数据:
- 数据库通过外键建立两表关系
- 实体类通过属性的方式建立两表关系:类名=表名,属性名=列名
1.一对多(老师->学生)
(1)创建数据表
学生表:
老师表:
需求:现实现根据老师id查询该老师师教的所有学生姓名
(2)创建实体类
注:在一方配置一个存储多方数据的集合
/**学生类**/
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;
}
}
/*教师类*/
import java.util.List;
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;
}
}
(3) 定义DAO接口
import bean.Teacher;
public interface TeacherDao {
//定义操作方法
public Teacher getById(int tid);
}
(4)定义实现类
import bean.Student;
import bean.Teacher;
import dao.TeacherDao;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class TeacherDaoImpl implements TeacherDao {
@Override
public Teacher getById(int tid) {
Connection connection = null; //创建连接引用
PreparedStatement preparedStatement = null; //创建预通道引用
ResultSet resultSet = null; //创建结果集引用
try {
//1.加载数据库驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取数据库连接
String user = "root";
String password = "root";
String url = "jdbc:mysql://localhost:3306/mydb6?serverTimezone=UTC";
connection = DriverManager.getConnection(url,user,password);
//3.建立状态通道
preparedStatement = connection.prepareStatement("select * from student " +
"inner join teacher on teacher.tid=student.teacherid where tid=?");
//4.给占位符赋值
preparedStatement.setInt(1,tid);
//5.获取结果集
resultSet = preparedStatement.executeQuery();
//创建教师对象
Teacher teacher = new Teacher();
//创建存储学生对象的集合
List<Student> list = 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"));
student.setTeacherid(resultSet.getInt("teacherid"));
list.add(student);
}
teacher.setStudentList(list);
return teacher;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return null;
}
}
(4)定义测试类
public class text {
public static void main(String[] args) {
//创建实现类
TeacherDao teacherDao = new TeacherDaoImpl();
//查询教师ID是3的老师的所有学生
Teacher teacher = teacherDao.getById(3);
System.out.println(teacher.getTname());
for(Student s :teacher.getStudentList()){
System.out.println(s.getStuname());
}
}
}
输出
王五
aaa
cc
2.多对一(学生->老师)
数据表不变
主要思路:在多方配置一个存储一方数据的对象
实体类
//教师类
import java.util.List;
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;
private Teacher teacher;
public int getStuid() {
return stuid;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
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;
}
}
DAO接口
public interface TeacherDao {
//操作方法二:查找所有学生姓名及其老师姓名
public List<Student> getAll();
}
DAO实现类
import bean.Student;
import bean.Teacher;
import dao.TeacherDao;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class TeacherDaoImpl implements TeacherDao {
@Override
public List<Student> getAll() {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1. 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
String user = "root";
String password = "root";
String url = "jdbc:mysql://localhost:3306/mydb6?serverTimezone=UTC";
connection = DriverManager.getConnection(url, user, password);
//3.创建预状态通道
String sql = "select * from student inner join teacher on teacher.tid=student.teacherid";
preparedStatement = connection.prepareStatement(sql);
//4.执行语句并获取结果集
resultSet = preparedStatement.executeQuery();
//提取数据
List<Student> list = new ArrayList<>();
while(resultSet.next()){
Student student = new Student();
Teacher teacher = new Teacher();
teacher.setTname(resultSet.getString("tname"));
teacher.setTid(resultSet.getInt("tid"));
student.setTeacher(teacher);
student.setStuid(resultSet.getInt("stuid"));
student.setStuname(resultSet.getString("stuname"));
student.setTeacherid(resultSet.getInt("teacherid"));
list.add(student);
}
return list;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return null;
}
}
测试类
public class text {
public static void main(String[] args) {
TeacherDao teacherDao = new TeacherDaoImpl();
List<Student> list = teacherDao.getAll();
for (Student student : list) {
System.out.println(student.getStuname()+":"+student.getTeacher().getTname());
}
}
}
输出
aaa:王五
bb:张三老师
cc:王五
dd:张三老师
ee:张三老师
ff:李四老师
3.一对一
要点:各自储存对方的对象即可
4.多对多(科目<——>学生)
数据表
middle表:
student学生表:
subject科目表:
需求:
(1)查询学生所学的科目
(2)查询科目及对应学员姓名
实体类
//学生类
public class Student {
private int stuid;
private String stuname;
private int teacherid;
private Teacher teacher;
private List<Subject> subjectList;
public List<Subject> getSubjectList() {
return subjectList;
}
public void setSubjectList(List<Subject> subjectList) {
this.subjectList = subjectList;
}
public int getStuid() {
return stuid;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
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;
}
}
//科目类
public class Subject {
private int subId;
private String subName;
private List<Student> studentList;
public int getSubId() {
return subId;
}
public void setSubId(int subId) {
this.subId = subId;
}
public String getSubName() {
return subName;
}
public void setSubName(String subName) {
this.subName = subName;
}
public List<Student> getStudentList() {
return studentList;
}
public void setStudentList(List<Student> studentList) {
this.studentList = studentList;
}
}
Dao接口
public interface SubjectDao {
//功能1查询学生科目
public Student findByStuId(int stuId);
//功能2查询科目对应学员
public Subject findBySubId(int subId);
}
实现类
public class SubjectDaoImpl implements SubjectDao {
@Override
public Student findByStuId(int stuId) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1. 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
String user = "root";
String password = "root";
String url = "jdbc:mysql://localhost:3306/mydb6?serverTimezone=UTC";
connection = DriverManager.getConnection(url, user, password);
//3.创建预状态通道
String sql = "select * from student inner join middle" +
" on middle.stuid=student.stuid inner join subject" +
" on subject.subid=middle.subid where student.stuid=?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,stuId);
//4.执行语句并获取结果集
resultSet = preparedStatement.executeQuery();
//提取数据
Student student = new Student();
List<Subject> list = new ArrayList<>();
while(resultSet.next()){
Subject subject = new Subject();
subject.setSubName(resultSet.getString("subName"));
subject.setSubId(resultSet.getInt("subId"));
list.add(subject);
student.setStuid(resultSet.getInt("stuId"));
student.setStuname(resultSet.getString("stuName"));
student.setTeacherid(resultSet.getInt("teacherId"));
}
student.setSubjectList(list);
return student;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return null;
}
@Override
public Subject findBySubId(int subId) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1. 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
String user = "root";
String password = "root";
String url = "jdbc:mysql://localhost:3306/mydb6?serverTimezone=UTC";
connection = DriverManager.getConnection(url, user, password);
//3.创建预状态通道
String sql = "select * from student inner join middle" +
" on middle.stuid=student.stuid inner join subject" +
" on subject.subid=middle.subid where subject.subid=?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1,subId);
//4.执行语句并获取结果集
resultSet = preparedStatement.executeQuery();
//提取数据
Subject subject = new Subject();
List<Student> list = new ArrayList<>();
while(resultSet.next()){
Student student = new Student();
student.setStuid(resultSet.getInt("stuId"));
student.setStuname(resultSet.getString("stuName"));
student.setTeacherid(resultSet.getInt("teacherId"));
list.add(student);
subject.setSubName(resultSet.getString("subName"));
subject.setSubId(resultSet.getInt("subId"));
}
subject.setStudentList(list);
return subject;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return null;
}
}
测试类
public class text {
public static void main(String[] args) {
SubjectDao subjectDao = new SubjectDaoImpl();
Student student = subjectDao.findByStuId(1);
System.out.println(student.getStuname());
for (Subject subject : student.getSubjectList()) {
System.out.println("\t"+subject.getSubName());
}
Subject subject1 = subjectDao.findBySubId(2);
System.out.println(subject1.getSubName());
for (Student student1 : subject1.getStudentList()) {
System.out.println("\t"+student1.getStuname());
}
}
}
输出
张三
java
ui
h5
c++
ui
张三
李四
王五
赵六
花花
潇潇
八、数据库事务
事务:一组要么同时执行成功,要么同时执行失败的SQL语句。是数据库操作的一个执行单元。
1、事务概述
数据库事务(Dtabase Transaction),是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全不执行。事务处理可以确保除非事务性单元内所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性,事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
事务开始于
- 连接到数据表库上,并执行一条DML语句:INSERT、UPDATE或DELETE
- 前一个事务结束后,又输入了另一条DML语句
事务结束于
- 执行commit或rollback语句。
- 执行一条DDL语句,例如create table语句,在这种情况下,会自动执行commit语句。
- 执行一条DDL语句,例如grant 语句,在这种情况下,会自动执行commit语句。
- 断开与数据库的连接。
- 执行了一条DML语句,该语句却失败了,在这种情况中,会为这个无效的DML语句执行rollback语句。
2、事务的四大特点
ACID
- actomicity(原子性)
表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败 - consistency(一致性)
表示一个事务内有一个操作失败时,所有更改过的数据必须全部回滚到修改前的状态 - isolation(隔离性)
事务查看数据事数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据 - durability(持久性)
持久性事务完成之后,他对于系统的影响是永久的。
3、JDBC中的事务应用
如果JDBC连接处于自动提交模式,默认情况下,则每个SQL语句在完成后都会提交到数据库。
事务使我们能够控制是否和何时更改应用于数据库。它将单个SQL语句或一组SQL语句视为一个逻辑单元,如果任何语句失败,则整个事务将失败。
要启用手动事务支持,而不是JDBC驱动程序默认的自动提交模式,则使用Connection对象的setAutoCommit()方法。如果将boolean false传递给它,则关闭自动提交。我们可以传递一个布尔值true来重新打开它。
4、事务的提交和回滚
完成更改后,我们要提交更改,然后在连接对象上调用commit()方法,如下所示:conn.commit();
否则,要使用连接名为conn的数据库回滚更新,请使用以下代码conn.rollbackIO;
5 、Savepoints
新的JDBC 3.0 Savepoint接口提供了额外的事务控制
设置保存点时,可以在事务中定义逻辑回滚点。如果通过保存点发生错误,则可以使用回滚方法来撤销所有更改或仅保存在保存带你之后所做的更改。
Connection对象有两种新的方法来帮助您管理保存点
- setSavepoint(String savepointName):定义新的保存点,它还返回一个Savepoint对象。
- releaseSavepoint(Savepoint savepointName):删除保存点。请注意,它还需要一个Savepoint对象作为参数。此对象通常是由setSavepoint()方法生成的保存点
6、事务案例-转账
数据表:
uid | yue |
---|---|
1 | 1000 |
2 | 1000 |
现有用户1、用户2 且余额都为1000元,现要在JDBC中实现转账需求(用户1扣钱,那么2必须同步加钱,不会存在钱凭空减少或增多的情况),这时候就不能开启自动提交,因为是两条更新语句,必须两条语句后手动一起提交,下面看代码
public class Demo3 {
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 user = "root";
String password = "root";
String url = "jdbc:mysql://localhost:3306/mydb6?serverTimezone=UTC";
connection = DriverManager.getConnection(url, user, password);
//设置手动提交
connection.setAutoCommit(false);
//3.定义SQL语句并创建预通道
statement = connection.createStatement();
//转账操作:用户1转100元给用户2
statement.executeUpdate("update money set yue=yue-100 where uid=1;");
//System.out.println(1/0);
statement.executeUpdate("update money set yue=yue+100 where uid=2;");
//手动提交
connection.commit();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
//倘若手动提交之前的语句产生异常,那么进入该catch块执行回滚操作
connection.rollback();
}
}
}
结果:
uid | yuan |
---|---|
1 | 900 |
2 | 1100 |
九、JDBC批处理
批量处理允许您将相关的SQL语句分组到批处理中,并通过对数据库的一次调用提交他们。
当我们一次向数据库发送多个SQL语句时,可以减少连接数据库的开销,从而提高性能。
1、Statement批处理
以下是使用通道对象的批处理的典型步骤
- 使用createStatement() 方法创建Statement 对象
- 使用setAutoCommit()方法 设置为手动提交
- 使用addBatch() 方法在创建的语句对象上添加需要的SQL语句到批处理中。
- 在创建的语句对象上使用executeBatch() 方法执行所有的SQL语句。
- 最后,使用commit () 方法提交所有更改。
public class StatementDemo4 {
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 user = "root";
String password = "root";
String url = "jdbc:mysql://localhost:3306/mydb6?serverTimezone=UTC";
connection = DriverManager.getConnection(url, user, password);
//设置手动提交
connection.setAutoCommit(false);
//3.创建状态通道
statement = connection.createStatement();
//定义SQL语句并加入通道批处理
String s1 = "insert into teacher(tname) values('张三a')";
statement.addBatch(s1);
String s2 = "insert into teacher(tname) values('张三b')";
statement.addBatch(s2);
String s3 = "insert into teacher(tname) values('张三c')";
statement.addBatch(s3);
String s4 = "insert into teacher(tname) values('张三d')";
statement.addBatch(s4);
//执行批处理,返回数组,存储每条语句影响的行数
int[] ints = statement.executeBatch();
//手动提交
connection.commit();
//遍历查看一下数组
for (int i : ints) {
System.out.println("影响行数:"+i);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
2、PreparedStatement批处理
- 使用占位符创建SQL语句。
- 使用prepareStatement()方法 创建预状态通道对象
- 使用setAutoCommit() 将auto-commit 设置为false
- 使用addBatch() 方法在创建的语句对象上添加所需要的SQL语句到批处理中。
- 在创建的语句对象上使用executeBatch() 方法执行所有语句。
- 最后手动提交
public class PreparedStatementDemo5 {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement pps = null;
ResultSet resultSet = null;
try {
//1.加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.创建连接
String user = "root";
String password = "root";
String url = "jdbc:mysql://localhost:3306/mydb6?serverTimezone=UTC";
connection = DriverManager.getConnection(url, user, password);
//设置手动提交
connection.setAutoCommit(false);
//3.创建预通道
pps = connection.prepareStatement("insert into teacher(tname) values(?);");
//赋值并加入通道批处理
pps.setString(1,"李四1");
pps.addBatch();
pps.setString(1,"李四2");
pps.addBatch();
pps.setString(1,"李四3");
pps.addBatch();
pps.setString(1,"李四4");
pps.addBatch();
//执行批处理,返回数组,存储每条语句影响的行数
int[] ints = pps.executeBatch();
//手动提交
connection.commit();
//遍历查看一下数组
for (int i : ints) {
System.out.println("影响行数:"+i);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
十、反射处理结果集
接口:
十一:工具类的定义
我们可以把JDBC中频繁使用的操作封装成一个工具类,把每个功能都封装成该类的一个方法,需要的时候,直接调用即可,这将大大简化我们的代码
//JDBC工具类
public class DbUtils {
//定义需要的工具类对象
protected Connection connection = null; //连接对象
protected PreparedStatement pps = null; //预状态通道对象
protected ResultSet resultSet = null; //结果集对象
protected int line = 0; //受影响的的行数
//定义需要配置信息
private static String className = "com.mysql.cj.jdbc.Driver";
private static String userName = "root";
private static String passWord = "root";
private static String url = "jdbc:mysql://localhost:3306/mydb6?serverTimezone=UTC";
//静态代码块加载数据库驱动,也就是说创建工具类对象的时候,自动加载驱动
static{
try {
Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//下面编写所需要的功能方法
//1.获取连接
protected Connection getConnection(){
try {
connection = DriverManager.getConnection(url);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return connection;
}
//2.获取预通道
protected PreparedStatement getPps(String sql){
try {
getConnection();
pps = connection.prepareStatement(sql);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return pps;
}
//3.给占位符赋值,list保存的是给占位符所附的值
protected void setParams(List list){
try {
if(list != null && list.size()>0){
for (int i = 0; i < list.size(); i++) {
pps.setObject(i+1,list.get(i));
}
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//4.增删改调取方法
protected int update(String sql,List params){
try {
getPps(sql); //获取预通道并写入SQL语句
setParams(params); //给SQL语句赋值
//执行SQL语句
line = pps.executeUpdate();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return line;
}
//5.查询方法
protected ResultSet query(String sql,List params){
try {
getPps(sql);
setParams(params);
resultSet = pps.executeQuery();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return resultSet;
}
//6.关闭资源
protected void close(){
try {
if (connection != null) {
connection.close();
}
if (pps != null) {
pps.close();
}
if (resultSet != null) {
resultSet.close();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
properties文件保存数据库信息
特点:key-value存储方式
例:db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb6?serverTimezone=UTC
username=root
password=root
那么怎么读取该文件呢
有两种方式:
方式一:
在工具类静态代码块编写如下代码:
InputStream inputStream = 当前类名.class.getClassLoader()
.getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(inputStream);
driverName = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("user");
password = properties.getProperty("password");
方式二:
在工具类静态代码块编写如下代码:
//参数只写属性文件名即可,不需要写后缀
ResourceBundle bundle = ResourceBundle.getBundle("db");
driver = bundle.getString("driver");
url = bundle.getString("url");
username = bundle.getString("user");
password = bundle.getString("password");
说明:使用ResourceBundle访问本地资源
在设计时,我们往往需要访问一些适合本地修改的配置信息,如果作为静态变量,那么每次修改都需要重新编译一个class, .config文件保存此类信息并不合适,这时我们需要ResourceBundle。
通过ResourceBundle,我们需要访问位于于/WEB-INF/classes目录下的一个后缀名为properties的文本 类型文件,从里面读取我们需要的值。