JDBC和DBUtils框架的使用

课程目录

一、JDBC概述
二、JDBC基本操作
三、使用PreparedStatement处理CRUD
四、数据库连接池
五、Apache的DBUtils
六、Dao类

一、JDBC概述

1. 为什么需要JDBC

没有JDBC时:
在这里插入图片描述
有了JDBC后:
在这里插入图片描述

2. JDBC概述

JDBC:Java Database Connectivity,它是代表一组独立于任何数据库管理系统(Database Management System, DBMS)的API,声明在java.sql与javax.sql包中,是SUN(现在Oracle)提供的一组接口规范。由各个数据库厂商来提供实现类,这些实现类的集合构成了数据库驱动jar。
在这里插入图片描述

即JDBC技术包含两个部分:

1)java.sql包和javax.sql包中的API

因为为了项目代码的可移植性,可维护性,SUN公司从最初就制定了Java程序连接各种数据库的统一接口规范。这样的话,不管是连接哪一种DBMS软件,Java代码可以保持一致性。

2)各个数据库厂商提供的jar

因为各个数据库厂商的DBMS软件各有不同,那么内部如何通过sql实现增、删、改、查等管理数据,只有这个数据库厂商自己更清楚,因此把接口规范的实现交给各个数据库厂商自己实现。

3. JDBC使用步骤

3.1. 新建MySQL数据库和表(完成建表)

create database jdbc_test;use jdbc_test;create table user(id int primary key auto_increment,username varchar(20),password varchar(20),nickname varchar(20)
);INSERT INTO `USER` VALUES(null,'zs','123456','老张');
INSERT INTO `USER` VALUES(null,'ls','123456','老李');
INSERT INTO `USER` VALUES(null,'wangwu','123','老王');

3.2. 注册驱动

1)将DBMS数据库管理软件的驱动jar拷贝到项目的lib目录中
      根据不同的数据库版本进行驱动的选择

  • 例如:mysql-connector-java-5.1.36-bin.jar

2)把驱动jar添加到项目的build path中,即将jar包放入在src目录下新建的lib目录下,然后右键lib目录选择add as library

3)将驱动类加载到内存中

Class.forName("com.mysql.jdbc.Driver");

如果是8版本则将驱动改为:

Class.forName("com.mysql.cj.jdbc.Driver");

3.3. 获取Connection连接对象

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

2)mysql的url:jdbc:mysql://localhost:3306/数据库名?参数名=参数值

String url = "jdbc:mysql://localhost:3306/数据库名?参数名=参数值&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true";
如果用户使用了 sha256_password 认证,密码在传输过程中必须使用 TLS 协议保护,但是如果 RSA 公钥不可用,可以使用服务器提供的公钥;可以在连接中通过 ServerRSAPublicKeyFile 指定服务器的 RSA 公钥,或者AllowPublicKeyRetrieval=True参数以允许客户端从服务器获取公钥;但是需要注意的是 AllowPublicKeyRetrieval=True可能会导致恶意的代理通过中间人攻击(MITM)获取到明文密码,所以默认是关闭的,必须显式开启
修改Timezone使MySQL的timezone和idea的一致后可以不写&serverTimezone=UTC&allowPublicKeyRetrieval=true

3)jdbc:mysql://localhost:3306/testdb?useUnicode=true&characterEncoding=utf8(如果JDBC程序与服务器端的字符集不一致,会导致乱码,那么可以通过参数指定服务器端的字符集)

String url = "jdbc:mysql://localhost:3306/jdbc_testuseUnicode=true&characterEncoding=utf8";
String user = "root";
String password = "root";
//获得连接
Connection connection = DriverManager.getConnection(url, user, password);

3.4. 执行sql并处理结果

编写sql

String sql = "select *from user";

创建Statement对象

Statement statement = connection.createStatement();

使用Statement对象执行sql

1)增删改:调用executeUpate方法

2)查询:调用executeQuery方法

ResultSet resultSet = statement.executeQuery(sql);

处理结果

1)增删改:返回的是整数值,表示受到影响的数据条数

2)查询:返回ResultSet结果

  • boolean next():判断是否还有下一行
  • getString(字段名或序号),getInt(字段名或序号),getObject(字段名或序号)
while (resultSet.next()) {//获取每一列的数据System.out.println(resultSet.getObject(1));System.out.println(resultSet.getObject(2));System.out.println(resultSet.getObject(3));System.out.println(resultSet.getObject(4));
}

3.5. 释放资源

原则是后创建的资源先关闭,我们会依次关闭ResultSet、Statement、Connection对象

//关闭资源
if(resultSet != null){resultSet.close();}
if(statement  != null){statement .close();
}
if(connection != null){connection.close();
}

3.6. JDBC使用小结

在这里插入图片描述

二、JDBC基本操作

1. 执行添加的SQL语句

IDEA/数据库连接

@Test
public void insert() throws Exception {//增加  insert into user values(null,'tq','77777','田七');//1.注册驱动// Class.forName("com.mysql.jdbc.Driver");Class.forName("com.mysql.cj.jdbc.Driver");//2.获得连接String url = "jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";String user = "root";String password = "root";Connection connection = DriverManager.getConnection(url, user, password);//3.创建执行sql语句对象Statement statement = connection.createStatement();//4.执行sql语句String sql = "insert into user values(null,'tq','77777','田七')";int rows = statement.executeUpdate(sql);System.out.println("几行收影响=" + rows);//5.释放资源if (statement != null) {statement.close();}if (connection != null) {connection.close();}}

2. 执行删除的SQL语句

@Test
//删除id为5的用户
public void delete() throws Exception {//1.注册驱动// Class.forName("com.mysql.jdbc.Driver");Class.forName("com.mysql.cj.jdbc.Driver");//2.获得连接String url = "jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";String user = "root";String password = "root";Connection connection = DriverManager.getConnection(url, user, password);//3.创建执行sql语句对象Statement statement = connection.createStatement();//4.执行sql语句String sql = "delete from user where id = 5";statement.executeUpdate(sql);//5.释放资源if (statement != null) {statement.close();}if (connection != null) {connection.close();}
}

3. 执行修改的SQL语句

@Test
//更新 把id为4的用户的密码改成88888888
public void update() throws Exception {//1.注册驱动//Class.forName("com.mysql.jdbc.Driver");Class.forName("com.mysql.cj.jdbc.Driver");//2.获得连接String url = "jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";String user = "root";String password = "root";Connection connection = DriverManager.getConnection(url, user, password);//3.创建执行sql语句对象Statement statement = connection.createStatement();//4.执行sql语句String sql = "update user set password = '88888888' where id = 4";statement.executeUpdate(sql);//5.释放资源if (statement != null) {statement.close();}if (connection != null) {connection.close();}}

4. 执行查询单行数据的SQL语句

要求: 将查询到的结果封装到User对象中

User类

public class User {private int id;private String username;private String password;private String nickname;//提供get/set方法 Alt+Insertpublic User() {}public User(int id, String username, String password, String nickname) {this.id = id;this.username = username;this.password = password;this.nickname = nickname;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getNickname() {return nickname;}public void setNickname(String nickname) {this.nickname = nickname;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' +", nickname='" + nickname + '\'' +'}';}
}

JDBC 代码

@Test
public void query() throws Exception {//查询id为1的用户//1.注册驱动//Class.forName("com.mysql.jdbc.Driver");Class.forName("com.mysql.cj.jdbc.Driver");//2.获得连接String url = "jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";String username = "root";String password = "root";Connection connection = DriverManager.getConnection(url, username, password);//3.创建执行sql语句对象Statement statement = connection.createStatement();//4.执行sql语句String sql = "select * from user where id = 1";ResultSet resultSet = statement.executeQuery(sql);User user = null;while (resultSet.next()) {//每遍历一次,就是一条数据.就是一个User对象(有数据才有user)user = new User(resultSet.getInt("id"),resultSet.getString("username"),resultSet.getString("password"),resultSet.getString("nickname"));}//获得用户名System.out.println("用户名="+user.getUsername());//5.释放资源if (resultSet != null) {resultSet.close();}if (statement != null) {statement.close();}if (connection != null) {connection.close();}
}

5. 执行查询多行数据的SQL语句

要求: 将查询到的多行数据封装到List<User>中

@Test
//查询所有用户
public void queryAll() throws Exception {//1.注册驱动//DriverManager.registerDriver(new Driver());//类全限定名(带包名), 加载Driver类, 静态代码块就会执行, 驱动就注册了//Class.forName("com.mysql.jdbc.Driver");Class.forName("com.mysql.cj.jdbc.Driver");//2.获得连接(连接数据库)//连接数据库路径String url="jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8";String username = "root";String password= "root";Connection connection = DriverManager.getConnection(url, username, password);//3.创建执行sql语句的对象Statement statement = connection.createStatement();//4.执行sql语句, 处理结果String sql = "select * from user";ResultSet resultSet = statement.executeQuery(sql);List<User> list = new ArrayList<User>();while (resultSet.next()){//每遍历一次就是一条数据, 就封装成一个User对象. 把封装的每一个User添加到list集合里面User user = new User(resultSet.getInt("id"),resultSet.getString("username"),resultSet.getString("password"),resultSet.getString("nickname"));list.add(user);}//获得第二个用户的用户名System.out.println(list.get(1));//5.释放资源(先创建的后关闭)if(resultSet != null){resultSet.close();}if(statement != null){statement.close();}if(connection != null){connection.close();}}

三、使用PreparedStatement处理CRUD

1. Statement存在的问题

每次执行一个SQL语句都需要先编译

String sql1 = "insert into user values(null,'tq','77777','田七')";
String sql2 = "insert into user values(null,'zl','666666','赵六')";
String sql3 = "insert into user values(null,'zs','333333','张三')";
//如果使用Statement执行上述SQL语句需要编译三次

sql注入漏洞

String username = "hahahahha' or '1'='1"
String sql = "SELECT * FROM user where username='" + username + "'";
//结果会把所有数据都查询出来
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);

2. PreparedStatement解决问题

预编译: PreparedStatement会先对参数化的SQL语句进行预编译,执行SQL语句的时候不会再进行编译

String sql = "insert into user values(null,?,?,?)";
//预编译
PreparedStatement pstm = connection.prepareStatement(sql);
//后续设置参数、执行添加多少条数据都不会再重新编译

防止SQL注入: PreparedStatement在进行预编译的时候,就已经确定好了SQL语句的格式,不会再因为后续的操作改变SQL语句的格式

String username = "hahahahha' or '1'='1"
String sql = "SELECT * FROM user where username=?";
//即使输入'张三' or '1'= '1'也没问题
PreparedStatement pst = conn.prepareStatement(sql);//中间加入设置?的值
pst.setObject(1, username);ResultSet rs = pst.executeQuery();

3. 获取自增长键值

3.1. 获取自增长键值的应用场景

主要使用在一些复杂的业务中,在添加完主键表的一条数据之后,要获取到这条数据的主键值,然后将该值添加进外键表的外键字段。

3.2. 获取自增长键值的步骤

步骤一:在预编译的时候,指定要返回自增长的key

PreparedStatement pst = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);

步骤二:在执行完添加数据的SQL语句之后,通过PreparedStatement的对象调用getGeneratedKeys()方法来获取自增长键值,遍历结果集

ResultSet rs = pst.getGeneratedKeys();

步骤三:遍历获取自增长的键值

if(rs.next()){Object key = rs.getObject(1);System.out.println("自增的key值did =" + key);
}

示例代码

数据库:id时auto_increment,自增长
在这里插入图片描述

public class TestAutoIncrement {public static void main(String[] args) throws Exception{//1、注册驱动Class.forName("com.mysql.jdbc.Driver");//2、获取连接Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8", "root", "123456");//3、执行sqlString sql = "insert into user values(null,?,?,?)";/** 这里在创建PreparedStatement对象时,传入第二个参数的作用,就是告知服务器端* 当执行完sql后,把自增的key值返回来。返回给了pst,通过pst.getGeneratedKeys();* 可以获取自增的key值*/PreparedStatement pst = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);//设置?的值pst.setObject(1, "aobama");pst.setObject(2, "12345678");pst.setObject(3, "圣枪游侠");//执行sqlint len = pst.executeUpdate();//返回影响的记录数if(len>0){//从pst中获取到服务器端返回的键值ResultSet rs = pst.getGeneratedKeys();//因为这里的key值可能多个,因为insert语句可以同时添加多行,所以用ResultSet封装//这里因为只添加一条,所以用if判断if(rs.next()){Object key = rs.getObject(1);System.out.println("自增的key值did =" + key);}}//4、关闭pst.close();conn.close();}
}

4. 批处理

4.1. 批处理优势和应用场景

批处理相比较单独一条条执行SQL语句来说,其效率高很多。批处理一般会使用在批量添加多条数据和批量修改多条数据。

4.2. 批处理的具体操作步骤

步骤一:在url中要加一个参数 rewriteBatchedStatements=true,那么此时url就变成了

jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true

步骤二:在完成所有参数设置之后,调用PreparedStatement的addBatch()方法,添加到批处理中

步骤三:最后执行PreparedStatement的executeBatch()方法执行批处理语句

public class TestBatch {public static void main(String[] args) throws Exception{long start = System.currentTimeMillis();//例如:在部门表t_department中添加1000条模拟数据//1、注册驱动Class.forName("com.mysql.jdbc.Driver");//2、获取连接Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true", "root", "123456");//3、执行sql,sql语句不能用value,只能用valuesString sql = "insert into user values(null,?,?,?)";PreparedStatement pst = conn.prepareStatement(sql);//设置?的值for (int i = 1; i <=1000; i++) {pst.setObject(1, "aobama"+i);pst.setObject(2, "000000"+i);pst.setObject(3, "圣枪游侠"+i);pst.addBatch();//添加到批处理一组操作中,攒一块处理}pst.executeBatch();//4、关闭pst.close();conn.close();long end = System.currentTimeMillis();System.out.println("耗时:" + (end - start));//耗时:821}
}

5. 事务

5.1. 事务操作的步骤

1)执行逻辑单元之前先开启事务

2)逻辑单元执行完毕,没有出现异常则提交事务

3)逻辑单元执行过程中出现异常,则回滚事务

5.2. 事务相关API

Connection中与事务有关的方法说明
setAutoCommit(boolean autoCommit)参数是true或false 如果设置为false,表示关闭自动提交,相当于开启事务; 类似sql里面的 start transaction;
void commit()提交事务; 类似sql里面的 commit;
void rollback()回滚事务; 类似sql里面的 rollback;

5.3. 使用JDBC的事务完成转账案例

事务中最经典的案例就是转账操作,如果A向B转了100块,在A转出了后系统出错了,B没有收到,那么A就平白无故少了100块钱了,这是非常严重的错误。这时候就需要引入事务对转账操作进行处理。
事务的四大特性如下:

  • 原子性:一个事务是一个不可分割的工作单位,事务中包括的所有操作要么都做,要么都不做。

  • 一致性:事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

  • 隔离性:一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

  • 持久性:持久性也称永久性,指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

所以转账是一个事务,也就具备了以上的四个特性,具备的原子性可以保证A转出100块后,B要收到100块,这个转账操作才算完成,如果B收不到100块,那么就将A转出的100块退回给他,之所以能够退回是因为这些操作都记录到了数据库中的日志表中,当转账失败事务执行回退时,也就是撤销该转账操作时,日志表中的该条操作就执行一个逆操作,增—>删,加—>减等。引入的事务操作主要就是保证原子性,其他特性数据库帮我们完成了,下面看下具体的代码逻辑。

5.3.1. 准备数据

use jdbc_test;
create table account(id int primary key auto_increment,name varchar(20),money double
);insert into account values (null,'zs',1000);
insert into account values (null,'ls',1000);
insert into account values (null,'ww',1000);

5.3.2. 代码实现

package com.atguigu.jdbc;import org.junit.Test;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;public class TestTransaction {@Testpublic void testTransfer() throws Exception {//测试转账//1. 注册驱动Class.forName("com.mysql.jdbc.Driver");//2. 获得连接Connection conn = DriverManager.getConnection("jdbc:mysql:///day04?characterEncoding=utf8","root","123456");//3. 预编译sql语句String sql = "update account set money=money+? where name=?";//改变用户的金额PreparedStatement preparedStatement = conn.prepareStatement(sql);//开启事务conn.setAutoCommit(false);try {//zs扣款500preparedStatement.setObject(1,-500);preparedStatement.setObject(2,"zs");//执行zs扣款的sql语句preparedStatement.executeUpdate();//ls收款500preparedStatement.setObject(1,500);preparedStatement.setObject(2,"ls");preparedStatement.executeUpdate();//提交事务conn.commit();} catch (Exception e) {e.printStackTrace();conn.rollback();}finally {//还原connection的AutoCommit为trueconn.setAutoCommit(true);}//关闭资源preparedStatement.close();conn.close();}
}

保证原子性的关键操作就是使用一个try…catch…将转账操作进行包裹,当抛出异常时执行回退逻辑,也就是撤销之前事务中执行的全部操作流程。如果没有异常则提交事务,将SQL操作所产生的影响进行持久化操作。

四、数据库连接池

1. 连接池概述

什么是连接池?

连接池是connection对象的缓冲区,它里面会存放一些connection,当我们Java程序需要使用connection的时候,如果连接池中有则直接从连接池获取,不需要去新创建connection了。连接池让Java程序能够复用连接、管理连接。

为什么要使用连接池?

1)因为每次创建和销毁连接都会带来较大的系统开销

2)每次创建和销毁连接都要消耗大概0.05~1s的时间。

3)可以防止大量用户并发访问数据库服务器。

2. 连接池的原理

  1. 连接池维护着两个容器空闲池活动池
  2. 空闲池用于存放未使用的连接,活动池用于存放正在使用的连接,活动池中的连接使用完之后要归还回空闲池
  3. 当Java程序需要连接时,先判断空闲池中是否有连接,如果空闲池中有连接则取出一个连接放置到活动池供Java程序使用
  4. Java程序需要连接时,如果空闲池中没有连接了,则先判断活动池的连接数是否已经达到了最大连接数,如果未达到最大连接数,则会新创建一个连接放置到活动池,供Java程序使用
  5. 如果空闲池中没有连接了,活动池中的连接也已经达到了最大连接数,则不能新创建连接了,那么此时会判断是否等待超时,如果没有等待超时则需要等待活动池中的连接归还回空闲池
  6. 如果等待超时了,则可以采取多种处理方式,例如:直接抛出超时异常,或者将活动池中使用最久的连接移除掉归还回空闲池以供Java程序使用

3. 连接池的实现

3.1. DataSource接口

JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口(通常被称为数据源),所有的Java数据库连接池都需要实现该接口。该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现。

3.2. 常见的数据库连接池

  1. DBCP 是Apache提供的数据库连接池,速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持
  2. C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以
  3. Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
  4. HikariCP 俗称光连接池,是目前速度最快的连接池
  5. Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池

3.3. Druid连接池的使用

第一步:加入jar包(druid-1.1.10.jar

第二步:创建druid连接池的配置文件druid.properties文件,放置到类路径下
类路径是什么?

  • 方式一:可以在项目下通过new选择resource bundle,就会创建一个resource文件夹,这就是类路径了:
    在这里插入图片描述
  • 方式二:先创建resource目录,然后右键选择Mark directory as —> Resources Boot
    在这里插入图片描述
    druid.properties内容如下:具体的配置作用讲解后面3.4会提到
    driverClassName=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/test?
    username=root
    password=123456
    initialSize=5
    maxActive=10
    maxWait=1000
    

第三步:在项目创建一个工具包utils,在里面新建JDBCUtil工具类:

package com.drimwai.utils;import com.alibaba.druid.pool.DruidDataSourceFactory;import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;public class JDBCUtil {private static DataSource dataSource;static {try {//读取配置文件,创建连接池//1. 创建一个Properties对象,让其去读取druid.properties文件Properties properties = new Properties();//2. 使用相对路径来将druid.properties配置文件转成字节输入流,我们可以使用类加载器来读取类路径下文件//JDBCUtil.class.getClassLoader()表示获取ClassLoader对象InputStream inputStream = JDBCUtil.class.getClassLoader().getResourceAsStream("druid.properties");//使用properties对象加载流properties.load(inputStream);//使用DruidDataSourceFactory创建连接池dataSource = DruidDataSourceFactory.createDataSource(properties);} catch (Exception e) {e.printStackTrace();}}/*** 获取连接池* @return*/public static DataSource getDataSource(){return dataSource;}/*** 获取连接* @return*/public static Connection getConnection(){try {return dataSource.getConnection();} catch (SQLException e) {e.printStackTrace();throw new RuntimeException(e.getMessage());}}/*** 归还连接的方法* @param connection*/public static void releaseConnection(Connection connection){try {connection.close();} catch (SQLException e) {e.printStackTrace();throw new RuntimeException(e.getMessage());}}
}

3.4. Druid.Properties连接池的配置参数列表

配置缺省说明
name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
url连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username连接数据库的用户名
password连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter
driverClassName根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive8最大连接池数量
maxIdle8已经不再使用,配置了也没效果
minIdle最小连接池数量
maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatementsfalse是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements-1要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdlefalse建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls物理连接初始化的时候执行的sql
exceptionSorter根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

3.6. 连接池使用总结

  1. 拷贝加入druid的jar包
  2. 拷贝druid的配置文件到类路径,并修改
  3. 拷贝JDBCUtil工具类
  4. 在需要连接的地方编写Connection conn = JDBCUtil.getConnection();此时拿到的连接就是从连接池拿的
  5. 连接使用完毕之后,调用JDBCUtil.releaseConnection(conn);归还连接
  6. 在需要使用连接池的地方调用JDBCUtil.getDataSource();获取连接池

五、Apache的DBUtils使用

1. DBUtils的概述

本节的DBUtil是由Apache所提供的jar包实现了对数据库增删改查操作,而第六节的BaseDao是由我们基于DBUtil自定义的用于实现通用的数据库增删改查操作。上面的JDBCUtils只是实现了数据库连接池的创建和连接的获取、释放等操作,并没有实现对数据的操作,而DBUtils和BaseDao就是实现对数据的操作的,但他们没有实现连接的获取以及连接池的创建等操作,所以要使用apache的DBUtil或者BaseDao都要先实现上面的Druid连接池的使用

Apache的DBUtils的使用

第一步:先根据上面的Druid连接池的使用创建JDBCUtils工具包,主要是要用到JDBCUtils里创建的Connection连接

第二步:导如以下包
在这里插入图片描述

第三步:创建一个类,用以下方法便可实现Apache的DBUtils的使用的增删改操作,下面有查功能,这里不赘述。

//1. 创建QueryRunner,并且传入连接池对象
QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());
//2. 执行SQL语句
String sql = "delete from user where id=?";
queryRunner.update(sql,1);

commons-dbutils是 Apache 组织提供的一个开源 JDBC工具框架(类库),它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。

其中QueryRunner类封装了SQL的执行,是线程安全的。

(1)可以实现增、删、改、查、批处理、

(2)考虑了事务处理需要共用Connection。

(3)该类最主要的就是简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。

2. DBUtils执行增删改的SQL语句

2.1 API介绍

  1. QueryRunner() ,创建QueryRunner对象,用于执行SQL语句
  2. QueryRunner的update(Connection conn, String sql, Object… params)方法,用于执行增删改的SQL语句

2.2 代码实现

@Test
public void testAddUser() throws SQLException {//目标:往user表中添加一行数据//1. 创建QueryRunner需先导入上面提到的jar包QueryRunner queryRunner = new QueryRunner();//2. 执行SQL语句String sql = "insert into user values (null,?,?,?)";Connection conn = JDBCUtil.getConnection();int i = queryRunner.update(conn, sql, "aolafu", "123456", "狂战士");//关闭连接JDBCUtil.releaseConnection(conn);
}@Test
public void testAddUserAnother() throws SQLException {//1. 创建QueryRunner,并且传入连接池对象QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());//2. 执行SQL语句String sql = "insert into user values (null,?,?,?)";queryRunner.update(sql,"neisesi","123456","狗头");//这种方式的缺点是用不了事务
}@Test
public void testDeleteUser() throws SQLException {//1. 创建QueryRunner,并且传入连接池对象QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());//2. 执行SQL语句String sql = "delete from user where id=?";queryRunner.update(sql,1);
}

3. DBUtils执行批处理

3.1 API介绍

  1. public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: 支持批处理INSERT, UPDATE, or DELETE语句
  2. public <T> T insertBatch(Connection conn,String sql,ResultSetHandler<T> rsh,Object[][] params) throws SQLException:只支持INSERT语句

3.2 代码实现

@Test
public void testBatch() throws SQLException {//使用DBUtils执行批处理//二维数组:数组内的元素还是数组{{1,2},{3,4},{5,6}}//批量往user表中添加五千条数据//1. 创建QueryRunner对象QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());//2. 编写SQL语句String sql = "insert into user (username,password,nickname) values (?,?,?)";//3. 设置二维数组参数:第一维表示我们需要处理多少条数据,第二维表示sql语句有多少个参数Object[][] params = new Object[5000][3];for(int i=0;i<5000;i++){//params[i]表示插入的第i条数据//params[i][1]表示插入的第i条数据的第一个问号处的参数params[i][0] = "username"+i;params[i][1] = "password"+i;params[i][2] = "nickname"+i;}//执行批处理queryRunner.batch(sql,params);
}

4. 使用QueryRunner类实现查询

4.1. API介绍

  1. query(String sql, ResultSetHandler rsh, Object… params) ,执行查询 select
  2. ResultSetHandler结果集处理类
Handler类型说明
ArrayHandler将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这条记录中的每一个字段的值
ArrayListHandler将结果集中的每一条记录都封装到一个Object[]数组中,将这些数组在封装到List集合中。
BeanHandler将结果集中第一条记录封装到一个指定的javaBean中。
BeanListHandler将结果集中每一条记录封装到指定的javaBean中,将这些javaBean在封装到List集合中
ColumnListHandler将结果集中指定的列的字段值,封装到一个List集合中
KeyedHandler将结果集中每一条记录封装到Map<String,Object>,在将这个map集合做为另一个Map的value,另一个Map集合的key是指定的字段的值。
MapHandler将结果集中第一条记录封装到了Map<String,Object>集合中,key就是字段名称,value就是字段值
MapListHandler将结果集中每一条记录封装到了Map<String,Object>集合中,key就是字段名称,value就是字段值,在将这些Map封装到List集合中。
ScalarHandler它是用于单个数据。例如select count(*) from 表。

4.2. 代码实现

@Test
public void testFindById() throws SQLException {//使用DBUtils执行查询的SQL语句,将查询到的一条数据封装到User对象中//1. 创建QueryRunner,并且传入连接池对象QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());//2. 执行SQL语句String sql = "select * from user where id=?";User user = queryRunner.query(sql, new BeanHandler<>(User.class), 2);System.out.println(user);
}@Test
public void testFindAll() throws SQLException {//查询多条数据,封装到List<JavaBean>//1. 创建QueryRunner,并且传入连接池对象QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());//2. 执行SQL语句String sql = "select * from user";List<User> userList = queryRunner.query(sql, new BeanListHandler<>(User.class));System.out.println(userList);
}@Test
public void testFindCount() throws SQLException {//查询数据条数//1. 创建QueryRunner,并且传入连接池对象QueryRunner queryRunner = new QueryRunner(JDBCUtil.getDataSource());//2. 执行SQL语句String sql = "select count(id) from user";long count = queryRunner.query(sql, new ScalarHandler<>());System.out.println(count);
}

六、自定义通用BaseDao

Dao是data access Object的缩写,中文翻译为数据访问对象,我们会将操作持久层的代码编写到对应的Dao类中。

BaseDao类:(所有Dao类的父类)需要先实现上面的Druid连接池的使用,注意BaseDao是基于DBUtils自定义实现的,所以BaseDao是需要依赖DBUtils的,所以也需要引入对应的apache的jar包。

BaseDao实现了通用的数据库增删改查操作。

import com.atguigu.bookstore.utils.JDBCUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;public class BaseDao<T> {private QueryRunner queryRunner = new QueryRunner();/*** 批处理方法* @param sql* @param paramArr* @return*/public int[] batchUpdate(String sql,Object[][] paramArr){Connection conn = JDBCUtil.getConnection();try {return queryRunner.batch(conn,sql,paramArr);} catch (SQLException e) {e.printStackTrace();throw new RuntimeException(e.getMessage());} finally {JDBCUtil.releaseConnection(conn);}}/*** 执行增删改的sql语句* @param sql* @param params* @return*/public int update(String sql,Object... params){Connection conn = JDBCUtil.getConnection();//执行增删改的sql语句,返回受到影响的行数try {return queryRunner.update(conn,sql,params);} catch (SQLException e) {e.printStackTrace();throw new RuntimeException(e.getMessage());} finally {JDBCUtil.releaseConnection(conn);}}/*** 执行查询一行数据的sql语句,将结果集封装到JavaBean对象中* @param clazz* @param sql* @param params* @return*/public T getBean(Class<T> clazz,String sql,Object... params){Connection conn = JDBCUtil.getConnection();try {return queryRunner.query(conn,sql,new BeanHandler<>(clazz),params);} catch (SQLException e) {e.printStackTrace();throw new RuntimeException(e.getMessage());} finally {JDBCUtil.releaseConnection(conn);}}/*** 执行查询多行数据的sql语句,并且将结果集封装到List<JavaBean>* @param clazz* @param sql* @param params* @return*/public List<T> getBeanList(Class<T> clazz, String sql, Object... params){Connection conn = JDBCUtil.getConnection();try {return queryRunner.query(conn,sql,new BeanListHandler<>(clazz),params);} catch (SQLException e) {e.printStackTrace();throw new RuntimeException(e.getMessage());} finally {JDBCUtil.releaseConnection(conn);}}
}

测试代码如下:

@Test
public void testUpdate() {BaseDao<Object> baseDao = new BaseDao<>();String sql = "insert into t_user(user_name,user_pwd) values(?,?)";int count = baseDao.update(sql, "罗志祥", "789456");System.out.println("count = " + count);
}@Test
public void testGetBean() {//这里的User表示数据类型,之前已经创建了一个User类了,用来存放用户的 id ,username ,password, nicknameBaseDao<User> baseDao = new BaseDao<>();// user_id userId// user_name userName// user_pwd userPwdString sql = "select user_id userId,user_name userName,user_pwd userPwd from t_user where user_id=?";User user = baseDao.getBean(User.class, sql, 2);System.out.println("user = " + user);
}@Test
public void testGetBeanList() {//这里的User表示数据类型,之前已经创建了一个User类了,用来存放用户的 id ,username ,password, nicknameBaseDao<User> baseDao = new BaseDao<>();String sql = "select user_id userId,user_name userName,user_pwd userPwd from t_user";List<User> userList = baseDao.getBeanList(User.class, sql);for (User user : userList) {System.out.println("user = " + user);}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/81501.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

东芝电视Z750的音画效果好吗?调校的够真实吗?

精准匹配声音与画面,呈现“音画合一”的影院级视听盛宴,东芝电视Z750真的很不错,东芝电视拥有70余年的原色调校技术,色彩看起来细腻且舒服,饱和度和景深等都处理的很恰当,而且还有火箭炮音响系统,也是经过日本专业调校的,针对不同家居场景,都有不同的声音处理方案,让我们听到的…

竞赛选题 基于深度学习的人脸表情识别

文章目录 0 前言1 技术介绍1.1 技术概括1.2 目前表情识别实现技术 2 实现效果3 深度学习表情识别实现过程3.1 网络架构3.2 数据3.3 实现流程3.4 部分实现代码 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学习的人脸表情识别 该项目较…

Flutter——启动页白屏的优化

flutter启动页白屏的优化&#xff1a;使用图片替代白屏 结构图 核心的代码如上图&#xff0c;修改两个launch_background.xml里的代码为&#xff1a; <item><bitmapandroid:gravity"center"android:src"mipmap/ic_launcher" /></item>…

数据库直连提示 No suitable driver found for jdbc:postgresql

背景&#xff1a;我在代码里使用直连的方式在数据库中创建数据库等&#xff0c;由于需要适配各个数据库服务所以我分别兼容了mysql、postgresql、oracal等。但是在使用过程中会出现错误&#xff1a; No suitable driver found for jdbc:postgresql 但是我再使用mysql的直连方式…

AutoAnimate - 无需任何配置,一行代码自动为元素添加优雅的过渡动画,可以搭配 Vue / React 和 Sevelt 使用

这个动画库只要一行代码就可以自动在我们的组件中添过渡动画&#xff0c;为什么这么省事高效呢&#xff1f; AutoAnimate 是一个无需任何配置&#xff0c;自动为我们开发的 Web 项目添加平滑过渡动画的 JavaScript 工具库。AutoAnimate 和之前推荐的一些 js 动画库相比&#x…

UINT64整型数据在格式化时使用了不匹配的格式化符%d导致其他参数无法打印的问题排查

目录 1、问题描述 2、格式化函数内部解析待格式化参数的完整机制说明 2.1、传递给被调用函数的参数是通过栈传递的 2.2、格式化函数是如何从栈上找到待格式化的参数值&#xff0c;并完成格式化的&#xff1f; 2.3、字符串格式化符%s对应的异常问题场景说明 2.4、为了方便…

鸟哥的LInux私房菜 基础学习篇 第四版 学习笔记

第一章 目前被称为纯种的Unix指的是System V以及BSD这两套软件。 要实现多任务的环境&#xff0c;除了硬件&#xff08;主要是CPU&#xff09;需要能够具有多任务的特性外&#xff0c;操作系统也需要支持这个功能。 如果网络有问题时&#xff0c;去/var/log目录查日志。 第二…

腾讯轻联:带你创造属于自己的AI小助手

陈老老老板&#x1f934; &#x1f9d9;‍♂️本文专栏&#xff1a;生活&#xff08;主要讲一下自己生活相关的内容&#xff09;生活就像海洋,只有意志坚强的人,才能到达彼岸。 &#x1f9d9;‍♂️本文简述&#xff1a;参加腾讯全球数字生态大会&#xff0c;了解到腾讯轻联企业…

易基因直播预告|细菌微生物基因表达调控表观研究易基因科技

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 DNA甲基化是在半个多世纪前在细菌中发现的。DNA碱基可以作为一个表观遗传调节因子——也就是说&#xff0c;它可以赋予相同的基因序列不同的和可逆的调控状态。在真核生物中&#xff0c;…

C++ -- 学习系列 std::array 容器

1. std::array 是什么&#xff1f; array 容器是 C 11 标准中新增的序列容器&#xff0c;简单地理解&#xff0c;它就是在 C 普通数组的基础上&#xff0c;添加了一些成员函数和全局函数。在使用上&#xff0c;它比普通数组更安全&#xff0c;且效率并没有因此变差。 与数组一…

【环境配置】基于Docker配置Chisel-Bootcamp环境

文章目录 Chisel是什么Chisel-Bootcamp是什么基于Docker配置Chisel-Bootcamp官网下载Docker安装包Docker换源启动Bootcamp镜像常用docker命令 可能产生的问题 Chisel是什么 Chisel是Scala语言的一个库&#xff0c;可以由Scala语言通过import引入。 Chisel编程可以生成Verilog代…

c语言练习58:⾃定义类型:结构体

⾃定义类型&#xff1a;结构体 结构体的概念 结构是⼀些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量。 结构体是一个种自定义的数据类型&#xff0c;它可以由很多个默认数据类型组成。它主要用于描述复杂场景下的变量。 例如&#xff0c;想…

文件上传漏洞~操作手册

目录 上传文件一般过滤方式 客服端校验 服务端校验 黑白名单机制 常规文件上传漏洞绕过 客户端绕过 1.游览器禁用JavaScript 2.正常burp suite抓包改包 服务端绕过 1.Content-Type绕过 2.黑名单绕过 1&#xff09;命名规则绕过 2&#xff09;大小写绕过 3&#x…

C++ 太卷,转 Java?

最近看到知乎、牛客等论坛上关于 C 很多帖子&#xff0c;比如&#xff1a; 2023年大量劝入C 2023年还建议走C方向吗&#xff1f; 看了一圈&#xff0c;基本上都是说 C 这个领域唯一共同点就是都使用 C 语言&#xff0c;其它几乎没有相关性。 的确是这样&#xff0c;比如量化交…

VM ware中Linux连网

在公司时,想搭建一下docker,结果发现连不上网,那就自己动手操作一下 一、联网配置 1.1 ping 发现ping不通&#xff0c;说明虚拟机此时无法与外界网络互连&#xff0c;需要修改设置联网 1.2 ifconfig查看网络配置 注意第一行显示的是ens33还是ens32,后面配置会用到 1.3 获取ro…

达梦数据库如何收集统计信息?

1、收集shema统计信息 CALL DBMS_STATS.GATHER_SCHEMA_STATS(大写的用户名,100,TRUE,FOR ALL COLUMNS SIZE AUTO); 2、收集表的统计信息 CALL DBMS_STATS.GATHER_TABLE_STATS(大写的用户名, 大写的表名,NULL,100,TRUE,FOR ALL COLUMNS SIZE AUTO); 3、收集索引的统计信息 CALL…

WebGL模型视图投影矩阵

WebGL透视投影_山楂树の的博客-CSDN博客中的PerspectiveView代码一个问题是&#xff0c;我们用了一大段枯燥的代码来定义所有顶点和颜色的数据。示例中只有6个三角形&#xff0c;我们还可以手动管理这些数据&#xff0c;但是如果三角形的数量进一步增加的话&#xff0c;那可真就…

如何在 Excel 中求平方根

需要在 Excel 中求一个数字的平方根吗&#xff1f;使用几个内置的 Excel 函数和公式可以轻松计算平方根。在本分步指南中&#xff0c;您将学习在 Excel 中计算平方根的 5 种不同方法&#xff0c;包括使用 SQRT 函数、POWER 函数、指数公式、VBA 代码和 Power Query。跟随教程&a…

项目知识点总结-分页(三)

后端分页查询接口&#xff1a; Controller Service&#xff1a; Mapper&#xff1a; //分页搜索会议的方法List<SearchMeeting> getAllSearchMeeting(Param("sm") SearchMeeting searchMeeting,Param("page") Integer page,Param("pageSize&q…

games101 作业2

题目 光栅化一个三角形 1. 创建三角形的 2 维 bounding box。 2. 遍历此 bounding box 内的所有像素&#xff08;使用其整数索引&#xff09;。然后&#xff0c;使用像素中心的屏幕空间坐标来检查中心点是否在三角形内。 3. 如果在内部&#xff0c;则将其位置处的插值深度值 (…