JAVA入门基础_JDBC和数据库连接池_基于MYSQL
JDBC概述
JDBC是什么?
-
JDBC实际上就是为了能够访问不同的数据库,而提供的一套接口规范。
-
各个数据库厂商实现这套接口规范再提供相对应的jar包让JAVA程序能够操作对应的数据库。
-
学习JDBC就是在学习这一套接口规范,变成面向接口编程。
-
既然是外部为JAVA提供这些jar包,因此这些接口规范一般都在 java.sql 和 javax.sql包中
JDBC快速入门
- 既然MYSQL实现了JDBC这套接口的规范,那么当然我们需要用到MYSQL实现这套接口的jar包
mysql-connector-java-5.1.40.jar
使用JDBC连接数据库并进行操作的5个步骤
-
(1)注册相对应的数据库驱动
-
(2)获取连接对象
-
(3)获取到命令对象(操作数据库的对象)
-
(4)使用命令对象对数据库进行操作(若是查询操作会返回结果集)
-
(5)关闭连接(包括结果集、命令对象、连接对象)
获取数据库连接的最原始的方式
import com.mysql.jdbc.Driver;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
/**
* @author codeStars
* @date 2022/8/17 9:35
*/
public class Test {
public static void main(String[] args) throws Exception{
// 准备连接数据库需要的数据
String url = "jdbc:mysql://localhost:13306/mydb";
String user = "root";
String pwd = "abc123";
// 1. 获取到驱动对象并进行驱动注册
Driver driver = new com.mysql.jdbc.Driver();
DriverManager.registerDriver(driver);
// 2. 获取连接对象
Connection connection = DriverManager.getConnection(url, user, pwd);
// 3. 获取到命令对象
Statement statement = connection.createStatement();
// 4. 对数据库进行操作
int affectedCount = statement.executeUpdate("INSERT INTO animal VALUES(2,'王五')");
System.out.println("影响的语句数量:" + affectedCount);
// 5. 关闭连接
statement.close();
connection.close();
}
}
该方式的弊端
-
(1) 在创建Driver对象时,其中的静态代码块就会完成驱动的注册(我们相当于多此一举)
- 解决方法:只创建一个Driver对象,但是不再进行驱动的注册,但是还有如下的问题。
- (2)虽然只创建了一个Driver对象,但是Driver类中的静态代码块有这么一句
DriverManager.registerDriver(new Driver());
-
由此可以看出,在Driver类加载时,就会创建一个新的Driver对象来进行驱动的注册。
-
而我们又new了一个就会导致创建了2次Driver对象,造成了资源的浪费。
-
因此我们可以采用:
Class.forName("com.mysql.jdbc.Driver");
的方式完成驱动的注册。
-
- (3)可以看到我们连接数据库的参数都是直接编写在JAVA代码中的,这很不利于程序的扩展与维护。因此我们需要把配置信息提取出来。
- (4)扩展:在MYSQL驱动5.1.6之后,在程序运行时会在MYSQL提供的jar包的 META-INF\services\java.sql.Driver文本中找到驱动的全限定类名,调用反射进行自动注册。但还是建议加上
Class.forName("com.mysql.jdbc.Driver");
常用的API介绍
DriverManager驱动管理类
getConnection(String url,String user, String password)
:获取一个连接对象
Connection接口
-
createStatement()
:获取一个Statement对象 -
prepareStatement(String sql)
:获取一个PrepareStatement对象,传入一个sql进行预编译
Statement接口
-
exexuteUpdate(sql)
:执行增删改语句,返回影响的行数 -
executeQuery(sql)
:执行查询,返回ResultSet对象 -
execute(sql)
:执行任意的sql命令,返回boolean值
PrepareStatement接口
-
exexuteUpdate()
:执行增删改语句,返回影响的行数 -
executeQuery()
:执行查询,返回ResultSet对象 -
execute(sql)
:执行任意的sql命令,返回boolean值 -
setXxx(占位符索引,占位符的值)
:为占位符赋对应的值,索引从1开始 -
setObject(占位符索引,占位符的值)
:为占位符赋对应的值,索引从1开始
为什么相较于Statement接口,我们要使用PreparedStatement接口
-
(1)Statement接口有个致命的缺陷,就是SQL注入问题
-
(2)PreparedStatement解决了SQL注入的问题。
-
(3)PreparedStatement会提前对sql命令进行预编译,使得sql命令的执行效率提升
封装JDBCUtils
为什么要封装一个JDBCUtils呢
-
(1)若是每个方法中都把获取连接等重复代码全都写一遍,会造成大量代码的冗余。
-
(2)其实就是为了减少代码的冗余
一个非常简单的JDBCUtils(附代码)
- (1)提供一个配置文件,放在src目录下
url=jdbc:mysql://localhost:13306/mydb
user=root
pwd=abc123
driver=com.mysql.jdbc.Driver
- (2)编写JDBCUtils工具类
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;
public class JDBCUtils {
private static String user; //用户名
private static String pwd; //密码
private static String url; //url
private static String driver; //驱动名
static {
try {
// 1. 获取配置文件信息
Properties props = new Properties();
props.load(new FileInputStream("src\\jdbc.Properties"));
// 2. 为静态变量赋值
user = props.getProperty("user");
pwd = props.getProperty("pwd");
url = props.getProperty("url");
driver = props.getProperty("driver");
// 3. 注册驱动(其实不写也行)
Class.forName(driver);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 获取连接
* @return
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, user, pwd);
}
/**
* 关闭连接
*/
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 throwables) {
throw new RuntimeException(throwables);
}
}
}
事物(数据一致性问题)
-
只需要在一个事务的最开头,将自动提交设置为false
-
在事务结束位置,自动提交数据
-
在catch中进行数据的回滚
举个小例子
public class Test {
public static void main(String[] args) throws Exception{
// 1.获取连接
Connection connection = JDBCUtils.getConnection();
// 2. 获取到命令对象
String sql1 = "UPDATE account SET money = money - 100 WHERE id = 1";
String sql2 = "UPDATE account SET money = money + 100 WHERE id = 2";
PreparedStatement preparedStatement1 = connection.prepareStatement(sql1);
PreparedStatement preparedStatement2 = connection.prepareStatement(sql2);
try {
// 设置自动提交为false
connection.setAutoCommit(false);
// 事务开始
preparedStatement1.execute();
int i = 1/0;
preparedStatement2.execute();
// 事务结束,提交数据
connection.commit();
}catch (Exception e) {
// 出现异常,回滚
connection.rollback();
e.printStackTrace();
}
}
}
批处理
准备步骤
-
连接数据库的URL需要添加上
rewriteBatchedStatements
参数,示例如下: -
url=jdbc:mysql://localhost:13306/mydb?rewriteBatchedStatements=true
批处理的原理
-
addBatch():第一次时会创建一个ArrayList<PreparedStatement>,赋值给PreparedStatement对象中的batchedArgs。往后执行的每次addBatch()都会不断的往开始创建的ArrayList集合中存储一个个的PreparedStatement。
-
executeBatch():发送一次请求给MYSQL进行批量的处理。
-
clearBatch():清空batchedArgs集合,这样之前添加的批处理对象就释放了。
使用实例
// 3. 批处理添加1000条数据
for (int i = 0; i < 1000; i++) {
// 3.1 添加参数
preparedStatement.setInt(1,i);
// 3.2 添加到批处理中
preparedStatement.addBatch();
}
// 3.3 一次性批量处理
preparedStatement.executeBatch();
// 3.4 清空批量处理
preparedStatement.clearBatch();
数据库连接池(解决了反复连接占用大量的网络资源)
为什么要使用数据库连接池
-
(1)就目前而言,每次连接数据库都需要获取连接,并且使用后就直接关闭了与数据库的连接
-
(2)会占用大量的网络资源,如果有大量的JAVA程序同时获取数据库的连接,那么数据库很可能不堪重负直接崩溃。
-
(3)针对如上问题,提出了线程池的思想。
-
在程序刚运行时,就获取一定量的数据库连接对象,将其放在一个容器当中。
-
在使用完了该数据库连接对象时,不直接关闭该对象与数据库的连接,而是将其放回容器当中。
-
综上就可以完成数据库连接对象的复用,减少了反复获取连接而造成的资源占用。
-
C3P0线程池
准备工作
- 既然想要使用的是其他人写好的线程池,那么当然就需要获取到对应的jar包了
c3p0-0.9.2.1.jar
mchange-commons-java-0.2.20.jar
mysql-connector-java-5.1.40.jar
- 在scr目录下添加一个配置文件c3p0-config.xml,创建ComboPooledDataSource对象时自动读取该配置文件
<c3p0-config>
<!--使用默认的配置读取数据库连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:13306/mydb?useSSL=false</property>
<property name="user">root</property>
<property name="password">abc123</property>
<!-- 连接池参数 -->
<!--初始化申请的连接数量-->
<property name="initialPoolSize">5</property>
<!--最大的连接数量-->
<property name="maxPoolSize">10</property>
<!--最小的连接数量,当线程池空闲时,会将线程池中的连接释放到只剩下5个 -->
<property name="minPoolSize">5</property>
<!--超时时间,如果超过该时间没有获取到数据库连接,就抛出异常,单位为毫秒 -->
<property name="checkoutTimeout">3000</property>
</default-config>
<!-- 注意:在创建ComboPooledDataSource时,可以指定配置的名称,这里做一个示例 -->
<!-- <named-config name="other_c3p0">-->
<!-- <property name="driverClass">com.mysql.jdbc.Driver</property>-->
<!-- <property name="jdbcUrl">jdbc:mysql://localhost:13306/mydb?useSSL=false</property>-->
<!-- <property name="user">root</property>-->
<!-- <property name="password">root</property>-->
<!-- <property name="initialPoolSize">5</property>-->
<!-- <property name="maxPoolSize">10</property>-->
<!-- <property name="checkoutTimeout">3000</property>-->
<!-- </named-config>-->
</c3p0-config>
获取连接的示例
// ComboPooledDataSource dataSource = new ComboPooledDataSource("other_c3p0");
ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 获取连接
Connection connection = dataSource.getConnection();
// 该行其实就是将连接放回线程池当中,并不是切断与MYSQL的连接。注意!!!
connection.close();
Druid线程池(推荐)
准备工作
- 既然想要使用的是其他人写好的线程池,那么当然就需要获取到对应的jar包了
c3p0-0.9.2.1.jar
mchange-commons-java-0.2.20.jar
mysql-connector-java-5.1.40.jar
- 在scr目录下添加一个配置文件c3p0-config.xml,创建ComboPooledDataSource对象时自动读取该配置文件
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:13306/mydb?useSSL=false
username=root
password=abc123
# 初始线程数
initialSize=5
# 最大线程数
maxActive=10
# 超时时间
maxWait=3000
获取连接示例
public static void main(String[] args) throws Exception{
// 获取到配置文件
Properties props = new Properties();
props.load(new FileInputStream("src\\druid.properties"));
// 1. 获取数据源
DataSource dataSource = DruidDataSourceFactory.createDataSource(props);
// 2. 获取连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
// 3. 关闭连接(其实是将连接对象放回线程池)
connection.close();
}
Apache-DBUtils工具类(ORM对象关系映射)
ResultSet有什么弊端?
-
(1)ResultSet结果集与Connection连接是关联的,一旦Connection连接关闭,将会导致ResultSet不可用
-
(2)ResultSet不利于数据管理(因为只能使用一次,毕竟不可能一直不关闭连接)
-
(3)ResultSet返回的信息难以处理,只能通过getXxx这种方式获取,不利于维护。
那么我们应该如何解决呢?
先想一想,ResultSet返回的是数据表中一行行的数据,那么我们是不是可以定义一个JAVA类与其字段进行匹配呢,这个思想称之为:ORM对象关系映射,这样的类称之为JaveBean或pojo或domain
-
(1)获取到一个ResultSet集合,我们将其中的一行行数据分别存储到一个个对应的JavaBean当中,再使用一个集合(例如ArrayList)来进行存储
-
(2)完成了上述步骤后,Connection连接对象即便关闭了,也不会影响到ArrayList容器中已经存储好的数据
-
(3)而Apache-DBUtils就很好的帮我们封装了上述的内容,非常好用。
Apache-DBUtils 使用的准备工作
- 准备jar包:
commons-dbutils-1.7.jar
Apache-DBUtils常用API
QueryRunner指令执行类
-
update()
:增删改
-
query()
:查询
ResultSetHandler 各处理器的作用
处理器的类名 | 作用 |
---|---|
ArrayHandler | 把结果集中的第一行数据转成对象数组 |
ArrayListHandler | 把结果集中的每一行数据都转成一个对象数组,再存放到List中 |
BeanHandler | 将结果集中的第一行数据封装到一个对应的JavaBean中 |
BeanListHandler | 将结果集中的每一行数据都封装到一个对应的JavaBean中,存放到List |
MapHandler | 将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值 |
MapListHandler | 将结果集中的每一行数据都封装到一个Map里,然后再存放到List |
ColumnListHandler | 将结果集中某一列的数据存放到List中 |
KeyedHandler(name) | 将结果集中的每一行数据都封装到一个Map里(List<Map>),再把这些map再存到一个map里,其key为指定的列 |
ScalarHandler | 将结果集第一行的某一列放到某个对象中 |
DAO和增删改查(提供通用的数据库访问对象,减少代码的冗余)
什么是DAO,为什么要使用DAO
-
DAO的英文全程是Data Access Object数据访问对象。理解为直接用于访问数据库的对象。
-
思考:我们在编写代码时,如果需要获取多个不同数据表当中的数据,我们应该怎么办?
传统的解决思维:不同的表,就对应不同的JavaBean。不过每次数据的获取,都要编写不同的代码,比如编写sql时,A表就写select A表,B表就写select B表。
如上的思维的问题在于:如果有相同的功能,比如说都是想获取表中的所有数据,都是想要通过id取得一条数据,都是想进行一个普通的增删改,那么依然要编写不同的代码,造成代码的大量冗余,也不便于管理
这个时候我们就可以将通用的方法,抽取为一个BasisDao,实现相对应的通用方法。再编写其余访问不同数据表的Dao来继承BasisDao,其中只需要编写专属于自己的特有方法。
小提示
编写工具类处理异常的小建议
-
可以不抛出编译时异常,直接抛出运行时异常。
-
这样的好处是:调用者可以选择捕获处理或者默认处理。