JDBC介绍
JDBC是Java提供的一套用于数据库操作的接口API,Java程序员只需要面向这套接口编程即可.同的数据库厂商,需要针对这套接口需提供不同实现
JDBC API是一系列的接口,它统一和规范了应用程序与数据库的连接,执行SQL语句,并到得到返回结果等各类操作,相关类和接口在java.sql包与javax.sql(扩展包)中
前置工作
- mysql官网下载mysql-connector-java-8.0.30.jar
- 在项目目录下创建一个文件夹lib,将jar文件放入,在idea中选该jar文件右键选择添加到项目
JDBC编写步骤
-
注册驱动 加载Driver类
-
获取连接 得到Connection 客户端到数据库的连接
-
执行sql语句 发送sql命令给mysql执行
-
释放资源 关闭相关连接
连接的五种方式
方式一
package src.day0824;
import com.mysql.cj.jdbc.Driver;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class Demo03 {
public static void main(String[] args) throws SQLException {
// 1 注册驱动
Driver driver = new Driver();// 创建driver对象
// 2 得到连接
/*
* jdbc:mysql:// 是规定好的,通过jdbc的方式连接mysql
* localhost 主机,远程连接也可以写成IP地址
* 3306 表示mysql监听的端口
* java 表示具体连接哪个数据库,这里连接的数据库名字就叫"java"
* mysql连接的本质就是socket连接
* */
String url = "jdbc:mysql://localhost:3306/java";
// 将用户名和密码放入到Properties对象
Properties properties = new Properties();
properties.setProperty("user", "root");// 第一个参数user是规定好的,第二个参数填用户名
properties.setProperty("password", "root");// 同上,第二个参数填密码
// 连接数据库,返回的是一个网络连接,存到了connect对象中
Connection connect = driver.connect(url, properties);
// 3 执行sql语句
String sql = "insert into actor values(null,'tom','m','1990-11-11','119')";
// 创建一个statement对象,该对象用于执行静态sql语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
int rows = statement.executeUpdate(sql);// 执行sql语句,如果是dml(增删改)语句,返回的是影响的行数;
System.out.println(rows > 0 ? "成功" : "失败");// 通过收到的受影响的行数判断是否执行成功
// 4 关闭连接资源
// 不关闭连接会浪费服务器端口
statement.close();
connect.close();
}
}
方式二
和方式一区别就是使用反射加载Driver类,更加灵活
package src.day0824;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class Demo04 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
// 1 使用反射加载Driver类,动态加载,更加灵活,减少依赖性
Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();
// 2 得到连接
String url = "jdbc:mysql://localhost:3306/java";
// 将用户名和密码放入到Properties对象
Properties properties = new Properties();
properties.setProperty("user", "root");// 第一个参数user是规定好的,第二个参数填用户名
properties.setProperty("password", "root");// 同上,第二个参数填密码
// 连接数据库,返回的是一个网络连接,存到了connect对象中
Connection connect = driver.connect(url, properties);
// 3 执行sql语句
String sql = "insert into actor values(null,'tom','m','1990-11-11','119')";
// 创建一个statement对象,该对象用于执行静态sql语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
int rows = statement.executeUpdate(sql);// 执行sql语句,如果是dml(增删改)语句,返回的是影响的行数;
System.out.println(rows > 0 ? "成功" : "失败");// 通过收到的受影响的行数判断是否执行成功
// 4 关闭连接资源
// 不关闭连接会浪费服务器端口
statement.close();
connect.close();
}
}
方式三
和第二种不同的是使用DriverManager替换Driver,进行统一管理
package src.day0824;
import java.sql.*;
public class Demo05 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
// 1 使用反射加载Driver
Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();
// 2 得到连接
String url = "jdbc:mysql://localhost:3306/java";
String user = "root";
String password = "root";
DriverManager.registerDriver(driver);// 注册Driver驱动
Connection connect = DriverManager.getConnection(url, user, password);// 用DriverManager来进行连接的获取
// 3 执行sql语句
String sql = "insert into actor values(null,'tom','m','1990-11-11','119')";
// 创建一个statement对象,该对象用于执行静态sql语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
int rows = statement.executeUpdate(sql);// 执行sql语句,如果是dml(增删改)语句,返回的是影响的行数;
System.out.println(rows > 0 ? "成功" : "失败");// 通过收到的受影响的行数判断是否执行成功
// 4 关闭连接资源
// 不关闭连接会浪费服务器端口
statement.close();
connect.close();
}
}
方式四
使用Class.forName自动完成注册驱动,简化代码,用的最多,推荐使用
package src.day0824;
import java.sql.*;
public class Demo06 {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
/*
在加载Driver类时,完成注册,静态代码块在类加载时会执行一次
DriverManager.registerDriver(new Driver());执行
注册Driver的工作已经完成
源码
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
} */
// 1 使用反射加载Driver类
Class.forName("com.mysql.cj.jdbc.Driver");
// 2 得到连接
String url = "jdbc:mysql://localhost:3306/java";
String user = "root";
String password = "root";
Connection connect = DriverManager.getConnection(url, user, password);
// 3 执行sql语句
String sql = "insert into actor values(null,'tom','m','1990-11-11','119')";
// 创建一个statement对象,该对象用于执行静态sql语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
int rows = statement.executeUpdate(sql);// 执行sql语句,如果是dml(增删改)语句,返回的是影响的行数;
System.out.println(rows > 0 ? "成功" : "失败");// 通过收到的受影响的行数判断是否执行成功
// 4 关闭连接资源
// 不关闭连接会浪费服务器端口
statement.close();
connect.close();
}
}
方式五
在第四中的基础上写个配置文件,将用户名密码写在配置文件中,更灵活,最为推荐使用
下面是名为mysql.properties的配置文件
user=root
password=root
url=jdbc:mysql://localhost:3306/java
driver=com.mysql.cj.jdbc.Driver
package src.day0824;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class Demo07 {
public static void main(String[] args) throws ClassNotFoundException, SQLException, IOException {
// 从配置文件中得到相关的值
Properties properties = new Properties();
properties.load(new FileInputStream("src\\day0912\\mysql.properties"));
// 相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
// 1 使用反射加载Driver类(可以不写这句话,看下面提示)
Class.forName(driver);
// 2 得到连接
Connection connect = DriverManager.getConnection(url, user, password);
// 3 执行sql语句
String sql = "insert into actor values(null,'tom','m','1990-11-11','119')";
// 创建一个statement对象,该对象用于执行静态sql语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
int rows = statement.executeUpdate(sql);// 执行sql语句,如果是dml(增删改)语句,返回的是影响的行数;
System.out.println(rows > 0 ? "成功" : "失败");// 通过收到的受影响的行数判断是否执行成功
// 4 关闭连接资源
// 不关闭连接会浪费服务器端口
statement.close();
connect.close();
}
}
提示
Class.forName("com.mysql.cj.jdbc.Driver");不写也行,因为从jdk1.5以后使用了jdbc4,不再需要显式调用class.forName()注册驱动而是自动调用驱动,jar包下META-INF\services\java.sql.Driver文本中的类名称去注册,但是建议还是写上Class.forName("com.mysql.cj.jdbc.Driver"),更加明确
结果集
结果集(ResultSet),表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
ResultSet对象保持一个光标指向其当前的数据行.最初,光标位于第一行之前.next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集
package src.month09.day0913.demo06;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
public class Demo06 {
public static void main(String[] args) throws ClassNotFoundException, SQLException, IOException {
// 这里用方式5连接数据库
// 前置工作
Properties properties = new Properties();
properties.load(new FileInputStream("src\\month09\\day0912\\demo01\\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 connect = DriverManager.getConnection(url, user, password);
// 得到Statement
Statement statement = connect.createStatement();
// 组织select查询的sql语句
String sql = "SELECT * FROM `tb01`";
// 执行给定的sql语句,该语句返回单个ResultSet对象
ResultSet resultSet = statement.executeQuery(sql);
// 使用while取出数据,循环一次就是取一行
while (resultSet.next()) {// 判断下一行还有没有数据,有的话继续取
int id = resultSet.getInt(1);// 获取并接收第一列数据
String name = resultSet.getString(2);// 获取并接收第二列数据
System.out.println("id是" + id + ", 姓名是" + name);// 输出
}
// 关闭连接
resultSet.close();
statement.close();
connect.close();
}
}
SQL注入问题
Statement对象用于执行静态SQL语句并返回其生成的结果的对象
在建立连接后,需要对数据库进行访问,执行命令或是SQL语句,可以通过:
- Statement [存在SQL注入问题] 在实际开发中不会用Statement
- PreparedStatement [预处理]
- CallableStatement
SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,恶意攻击数据库
如SELECT * FROM xxx WHERE ' 1' OR ' = ' OR '1' = '1 '
要防范SQL注入,只要用PreparedStatement(从Statement扩展而来)取代Statement就可以了
PreparedStatement
- PreparedStatement执行的SQL语句中的参数用问号(?)来表示,调用PreparedStatement对象的setXxx()方法来设置这些参数,setXxx()方法有两个参数,第一个参数是要设置的SQL语句中的参数的索引(从1开始),第二个是设置的SQL语句中的参数的值
- 调用executeQuery() 返回ResultSet对象
- 调用executeUpdate() 执行更新,包括增,删,改
好处:
- 不再使用+拼接sql语句,减少语法错误
- 有效的解决了sql注入问题
- 大大减少了编译次数,效率较高
由此引出以下采用PreparedStatement的连接方式
实际开发中,使用如下的方式连接
若是SELECT查询语句,ResultSet resultSet = preparedStatement.executeQuery()返回结果集
package src.month09.day0913.demo06;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
public class Demo06 {
public static void main(String[] args) throws ClassNotFoundException, SQLException, IOException {
// 前置工作
Properties properties = new Properties();
properties.load(new FileInputStream("src\\month09\\day0912\\demo01\\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 connect = DriverManager.getConnection(url, user, password);
// 三 得到PreparedStatement
// 3.1 组织sql语句,?就是占位符
String sql = "select * from tb01 where id = ?";
// 3.2 preparedStatement对象实现了PreparedStatement接口的实现类的对象
PreparedStatement preparedStatement = connect.prepareStatement(sql);
// 3.3 给sql中的?赋值
preparedStatement.setString(1, "3");// 第一个参数代表是第几个?,从1开始数,第二个参数是要填充的值
// 四 执行给定的sql语句,该语句返回单个ResultSet对象,查询使用executeQuery方法,如果执行增删改语句用executeUpdate
ResultSet resultSet = preparedStatement.executeQuery();// 这里执行executeQuery,不要再写sql
// 五 使用while取出数据,循环一次就是取一行
while (resultSet.next()) {// 判断下一行还有没有数据,有的话继续取
int id = resultSet.getInt(1);// 获取并接收第一列数据
String name = resultSet.getString(2);// 获取并接收第二列数据
System.out.println("id是" + id + ", 姓名是" + name);// 输出
}
// 六 关闭连接
resultSet.close();
preparedStatement.close();
connect.close();
}
}
若是增删改语句,返回的是受影响的行数int rows = preparedStatement.executeUpdate()
package src.month09.day0914.demo02;
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;
public class Demo02 {
public static void main(String[] args) throws Exception {
// 前置工作
Properties properties = new Properties();
properties.load(new FileInputStream("src\\month09\\day0912\\demo01\\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);
// 三 得到PreparedStatement
// 3.1 组织sql语句,?就是占位符
String sql = "insert into tb01 values (?,?)";
// 3.2 preparedStatement对象实现了PreparedStatement接口的实现类的对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 3.3 给sql中的?赋值
preparedStatement.setString(1, "8");
preparedStatement.setString(2, "250");
// 四 执行给定的sql语句,返回受影响的行数
int rows = preparedStatement.executeUpdate();
// 五 根据受影响的行数判断是否执行成功
System.out.println(rows > 0 ? "成功" : "失败");
// 六 关闭连接
preparedStatement.close();
connection.close();
}
}
JDBC API总结
-
DriverManager 驱动管理类
getConnection(url,user,pwd) 获取到连接
-
Connection 接口
createStatement 创建Statement对象
prepareStatement(sql) 生成预处理对象
-
Statement 接口
executeUpdate(sql) 执行增删改语句,返回受影响的行数
executeQuery(sql) 执行查询语句,返回结果集ResultSet对象
execute(sql) 可以执行任意的sql语句,返回布尔值,可用于创建/删除表等
-
PreparedStatement 接口
executeUpdate() 执行增删改语句,返回受影响的行数
executeQuery() 执行查询语句,返回结果集ResultSet对象
execute() 可以执行任意的sql语句,返回布尔值,可用于创建/删除表等
setXXX(展位符?的索引, 占位符的值) 解决sql注入问题,用来给带问号的sql语句赋具体值
setObject(展位符?的索引, 占位符的值) 用法同上一条,不过是对象形式
-
ResultSet 结果集
next() 向下移动一行,初始在第一行的前一行,如果没有下一行,会返回false
previous() 向上移动一行,如果没有上一行,会返回false
getXXX(字段的索引或字段名) 返回当前行的指定字段的值
getObject(字段的索引或字段名) 返回当前行的指定字段的值,接受类型为Object
JDBC 工具类
可以看出,每次操作数据库都有连接和关闭的步骤,所以可以将其封装到 JdbcUtils 工具类中,方便以后使用
JdbcUtils类
package src.month09.day0914.utils;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils {
// 定义4个相关属性,因为只需要一份,所以定义成静态的
private static String user; // 用户名
private static String password; // 密码
private static String url; // url
private static String driver; // 驱动名
// 在static代码块中初始化
static {
try {
Properties properties = new Properties();
// 路径自己改,需要一份配置文件
properties.load(new FileInputStream("src\\month09\\day0912\\demo01\\mysql.properties"));
// 读取相关属性值
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
driver = properties.getProperty("driver");
} catch (IOException e) {
// 实际开发中,我们可以这样处理
// 1. 将编译异常转成运行异常
// 2. 调用者可以选择捕获该异常,也可以选择默认处理该异常,比较方便
throw new RuntimeException(e);
}
}
// 连接数据库,返回一个connection
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// 关闭相关资源,如果需要关闭资源,就传入对象,否则传入null
// 1. ResultSet结果集
// 2. Statement 或者 PreparedStatement
// 3. Connection
public static void closeConnection(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);
}
}
}
使用JdbcUtils类进行连接,增删改操作
package src.month09.day0914.demo03;
import src.month09.day0914.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
// 使用jdbc工具类连接数据库
public class Demo03 {
public static void main(String[] args) {
// 1. 得到连接
Connection connection = null;
// 2. 组织sql语句
String sql = "insert into tb01 values (?,?)";
// 3. 创建一个PreparedStatement对象
PreparedStatement preparedStatement = null;
try {
// 获得connection的语句放在这是为了统一捕获异常
connection = JdbcUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
// 给占位符赋值
preparedStatement.setInt(1, 9);
preparedStatement.setString(2, "李易峰");
// 执行
int rows = preparedStatement.executeUpdate();
System.out.println(rows > 0 ? "成功" : "失败");
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(null, preparedStatement, connection);
}
}
}
使用JdbcUtils类进行连接,查询操作
package src.month09.day0914.demo04;
import src.month09.day0914.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Demo04 {
public static void main(String[] args) {
// 1. 得到连接
Connection connection = null;
// 2. 组织sql语句
String sql = "select * from tb01 where id < ?";
// 3. 创建一个PreparedStatement对象
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 获得connection的语句放在这是为了统一捕获异常
connection = JdbcUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
// 给占位符赋值
preparedStatement.setInt(1, 5);
// 执行
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
System.out.println(id + "\t" + name);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(resultSet, preparedStatement, connection);
}
}
}
JDBC 事务
- JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务,即每次执行一个SQL语句
preparedStatement.executeQuery()时,如果执行成功,就会向数据库自动提交,而不能回滚 - JDBC程序中为了让多个SQL语句作为一个整体执行,需要使用事务
- 调用Connection的setAutoCommit(false)可以取消自动提交事务
- 在所有的SQL语句都成功执行后,调用commit() 方法提交事务 在其中某个操作失败或出现异常时,调用rollback() 方法回滚事务
/*
获取到connection对象后,就设置setAutoCommit(false),取消自动提交,相当于开启了事务
在所有的SQL语句都成功执行后,调用connection对象的commit()方法提交事务
*/
package src.month09.day0914.demo06;
import src.month09.day0914.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Demo06 {
public static void main(String[] args) {
// 1. 得到连接
Connection connection = null;
// 2. 组织sql语句
String sql1 = "update tb02 set money = money - ? where name = ?";
String sql2 = "update tb02 set money = money + ? where name = ?";
// 3. 创建一个PreparedStatement对象
PreparedStatement preparedStatement = null;
try {
connection = JdbcUtils.getConnection();// 默认为自动提交事务
connection.setAutoCommit(false);// 将connection设置为不自动提交,相当于开启了事务
// 执行我给马云转账的操作
preparedStatement = connection.prepareStatement(sql1);
preparedStatement.setInt(1, 100);
preparedStatement.setString(2, "me");
int i = preparedStatement.executeUpdate();
System.out.println(i > 0 ? "success" : "fail");
// int a = 1 / 0; // 可以看到,这里一旦发生异常,sql1即使执行了,也不会在数据库里生效,要等到commit后才算成功,这就是事务
// 执行马云收到转账的操作
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.setInt(1, 100);
preparedStatement.setString(2, "mayun");
int j = preparedStatement.executeUpdate();
System.out.println(j > 0 ? "success" : "fail");
// 要是转账途中没有异常,就会执行到这里,进行事务提交,这时sql语句就会真正永久地将数据存到数据库中
connection.commit();
} catch (SQLException e) {
try {
// 一旦转账过程中发生异常,就撤销已经执行的sql,不填保存点参数就会默认回滚到开启事务的地方
connection.rollback();
System.out.println("sql语句执行发生了异常,已经撤销了已执行的sql语句");
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
JdbcUtils.closeConnection(null, preparedStatement, connection);
}
}
}
批处理
当需要成批插入或者更新记录时,可以采用java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理,通常情况下比单独提交处理更有效率,相当于将多条sql语句打包一块发送给数据库
JDBC的批量处理语句包括下面方法 addBatch() 添加需要批量处理的SQL语句或参数 executeBatch() 执行批量处理语句 clearBatch() 清空批处理包的语句
JDBC连接MySQL时,如果要使用批处理功能,请在url中添加参数 ?rewriteBatchedStatements=true
批处理往往和PreparedStatement一起搭配使用,既可以减少编译次数,又减少运行次数,效率大大提高
# 要想批处理,配置文件的url得加上参数,写成这样
user=root
password=root
url=jdbc:mysql://localhost:3306/jdbc?rewriteBatchedStatements=true
driver=com.mysql.cj.jdbc.Driver
package src.month09.day0914.demo07;
import src.month09.day0914.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
public class Demo07 {
public static void main(String[] args) throws Exception {
Connection connection = JdbcUtils.getConnection();
String sql = "insert into tb03 values ( null , ? )";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < 10000; i++) {
preparedStatement.setString(1, "jack" + i + "号");
// 将sql语句加入到批量处理包中
preparedStatement.addBatch();
// 每1000条语句,批量执行一次
if ((i + 1) % 1000 == 0) {
preparedStatement.executeBatch();
// 清空一下,准备装下一批
preparedStatement.clearBatch();
}
}
}
}
连接池
- 传统的JDBC数据库连接使用DriverManager来获取,每次向数据库建立连接的时候都要将Connection加载到内存中,再验证IP地址,用户名和密码(0.05s~1s时间).需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃
- 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库
- 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MySQL崩溃
- 解决传统开发中的数据库连接问题,可以采用数据库连接池技术(connection pool)
数据库连接池实现原理
- 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从"缓冲池"中取出一个,使用完毕之后再放回去
- 数据库连接池负责分配,管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
- 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中
数据库连接池种类
- JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供实现
- C3P0数据库连接池,速度相对较慢,稳定性不错(hibernate,spring底层在用),是个老牌连接池
- DBCP数据库连接池,速度相对C3P0较快,但不稳定
- Proxool数据库连接池,有监控连接池状态的功能,稳定性较C3P0差一点
- BoneCP数据库连接池,速度快
- Druid(德鲁伊)是阿里提供的数据库连接池,集DBCP,C3P0,Proxool优点于一身的数据库连接池,做项目一般用这个
C3P0
-
C3P0获取连接,网上找jar包,复制到 libs 路径,再导入到项目才能用
package src.month09.day0914.demo08; import com.mchange.v2.c3p0.ComboPooledDataSource; import java.io.FileInputStream; import java.sql.Connection; import java.util.Properties; public class Demo08 { public static void main(String[] args) throws Exception { // 1 创建一个数据源对象(里面有一些连接,连接池) ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); // 2 通过配置文件获取相关连接信息 Properties properties = new Properties(); properties.load(new FileInputStream("src/mysql.properties")); // 3 读取相关属性值 String user = properties.getProperty("user"); String password = properties.getProperty("password"); String url = properties.getProperty("url"); String driver = properties.getProperty("driver"); // 4 给数据源comboPooledDataSource设置参数,现在整个的管理交给了数据源,它拿到用户名密码后负责和数据库打交道 comboPooledDataSource.setDriverClass(driver); comboPooledDataSource.setJdbcUrl(url); comboPooledDataSource.setUser(user); comboPooledDataSource.setPassword(password); // 5 设置初始化连接数和最大连接数(到达最大连接数后会进入等待队列) comboPooledDataSource.setInitialPoolSize(10); comboPooledDataSource.setMaxPoolSize(50); // 6 获取连接 Connection connection = comboPooledDataSource.getConnection(); System.out.println("连接成功"); // 7 关闭连接 connection.close(); } } -
使用配置文件的方式也可以用C3P0
先准备好名为 c3p0-config.xml 的配置文件,一般放在src目录下
<?xml version="1.0" encoding="utf-8"?> <c3p0-config> <!-- 连接池名称--> <named-config name="test_c3p0"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc</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>有配置文件后,通过名字直接连接
package src.month09.day0914.demo09; import com.mchange.v2.c3p0.ComboPooledDataSource; import java.sql.Connection; import java.sql.SQLException; public class Demo09 { public static void main(String[] args) throws SQLException { // 这里参数填的的名字就是配置文件里的<named-config name="test_c3p0"> ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("test_c3p0"); Connection connection = comboPooledDataSource.getConnection(); System.out.println("连接成功"); connection.close(); } }
Druid
名为 druid.properties 的配置文件,一般放在src目录下
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc?rewriteBatchedStatements=true
username=root
password=root
initialSize=10
minIdle=5
maxActive=20
maxWait=5000
Druid获取连接
package src.month09.day0914.demo10;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
public class Demo10 {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
properties.load(new FileInputStream("druid.properties"));
// 创建一个指定参数的数据库连接池,Druid连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 得到连接
Connection connection = dataSource.getConnection();
System.out.println("连接成功~~");
connection.close();
}
}
Druid 工具类
既然有了这么吊的德鲁伊连接池,那么是时候重新封装一下以前封装的JDBC工具类了
JDBCUtilsByDruid.java
package utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
// 基于druid数据库连接池的工具类
public class JDBCUtilsByDruid {
private static DataSource ds;
// 在静态代码块完成ds初始化
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("druid.properties"));// 加载配置文件
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
// 得到连接,getConnection方法
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
// 这里是把connection对象连接放回到连接池,没有真正关闭
public static void closeConnection(ResultSet rs, Statement statement, Connection connection) {
try {
if (rs != null) {
rs.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
通过Druid工具类进行连接测试
package src.month09.day0914.demo11;
import utils.JDBCUtilsByDruid;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Demo11 {
public static void main(String[] args) throws SQLException {
Connection connection = null;
String sql = "select * from tb01 where id < ?";
PreparedStatement preparedStatement = null;
ResultSet set = null;
try {
connection = JDBCUtilsByDruid.getConnection();// 这里的connection连接是通过自己写的德鲁伊工具类得到的
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, 6);
set = preparedStatement.executeQuery();
while (set.next()) {
int id = set.getInt("id");
String name = set.getString("name");
System.out.println(id + "\t" + name);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtilsByDruid.closeConnection(set, preparedStatement, connection);// 这里不是真关闭,而是将连接放回到连接池
}
}
}
Apache-DBUtils
前面讲了这么多,还是有瑕疵,因为查询语句返回的结果集是临时的,它和connection是关联的,只要connection关闭,结果集也将不能使用,结果集ResultSet不利于操作和管理数据,所以就要将查询到结果集中的数据重新放到一个集合中,假如从actor表中取了一些数据在结果集中,就可以再搞一个ArrayList<acotr>,这个集合里面放的每个元素就对应actor表中的每条记录,这样一来就可以不要结果集了,关闭连接后也能操作得到的数据
commons-dbutils是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutilsi能极大简化JDBC编码的工作量
QueryRunner类 该类封装了SQL的执行,是线程安全的,可以实现增,删,改,查,批处理,QueryRunner类实现查询
ResultSetHandler接口 该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式
ArrayHandler 把结果集中的第一行数据转成对象数组 ArrayListHandler 把结果集中的每一行数据都转成一个数组,再存放到List中 BeanHandler 将结果集中的第一行数据封装到一个对应的JavaBean实例中 BeanListHandler 将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里 ColumnListHandler 将结果集中某一列的数据存放到List中 KeyedHandler(name) 将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,其key为指定的key MapHandler 将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值 MapListHandler 将结果集中的每一行数据都封装到一个Map里,然后再存放到List
- 先需要自己创建一个类,该类new创建的实例用来和表中的记录对应
package src.month09.day0915.demo01;
// Tb01 创造出来的对象和 tb01 表中的记录对应
public class Tb01 {// Javaben / POJO / Domain 对象
private Integer id;// 注意这里是Integer,对应数据库中的int
private String name;
// 需要给一个无参构造器(反射需要)
public Tb01() {
}
public Tb01(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return id + "\t" + name;
}
}
- 有了上面的类后,就可以用apache工具类连接数据库查询并保存数据了,结果保存在list集合中
package src.month09.day0915.demo01;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import utils.JDBCUtilsByDruid;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class Demo01 {
// 使用apache-DBUtils工具类 + druid完成对表的CRUD操作
public static void main(String[] args) throws SQLException {
// 1 通过德鲁伊工具类得到连接
Connection connection = JDBCUtilsByDruid.getConnection();
// 2 使用DBUtils类和接口,先引入相关的jar包,创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
// 3 queryRunner对象可以执行query方法,返回一个ArrayList集合
String sql = "select * from tb01 where id < ?";
/*
* - query方法就是执行sql语句,得到resultSet---封装到-->ArrayList集合中,返回集合
* - 参数解释:
* - connection 连接
* - sql 执行的sql语句
* - new BeanListHandler<>(Tb01.class) 在将resultset->Actor对象->封装到ArrayList
* 底层使用反射机制去获取Actor类的属性,然后进行封装
* - 最后的参数5就是给sql语句中第一个?处填5,5的后面还可以有多个值,因为是可变参数的
* */
List<Tb01> list = queryRunner.query(connection, sql, new BeanListHandler<>(Tb01.class), 7);
for (Tb01 t : list) {
System.out.println(t);
}
/*
可以看出,都存到了集合中
1 tom
2 jack
3 jerry
4 smith
*/
// 4 释放资源,只需要自己关闭connection连接即可,resultSet和preparedStatement不用管,底层会自己关闭他们
JDBCUtilsByDruid.closeConnection(null, null, connection);
}
}
- 返回的查询结果除了是集合,也可以是单个对象,对应一条记录
package src.month09.day0915.demo01;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import utils.JDBCUtilsByDruid;
import java.sql.Connection;
import java.sql.SQLException;
public class Demo01 {
// 使用apache-DBUtils工具类 + druid完成对表的CRUD操作
public static void main(String[] args) throws SQLException {
// 1 通过德鲁伊工具类得到连接
Connection connection = JDBCUtilsByDruid.getConnection();
// 2 使用DBUtils类和接口,先引入相关的jar包,创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
// 3 queryRunner对象可以执行query方法,返回单个对象
String sql = "select * from tb01 where id = ?";
// 因为我们返回的是单行记录<--->单个对象,所以使用的Hander是BeanHandler
Tb01 tb01 = queryRunner.query(connection, sql, new BeanHandler<>(Tb01.class), 2);
System.out.println(tb01);
// 结果为 2 jack
// 4 释放资源,只需要自己关闭connection连接即可,resultSet和preparedStatement不用管,query函数会自己关闭他们
JDBCUtilsByDruid.closeConnection(null, null, connection);
}
}
- 若查询结果是单行单列,返回的就是Object
package src.month09.day0915.demo01;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import utils.JDBCUtilsByDruid;
import java.sql.Connection;
import java.sql.SQLException;
public class Demo01 {
// 使用apache-DBUtils工具类 + druid完成对表的CRUD操作
public static void main(String[] args) throws SQLException {
// 1 通过德鲁伊工具类得到连接
Connection connection = JDBCUtilsByDruid.getConnection();
// 2 使用DBUtils类和接口,先引入相关的jar包,创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
// 3 queryRunner对象可以执行query方法,也可以是单行单列,返回的就是Object
String sql = "select name from tb01 where id = ?";
// 因为查询结果是单行单列,所以使用的handler就是ScalarHandler,返回的是个Object
Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 2);
System.out.println(obj);
// 结果为 jack
// 4 释放资源,只需要自己关闭connection连接即可,resultSet和preparedStatement不用管,query函数会自己关闭他们
JDBCUtilsByDruid.closeConnection(null, null, connection);
}
}
- 增删改语句的使用
package src.month09.day0915.demo01;
import org.apache.commons.dbutils.QueryRunner;
import utils.JDBCUtilsByDruid;
import java.sql.Connection;
import java.sql.SQLException;
public class Demo01 {
// 使用apache-DBUtils工具类 + druid完成对表的CRUD操作
public static void main(String[] args) throws SQLException {
// 1 通过德鲁伊工具类得到连接
Connection connection = JDBCUtilsByDruid.getConnection();
// 2 使用DBUtils类和接口,先引入相关的jar包,创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
// 3 queryRunner对象可以执行query方法,也可以是单行单列,返回的就是Object
String sql = "update tb01 set name = ? where id = ?";
// 执行增删改语句用queryRunner.update方法,返回值是受影响的行数
int affectedRows = queryRunner.update(connection, sql, "liyifeng", 1);
System.out.println(affectedRows > 0 ? "成功" : "失败");
// 4 释放资源,只需要自己关闭connection连接即可,resultSet和preparedStatement不用管,query函数会自己关闭他们
JDBCUtilsByDruid.closeConnection(null, null, connection);
}
}
DAO
apache-dbutils+Druid简化了JDBC开发,但还有不足/(ㄒoㄒ)/~~
- SQL语句是固定的,不能通过参数传入,通用性不好,需要进行改进,更方便执行增删改查
- 对于select操作,如果有返回值,返回类型不能固定,需要使用泛型
- 将来的表很多,业务需求复杂,不可能只靠一个Java类完成
DAO data access object数据访问对象
通用类称为BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作,在BaiscDao的基础上,实现一张表对应一个Dao,更好的完成功能,比如Customer表 ==> Customer.java(javabean) ==> CustomerDao.java
如何使用DAO操作数据库
-
建4个包,分别是dao,domain,test,utils
- dao 包里放BaiscDao和每个表对应的dao,如actor表对应的dao就是ActorDAO,ActorDAO继承了BaiscDao里的操作数据库的方法,当然也可以加一些特有的方法,用来专门对actor表操作
- domain 包里放 Javaben / POJO / Domain 的类,即actor表对应的domain类就是Actor,该类有actor表中字段对应的属性,无参构造器,有参构造器,getter,setter,重写toString等,将来用来创建实例和表中的记录一一对应
- test 包里放测试类,用来对dao包里的如ActorDAO进行操作
- utils 包里放工具类,如JDBCUtilsByDruid类
-
在dao包里创建BaiscDao,实现crud的方法
package src.month09.day0915.demo04.dao; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.dbutils.handlers.ScalarHandler; import src.month09.day0915.demo04.utils.JDBCUtilsByDruid; import java.sql.Connection; import java.sql.SQLException; import java.util.List; // 开发BasicDAO是其他DAO的父类 public class BasicDAO<T> {// 泛型指定具体类型 private QueryRunner qr = new QueryRunner(); // 开发通用的dml增删改方法,针对任意表 public int update(String sql, Object... params) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return qr.update(connection, sql, params); } catch (SQLException e) { throw new RuntimeException(e);// 将编译异常-->运行异常,抛出, } finally { JDBCUtilsByDruid.closeConnection(null, null, connection); } } // 返回多个对象(即查询的结果是多行的),针对任意表 public List<T> queryMultiple(String sql, Class<T> clazz, Object... params) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return qr.query(connection, sql, new BeanListHandler<T>(clazz), params); } catch (SQLException e) { throw new RuntimeException(e);// 将编译异常-->运行异常,抛出, } finally { JDBCUtilsByDruid.closeConnection(null, null, connection); } } // 查询单行的通用方法 public T querySingle(String sql, Class<T> clazz, Object... params) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return qr.query(connection, sql, new BeanHandler<T>(clazz), params); } catch (SQLException e) { throw new RuntimeException(e);// 将编译异常-->运行异常,抛出, } finally { JDBCUtilsByDruid.closeConnection(null, null, connection); } } // 查询单行单列的方法,即返回单值的方法 public Object queryScalar(String sql, Object... params) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return qr.query(connection, sql, new ScalarHandler<>(), params); } catch (SQLException e) { throw new RuntimeException(e);// 将编译异常-->运行异常,抛出, } finally { JDBCUtilsByDruid.closeConnection(null, null, connection); } } } -
如现数据库里有一个actor表,则在dao包里创建ActorDAO类,继承了BasicDAO
package src.month09.day0915.demo04.dao; import src.month09.day0915.demo04.domain.Actor; public class ActorDAO extends BasicDAO<Actor>{ // 因为继承了BasicDAO,所以有了BasicDAO的所有方法 // 根据业务需求可以写特有方法 } -
还需要再domain包里创建一个Actor类,将来实例化的对象会和得到的记录一一对应,所以属性值得和字段对上(完全一致)
package src.month09.day0915.demo04.domain; public class Actor {// Javaben / POJO / Domain 对象 private Integer id; private String name; private Integer age; private String addr; // 需要给一个无参构造器(反射需要) public Actor() { } public Actor(Integer id, String name, Integer age, String addr) { this.id = id; this.name = name; this.age = age; this.addr = addr; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public String getAddr() { return addr; } public void setAddr(String addr) { this.addr = addr; } @Override public String toString() { return id + "\t" + name + '\t' + age + "\t" + addr; } } -
在utils包里创建一个JDBCUtilsByDruid工具类,具体配置啥的看前面的
package src.month09.day0915.demo04.utils; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.FileInputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; // 基于druid数据库连接池的工具类 public class JDBCUtilsByDruid { private static DataSource ds; // 在静态代码块完成ds初始化 static { Properties properties = new Properties(); try { properties.load(new FileInputStream("druid.properties")); ds = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } // 得到连接,getConnection方法 public static Connection getConnection() throws SQLException { return ds.getConnection(); } // 这里是把connection对象连接放回到连接池,没有真正关闭 public static void closeConnection(ResultSet rs, Statement statement, Connection connection) { try { if (rs != null) { rs.close(); } if (statement != null) { statement.close(); } if (connection != null) { connection.close(); } } catch (SQLException e) { throw new RuntimeException(e); } } } -
在test包里创建测试类,用来通过ActorDAO操作actor表
package src.month09.day0915.demo04.test; import src.month09.day0915.demo04.dao.ActorDAO; import src.month09.day0915.demo04.domain.Actor; import java.util.List; public class Test { public static void main(String[] args) { // 测试ActorDAO对actor表的crud操作 ActorDAO actorDAO = new ActorDAO(); // 插入 int update = actorDAO.update("insert into actor values (?,?,?,?)", 10, "tom", 18, "beijing"); System.out.println(update > 0 ? "插入成功" : "未生效"); // 返回结果是多条记录的情况 List<Actor> actors = actorDAO.queryMultiple("select * from actor where id > ?", Actor.class,5); for (Actor actor : actors) { System.out.println(actor); } // 返回结果是一条语句 Actor actor = actorDAO.querySingle("select * from actor where id = ?", Actor.class, 6); System.out.println(actor); // 返回结果是单行单列 Object o = actorDAO.queryScalar("select name from actor where id = ?", 6); System.out.println(o); } }