详解 JDBC和连接池

目录

一.JDBC概述

1.介绍

2.好处

3.JDBC API

二.数据库连接

1.前置工作

2.方法一

3.方法二

4.方法三

5.方法四

6.方式五(配置文件)

三.ResultSet(结果集)

1.介绍

2.代码演示

3.底层原理

四.预处理

1.SQL注入

2.PreparedStatement 接口

五.JDBCUtils 工具类

1.介绍

2.Utils类

3.应用示例

六.事务

七.批处理(Batch)

1.介绍

2.使用

八.数据库连接池

1.传统连接方法介绍

2.连接池介绍

3.连接池种类

九.C3P0连接池(了解)

1.前置工作

2.方法一

3.方法二

十.Druid连接池

1.前置工作

2.实现方法

3.德鲁伊工具类

十一.ApDBUtils工具类

1.介绍

2.实现

3.源码

十二.BasicDAO

1.介绍

2.实现

十三.总结


一.JDBC概述

1.介绍

JDBC为访问不同的数据库提供了统一的接口,我们可以使用JDBC连接任何提供了JDBC驱动程序的数据库系统。简单说就是,Java厂商为了避免如因数据库版本迭代而引起的修改代码的麻烦,不利于程序管理,于是让数据库厂商自己写好内容,Java只调用接口

2.好处

JDBC是Java提供了一套用于数据库操作的接口API,Java程序员只需要面向这套接口编程即可,不同的数据库厂商需要针对这套接口提供不同的实现。

3.JDBC API

JDBC API是一系列的接口,它统一和规范了应用程序与数据库的连接、执行SQL语句,并得到返回结果等各类操作,相关类和接口在java.sql与javax.sql包中。

二.数据库连接

1.前置工作

前置工作:在项目创建一个文件夹,并将jar包放到该文件下,加入到项目中。

实现声明,本篇文章使用的jar包是8.0.16版本。

首先是哪里获得这个jar包?

打开MySQL官网:MySQL

点击DOWNLOADS:

点击下面圈出来的部分:

可以看到右面的是连接器,点击圈出来的部分:

大家可以根据自己的需求下载对应的版本:

下载完成之后我们可以得到一个文件夹,里面有一个这样的文件:

将这个文件复制一份,打开idea,在我们的目录下面创建一个文件夹,将这个文件放入我们新创建的文件夹底下:

放入之后,右键加入的jar包,点击下面圈出的部分:

完成上述的操作后前置工作算完成了。

2.方法一

数据库连接大致可以分为4步:

注册驱动 -> 得到连接 -> 执行SQL -> 关闭资源连接

第一步:注册驱动

//1. 注册驱动,com.mysql.cj.jdbc.Driver
        Driver driver = new Driver();

我们可以使用var让其自动为我们补全:

var是一个代码补全的小技巧。

第二步:得到连接

//2.得到连接
String url="jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai";

//解释:jdbc:mysql://localhost:3306/数据库名?serverTimezone=UTC
//jdbc:mysql://    规定好表示协议
//localhost:       主机,可以是ip地址
//3306             MySQL要监听的端口
//UTC              世界标准时间,北京时间比UTC早八小时,可改为Asia/Shanghai(亚洲上海时间)

//讲用户名和密码放入Properties对象
Properties properties = new Properties();
properties.setProperty("user","root");  //用户
properties.setProperty("password","123456");    //密码
//连接
Connection connect = driver.connect(url, properties);

完成上述步骤即可成功连接

第三步:执行SQL

//3. 执行MySQL
String sql="insert into student values(5,'淳平',1)";
//String sql="delete from student where id=5";
//用于执行静态SQL语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
//如果是dml(update,insert,delete)语句,返回的就是影响的行数
int rows = statement.executeUpdate(sql);
System.out.println(rows>0?"成功":"失败");

第四步:关闭连接

//4.关闭资源连接
statement.close();
//就像是开了很多个窗口一样,所以必须关闭网络连接
connect.close();

3.方法二

//使用反射加载到Driver类,动态加载,更加灵活,减少依赖性
Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();

String url="jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai";

Properties properties = new Properties();
properties.setProperty("user","root");  //用户
properties.setProperty("password","123456");    //密码

Connection connect = driver.connect(url, properties);
System.out.println("方式二:"+connect);

如果但从代码量上来看,区别不大,但是这个方法更加灵活。

4.方法三

//使用反射加载Driver
Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();

//创建url、user和password
String url="jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai";
String user="root";
String password="123456";

DriverManager.registerDriver(driver);   //注册Driver驱动

Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("方式三:"+connection);

这里使用 DriverManager 来去注册驱动。

5.方法四

这是最推荐的一种方法。

Class.forName("com.mysql.cj.jdbc.Driver");
//创建url、user和password
String url="jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai";
String user="root";
String password="123456";
Connection connection = DriverManager.getConnection(url, user, password);

System.out.println("方式四:"+connection);

使用Class.forName 自动完成注册驱动,简化代码。在加载Driver类时,会自动完成注册。

如果我们看Driver类的源码的时候就会发现原因:

源码:
static {
    try {
        DriverManager.registerDriver(new Driver());
    } catch (SQLException var1) {
        throw new RuntimeException("Can't register driver!");
    }
}

从这里我们可以看到源码自己就完成了注册。

当然如果你不写Class.forName("com.mysql.cj.jdbc.Driver")也是Okk的
mysql驱动5.1.6可以无需Class.forName()。从 JDK1.5 以后使用 JDBC4 ,不在需要显示调用Class.forName() 注册驱动而是自动调用驱动。jar包下META-INF\services\java.sql.Driver文本中的类名称去注册。

6.方式五(配置文件)

在第四中基础上改进,增加配置文件,让连接MySQL更加灵活。

//通过对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关的值
String user=properties.getProperty("user");
String password= properties.getProperty("password");
String driver= properties.getProperty("driver");
String url= properties.getProperty("url");

Class.forName(driver);

Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("方法五:"+connection);

配置文件的方法:

在文件中写入:

使用配置文件,我们以后在改动一些上述内容时不用修改代码,直接修改文件即可,提高了灵活性。

三.ResultSet(结果集)

1.介绍

表示数据库结果集的数据表,通常通过执行查询数据库的语句生成。

ResultSet对象会有一个光标,一开始这个光标会在第一行之前,我们使用了next会将光标往后动,直到没有数据行。

通过以上特性,我们可以使用while循环来遍历结果集。

2.代码演示

//通过对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));

//获取相关的值
String user=properties.getProperty("user");
String password= properties.getProperty("password");
String driver= properties.getProperty("driver");
String url= properties.getProperty("url");

//注册驱动
Class.forName(driver);
//连接
Connection connection = DriverManager.getConnection(url, user, password);
//得到Statement
Statement statement = connection.createStatement();
//组织sql
String sql="select id,name,class_id from student";

//执行给定的sql语句,并返回ResultSet结果集
//ResulSet只是一个接口,真正的类是ResultSetImpI
java.sql.ResultSet resultSet = statement.executeQuery(sql);

//使用while取出数据
while(resultSet.next()){
    //光标下移,如果没有行了就返回false
    //得到一行数据
    int id=resultSet.getInt(1);     //第一列
    String name = resultSet.getString(2);   //第二列
    int class_id = resultSet.getInt(3);     //第三列
    System.out.println(id+"\t"+name+"\t"+class_id);
}

//关闭连接
resultSet.close();
statement.close();
connection.close();

大家可以将以上代码带入idea中跑一下(要修改成自己的数据库)。

3.底层原理

在上面我们可以看到,结果集每次读一行,然后再一列一列的读。

我们可以通过debug来看一下其在底层是怎么存储的。

执行:java.sql.ResultSet resultSet = statement.executeQuery(sql);

找到圈出的部分:

在resultSet中找到rowData(在比较下面的位置)

rows中的五个数据就是我的数据库中的5个数据:

下面是数据的详细说明:

四.预处理

1.SQL注入

再说预处理之前,我们不得不先说一下SQL注入。

什么是SQL注入?利用一些奇奇怪怪的字符串输入到数据库中,扰乱数据库管理系统(DBMS)的判断,从而用根本就不存在的数据获得数据库里的数据。对于一些保密要求高的数据库来说这个是很致命的。如果数据库的数据被窃取或修改删除,后果不堪设想。

我们这里可以简单模拟一些SQL注入:

Scanner scan=new Scanner(System.in);
String name=scan.nextLine();
Properties properties = new Properties();
properties.load(new FileInputStream(("src\\mysql.properties")));
//获取配置文件里的内容
String user=properties.getProperty("user");
String password=properties.getProperty("password");
String driver=properties.getProperty("driver");
String url=properties.getProperty("url");

Class.forName(driver);

Connection connection = DriverManager.getConnection(url, user, password);

Statement statement = connection.createStatement();
String sql="select id,name,class_id from student where name='"+name+"'";
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()){
    System.out.println(resultSet.getInt(1)+"\t"+resultSet.getString(2)+"\t"+resultSet.getInt(3));
}
//关闭连接
resultSet.close();
statement.close();
connection.close();

上述代码是通过输入一个姓名,如果存在取出与这个名字相关的数据。

这是这个表内现在有的数据:

我们可以在idea上查张三的数据:

但是如果我们输入的是这个呢:

' or 1=1;--

就可以将全部数据一下子全部取出!

这就很危险了,数据泄露了!

它是怎么实现的呢?

select id,name,class_id from student where name='' or 1=1;--'

将其带入就可以发现,前面的(')变成了一个('')后面的(--)起到了注释的作用。

where条件变成了name=''或者1=1,1肯定等于1的,所有where永远是成立的。

2.PreparedStatement 接口

前面的Statement是不安全的,因此我们要选择一个安全的方式。PreparedStatement 接口就是一个很好的选择。

PreparedStatement 接口会进行预处理,预处理有效的解决了SQL注入问题,并且大大减少了编译的次数,提高了效率。

这里只演示与前面不同的地方:

String sql="select id,name from student where id=?";
//preparedStatement对象实现了PreparedStatement接口的实现类的对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//给?赋值
preparedStatement.setInt(1,id);

使用preparedStatement时,我们要将传入的值哪里用?代替,像上面的id我们就用了?代替。当然,不能只代替,下面还会给?进行赋值。

PreparedStatement 接口中有两个接口:

一个是:executeQuery() -> 执行查询,返回ResultSet

一个是:executeUpdate() -> 执行增加,删除,修改

上面的例子是查询,那我们这里就写一个查询的例子:

ResultSet resultSet = preparedStatement.executeQuery();

现在我们得到结果集就可以输入数据了。

五.JDBCUtils 工具类

1.介绍

在JDBC操作中,获取连接和释放资源是经常会用的,反反复复的使用会很麻烦。这里我们可以包装一个工具类,直接调用就行了,方便安全。

2.Utils类

Utils类中有两个方法:getConnect和close。

public class Utils {
    private static String user;
    private static String password;
    private static String url;
    private static String driver;

    static {
        try {
            Properties properties = new Properties();
            properties.load(new FileInputStream("src\\mysql.properties"));
            //读取数据
            user = properties.getProperty("user");
            password = properties.getProperty("password");
            url = properties.getProperty("url");
            driver = properties.getProperty("driver");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //连接数据库,返回Connection
    public static Connection getConnect(){
        try {
            return DriverManager.getConnection(url,user,password);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    //关闭相关资源
    public static void close(ResultSet set, Statement statement,Connection connection){
        try {
            if(set!=null){
                set.close();
            }
            if(statement!=null){
                statement.close();
            }
            if(connection!=null){
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

通过这么一整合,我们现在写就方便很多了。

3.应用示例

这里就就举一个修改的例子:

public static void testDML(){
    //1.得到连接
    Connection connection=Utils.getConnect();

    //2.组织一个sql
    String sql="update student set name=? where id =?";

    //3.创建一个PreparedStatement对象
    PreparedStatement preparedStatement = null;

    try {
        preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1,"田所");
        preparedStatement.setInt(2,5);
        preparedStatement.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    }finally {
        Utils.close(null,preparedStatement,connection);
    }
}

可见,步骤真的简化了不少。

六.事务

JDBC程序中当一个Connection对象创建时,默认情况下会自动提交事务,如果执行成功就自动提交。这里会有一个问题,如果在执行的过程中有一个地方出现异常了,那么后面的SQL语句就不能执行了。这个时候就要使用事务了。

先举一个执行异常的例子:

public static void testDML(){
    //1.得到连接
    Connection connection= Utils.getConnect();

    //2.组织一个sql
    String sql1="update account set salary=salary+100 where id =1";
    String sql2="update account set salary=salary-100 where id =2";
    //3.创建一个PreparedStatement对象
    PreparedStatement preparedStatement = null;

    try {
        preparedStatement = connection.prepareStatement(sql1);
        preparedStatement.executeUpdate();
        int i=1/0;
        preparedStatement = connection.prepareStatement(sql2);
        preparedStatement.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    }finally {
        Utils.close(null,preparedStatement,connection);
    }
}

示例中有一个地方会出现异常:1/0。

这个时候只会执行前面的sql1不会执行sql2,这就出现错误了。

可以明显看到淳平凭空多了100块,裕太的钱没有增加。

为了解决这个问题我们使用了事务:

public static void testDML(){
    //1.得到连接
    Connection connection= Utils.getConnect();

    //2.组织一个sql
    String sql1="update account set salary=salary+100 where id =1";
    String sql2="update account set salary=salary-100 where id =2";
    //3.创建一个PreparedStatement对象
    PreparedStatement preparedStatement = null;

    try {
        //将connection设为不自动提交
        connection.setAutoCommit(false);    //开启了事务
        preparedStatement = connection.prepareStatement(sql1);
        preparedStatement.executeUpdate();
        //人为设置异常
        int i=1/0;
        preparedStatement = connection.prepareStatement(sql2);
        preparedStatement.executeUpdate();
    } catch (SQLException e) {
        //如果出现异常会来的这里
        //在这里我们可以撤销原来的执行,即进行回滚
        try {
            //默认回滚到事务开始的状态
            connection.rollback();
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
        e.printStackTrace();
    }finally {
        Utils.close(null,preparedStatement,connection);
    }
}

代码上的注释已经解释了,这里只说一下出现的方法:

connection.setAutoCommit() -> 设置手动提交(false)或自动提交(true)

commit() -> 提交业务

rollback() -> 回滚事务

七.批处理(Batch)

1.介绍

有这么一个场景,我们要给MySQL输入大量数据,我们可以选择在Java中一行一行输入到MySQL,但是像这种提交一次运行一次,比较耗时间。就像是用巴士来运人,一次只运一个人,效率极低,但是如果我们一次运100人,这效率不就提高了嘛。

这就引出了批处理。批处理支持一次提交多条MySQL语句进行运行,这大大提高了效率。下面一张图来让大家直观感受一下:

同样是输入5000条数据,前后差距竟然这么大!

可见,这个批处理是个好东西啊!

注意!!!!!

MySQL8后版本的在时区后加上&rewriteBatchedStatements=true

2.使用

首先介绍一下批处理的一些方法:

addBatch() -> 添加需要批量处理的SQL语句或参数

executeBatch() -> 执行批量处理语句,相当于executeQuery()或executeUpdate()

clearBatch() -> 清空批量处理包的语句

下面是示例:

//1.建立连接
Connection connection= Utils.getConnect();
//2.写SQL语句
String sql="insert into account values(?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);

long start=System.currentTimeMillis();

//3.插入数据
for(int i=0;i<5000;i++){
    preparedStatement.setInt(1,i);
    preparedStatement.setString(2,"代号"+i);
    preparedStatement.addBatch();
    if((i+1)%1000==0){
        preparedStatement.executeBatch();
        //清空一下
        preparedStatement.clearBatch();
    }
}

long end=System.currentTimeMillis();
System.out.println(end-start);

//4.关闭连接
Utils.close(null,preparedStatement,connection);

八.数据库连接池

1.传统连接方法介绍

我们知道,连接数据库后大致分为以下操作:连接 -> 执行SQL语句 -> 关闭连接。有这么一个场景,如果我们同时执行很多次数据库连接操作,数据库就会崩溃。

上面连接数据库的方法我们称之为:传统连接方法

传统方法有什么问题呢?

使用传统方法,每次向数据库建立连接的时候都要将Connection 加载到内存中,再验证IP地址、用户名和密码。需要数据库连接时,就向数据库请求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃。

再者,每一次数据库连接,使用完都要断开,如果程序出现异常没有关闭(像上面事务举的例子一样),数据库的内存会泄露,最终将导致重启数据库。

最后,传统的连接方法无法控制创建的连接数量,如果连接过多会导致内存泄漏和MySQL崩溃。

2.连接池介绍

原本是Java程序直接连接数据库,现在再Java程序中放一个连接池。连接池内是一些连接对象,如果我们要用连接对象的话,就从连接池中取出一个连接对象,用完了之后要放回去的。

如果Java程序向连接池请求的连接次数超过最大连接数量时,这些请求会被放入等待队列中。等到Java程序的一个连接用完放回后,请求才能得到回应。

这个就像一些交通枢纽的安检口一样,如果人多的话,工作人员会控制进入的数量,等到缓存区的人没了之后,才能放等待区的人进去。

这里注意,连接池只是负责分配、管理和释放连接,它只是重复使用一个连接,没有将连接重建一个。就像安检口一样,一直用的都是那几个,没有重新建一个。

3.连接池种类

JDBC中的连接池使用 javax.sql.DataSourse 来表示,DataSourse只是一个接口,该接口通常由第三方提供实现

连接池 介绍
C3P0 速度相对较慢,稳定性不错
DBCP 速度相对C3P0较快,但不稳定
Proxool 有监控连接池状态的功能,稳定性较C3P0差一些
BoneCP 速度快
Druid 阿里提供的连接池,集C3P0、DBCP、Proxool优点于一身

九.C3P0连接池(了解)

C3P0连接池是比较落版本的连接池,现在很少用它,大家对它有个印象就行,了解就行。

1.前置工作

先引入C3P0的jar包,引入方法跟引入mysql的jar包一样。

如果你用的C3P0版本是0.9以上,还要额外导入mchange-commons-java-0.2包。

导入配置文件c3p0-config.xml

<c3p0-config>
    <!--配置连接池mysql-->
    <named-config name="c3p0_mysql"> <!--数据源名称代表连接池 这里等会连接时候使用-->
    <property name="driverClass">com.mysql.cj.jdbc.Driver</property> <!--驱动类-->
    <property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/test</property> <!--url-->
    <property name="user">root</property> <!--用户名-->
    <property name="password"></property> <!--密码-->
    <property name="initialPoolSize">10</property> <!--初始化的连接数-->
    <property name="acquireIncrement">5</property> <!--每次增长的连接数-->
    <property name="maxIdleTime">30</property>
    <property name="maxPoolSize">60</property> <!--最大的连接数-->
    <property name="minPoolSize">10</property> <!--最小的连接数-->
    </named-config>
<!--配置连接池2,可以配置多个-->
</c3p0-config>

2.方法一

相关参数,在程序中指定user,url,password等,比较笨拙。

//1.创建一个数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();

//2.通过配置文件 获取相关信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//读取配置文件里的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");

//3.给数据源 comboPooledDataSource 设置相关参数
//注意:连接管理是由 comboPooledDataSource 来管理
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);

//设置初始化连接数
comboPooledDataSource.setInitialPoolSize(10);
//最大连接数
comboPooledDataSource.setMaxPoolSize(50);

//4.获取连接
Connection connection = comboPooledDataSource.getConnection();
connection.close();

3.方法二

使用配置文件模板来完成,简单简洁。

ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("c3p0_mysql");
Connection connection = comboPooledDataSource.getConnection();
System.out.println("连接成功");
connection.close();

总而言之,看看就行,不重要。

十.Druid连接池

Druid(德鲁伊)连接池是由阿里巴巴开发的,性能均衡,十分好用,是用的最多的连接池。

1.前置工作

我们在连接德鲁伊时也要先导入jar包,加入的方法与前面的相同,这里就不过多赘述了。

引入jar包后我们要配置文件,这里的文件可以自己手写:

自己在src下创建一个File

在File中输入:

#key=value
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
username=root
password=123456
initialSize=10
minIdle=5
maxActive=50
maxWait=5000

这里注意,有几个地方你们要改成自己的:

test改成自己要连接的数据库名字,密码写自己的。

2.实现方法

下面是连接方法:

Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));

//创建一个指定参数的数据库连接池,连接池的相关信息与配置文件相符
DataSource dataSources = DruidDataSourceFactory.createDataSource(properties);
Connection connection = dataSources.getConnection();
connection.close();

步骤也是很简单的。

这里有一点要注意,德鲁伊的connection.close不是将数据库关闭,而是将连接断开

为什么是断开呢?因为Connetion是一个接口,不同的实现类连上这个接口,实现的方法也不同,德鲁伊的实现就是将数据库断开,这不就是多态嘛。

3.德鲁伊工具类

下面是具体实现方法,与前面Untils工具类差不多:

private static DataSource ds;
static {
    Properties properties = new Properties();
    //ds初始化
    try {
        properties.load(new FileInputStream("src\\druid.properties"));
        ds= DruidDataSourceFactory.createDataSource(properties);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

//getConnection方法
public static Connection getConnection() throws SQLException {
    return ds.getConnection();
}

//把连接放回连接池
public static void close(ResultSet resultSet, Statement statement,Connection connection){
    try {
        if(resultSet!=null){
            resultSet.close();
        }
        if(statement!=null){
            statement.close();
        }
        if(connection!=null){
            connection.close();
        }
    } catch (SQLException e) {
        throw new RuntimeException(e);
    }
}

使用也是与Utils相同的,这里就不过多赘述了。

十一.ApDBUtils工具类

1.介绍

ApDBUtils全称Apache-DBUtils(阿帕奇)。

为什么要引入这个工具类呢?

因为有这个一个问题:关闭connection后,resultSet结果集无法使用;并且resultSet不利于管理数据。ResultSet依赖于Statement,而Statement又依赖于Connection。当Connection关闭时,底层的数据库连接资源被释放,与该连接相关的Statement和ResultSet就失去了其在数据库中的有效执行环境。

因此我们想到了使用ApDBUtils工具类来解决这个问题。

2.实现

使用 Apache-DBUtils 类和接口,先引入jar包。

我们先创建一个对象,这个对象里数据类型与我们要取的表的数据类型相同。然后我们将结果集记录,封装到ArrayList<对象>中。

先给大家看一下例子要用的对象Actor:

public class Actor {
    //JavaBean设计模式
    private Integer id;
    private String name;
    private String sex;

    public Actor(){
    }

    public Actor(Integer id, String name, String sex) {
        this.id = id;
        this.name = name;
        this.sex = sex;
    }

    public Integer getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getSex() {
        return sex;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Actor{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
}

下一步就是使用阿帕奇工具类了。

//使用Apache-DBUtils 工具类 + druid 完成 返回的结果是多行数据
public void testQueryMany() throws SQLException {
    //1.连接
    Connection connection = JDBCUtilsDruid.getConnection();
    //2.创建一个QueryRunner
    QueryRunner queryRunner = new QueryRunner();
    //3.执行相关方法,返回结果集
    String sql="select * from actor where id>=?";
    List<Actor> list =
            queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
    for(Actor actor:list){
        System.out.println(actor);
    }

    //4.释放资源
    JDBCUtilsDruid.close(null,null,connection);
}

对于List那一行的注解:

List<Actor> list =
        queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
/*
    1.query 方法就是执行sql语句,得到 ResultSet ---封装--> ArrayList集合中
    2.返回集合
    3.connection 连接
    4.sql:要执行的sql语句
    5.new BeanListHandler<>(Actor.class):将ResultSet根据Actor对象取出封装到ArrayList中(底层使用反射机制去获取对象的属性)
    6.最后的 1 给sql中 ? 赋值的,可以由多个值
    7.结果集底层得到的ResultSet,会在query中关闭,PreparedStatement也会关闭
 */

这是返回单行数据的方法:

//使用Apache-DBUtils 工具类 + druid 完成 返回的结果是单行记录(单个对象)
public void testQuerySingle() throws SQLException {
    //1.连接
    Connection connection = JDBCUtilsDruid.getConnection();
    //2.使用 Apache-DBUtils 类和接口,先引入jar包
    //3.创建一个QueryRunner
    QueryRunner queryRunner = new QueryRunner();
    //4.执行相关方法,返回单个对象
    String sql="select * from actor where id = ?";
    Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 1);
    //如果没有要查询的对象,会返回null
    System.out.println(actor);
    JDBCUtilsDruid.close(null,null,connection);
}

这是返回单行单列的方法:

//使用Apache-DBUtils 工具类 + druid 完成 返回的结果是单行单列,返回的是object
public void testScalar() throws SQLException {
    //1.连接
    Connection connection = JDBCUtilsDruid.getConnection();
    //2.使用 Apache-DBUtils 类和接口,先引入jar包
    //3.创建一个QueryRunner
    QueryRunner queryRunner = new QueryRunner();

    //4.执行相关方法,返回单个对象
    String sql="select name from actor where id = ?";

    Object query = queryRunner.query(connection, sql, new ScalarHandler(), 1);
    System.out.println(query);

    JDBCUtilsDruid.close(null,null,connection);
}

这是dml操作的方法:

//使用Apache-DBUtils 工具类 + druid 完成dml操作
public void testDML() throws SQLException {
    //1.连接
    Connection connection = JDBCUtilsDruid.getConnection();
    //2.使用 Apache-DBUtils 类和接口,先引入jar包
    //3.创建一个QueryRunner
    QueryRunner queryRunner = new QueryRunner();

    //4.组织sql语句完成dml
    String sql1="update actor set name=? where id = ?";
    String sql2="insert into actor values(?,?,?)";
    String sql3="delete from actor where id= ? ";

    //int affectedRow = queryRunner.update(connection, sql1, "德川裕太", 2);
    //int affectedRow = queryRunner.update(connection, sql2, 4,"野兽先辈","未知");
    int affectedRow = queryRunner.update(connection, sql3,5);
    /*
        1.执行dml的是queryRunner.update()
        2.返回值的是受影响行数,即生效的行数
     */
    System.out.println(affectedRow>0?"执行成功":"执行没有影响到表");

    //关闭资源
    JDBCUtilsDruid.close(null,null,connection);
}

3.源码

内容都写在注释上:

public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
    PreparedStatement stmt = null;  //定义PreparedStatement
    ResultSet rs = null;    //接收返回的ResultSet
    Object result = null;   //返回ArrayList

    try {
        stmt = this.prepareStatement(conn, sql);    //创建PreparedStatement
        this.fillStatement(stmt, params);   //对sql 进行?赋值
        rs = this.wrap(stmt.executeQuery());    //执行sql 返回结果集
        result = rsh.handle(rs);    //将结果集封装到ArrayList
    } catch (SQLException var33) {
        this.rethrow(var33, sql, params);
    } finally {
        try {
            this.close(rs);     //先关闭了ResultSet
        } finally {
            this.close((Statement)stmt);    //关闭了PreparedStatement
        }
    }

    return result;
}

十二.BasicDAO

1.介绍

首先我们要知道为什么需要它,我们为什么要用它。

要用它,肯定是因为它好,那么它相对于我们以前的写法有什么好处,或者说以前的写法有什么问题。

之前我们使用了阿帕奇+德鲁伊简化开发,但是这种方式仍有不足:

1.SQL语句是固定的,不能通过参数传入,通用性不好;

2.对于select操作,如果有返回值,返回类型不能固定,需要使用泛式;

3.如果表很多,业务需求复杂,不可能只靠一个Java类完成。

明白了以前的方法有什么不足,我们现在介绍BasicDAO。

DAO全称:data access object 数据访问对象

BasicDAO是一个通用类,这个类是用来做像ActorDAO的父类一样的。给每个表写一个DAO,把一些通用的方法写在BasicDAO中,一些特殊的方法和业务写在类对应的DAO中。

2.实现

这里只展示BasicDAO的写法:

public class BasicDAO<T> {
    private QueryRunner qr=new QueryRunner();

    //通用的dml方法,针对任意的表
    public int update(String sql,Object... parameters){
        Connection connection=null;
        try {
            connection= JDBCUtilsDruid.getConnection();

            int update = qr.update(connection, sql, parameters);
            return update;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtilsDruid.close(null,null,connection);
        }
    }

    //返回多个对象(查询的结果是多行),针对任意表
    public List<T> queryMulti(String sql,Class<T> clazz,Object... parameters){
        Connection connection=null;
        try {
            connection= JDBCUtilsDruid.getConnection();

            return qr.query(connection,sql,new BeanListHandler<T>(clazz),parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtilsDruid.close(null,null,connection);
        }
    }

    //查询单行结果的通用方法
    public T querySingle(String sql,Class<T> clazz,Object... parameters){
        Connection connection=null;
        try {
            connection= JDBCUtilsDruid.getConnection();

            return qr.query(connection,sql,new BeanHandler<T>(clazz),parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtilsDruid.close(null,null,connection);
        }
    }

    //查询单行单列的方法,即返回单值的方法
    public Object queryScalar(String sql,Object... parameters){
        Connection connection=null;
        try {
            connection= JDBCUtilsDruid.getConnection();

            return qr.query(connection,sql,new ScalarHandler(),parameters);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JDBCUtilsDruid.close(null,null,connection);
        }
    }
}

十三.总结

本篇文章的内容是循序渐进的。开始时介绍一些比较复杂的写法,但这些复杂的解法又是基础的解法,大家不要觉得烦,会了这些基础的解法后更容易理解后面的简洁写法。

开始我们介绍了JDBC的相关内容,教大家怎么连接数据库,给出了五种方法。

后面说了结果集是什么,这对后面内容的理解至关重要。

预处理 的出现是为了解决SQL注入的问题,引入了PreparedStatement接口,从此以后我们不会使用Statement接口了(当然Statement接口是PreparedStatement接口的父类)。

JDBCUtils 工具类 出现是为了解决我们反反复复连接和关闭数据库代码太过繁琐的问题,使用了工具类之后可以简化代码,让代码更简洁。

批处理(Batch) 的出现是为了解决一个一个输入语句到数据库太耗时的问题,将一堆SQL语句打包送入数据库可以节省大量时间。

下面就开始介绍连接池了。主要说了两个连接池,一个是C3P0,一个Druid(德鲁伊)。

连接池 的出现是为了解决数据大量输入造成的数据库崩溃或因异常没关数据库造成的内存泄露等问题。

ApDBUtils工具类 的出现是为了解决connection关闭后,结果集无法使用的问题。

BasicDAO 的出现是为了继续优化数据库连接。

可以看到,每一个新内容的加入其实都是为了解决一些问题,让数据库连接变得更简洁更高效。

上一篇:IDEA:Properties in parent definition are prohibited


下一篇:YOLOv8 Flask整合问题