JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)

目录

1.JDBC简介

2. JDBC体系结构

3 JDBC核心组件(工具类)

4.JDBC使用步骤

5.JDBC连接步骤详解

5.1.导入JDBC包

5.2.注册驱动程序2种方法

5.3.数据库连接地址URL配置和创建数据库连接对象

5.4.JDBC执行SQL语句

5.5.关闭数据库连接

6.JDBC演示CRUD

7.SQL注入问题

8.预状态通道解决SQL注入

 9.对比statement和PreparedStatement 

10.Java操作两表关系

10.1.一对多(老师-学生)

10.2.多对一(学生->老师)

10.3.一对一(妻子丈夫)

10.4.多对多(科目-学生)

11.数据库事务

11.1.事务概述

11.2.事务四大特性

11.3.JDBC中事务应用

11.4.事务的提交和回滚

11.5.Savepoints

11.6.事务案例-转账

12.JDBC批处理

12.1.Statement批处理

12.2.PreparedStatement批处理

13.反射处理(简化)结果集

14.原生JDBC抽取的工具类

15.连接池

15.1.自定义连接池

15.1.1.数据连接池原理

15.1.2.自定义连接池

15.1.3.java规范实现连接池

15.1.4.基于规范实现的连接池

15.1.5.连接池参数

15.2.DBCP连接池

15.3.DBCP与C3P0区别

15.3.C3P0连接池

15.4.Druid(德鲁伊)连接池

15.5.获取连接4种方法


1.JDBC简介

  • JDBCJava DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多 种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可 以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序
  • Java 具有坚固、安全、易于使用、易于理解和可从网络上自动下载等特性,是编写数据库应用程序的杰出语言。所需要的只是 Java应用程序与各种不同数据库之间进行对话的方法。
  • JDBC可以在各种平台上使用Java,如WindowsMac OS和各种版本的UNIX
  • JDBC库包括通常与数据库使用相关的下面提到的每个任务的API
    • - 连接数据库
    • - 创建SQL或MySQL语句
    • - 在数据库中执行SQL或MySQL查询。
    • - 查看和修改生成的记录。

2. JDBC体系结构

  • JDBC API支持用于数据库访问的两层和三层处理模型,但通常,JDBC体系结构由两层处理模型组成
    • - JDBC API这提供了应用程序到JDBC管理器连接。
    • - JDBC驱动程序API这支持JDBC管理器到驱动程序连接。
  • JDBC API使用驱动程序管理器和特定于数据库的驱动程序来提供与异构数据库的透明连接。

JDBC在整个项目中是如何操作的?执行流程

  • 首先最上面是Java代码,通过代码可以去调用JDBC的一些API,
  • 这些API包含了JDBC Driver Manager驱动管理器,
  • JDBC驱动管理器有好几种JDBC驱动程序,根据不同数据库注册不同的JDBC驱动程序来打开对应的数据库通信通道。
  • 每一个数据库都有对应的JDBC Driver(JDBC驱动),这些Driver由JDBC Driver Manager统一管理。
  • 总之:Java代码调用JDBC API,JDBC API底层通过Driver Manager管理JDBC中每一个Driver驱动,Driver进行具体的数据库连接。

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第1张图片

3 JDBC核心组件(工具类)

DriverManager:

  • 此类管理数据库驱动程序列表。使用通信子协议将来自java应用程序的连接请求 与适当的数据库驱动程序匹配
Driver:
  • 此接口处理与数据库服务器的通信,我们很少会直接与Driver对象进行交互。而是使用 DriverManager对象来管理这种类型的对象。
Connection:
  • 该界面具有用于联系数据库的所有方法。连接对象表示通信上下文,即,与数据库 的所有通信仅通过连接对象。
Statement
  • 使用从此接口创建的对象将SQL语句提交到数据库。除了执行存储过程之外,一些派 生接口还接受参数。
ResultSet:
  • 在使用Statement对象执行SQL查询后,这些对象保存从数据库检索的数据。它作为一 个迭代器,允许我们移动其数据。
SQLException:
  • 此类处理数据库应用程序中发生的任何错误

4.JDBC使用步骤

构建JDBC应用程序涉及以下六个步骤:
  • 1.导入包:导入JDBC API。大多数情况,使用import java.sql.*就足够 了。
  • 2.注册JDBC驱动程序:需要使用Class.forName(""),由jvm将驱动程序以字节码形式加载到内存并自动注册打开与指定数据库的通信通道
  • 3.打开连接:需要使用DriverManager.getConnection(方法创建一个Connection对象,该对象表示与数据库的物理连接即连接指定的数据库(前提数据库服务器要打开)
  • 4.执行查询:创建状态通道Statement构建和提交SQL语句到数据库
  • 5.从结果集中提取数据:需要使用相应的ResultSet.getXXX()方法从结果集中检索数据
  • 6. 释放资源:需要明确地关闭所有数据库资源,而不依赖于JVM的垃圾收集。

5.JDBC连接步骤详解

建立JDBC连接所涉及的编程相当简单。这是简单的四个步骤
  • 导入JDBC包:Java语言的*import*语句添加到Java代码中导入所需的类。
  • 注册JDBC驱动程序:JVM将所需的驱动程序加载到内存中,以便它可以满足您的JDBC 请求。
  • 数据库URL配置:这是为了创建一个格式正确的地址,指向要连接到的数据库
  • 创建连接对象:调用DriverManager对象的getConnection()方法来建立实际的数据库连 接。

5.1.导入JDBC包

5.2.注册驱动程序2种方法

方法一: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 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);
}

5.3.数据库连接地址URL配置和创建数据库连接对象

1.DriverManager.getConnection()

  • 加载驱动程序,打开java程序与数据之间通信通道后,需要连接指定的数据库,可以使用DriverManager.getConnection()方法建立连接。为了方便参考,列出三个重载的方法
  • - getConnectionString url
  • - getConnectionString urlProperties prop
  • - getConnectionString urlString userString password

2.不同的数据库对应的JDBC驱动程序和连接地址url都不同

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第2张图片

完整的连接地址:
jdbc:mysql://localhost:3306/数据库名? useSSL=false&useUnicode=true&characterEncoding=UTF-8

3.创建数据库连接对象

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第3张图片
4.使用数据库URL和属性对象创建连接对象

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第4张图片

5.4.JDBC执行SQL语句

  • 一旦获得了连接,我们可以与数据库进行交互。JDBC StatementPreparedStatement接口定义了能够发送SQL命令并从数据库接收数据的方法和属性
  • 1.在使用Statement对象执行SQL语句之前,需要使用Connection对象的createStatement()方法创建Statement对象
JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第5张图片
  • 2.创建Statement对象后,可以使用它来执行一个SQL语句,其中有三个执行方法之一。
JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第6张图片
  • 3.关闭Statement对象

5.5.关闭数据库连接

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

6.JDBC演示CRUD

  • 1.启动mysql服务器 net -start mysql;
  • 2.准备一张表
CREATE TABLE `subjecttable` (
  `subjectid` int NOT NULL,
  `subject_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `subject_time` int DEFAULT NULL,
  `gradeid` int DEFAULT NULL,
  PRIMARY KEY (`subjectid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第7张图片

  • 3.在IDEA新建项目中,新建lib文件,将JDBC API(mysql-connector-java-8.0.16.jar)导入到项目中。
  • 4.开始编写程序
package demo01;
import java.sql.*;

/**
 * JDBC操作步骤
 */
public class Demo1 {
    public static void main(String[] args) throws SQLException {
        Connection conn=null;
        Statement statement=null;
        ResultSet resultSet=null;
        try{
            //0.导入JDBC API(jdbc jar包),程序可以调用这些API
            //1.注册JDBC驱动程序--打开程序与数据库的通信通道
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取连接--即通过连接地址url,用户名和密码连接上数据库student
            String userName="root";
            String passWord="你自己密码";
            String url="jdbc:mysql://localhost:3306/student?serverTimezone=UTC";
            conn = DriverManager.getConnection(url,userName,passWord);
            //3.执行sql语句--创建状态通道Statement,执行sql语句的发送
            statement = conn.createStatement();

            System.out.println("JDBC查询操作:");
            //4.executeQuery(sql)执行查询的时候使用,返回结果集
            resultSet = statement.executeQuery("select * from subjecttable");
            //5.获取结果集信息
            while (resultSet.next()){
                //取出数据:resultSet.getXXX("列名"); XXX表述列名的类型
                System.out.println("科目编号:"+resultSet.getInt("subjectid")
                        +",科目名称:"+resultSet.getString("subject_name")
                        +",科目学时:"+resultSet.getInt("subject_time")
                        +",科目阶段:"+resultSet.getInt("gradeid")
                );
            }

            System.out.println("JDBC插入操作");
            //5.executeUpdate(sql)执行增删改的时候使用,返回受影响的行数
            int result = statement.executeUpdate("insert into subjecttable values(5,'Java',100,3)");
            if (result>0){
                System.out.println("插入操作执行成功");
            }else {
                System.out.println("插入操作执行失败");
            }

            System.out.println("JDBC修改操作:");
            int result1 = statement.executeUpdate("update subjecttable set subject_time=5000");
            if (result>0){
                System.out.println("执行成功,受影响的行数:"+result);
            }else {
                System.out.println("执行失败");
            }
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        }catch (SQLException throwables){
            throwables.printStackTrace();
        }finally {
            //5.关闭资源,需要判空
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (conn != null) {
                conn.close();
            }
        }
    }
}

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第8张图片

7.SQL注入问题

  • 通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎 执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据 库,而不是按照设计者意图去执行SQL语句。比如先前的很多影视网站泄露VIP会员密码大多就是通过 WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击。
String userid = "aaa";
String pass = " 'abc' or 1=1 ";
resultSet = statement.executeQuery(
       "select * from login where userid='" + userid + "' and pass=" + pass
);
if (resultSet.next()) {
        System.out.println("登录成功");
} else {
        System.out.println("登录失败");
 }

用户名密码都不对,也能登录上。

8.预状态通道解决SQL注入

如何解决的:

  • 预状态通道在处理值的时候以字符串的方式处理,使用占位符,可以动态地提供参数

演示:

            //0.导入JDBC API(jdbc jar包),程序可以调用这些API
            //1.注册JDBC驱动程序--打开程序与数据库的通信通道
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取连接--即通过连接地址url,用户名和密码连接上数据库student
            String userName = "root";
            String passWord = "你自己密码";
            String url = "jdbc:mysql://localhost:3306/student?serverTimezone=UTC";
            conn = DriverManager.getConnection(url, userName, passWord);
            //定义sql
            String sql="select * from login where userid= ? and pass=? ";
            //3.执行sql语句--创建预状态通道PreparedStatement,执行sql语句的发送
            pps = conn.prepareStatement(sql);
            //给占位符设置
            String userid = "aaa";
            String pass = " '' or 1=1 ";
            pps.setString(1,userid);
            pps.setString(2,pass);
            resultSet = pps.executeQuery();
            if (resultSet.next()) {
                System.out.println("登录成功");
            } else {
                System.out.println("登录失败");
            }

说明

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

 9.对比statement和PreparedStatement 

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第9张图片

10.Java操作两表关系

  • 四种:双向一对一,一对多,多对一,多对多
  • 多表关系处理数据
    • (1) 数据库通过外键建立两表关系
    • (2) 实体类通过属性的方式建立两表关系
    • 实体类要求:类名=表名,列名=属性名

10.1.一对多(老师-学生)

先启动mysql服务器

(老师角度)

  • 需求通过指定老师可查询其下学生信息。
  • 实现:老师和学生是一对多关系,实体类中在一方中定义集合属性,存储多方的信息(即在老师中定义一个学生集合),达到两表关联效果。
(1)创建数据表
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', '王五');
JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第10张图片
(2)创建实体类
  • 实体类中在一方(老师)定义集合属性,存储多方(学生)的信息,达到两表关联效果。
package bean;
/**
 * student表对应的java实体类
 * 老师-学生 一对多
 */
public class Student {
    private int stuid;
    private String sname;
    //外键列一般不生成属性
    private int teacherid;//在多方设置外键指向一方的主键

    public int getStuid() { return stuid; }
    public void setStuid(int stuid) { this.stuid = stuid;  }
    public String getSname() { return sname;  }
    public void setSname(String sname) { this.sname = sname; }
    public int getTeacherid() { return teacherid; }
    public void setTeacherid(int teacherid) { this.teacherid = teacherid; }
}
package bean;

import java.util.ArrayList;

/**
 * teacher表对应的实体类
 * teacher-student 一对多关系
 */
public class Teacher {
    private int tid;
    private String tname;
    //在一方定义集合属性,存储多方的信息,达到两表关联效果
    ArrayListlist =new ArrayList<>();

    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 ArrayList getList() { return list;  }
    public void setList(ArrayList list) {  this.list = list;  }
}
(3)定义dao接口--java操作数据库在dao层完成
package dao;
import bean.Teacher;

public interface TeacherDao {
    //定义操作方法
    //1.定义一个根据老师id查询老师信息(包括对应学生信息)只需要返回老师对象,它包含学生集合
    public Teacher getById(int tid);
}
(4)定义dao实现类--连接并操作数据库
package dao.impl;

import bean.Student;
import bean.Teacher;
import com.mysql.cj.jdbc.Driver;
import dao.TeacherDao;

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

public class TeacherDaoImpl implements TeacherDao {
    @Override
    public Teacher getById(int tid) {
        //先通过JDBC连接上数据库
        Connection conn = null;
        PreparedStatement pps = null;
        ResultSet resultSet = null;
        try {
            //注册JDBC驱动程序-打开通信通道
            Class.forName("com.mysql.cj.jdbc.Driver");
            //获取连接-连接指定数据库
            String userName = "root";
            String passWord = "你自己密码";
            String url = "jdbc:mysql://localhost:3306/student1?serverTimezone=UTC";
            conn = DriverManager.getConnection(url, userName, passWord);
            //定义sql,创建预状态通道,进行sql语句的发送
            String sql = "select * from student s,teacher t where s.teacherid=t.tid and t.tid=?";
            pps = conn.prepareStatement(sql);
            //占位符赋值
            pps.setInt(1, tid);
            //执行sql,返回查询结果集
            resultSet = pps.executeQuery();
            Teacher teacher = new Teacher();//创建对象,存储tid对应的老师信息,老师只有一个,只创建一次
            ArrayList 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.setSname(resultSet.getString("stuname"));
                //将学生存储到集合中
                students.add(student);
            }
            teacher.setList(students);//建立学生和老师之间关系,将学生存储到老师的list集合中
            return teacher;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            //释放资源
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (pps != null) {
                try {
                    pps.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
        return null;
    }
}
(4)定义测试类
package test;

import bean.Student;
import bean.Teacher;
import dao.impl.TeacherDaoImpl;

import java.util.ArrayList;
/**
 * java操作数据库测试
 * 老师-学生 一对多关系
 * 查询指定老师信息及该老师带的学生信息
 */
public class Demo1 {
    public static void main(String[] args) {
        TeacherDaoImpl dao = new TeacherDaoImpl();
        Teacher teacher = dao.getById(1);
        System.out.println("老师姓名:"+teacher.getTname());
        ArrayList studentList = teacher.getList();//获取该老师的学生
        for (Student stu : studentList) {
            System.out.println("\t 学生姓名:"+stu.getSname());
        }
    }
}

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第11张图片

10.2.多对一(学生->老师)

(学生角度)

  • 需求:查询所有的学生(包含老师的信息)
  • 实现: 学生与老师是多对一关系,在多方创建一个存储一方数据的对象,即在学生类中定义一个老师属性。将老师存入学生中。
数据表同上
实体类:
package bean;
/**
 * student表对应的java实体类
 * 学生-老师 多对一
 * 站在学生角度:在多方创建一个存储一方数据的对象,即在学生类中定义一个老师属性。
 */
public class Student2 {
    private int stuid;
    private String sname;
    private Teacher2 teacher;

    public int getStuid() { return stuid; }
    public void setStuid(int stuid) {  this.stuid = stuid; }
    public String getSname() { return sname; }
    public void setSname(String sname) {  this.sname = sname; }
    public Teacher2 getTeacher() { return teacher; }
    public void setTeacher(Teacher2 teacher) {  this.teacher = teacher; }
}
package bean;

import java.util.ArrayList;

/**
 * teacher表对应的实体类
 * 学生-老师 多对一
 */
public class Teacher2 {
    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; }
}
学生接口:
package dao;

import bean.Student2;

import java.util.ArrayList;

public interface StudentDao {
    //查询所有学生信息包括对应老师信息
    public ArrayList getAll();
}

学生接口实现类:

package dao.impl;

import bean.Student2;
import bean.Teacher2;
import dao.StudentDao;

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

public class StudentDaoImpl implements StudentDao {
    //查询所有学生信息包括对应老师信息(将老师信息放入到学生中)
    @Override
    public ArrayList getAll() {
        Connection conn = null;
        PreparedStatement pps = null;
        ResultSet resultSet = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");//加载驱动
            String userid = "root";
            String pass = "你自己密码";
            String url = "jdbc:mysql://localhost:3306/student1?serverTimezone=UTC";
            conn = DriverManager.getConnection(url, userid, pass);//获取连接
            String sql = "select * from student s,teacher t where s.teacherid=t.tid";//定义sql
            pps = conn.prepareStatement(sql);//创建预状态通道,提交sql
            resultSet = pps.executeQuery();//返回结果集
            ArrayList students = new ArrayList<>();//存储学生的集合,每个学生中包含一位老师
            while (resultSet.next()) { //一行行读取数据
                Student2 student2 = new Student2();//将每一行中学生信息存入一个学生对象
                student2.setStuid(resultSet.getInt("stuid"));
                student2.setSname(resultSet.getString("stuname"));

                Teacher2 teacher2 = new Teacher2();//每一行中老师信息存入一个老师对象
                teacher2.setTid(resultSet.getInt("teacherid"));
                teacher2.setTname(resultSet.getString("tname"));
                //建立学生和老师之间关系:即将老师存入学生中
                student2.setTeacher(teacher2);
                students.add(student2);//学生放入集合中
            }
            return students;//返回学生集合(里面包含了老师)
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            //释放资源
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (pps != null) {
                try {
                    pps.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
        return null;
    }
}

测试

package test;

import bean.Student2;
import dao.impl.StudentDaoImpl;

import java.util.ArrayList;

/**
 * java操作数据库测试
 *  * 学生-老师 多对一关系
 *  * 查询所有的学生(包含老师的信息
 */
public class Demo2 {
    public static void main(String[] args) {
        StudentDaoImpl dao = new StudentDaoImpl();//操作数据库
        ArrayList students = dao.getAll();
        for (Student2 stu:students){
            System.out.println(stu.getSname()+","+stu.getTeacher().getTname());
        }
    }
}

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第12张图片

10.3.一对一(妻子丈夫)

需求:

  • 查询妻子信息(包含丈夫信息)
  • 查询丈夫信息(包含妻子信息)

实现:

  • 数据库中一对一关系的两张表可以设置外键维系两张表(通常一对一可以合成一张表)
  • java程序中两个实体类中分别包含彼此信息,即妻子实体类中有丈夫做成员变量,反之。

数据表:

CREATE TABLE `wife` (
  `wifeid` int NOT NULL AUTO_INCREMENT,
  `wifename` varchar(255) DEFAULT NULL,
  `hid` int DEFAULT NULL,
  PRIMARY KEY (`wifeid`),
  UNIQUE KEY `uq_wife_hid` (`hid`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `wife` VALUES ('1', '孙俪', '1');

CREATE TABLE `husband` (
  `husid` int NOT NULL AUTO_INCREMENT,
  `husname` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`husid`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `husband` VALUES ('1', '邓超');

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第13张图片

实体类:
package bean;

/**
 * 妻子-丈夫  双向一对一关系
 * 双方实体类各保存对方的信息,达到双向一对一效果
 */
public class Wife {
    //Wife实体类需要的属性(包含Husband)
    private int wifeId;
    private String wifeName;
    private Husband husband;

    public int getWifeId() { return wifeId; }
    public void setWifeId(int wifeId) {  this.wifeId = wifeId; }
    public String getWifeName() { return wifeName; }
    public void setWifeName(String wifeName) {  this.wifeName = wifeName;  }
    public Husband getHusband() {  return husband; }
    public void setHusband(Husband husband) { this.husband = husband; }
}
package bean;

/**
 * 丈夫-妻子  双向一对一关系
 * 双方实体类各保存对方的信息,达到双向一对一效果
 */
public class Husband {
    private int husid;
    private String husName;
    private Wife wife;

    public int getHusid() { return husid; }
    public void setHusid(int husid) { this.husid = husid; }
    public String getHusName() {  return husName; }
    public void setHusName(String husName) {  this.husName = husName;}
    public Wife getWife() {  return wife; }
    public void setWife(Wife wife) {   this.wife = wife; }
}

接口:这里就写一个接口,妻子丈夫方法放在一起

package dao;

import bean.Husband;
import bean.Wife;

public interface wifeDao {
    /**
     * 查询妻子信息(要求包含丈夫信息)
     * @param wid
     * @return
     */
    public Wife getWife(int wid);

    /**
     * 查询丈夫信息(要求包含妻子信息)
     * @param hid
     * @return
     */
    public Husband getHusband(int hid);
}

接口实现类:

package dao.impl;

import bean.Husband;
import bean.Wife;
import dao.wifeDao;

import java.sql.*;

public class WifeDaoImpl implements wifeDao {
    static Connection conn = null;
    static PreparedStatement pps = null;
    static ResultSet resultSet = null;

    /**
     * 查询妻子信息(要求包含丈夫信息)--即将丈夫对象作为妻子对象的成员变量
     * @param wid
     * @return
     */
    @Override
    public Wife getWife(int wid) {
        String sql="select * from wife w,husband h where w.hid=h.husid and w.wifeid=?";//定义sql
        try{
            conn = WifeDaoImpl.getConnection();//通过封装的静态方法获取连接
            pps = conn.prepareStatement(sql);//创建预状态通道,提交sql
            pps.setInt(1,wid); //传递的参数作为占位符值-要查找的wife的id
            resultSet = pps.executeQuery();//执行sql,获取到要查询的结果
            Wife wife = new Wife();//将查询到的结果存入java对象,将该对象放在外面是因为它只有一个并且要被返回
            while(resultSet.next()){ //只有一条记录
                wife.setWifeId(resultSet.getInt("wifeid"));
                wife.setWifeName(resultSet.getString("wifename"));

                Husband husband = new Husband();
                husband.setHusid(resultSet.getInt("husid"));
                husband.setHusName(resultSet.getString("husname"));

                //建立妻子与丈夫关系:将丈夫对象放入妻子对象中
                wife.setHusband(husband);
            }
            return wife;//返回查询结果
        }catch (SQLException throwables){
            throwables.printStackTrace();
        }finally {
            WifeDaoImpl.close(resultSet,pps,conn);
        }
        return null;//若出现异常,返回Null
    }

    /**
     * 查询丈夫信息(要求包含妻子信息):
     * @param hid
     * @return
     */
    @Override
    public Husband getHusband(int hid) {
        String sql="select * from wife w, husband h where w.hid=h.husid and h.husid=? ";//定义sql
        try{
            conn = WifeDaoImpl.getConnection();//获取连接
            pps = conn.prepareStatement(sql);//预状态通道,提交sql到数据库
            pps.setInt(1,hid);//占位符赋值
            resultSet = pps.executeQuery();//执行sql,获取查询结果集(这里只有一条记录)
            Husband husband = new Husband();//将查询结果存入该对象并返回
            while(resultSet.next()){
                husband.setHusid(resultSet.getInt("husid"));
                husband.setHusName(resultSet.getString("husname"));

                Wife wife = new Wife();
                wife.setWifeId(resultSet.getInt("wifeid"));
                wife.setWifeName(resultSet.getString("wifename"));

                //建立丈夫与妻子一对一关系  即将妻子对象放入丈夫对象中,返回丈夫对象
                husband.setWife(wife);
                return husband; //没有异常情况下返回
            }
        }catch (SQLException throwables){
            throwables.printStackTrace();
        }finally {
            WifeDaoImpl.close(resultSet,pps,conn);
        }
        return null;
    }

    /**
     * 抽取 获取连接conn
     * @return
     */
    private static Connection getConnection(){
        try {
            //注册JDBC驱动程序-打开通信通道
            Class.forName("com.mysql.cj.jdbc.Driver");
            //获取连接-连接指定数据库
            String userName = "root";
            String passWord = "你自己密码";
            String url = "jdbc:mysql://localhost:3306/student1?serverTimezone=UTC";
            conn = DriverManager.getConnection(url, userName, passWord);
            return conn;
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        }catch (SQLException throwables){
            throwables.printStackTrace();
        }
        return null;
    }

    /**
     * 抽取关闭资源
     * @param resultSet
     * @param pps
     * @param conn
     */
    private static void close(ResultSet resultSet,PreparedStatement pps,Connection conn){
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (pps!=null){
            try {
                pps.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (conn!=null){
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

测试类

package test;

import bean.Husband;
import bean.Wife;
import dao.impl.WifeDaoImpl;

/**
 * java操作数据库
 * 妻子-丈夫 双向一对一关系
 */
public class Demo3 {
    public static void main(String[] args) {
        WifeDaoImpl wifeDao = new WifeDaoImpl();

        Wife wife = wifeDao.getWife(1);//dao查询1号妻子信息(包含其丈夫信息)
        System.out.println(wife.getWifeName()+"的丈夫是:"+wife.getHusband().getHusName());

        Husband husband = wifeDao.getHusband(1);//dao查询1号丈夫信息(包含其妻子信息)
        System.out.println(husband.getHusName()+"的妻子是:"+husband.getWife().getWifeName());
    }
}

10.4.多对多(科目-学生)

需求:

  • 查询某个学生信息(查询出所学科目)
  • 查询某个科目以及对应的学生姓名

实现:

  • 数据库中表的多对多关系由第三张表维系。第三张表中两个字段作为外键分别指向另外两张表的主键。
  • java程序中多对多关系的维系是:两个实体类各自定义一个集合存储对方信息。一个学生有多个科目,则在学生实体类中定义集合存储多个科目信息,一个科目有多个学生,则在科目实体类中定义集合存储多个学生信息。

数据表

CREATE TABLE `middle` (
    `middleid` int(11) NOT NULL AUTO_INCREMENT,
    `stuid` int(11) DEFAULT NULL,
    `subid` int(11) DEFAULT NULL,
    PRIMARY KEY (`middleid`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of middle
-- ----------------------------
INSERT INTO `middle` VALUES ('1', '1', '1');
INSERT INTO `middle` VALUES ('2', '1', '2');
INSERT INTO `middle` VALUES ('3', '1', '3');
INSERT INTO `middle` VALUES ('4', '1', '5');
INSERT INTO `middle` VALUES ('5', '2', '2');
INSERT INTO `middle` VALUES ('6', '3', '2');
INSERT INTO `middle` VALUES ('7', '4', '2');
INSERT INTO `middle` VALUES ('8', '5', '2');
INSERT INTO `middle` VALUES ('9', '6', '2');
-- ----------------------------
-- Table structure for `student`
-- ----------------------------
DROP TABLE IF EXISTS `student`;
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;
-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES ('1', '张三', '3');
INSERT INTO `student` VALUES ('2', '李四', '1');
INSERT INTO `student` VALUES ('3', '王五', '3');
INSERT INTO `student` VALUES ('4', '赵六', '1');
INSERT INTO `student` VALUES ('5', '花花', '1');
INSERT INTO `student` VALUES ('6', '潇潇', '2');
-- ----------------------------
-- Table structure for `subject`
-- ----------------------------
DROP TABLE IF EXISTS `subject`;
CREATE TABLE `subject` (
    `subid` int(11) NOT NULL AUTO_INCREMENT,
    `subname` varchar(255) DEFAULT NULL,
    PRIMARY KEY (`subid`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of subject
-- ----------------------------
INSERT INTO `subject` VALUES ('1', 'java');
INSERT INTO `subject` VALUES ('2', 'ui');
INSERT INTO `subject` VALUES ('3', 'h5');
INSERT INTO `subject` VALUES ('4', 'c');
INSERT INTO `subject` VALUES ('5', 'c++');
INSERT INTO `subject` VALUES ('6', 'c#');

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第14张图片

实体类:

package bean;

import java.util.List;

public class Subject3 {
    private int subid;
    private String subname;
    private List stulist;//定义list集合存放学习这个科目的学生

    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 getStulist() { return stulist; }
    public void setStulist(List stulist) {  this.stulist = stulist;  }
}
package bean;

import java.util.List;
public class Student3 {
    private int stuid;
    private String stuname;
    //外键列一般不生成属性
    //private int teacherid;
    private Teacher teacher;//定义这个学生对应的老师
    private Listsubjects;//定义集合存放一个学生对应的多个科目

    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 Teacher getTeacher() { return teacher; }
    public void setTeacher(Teacher teacher) {  this.teacher = teacher; }
    public List getSubjects() {  return subjects; }
    public void setSubjects(List subjects) { this.subjects = subjects;  }
}

接口:没有分开定义

package dao;
import bean.Student3;
import bean.Subject3;

public interface Subject3Dao {
    /**
     * 查询某个学生信息(包含其所学科目)
     * 返回学生对象,学生中的subjects集合存放该学生的科目
     * @param id
     * @return
     */
    public Student3 findById(int id);

    /**
     * 查询某个科目以及对应的学生姓名
     * 返回科目对象,科目中定义了stulist集合存放该科目对应的学生
     * @param id
     * @return
     */
    public Subject3 findBySubId(int id);
}

接口实现类:

package dao.impl;

import bean.Student3;
import bean.Subject3;
import dao.Subject3Dao;

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

/**
 * dao层操作数据--先来连接数据库-执行sql获取结果集-存入java对象--返回该对象
 */
public class Subject3DaoImpl implements Subject3Dao {
    static Connection conn=null;
    static PreparedStatement pps=null;
    static ResultSet resultSet=null;
    /**
     * 查询某个学生信息(包括查询出所学科目)
     * @param id
     * @return
     */
    @Override
    public Student3 findById(int id) {
       try {
           //获取连接
           conn = Subject3DaoImpl.getConnection();
           //定义sql
           String sql="select * from student st,subject su,middle m where st.stuid=m.stuid and m.subid=su.subid and st.stuid=?";
           //创建预状态通道,提交sql
           pps = conn.prepareStatement(sql);
           //占位符赋值
           pps.setInt(1,id);
           //执行sql
           resultSet = pps.executeQuery();
           //因为要返回学生,且查询的学生只有一个,不要在循环中创建对象,学科就需要,一个学生有多个科目
           Student3 stu3 = new Student3();
           ArrayList subjects = new ArrayList<>();//集合存放科目对象
           while(resultSet.next()){//便利每一条记录,将信息分别存入对应的对象
               stu3.setStuid(resultSet.getInt("stuid"));
               stu3.setStuname(resultSet.getString("stuname"));

               //将科目相关信息存入到科目对象中
               Subject3 sub3 = new Subject3();
               sub3.setSubid(resultSet.getInt("subid"));
               sub3.setSubname(resultSet.getString("subname"));

               //将科目对象装入到集合中
               subjects.add(sub3);
           }
           //建立学生与科目之间的关系--将科目集合赋值到学生的集合属性中
           stu3.setSubjects(subjects);
           return stu3;
       }catch (SQLException throwables){
           throwables.printStackTrace();
       }finally {
           Subject3DaoImpl.chose(resultSet,conn,pps);
       }
        return null;//上述异常,输出null
    }

    /**
     * 查询某个科目以及对应的学生姓名
     * @param id
     * @return
     */
    @Override
    public Subject3 findBySubId(int id) {
        try{
            conn = Subject3DaoImpl.getConnection();
            String sql="select * from subject su,middle m,student st where su.subid=m.subid and m.stuid=st.stuid and su.subid=?";
            pps = conn.prepareStatement(sql);
            pps.setInt(1,id);
            resultSet = pps.executeQuery();
            Subject3 subject3 = new Subject3();//查询科目只有一个,且要返回,不放入循环
            ArrayList students = new ArrayList<>();//创建集合,存放该科目对应的所有学生
            while(resultSet.next()){
                subject3.setSubid(resultSet.getInt("subid"));//将科目信息存入对象
                subject3.setSubname(resultSet.getString("subname"));

                Student3 student3 = new Student3();//将学生信息存入对象
                student3.setStuid(resultSet.getInt("stuid"));
                student3.setStuname(resultSet.getString("stuname"));
                students.add(student3);//学生装入集合
            }
            subject3.setStulist(students);//将学生集合存入科目中
            return subject3;
        }catch (SQLException throwables){
            throwables.printStackTrace();
        }finally {
            Subject3DaoImpl.chose(resultSet,conn,pps);
        }
        return null;
    }

    /**
     * 抽取 获取连接的方法
     * @return
     */
    private static Connection getConnection(){
        try{
            //加载驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            //获取连接
            String userName="root";
            String passWord="你自己密码";
            String url="jdbc:mysql://localhost:3306/student_subject?serverTimezone=UTC";
            conn = DriverManager.getConnection(url, userName, passWord);
            return conn;
        }catch (ClassNotFoundException e){
            e.printStackTrace();
        } catch (SQLException throwables){
            throwables.printStackTrace();
        }
        return null;//出现异常,返回null
    }

    private static void chose(ResultSet resultSet,Connection conn,PreparedStatement pps)  {
      .......
    }
}

测试类

package test;

import bean.Student3;
import bean.Subject3;
import dao.impl.Subject3DaoImpl;

import java.util.List;

/**
 * 多对多(科目-学生)
 */
public class Demo4 {
    public static void main(String[] args) {
        Subject3DaoImpl dao = new Subject3DaoImpl();
        //查询某个学生信息(查询出所学科目)
        Student3 stu = dao.findById(5);
        System.out.println("姓名:"+stu.getStuname());
        List subjects = stu.getSubjects();
        for (Subject3 sub:subjects) {
            System.out.println("\t"+sub.getSubname());
        }
        ///查询某个科目以及对应的学生姓名
        Subject3 sub = dao.findBySubId(1);
        System.out.println("所查科目:"+sub.getSubname());
        List stulist = sub.getStulist();
        for (Student3 student3:stulist){
            System.out.println("\t"+student3.getStuname());
        }
    }
}

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第15张图片

11.数据库事务

11.1.事务概述

  • 是数据库操作的一个执行单元,管理一组的SQL语句,要么同时执行成功,要么同时执行失败。一个逻辑工作单元要成为事务,必须满足所谓的ACID

事务开始于

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

事务结束于

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

11.2.事务四大特性

1. 原⼦性(Atomicity)

  • 表示一个事务内的所有操作是一个整体,要么全部成功,要么全部失败

2. ⼀致性(Consistency)

  • 表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改前状态
  • 比如:如果从A账户转账到B账户,不可能因为A账户扣了钱,⽽B账户没有加钱。

3. 隔离性(Isolation)

  • 事务的隔离性是指在并发环境中,并发的事务是互相隔离的。也就是说,不同的事务并发操作相同的数 据时,每个事务都有各自完整的数据空间。比如某个时间点有100人转账,这些数据不会出现交错现象。
  • 事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。

  • ⼀个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务是不能互相干扰的。 隔离性分4个级别,下面会介绍。

4. 持久性(Duration)

  • 事务的持久性是指事务⼀旦提交后,数据库中的数据被永久的保存下来。即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动,那么一定能够将其恢复到事务成功结束后的状态

11.3.JDBC中事务应用

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

11.4.事务的提交和回滚

  • 完成更改后,我们要提交更改,然后在连接对象上调用commit()方法
conn.commit( );
  • 否则,要使用连接名为conn的数据库回滚更新
conn.rollback( );
try{
    //Assume a valid connection object conn
    conn.setAutoCommit(false);
    Statement stmt = conn.createStatement();
    String SQL = "INSERT INTO Employees values (106, 20, 'Rita', 'Tez')";
    stmt.executeUpdate(SQL);
    //Submit a malformed SQL statement that breaks
    String SQL = "INSERTED IN Employees VALUES (107, 22, 'Sita', 'Singh')";
    stmt.executeUpdate(SQL);
    // If there is no error.
    conn.commit();
}catch(SQLException se){
    // If there is any error.
    conn.rollback();
}

11.5.Savepoints

  • 新的JDBC 3.0 Savepoint接口提供了额外的事务控制。
  • 设置保存点时,可以在事务中定义逻辑回滚点。如果通过保存点发生错误,则可以使用回滚方法来撤消 所有更改或仅保存在保存点之后所做的更改。
  • Connection对象有两种新的方法来帮助您管理保存点 
 setSavepoint(String savepointName):定义新的保存点。它还返回一个Savepoint对象。
 releaseSavepoint(Savepoint savepointName):删除保存点。它需要一个Savepoint 对象作为参数。此对象通常是由setSavepoint()方法生成的保存点。
try{
    //Assume a valid connection object conn
    conn.setAutoCommit(false);//开启手动提交模式
    Statement stmt = conn.createStatement();
    Savepoint savepoint1 = conn.setSavepoint("Savepoint1");//设置回滚保存点
    String SQL = "INSERT INTO Employees VALUES (106, 20, 'Rita', 'Tez')";
    stmt.executeUpdate(SQL);
    String SQL = "INSERTED IN Employees VALUES (107, 22, 'Sita', 'Tez')";
    stmt.executeUpdate(SQL);
    conn.commit();  //提交
}catch(SQLException se){
    conn.rollback(savepoint1);  //出现异常,则回滚到保存点
}
  • 如果所有语句执行成功,则提交事务 commit();如果出现了错误,则回滚:rollback()
  • 以上就是java代码利用jdbc操作数据库的最简单版本,数据库事务通常要借助补捉异常语句

11.6.事务案例-转账

转账流程:

  • 用户A转出1000元,
  • 用户B增加1000元
  • 需要开启手动提交事务模式,这样才可以在一个事务中执行多条sql语句并且保证这样业务要么全部执行要么全部不执行,自动提交模式下,每一条sql语句都会自动提交事务。
 public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pps = null;
        Savepoint sp = null;
        try {
            //0.导入JDBC API(jdbc jar包),程序可以调用这些API
            //1.注册JDBC驱动程序--打开程序与数据库的通信通道
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取连接--即通过连接地址url,用户名和密码连接上数据库student
            String userName = "root";
            String passWord = "你自己密码";
            String url = "jdbc:mysql://localhost:3306/emp_dept_sala?serverTimezone=UTC";
            conn = DriverManager.getConnection(url, userName, passWord);
            conn.setAutoCommit(false);//开启 手动提交事务 模式,执行一组sql语句
            sp = conn.setSavepoint("回滚保存点1");//设置回滚保存点
            //3.执行sql语句--创建预状态通道preparedStatement,执行sql语句的发送
            pps = conn.prepareStatement("update money set usermoney=usermoney-1000 where userid=?");
            pps.setInt(1, 1);//占位符赋值
            int result = pps.executeUpdate();
            if (result > 0) System.out.println("转账成功");
            pps = conn.prepareStatement("update money set usermoney=usermoney+1000 where userid=?");
            pps.setInt(1, 2);
            int result1 = pps.executeUpdate();
            if (result1 > 0) System.out.println("到账成功");
            conn.commit();//以上没有异常,则提交事务
        } catch (Exception e) {
            e.printStackTrace();
            try {
                conn.rollback(sp);//上面出现异常,则回滚到保存点,事务结束
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }finally {
           .......
        }
    }

12.JDBC批处理

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

12.1.Statement批处理

以下是使用语句对象的批处理的典型步骤序列
  • - 使用createStatement()方法创建Statement对象(状态通道)。
  • - 使用setAutoCommit()auto-commit设置为false (手动提交)
  • - 使用addBatch()方法在创建的语句对象上添加SQL语句到批处理中。
  • - 在创建的语句对象上使用executeBatch()方法执行所有SQL语句。
  • - 最后,使用commit()方法提交所有更改
package demo5;
import java.sql.*;

public class Demo {
    public static void main(String[] args) {
        Connection conn = null;
        Statement statement = null;
        try {
            //0.导入JDBC API(jdbc jar包),程序可以调用这些API
            //1.注册JDBC驱动程序--打开程序与数据库的通信通道
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取连接--即通过连接地址url,用户名和密码连接上数据库student
            String userName = "root";
            String passWord = "你自己密码";
            String url = "jdbc:mysql://localhost:3306/student1?serverTimezone=UTC";
            conn = DriverManager.getConnection(url, userName, passWord);
            conn.setAutoCommit(false);//开启 手动提交事务 模式,执行一组sql语句
            statement = conn.createStatement();//创建状态通道
            String sql1="insert into teacher values(null,'张三a')";//tid自增
            statement.addBatch(sql1);//添加sql到批处理
            String sql2="insert into teacher values(null,'张三b')";
            statement.addBatch(sql2);//添加sql到批处理
            String sql3="insert into teacher values(null,'张三c')";
            statement.addBatch(sql3);//添加sql到批处理
            int[] ints = statement.executeBatch();//数组每一个元素对应每条sql影响的行数
            conn.commit();//提交事务,数据保存到数据库
            for (int i = 0; i < ints.length;) {
                if (ints[i]>0) System.out.println("第"+(++i)+"条sql语句插入成功");
            }
        }catch (Exception e){
            e.printStackTrace();//若上述有一个地方异常,则回滚所有sql操作
            try {
                System.out.println("异常,事务回滚了");
                conn.rollback();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }finally {
            //5.关闭资源,需要判空
           .............
        }
    }
}

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第16张图片

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第17张图片

12.2.PreparedStatement批处理

  • 1. 使用占位符创建SQL语句。
  • 2. 使用prepareStatement() 方法创建PrepareStatement对象。
  • 3. 使用setAutoCommit()auto-commit设置为false
  • 4. 使用addBatch()方法在创建的语句对象上添加您喜欢的SQL语句到批处理中。
  • 5. 在创建的语句对象上使用executeBatch()方法执行所有SQL语句。
  • 6. 最后,使用commit()方法提交所有更改。
public class Demo1 {
    public static void main(String[] args) {
        Connection conn = null;
        PreparedStatement pps = null;
        try {
            //0.导入JDBC API(jdbc jar包),程序可以调用这些API
            //1.注册JDBC驱动程序--打开程序与数据库的通信通道
            Class.forName("com.mysql.cj.jdbc.Driver");
            //2.获取连接--即通过连接地址url,用户名和密码连接上数据库student
            String userName = "root";
            String passWord = "你自己密码";
            String url = "jdbc:mysql://localhost:3306/student1?serverTimezone=UTC";
            conn = DriverManager.getConnection(url, userName, passWord);
            conn.setAutoCommit(false);//开启 手动提交事务 模式,执行一组sql语句
            pps = conn.prepareStatement("insert into teacher values(null,?)");//创建预状态通道
            pps.setString(1,"王五a"); //占位符赋值
            pps.addBatch(); //添加sql到批处理

            pps.setString(1,"王五b");
            pps.addBatch();

            pps.setString(1,"王五c");
            pps.addBatch();

            int[] ints = pps.executeBatch();//数组每一个元素对应每条sql影响的行数
            conn.commit();//提交事务,数据保存到数据库
            for (int i = 0; i < ints.length;) {
                if (ints[i]>0) System.out.println("第"+(++i)+"条sql语句插入成功");
            }
        }catch (Exception e){
            e.printStackTrace();//若上述有一个地方异常,则回滚所有sql操作
            try {
                System.out.println("异常,事务回滚了");
                conn.rollback();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }finally {
           ............
        }
    }
}

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第18张图片

13.反射处理(简化)结果集

  • java程序从数据库获取到结果集,如果记录的字段很多,就需要写大量的set方法
try {
    Class.forName("com.mysql.cj.jdbc.Driver");//1.加载驱动
    String userid = "root";
    String pass = "你自己密码";
    String url = "jdbc:mysql://localhost:3306/student1?serverTimezone=UTC";
    conn = DriverManager.getConnection(url, userid, pass);//2.获取连接
    String sql = "select * from student";//定义sql
    pps = conn.prepareStatement(sql);//3.创建预状态通道,提交sql
    resultSet = pps.executeQuery();//4.执行sql,返回结果集
    ArrayList students = new ArrayList<>();//存储学生的集合
    while (resultSet.next()) { //一行行读取数据
        Student student2 = new Student();//将每一行中学生信息存入一个学生对象
        student2.setStuid(resultSet.getInt("stuid"));
        student2.setSname(resultSet.getString("stuname"));
        students.add(student2);//学生放入集合中
     }
    return students;//返回学生集合
} 
  • 下面通过反射技术处理结果集--单表的结果集

业务逻辑

1.获取数据库查询结果集中所有列名(字段)

2.反射得到Student实体类中所有的方法

3.遍历结果集,取出各自的信息

3.1第一层循环,遍历列

  • 装饰列名实现与实体类中属性的setter方法一致
  • 第二层循环,遍历Student实体类中所有的方法名,找到与当前装饰列名相同的setter方法
    • 给stu对象通过指定的set方法赋值,值是当前记录的当前列的值
    • 当前记录的当前这个列内容已经set完成,break;
3.2当前记录所有列的内容都set到stu对象,stu装入集合,继续结果集下一条记录
4.返回学生集合

数据表

CREATE TABLE `student` (
  `stuid` int NOT NULL AUTO_INCREMENT,
  `stuname` varchar(255) DEFAULT NULL,
  `teacherid` int DEFAULT NULL,
  PRIMARY KEY (`stuid`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第19张图片

bean包的实体类

package bean;
/**
 * 数据库student1中student表对应的java实体类
 * 切记:实体类属性要与表字段命名一致
 */
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 sname) {  this.stuname = sname; }
    public int getTeacherid() { return teacherid; }
    public void setTeacherid(int teacherid) { this.teacherid = teacherid; }
}

dao层接口

package dao;
import bean.Student;
import java.util.ArrayList;
import java.util.List;

public interface StudentDao {
    /**
     * 查询数据表student所有学生信息,使用反射技术实现,
     * 参数传递Student实体类的字节码对象
     * @return
     */
    public List getAllStudent(Class cla);
}

dao层接口实现类

package dao.Impl;

import bean.Student;
import dao.StudentDao;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class StudentDaoImpl implements StudentDao {
    /**
     * 查询所有学生信息
     * 使用反射技术动态获取实体类Student中setter方法
     * @return
     */
    @Override
    public List getAllStudent(Class cla) {
        Connection conn = null;
        PreparedStatement pps = null;
        ResultSet resultSet = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");//1.加载驱动
            String userid = "root";
            String pass = "你自己密码";
            String url = "jdbc:mysql://localhost:3306/student1?serverTimezone=UTC";
            conn = DriverManager.getConnection(url, userid, pass);//2.获取连接
            String sql = "select * from student";//定义sql
            pps = conn.prepareStatement(sql);//3.创建预状态通道,提交sql
            resultSet = pps.executeQuery();//4.执行sql,返回结果集
            List students = new ArrayList();//存储学生的集合
            //反射技术处理结果集
            //通过反射技术让结果集自动匹配实体类的setter方法

            //1.获取数据库查询结果集中所有列名(字段)
            ResultSetMetaData metaData = resultSet.getMetaData();//得到结果集的结构信息,比如字段数、字段名等
            int columnCount = metaData.getColumnCount();//得到列数
            String[] columnNames = new String[columnCount];//创建字符串数组准备存入列名
            for (int i = 0; i < columnNames.length; i++) {
                columnNames[i]=metaData.getColumnName(i+1);//依次从metaData中获取列名存入数组,从第1列开始
                System.out.println("columnname="+columnNames[i]);
            }
            //2.得到Student实体类中所有的方法
            Method[] declaredMethods = cla.getDeclaredMethods();
            //3.便利结果集,取出各自的信息
            while(resultSet.next()){
                try {
                    Object stu = cla.newInstance();//创建对象,准备存储结果集每条记录信息
                    for(String columnName: columnNames){//遍历列
                        //装饰列名实现与实体类中属性的setter方法一致,(规范:表名与类名要同,列名与属性名要同)
                        String methodName="set"+columnName;//比如列名stuid,装饰成了setstuid
                        for(Method declaredMethod:declaredMethods){
                            //遍历Student实体类中所有的方法名,找到与当前装饰列名相同的方法名,比如找到setStuid与装饰的setstuid同
                            if (declaredMethod.getName().equalsIgnoreCase(methodName)){
                                //给stu对象通过指定的set方法赋值,值是当前记录的当前列的值,不知道列类型,都视为Object
                                declaredMethod.invoke(stu,resultSet.getObject(columnName));
                                break; //表示当前记录的当前这个列已经set完成,当前记录还有好多列等着循环,结果集还有好多记录等着循环
                            }
                        }
                    }
                    //当前记录所有列的内容都set到stu对象,stu装入集合,继续结果集下一条记录
                    students.add(stu);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
            return students;//返回学生集合
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            //释放资源
           .............
        }
        return null;
    }
}

测试类

package test;
import bean.Student;
import dao.Impl.StudentDaoImpl;
import java.util.List;

public class Demo {
    public static void main(String[] args) {
        StudentDaoImpl dao = new StudentDaoImpl();
        List allStudent = dao.getAllStudent(Student.class);
        for (Student student:allStudent){
          System.out.println(student.getStuid()+","+student.getStuname()+","+student.getTeacherid());
        }
    }
}

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第20张图片

14.原生JDBC抽取的工具类

  • 其实就是将原来操作数据库的步骤以方法的方式进行独立的封装
  • 使用属性文件db.properties保存数据库信息driver、userName、userPass、url

加载属性文件

  • 方式一:类加载器
  • 方式二:ResourceBundle访问加载本地properties资源文件

方式一:类加载器

db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/student1?serverTimezone=UTC
uname=root
upass=你自己密码
package util;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.List;
import java.util.Properties;

public class DBUtils {
    //1.定义变量
    private Connection connection; //连接
    private PreparedStatement pps; //预状态通道
    private ResultSet resultSet; //结果集
    private int count; //存储受影响的行数

    private static String driverName;//驱动程序名称
    private static String userName;//数据库用户名
    private static String userPass;//数据库密码
    private static String url;//数据库连接地址

    //2.加载驱动 (只需要加载一次,放入静态代码块中),使用配置文件
    static{
        try {
            InputStream is = DBUtils.class.getClassLoader().getResourceAsStream("db.properties");
            Properties pro = new Properties();
            pro.load(is); //配置文件信息已加载进pro集合
            driverName = pro.getProperty("driver");//从pro集合获取信息
            userName = pro.getProperty("uname");
            userPass = pro.getProperty("upass");
            url = pro.getProperty("url");
            Class.forName(driverName);//加载驱动
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    //3.获得连接
    protected Connection getConnection(){
        //protected方法受保护,特点能被子类访问,防止任意一个类调用该方法
        try {
            connection = DriverManager.getConnection(url, userName, userPass);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return connection;
    }
    //4.得到预状态通道
    protected PreparedStatement getPps(String sql){
        try {
            pps = getConnection().prepareStatement(sql);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return pps;
    }
    //5.绑定参数(sql占位符赋值),传递过来一个存放参数的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));//给pps每个占位符赋值
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
    //6.执行sql,(增删改)
    protected int update(String sql,List list){
        getPps(sql);//获取预状态通道pps
        param(list);//给pps每个占位符赋值(绑定参数)
        try {
            count=pps.executeUpdate();//执行,返回影响的行数
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return count;
    }
    //7.执行sql,(查询)
    protected ResultSet query(String sql,List list){
        getPps(sql);//获取预状态通道pps
        param(list);//给pps每个占位符赋值(绑定参数)
        try {
            resultSet = pps.executeQuery();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return resultSet;
    }
    //8.关闭资源
    protected void closeAll(){
       ...........
    }
}

方式2:使用ResourceBundle访问properties文件

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

15.连接池

15.1.自定义连接池

15.1.1.数据连接池原理

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

15.1.2.自定义连接池

  • 我们可以通过自定义的方式实现连接池!分析连接池类应该包含特定的属性和方法!
  • 属性: 集合 放置Connection
  • 方法: 获取连接方法    回收连接方法
自定义连接池实现逻辑
  • 1.创建LinkedList集合作为连接池准备存放连接对象
  • 2.静态代码块,给集合初始化10个连接对象
  • 3.从连接池获取连接
    • 3.1.集合非空判断,若空则创建一个连接添加到集合末尾
    • 3.2.不空,则取出集合中第一个连接。
  • 4.回收连接
    • 41.判断连接池连接>=10,则超过最大连接数,多余连接关闭
    • 4.2.否则,将返回的连接添加到连接池末尾addList
public class Pool{static LinkedList list = new LinkedList();
static{
    for (int i = 0; i < 10; i++) {
    Connection connection = JDBCUtils.newInstance().getConnection();
    list.add(connection);
}
}
/**
* 从连接池子中获取连接的方式
* @return
*/
public static Connection getConnection(){
    if (list.isEmpty()) {
        //JDBCUtils类是自定义类,封装了连接数据库的信息代码
        Connection connection = JDBCUtils.newInstance().getConnection();
        list.addLast(connection);
    }
    Connection conn = list.removeFirst();
    return conn;
}
/**
* 返回到连接池子中
*/
public static void addBack(Connection conn){
    if (list.size() >= 10) {
        try {
            conn.close();
        } catch (SQLException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        }
    }else{
        list.addLast(conn); //10
    }
}
/**
* 获取连接池子中连接数量的方法
*/
public static int getSize(){
    return list.size();
}
}

15.1.3.java规范实现连接池

  • Java为连接池实现提供了一个规范(接口),规范的写法,我们需要实现DataSource接口! 但是实现DataSource接口有一个弊端,没有提供回收连接方法!这里我们将使用装饰者模式!装饰Connection!具体实现代码如下:
1. 创建装饰Connection
public class MyConnection implements Connection{
    //将被装饰者导入
    private Connection conn;
    private LinkedList list;
    public MyConnection(Connection conn, LinkedList list) {
        super();
        this.conn = conn;
        this.list = list;
    }

    @Override
    public  T unwrap(Class iface) throws SQLException {
        return conn.unwrap(iface);
    }
    @Override
    public boolean isWrapperFor(Class iface) throws SQLException {
        return conn.isWrapperFor(iface);
    }
    @Override
    public Statement createStatement() throws SQLException {
        return conn.createStatement();
    }
    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return conn.prepareStatement(sql);
    }
    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return null;
    }
    @Override
    public String nativeSQL(String sql) throws SQLException {
        return null;
    }
    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException { }
    @Override
    public boolean getAutoCommit() throws SQLException {
        return false;
    }
    @Overridepublic void commit() throws SQLException {
        conn.commit();
    }
    @Override
    public void rollback() throws SQLException {
        conn.rollback();
    }
    @Override
    public void close() throws SQLException {
        list.addLast(conn);
    }
    ...
}

15.1.4.基于规范实现的连接池

/**
* 创建一个规范的连接池子
*/
public class DataSourcePool implements DataSource{
    static LinkedList list = new LinkedList();
    static{
        for (int i = 0; i < 10; i++) {
            Connection connection = JDBCUtils.newInstance().getConnection();
            list.add(connection);
        }
    }
    public static int getSize(){
        return list.size();
    }
    @Override
    public Connection getConnection() throws SQLException {
        Connection conn = list.removeFirst();
        MyConnection conn1 = new MyConnection(conn, list);
        return conn1;
    }
    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }
    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {}
    @Override
    public void setLoginTimeout(int seconds) throws SQLException {}
    @Overridepublic int getLoginTimeout() throws SQLException {
        return 0;
    }
    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
    @Override
    public  T unwrap(Class iface) throws SQLException {
        return null;
    }
    @Override
    public boolean isWrapperFor(Class iface) throws SQLException {
        return false;
    }
    @Override
    public Connection getConnection(String username, String password) throws
    SQLException {
        return null;
    }
}

15.1.5.连接池参数

最小连接数:
  • 是数据库一直保持的数据库连接数,所以如果应用程序对数据库连接的使用量不大,将有大量的数据库 资源被浪费。
初始化连接数:
  • 连接池启动时创建的初始化数据库连接数量。
最大连接数
  • 是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求被加入到等待队列中。
最大等待时间:
  • 当没有可用连接时,连接池等待连接被归还的最大时间,超过时间则抛出异常,可设置参数0或者负 数使得无限等待(根据不同连接池配置)
JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第21张图片
  • 1:DBCP连接池的配置中,还有一个maxIdle的属性,表示最大空闲连接数,超过的空闲连接将被释 放,默认值为8。对应的该属性在Druid连接池已不再使用,配置了也没有效果,c3p0连接池则没有对 应的属性。
  • 2:数据库连接池在初始化的时候会创建initialSize个连接,当有数据库操作时,会从池中取出一个连 接。如果当前池中正在使用的连接数等于maxActive,则会等待一段时间,等待其他操作释放掉某一个 连接,如果这个等待时间超过了maxWait,则会报错;如果当前正在使用的连接数没有达到 maxActive,则判断当前是否空闲连接,如果有则直接使用空闲连接,如果没有则新建立一个连接。在 连接使用完毕后,不是将其物理连接关闭,而是将其放入池中等待其他操作复用。

15.2.DBCP连接池

  • DBCP是一个依赖Jakarta commons-pool对象池机制的数据库连接池.DBCP可以直接的在应用程序中 使用,Tomcat的数据源使用的就是DBCP
  • DBCP(DataBase connection pool)
DBCP连接池的使用

导入相应jar

  • commons-pool-1.5.6.jar
  • commons-dbcp-1.4.jar
  • mysql-connector-java-8.0.16.jar
硬编码使用DBCP
  • 所谓的硬编码方式就是在代码中添加配置
@Test
public void testHard() throws SQLException{
    // 硬编码 使用DBCP连接池子
    BasicDataSource source = new BasicDataSource();
    //设置连接的信息
    source.setDriverClassName("com.mysql.cj.jdbc.Driver");
    source.setUrl("jdbc:mysql://localhost:3306/student1?serverTimezone=UTC");
    source.setUsername("root");
    source.setPassword("111");
    Connection connection = source.getConnection();
    String sql = "select * from student";
    Statement createStatement = connection.createStatement();
    ResultSet executeQuery = createStatement.executeQuery(sql);
    while (executeQuery.next()) {
    System.out.println(executeQuery.getString(2));
}
    connection.close(); //回收
}
软编码使用DBCP的实现
  • 所谓的软编码,就是在项目中添加配置文件,这样就不需要每次代码中添加配置!
  • 文件名称:db.properties 文件位置: src
  • 使用ResourceBundle访问加载本地资源db.properties
  • 创建一个工具类BasicDataSource,加载驱动和获取连接都由它完成
db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/student1?serverTimezone=UTC
uname=root
upass=你自己密码
#
initialSize=10
#最大连接数量
maxActive=50
#
maxIdle=20
#
minIdle=5
#
maxWait=6000
JDBC_DBCP连接池工具类
package util;

import org.apache.commons.dbcp.BasicDataSource;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.List;
import java.util.Properties;
import java.util.ResourceBundle;

/**
 * JDBC_DBCP工具类
 */
public class DBUtils {
    //1.定义变量
    private Connection connection; //连接
    private PreparedStatement pps; //预状态通道
    private ResultSet resultSet; //结果集
    private int count; //存储受影响的行数

    private static String driverName;//驱动程序名称
    private static String userName;//数据库用户名
    private static String userPass;//数据库密码
    private static String url;//数据库连接地址

    private static BasicDataSource basicDataSource = new BasicDataSource();//创建一个工具类,加载驱动和获取连接都由它完成

    //2.加载驱动 (只需要加载一次,放入静态代码块中),使用ResourceBundle加载本地资源db.properties
    static{
            //使用ResourceBundle访问本地资源db.properties
            ResourceBundle bundle = ResourceBundle.getBundle("db");
            driverName = bundle.getString("driver");
            url = bundle.getString("url");
            userName = bundle.getString("uname");
            userPass = bundle.getString("upass");
            //BasicDataSource加载驱动和获取连接都由它完成
            basicDataSource.setUsername(userName); //设置信息
            basicDataSource.setPassword(userPass);
            basicDataSource.setUrl(url);
            basicDataSource.setDriverClassName(driverName);
    }
    //3.获得连接
    protected Connection getConnection(){
        //protected方法受保护,特点能被子类访问,防止任意一个类调用该方法
        try {
            connection = basicDataSource.getConnection();
            //connection = DriverManager.getConnection(url, userName, userPass);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return connection;
    }
    //4.得到预状态通道
    protected PreparedStatement getPps(String sql){
        try {
            pps = getConnection().prepareStatement(sql);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return pps;
    }
    //5.绑定参数(sql占位符赋值),传递过来一个存放参数的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));//给pps每个占位符赋值
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
    //6.执行sql,(增删改)
    protected int update(String sql,List list){
        getPps(sql);//获取预状态通道pps
        param(list);//给pps每个占位符赋值(绑定参数)
        try {
            count=pps.executeUpdate();//执行,返回影响的行数
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return count;
    }
    //7.执行sql,(查询)
    protected ResultSet query(String sql,List list){
        getPps(sql);//获取预状态通道pps
        param(list);//给pps每个占位符赋值(绑定参数)
        try {
            resultSet = pps.executeQuery();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return resultSet;
    }
    //8.关闭资源
    protected void closeAll(){
       ...............
    }
}

15.3.DBCP与C3P0区别

  • dbcp没有自动回收空闲连接的功能 ,c3p0有自动回收空闲连接功能
  • dbcp需要手动设置配置文件(即需要BasicDataSource调用set方法设置数据库信息),c3p0不需要手动设置,创建ComboPooledDataSource对象时默认加载xml,加载驱动,

15.3.C3P0连接池

  • c3p0是一个开放源代码的JDBC连接池
实现步骤
1.导入jar包
  • c3p0-0.9.1.2.jar
  • mysql-connector-java-8.0.16.jar

2.添加配置文件

  • c3p0是在外部添加配置文件,工具直接进行应用,因为直接引用,所以要求固定的命名和文件位置,文件名src/c3p0-config.xml
  • 配置数据库信息。


    
    
        
        com.mysql.cj.jdbc.Driver
        jdbc:mysql://localhost:3306/student1?serverTimezone=UTC
        root
        你自己密码
        
        
        30000
        
        30
        10
        
        30
        100
        10
        200
    

3.JDBC_C3P0工具类定义

  • 手动创建 ComboPooledDataSource工具类,创建该对象时默认加载xml加载驱动
  • 通过 ComboPooledDataSource获取连接
package util;
import com.mchange.v2.c3p0.ComboPooledDataSource;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.ResourceBundle;

/**
 * JDBC_C3P0连接池工具类
 */
public class DBUtils {
    //1.定义变量
    private Connection connection; //连接
    private PreparedStatement pps; //预状态通道
    private ResultSet resultSet; //结果集
    private int count; //存储受影响的行数

    private static String driverName;//驱动程序名称
    private static String userName;//数据库用户名
    private static String userPass;//数据库密码
    private static String url;//数据库连接地址
    //1.创建一个工具类ComboPooledDataSource,创建时默认加载xml,加载驱动
    private static ComboPooledDataSource comboPooledDataSource= new ComboPooledDataSource();

    //3.获得连接
    protected Connection getConnection(){
        //protected方法受保护,特点能被子类访问,防止任意一个类调用该方法
        try {
            connection = comboPooledDataSource.getConnection();
            //connection = basicDataSource.getConnection();
            //connection = DriverManager.getConnection(url, userName, userPass);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return connection;
    }
    //4.得到预状态通道
    protected PreparedStatement getPps(String sql){
        try {
            pps = getConnection().prepareStatement(sql);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return pps;
    }
    //5.绑定参数(sql占位符赋值),传递过来一个存放参数的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));//给pps每个占位符赋值
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
    //6.执行sql,(增删改)
    protected int update(String sql,List list){
        getPps(sql);//获取预状态通道pps
        param(list);//给pps每个占位符赋值(绑定参数)
        try {
            count=pps.executeUpdate();//执行,返回影响的行数
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return count;
    }
    //7.执行sql,(查询)
    protected ResultSet query(String sql,List list){
        getPps(sql);//获取预状态通道pps
        param(list);//给pps每个占位符赋值(绑定参数)
        try {
            resultSet = pps.executeQuery();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return resultSet;
    }
    //8.关闭资源
    protected void closeAll(){
        try {
            if (connection != null) {
                connection.close();
            }
            if (pps != null) {
               pps.close();
            }
            if (resultSet != null) {
                resultSet.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

4.JDBC_C3P0连接池工具类的使用

数据表

CREATE TABLE `student` (
  `stuid` int NOT NULL AUTO_INCREMENT,
  `stuname` varchar(255) DEFAULT NULL,
  `teacherid` int DEFAULT NULL,
  PRIMARY KEY (`stuid`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第22张图片

bean包实体类

package bean;
public class Student {
    private int stuid;
    private String sname;
    public int getStuid() { return stuid;}
    public void setStuid(int stuid) {  this.stuid = stuid; }
    public String getSname() {   return sname; }
    public void setSname(String sname) {   this.sname = sname;}
}

dao接口

package dao;
import bean.Student;

/**
 * 根据id查询学生信息
 */
public interface StudentDao {
    public Student getStudentById(int id);
}

dao实现类

package dao.impl;

import bean.Student;
import dao.StudentDao;
import util.DBUtils;

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

/**
 * 根据id查询学生信息
 * 使用JDBC_C3P0连接池工具类
 */
public class StudentDaoImpl extends DBUtils implements StudentDao {

    @Override
    public Student getStudentById(int id) {
        Student student = null;
        try {
            student = new Student();
            String sql="select * from student where stuid=?";
            List list = new ArrayList();
            list.add(id);//集合存放占位符
            ResultSet rs = query(sql, list);//继承DBUtils,作为它的子类才可以使用它的protected方法
            while(rs.next()){
                student.setStuid(rs.getInt("stuid"));
                student.setSname(rs.getString("stuname"));
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            closeAll();
        }
        return student;
    }
}

测试类

package test;

import bean.Student;
import dao.impl.StudentDaoImpl;

public class Demo {
    public static void main(String[] args) {
        StudentDaoImpl dao = new StudentDaoImpl();
        Student student = dao.getStudentById(1);
        System.out.println(student.getStuid()+":"+student.getSname());
    }
}

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第23张图片

15.4.Druid(德鲁伊)连接池

  • 阿里出品,淘宝和支付宝专用数据库连接池,但它不仅仅是一个数据库连接池,它还包含一个 ProxyDriver(代理驱动),一系列内置的JDBC组件库,一个SQL Parser(sql解析器)支持所有JDBC兼容的数据库,包括OracleMySqlDerbyPostgresqlSQL ServerH2等等。
  • Druid针对Oracle和MySql做了特别优化,比如OraclePS Cache内存占用优化,MySqlping检测优 化。
  • Druid提供了MySqlOraclePostgresqlSQL-92SQL的完整支持,这是一个手写的高性能SQLParser,支持Visitor模式,使得分析SQL的抽象语法树很方便。
  • 简单SQL语句用时10微秒以内,复杂SQL用时30微秒。

优势

亚秒级查询

  • druid提供了快速的聚合能力以及亚秒级的OLAP查询能力,多租户的设计,是面向用户分析应用的理 想方式。

实时数据注入

  • druid支持流数据的注入,并提供了数据的事件驱动,保证在实时和离线环境下事件的实效性和统一性

.可扩展的PB级存储

  • druid集群可以很方便的扩容到PB的数据量,每秒百 万级别的数据注入。即便在加大数据规模的情况 下,也能保证时其效性

多环境部署

  • druid既可以运行在商业的硬件上,也可以运行在云上。它可以从多种数据系统中注入数据,包括 hadoop,sparkkafkastormsamza
使用步骤

1.导入jar

  • mysql-connector-java-8.0.16.jar
  • druid-1.0.9.jar

2.配置文件

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/student1?serverTimezone=UTC
uname=root
upass=密码
#
initialSize=10
#最大连接数量
maxActive=50
#
maxIdle=20
#
minIdle=5
#
maxWait=6000

3.编写工具类

  • 使用ResourceBundle来读取properties配置文件信息
  • 创建DruidDataSource(对象)工具类
  • 需要给它设置用户名连接数据库的信息,
package util;
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.ResourceBundle;

/**
 * JDBC_Druid连接池工具类
 */
public class DBUtils {
    //1.定义变量
    private Connection connection; //连接
    private PreparedStatement pps; //预状态通道
    private ResultSet resultSet; //结果集
    private int count; //存储受影响的行数

    private static String driverName;//驱动程序名称
    private static String userName;//数据库用户名
    private static String userPass;//数据库密码
    private static String url;//数据库连接地址
    //创建DruidDataSource(对象)工具类,需要给它设置用户名连接数据库的信息
    private static DruidDataSource druidDataSource=new DruidDataSource();

    //使用ResourceBundle来读取properties配置文件信息,并赋值给DruidDataSource来加载驱动和获取连接。
    static{
        ResourceBundle bundle = ResourceBundle.getBundle("db");
        driverName = bundle.getString("driver");
        userName = bundle.getString("uname");
        userPass = bundle.getString("upass");
        url = bundle.getString("url");

        druidDataSource.setUsername(userName);
        druidDataSource.setPassword(userPass);
        druidDataSource.setUrl(url);
        druidDataSource.setDriverClassName(driverName);
    }
    //3.获得连接
    protected Connection getConnection(){
        //protected方法受保护,特点能被子类访问,防止任意一个类调用该方法
        try {
             connection = druidDataSource.getConnection();
            //connection = comboPooledDataSource.getConnection();
            //connection = basicDataSource.getConnection();
            //connection = DriverManager.getConnection(url, userName, userPass);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return connection;
    }
    //4.得到预状态通道
    protected PreparedStatement getPps(String sql){
        try {
            pps = getConnection().prepareStatement(sql);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return pps;
    }
    //5.绑定参数(sql占位符赋值),传递过来一个存放参数的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));//给pps每个占位符赋值
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
    //6.执行sql,(增删改)
    protected int update(String sql,List list){
        getPps(sql);//获取预状态通道pps
        param(list);//给pps每个占位符赋值(绑定参数)
        try {
            count=pps.executeUpdate();//执行,返回影响的行数
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return count;
    }
    //7.执行sql,(查询)
    protected ResultSet query(String sql,List list){
        getPps(sql);//获取预状态通道pps
        param(list);//给pps每个占位符赋值(绑定参数)
        try {
            resultSet = pps.executeQuery();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return resultSet;
    }
    //8.关闭资源
    protected void closeAll(){
        try {
            if (connection != null) {
                connection.close();
            }
            if (pps != null) {
               pps.close();
            }
            if (resultSet != null) {
                resultSet.close();
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}
  • 下面这个是使用类加载器读取properties文件到properties对象中
  • 然后创建DruidDataSource,并从properties配置对象中获取数据库信息设置到DruidDataSource中
  • 然后DruidDataSource,获取连接
/**
* 阿里的数据库连接池 性能最好的
* Druid
* */
public class DruidUtils {
    //声明连接池对象
    private static DruidDataSource ds;
    static{
        ///实例化数据库连接池对象
        ds=new DruidDataSource();
        //实例化配置对象
        Properties properties=new Properties();
    try {
        //加载配置文件内容
      properties.load(DruidUtils.class.getResourceAsStream("dbcpconfig.properties"));
    //设置驱动类全称
    ds.setDriverClassName(properties.getProperty("driverClassName"));
    //设置连接的数据库
    ds.setUrl(properties.getProperty("url"));
    //设置用户名
    ds.setUsername(properties.getProperty("username"));
    //设置密码
    ds.setPassword(properties.getProperty("password"));
    //设置最大连接数量
    ds.setMaxActive(Integer.parseInt(properties.getProperty("maxActive")));
  } catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
 }
}


//获取连接对象
public static Connection getConnection() {
    try {
        return ds.getConnection();
    } catch (SQLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    return null;
}

}
  • :Druid连接池的配置中,driverClassName可配可不配,如果不配置会根据url自动识别dbType(据库类型),然后选择相应的driverClassName
package yhp.util;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import javax.xml.transform.Result;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class DruidUtil {

    private static DataSource ds;
    static{
        try {
            Properties ppt = new Properties();
            ppt.load(DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties"));
            ds = DruidDataSourceFactory.createDataSource(ppt);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 从连接池中取出一个连接给用户
     * @return
     */
    public static Connection getConnection(){
        try {
            return ds.getConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }


    public static void close(Connection conn, Statement state, ResultSet rs){
        if (rs!=null){
            try {
                rs.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        if (state!=null){
            try {
                state.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
       if (conn!=null){
           try {
               conn.close();
           } catch (SQLException throwables) {
               throwables.printStackTrace();
           }
       }
    }
}

15.5.获取连接4种方法

  • 1.使用驱动管理器DriverManager,需要程序给pps进行set设置数据库信息
  • 2.使用DBCP连接池的BasicDataSource,需要程序给BasicDataSource进行set设置数据库信息
  • 3.使用C3P0连接池的ComboPooledDataSource,创建该工具对象时自动加载xml,加载驱动和数据库信息
  • 4.使用Druid连接池的DruidDataSource,需要程序给DruidDataSource进行set设置数据库信息

JDBC使用详解(体系结构、statement和PreparedStatement 、Java操作两表、事务案例、JDBC批处理、JDBC-DBCP-C3P0-Druid-JDBCTemplate)_第24张图片

MySQL基本使用及单表的增删改查语句

https://blog.csdn.net/qq_40454863/article/details/113809607

常用SQL语句练习整理

https://blog.csdn.net/qq_40454863/article/details/113925975

MySQL高级使用--事务详解

https://blog.csdn.net/qq_40454863/article/details/113899837

数据库的设计、表的约束(完整性)、多表设计与查询

https://blog.csdn.net/qq_40454863/article/details/114067770

你可能感兴趣的:(数据库,mysql,数据库)