韩顺平JDBC学习笔记
第一节 JDBC概述
1.1 JDBC原理图
Java不可能具体地去操作数据库,因为数据库有许多种,直接操作数据库是一种很低效且复杂的过程。
因此,Java引入JDBC,规定一套操作数据库的接口规范,从而要求数据库厂商去实现JDBC接口。
Java程序员只需要面向JDBC接口编程即可。
1.2 JDBC快速入门
具体步骤:
- 注册驱动
- 得到连接
- 执行SQL
- 关闭连接资源
public class JDBC01 {
public static void main(String[] args) throws SQLException {
//前置工作: 在项目下创建一个文件夹比如 libs
// 将 mysql.jar 拷贝到该目录下,点击 add to project ..加入到项目中
//1. 注册驱动
Driver driver = new Driver(); //创建driver对象
//2. 得到连接
// 老师解读
//(1) jdbc:mysql:// 规定好表示协议,通过jdbc的方式连接mysql
//(2) localhost 主机,可以是ip地址
//(3) 3306 表示mysql监听的端口
//(4) hsp_db02 连接到mysql dbms 的哪个数据库
//(5) mysql的连接本质就是前面学过的socket连接
String url = "jdbc:mysql://localhost:3306/hsp_db02";
//将 用户名和密码放入到Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "zouwenhao"); //密码
Connection connect = driver.connect(url, properties);
//3. 执行sql
//String sql = "insert into actor values(null, '刘德华', '男', '1970-11-11', '110')";
//String sql = "update actor set name='周星驰' where id = 1";
String sql = "delete from actor where id = 1";
//statement 用于执行静态SQL语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
// rows返回0,表示执行失败
int rows = statement.executeUpdate(sql); // 如果是 dml语句,返回的就是影响行数,executeUpdate其实就是执行语句
System.out.println(rows > 0 ? "成功" : "失败"); // 受影响的行数大于0,那么执行成功,反之为0,则执行失败
//4. 关闭连接资源
statement.close();
connect.close();
}
}
第二节 连接数据库的五种方式
2.1 使用Driver
属于静态加载,依赖第三方Driver
@Test
public void connect01() throws SQLException {
Driver driver = new Driver(); //创建driver对象
String url = "jdbc:mysql://localhost:3306/school?useSSL=FALSE&serverTimezone=UTC";
//将 用户名和密码放入到Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "zouwenhao"); //密码
Connection connect = driver.connect(url, properties);
System.out.println("方式一:" + connect);
}
2.2 使用反射机制
相对比上一种方法,现在是通过反射机制获取Driver
//方式二
@Test
public void connect02() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, SQLException {
// 使用反射加载Driver类,动态加载,更加灵活,减少依赖性
Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) aClass.getDeclaredConstructor().newInstance();
String url = "jdbc:mysql://localhost:3306/school?useSSL=FALSE&serverTimezone=UTC";
//将 用户名和密码放入到Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "zouwenhao"); //密码
Connection connect = driver.connect(url, properties);
System.out.println("方式二:" + connect);
}
2.3 使用DriverManager.getConnection替代Driver.connect
相对比与上一种方法,使用DriverManager来注册,不用写配置文件, 更加方便
方式三,更加简便一些,连properties类都不需要使用。
//方式3 使用DriverManager 替代 driver 进行统一管理
@Test
public void connect03() throws IllegalAccessException, InstantiationException, ClassNotFoundException, SQLException, NoSuchMethodException, InvocationTargetException {
//使用反射加载Driver
Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) aClass.getDeclaredConstructor().newInstance();
//创建url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/school?useSSL=FALSE&serverTimezone=UTC";
String user = "root";
String password = "zouwenhao";
DriverManager.registerDriver(driver); // 注册Driver驱动
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("方式三:" + connection);
}
2.4 自动完成注册驱动(推荐使用) *
相对比于上一种方法,直接可以不用注册驱动
@Test
public void connect04() throws ClassNotFoundException, SQLException {
// 使用反射机制加载Driver类
// 在加载 Driver类时,完成注册
Class aclass = Class.forName("com.mysql.cj.jdbc.Driver");
//创建url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/school?useSSL=FALSE&serverTimezone=UTC";
String user = "root";
String password = "zouwenhao";
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("方式四:" + connection);
// 类加载的源码分析
/*
源码:
1. 静态代码块,在类加载时,会执行一次.
2. DriverManager.registerDriver(new Driver());
3. 因此注册driver的工作已经完成
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
*/
}
甚至连反射机制都可以不写,但建议写上!!!
2.5 改善第四种方法——使用配置文件(推荐使用) **
将第四种方法中,把写死的连接数据库的配置信息,通过properties类来进行获取配置。
@Test
public void connect05() throws IOException, ClassNotFoundException, SQLException {
// 通过Properties对象获取配置文件的信息
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");
// 利用反射进行Driver注册
Class.forName(driver);
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("方式五:" + connection);
}
第三节 ResultSet(结果集)
查询数据库的结果,存放在一个结果集对象中。
public class ResultSet_ {
public static void main(String[] args) throws Exception {
//通过Properties对象获取配置文件的信息
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");
//1. 注册驱动
Class.forName(driver);//建议写上
//2. 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
//3. 得到Statement
Statement statement = connection.createStatement();
//4. 组织SQL
String sql = "select id, name , sex, borndate from actor";
//执行给定的SQL语句,该语句返回单个 ResultSet对象
/*
+----+-----------+-----+---------------------+
| id | name | sex | borndate |
+----+-----------+-----+---------------------+-------+
| 4 | 刘德华 | 男 | 1970-12-12 00:00:00 |
| 5 | jack | 男 | 1990-11-11 00:00:00 |
+----+-----------+-----+---------------------+-------+
*/
/*
老韩阅读debug 代码 resultSet 对象的结构
*/
ResultSet resultSet = statement.executeQuery(sql);
//5. 使用while取出数据
while (resultSet.next()) { // 让光标向后移动,如果没有更多行,则返回false
int id = resultSet.getInt(1); //获取该行的第1列
//int id1 = resultSet.getInt("id"); //通过列名来获取值, 推荐
String name = resultSet.getString(2);//获取该行的第2列 (select顺序)
String sex = resultSet.getString(3);
Date date = resultSet.getDate(4);
System.out.println(id + "\t" + name + "\t" + sex + "\t" + date);
}
//6. 关闭连接
resultSet.close();
statement.close();
connection.close();
}
}
ResultSet结果集对象结构:
ResultSet是接口,而JDBC42ResultSet才是实现接口的实例对象。
其中rowData才是存放查询数据的:
rows是一个ArrayLits,才是真正存放数据的地方:(放在其中elementData中)
第四节 SQL注入
不推荐使用 Statement(SQL注入问题很严重) ,而是使用PreparedStatement。
比如,万能密码登入数据库、一些复杂的密码破坏数据库。
第五节 PreparedStatement(预处理)
5.1 预处理DQL
使用Statement语句有存在SQL注入的风险,推荐使用PreparedStatement。
总结要点:
- PreparedStatement使用 ? 作为占位符,而我们需要用set方法来指定第几个占位符的参数。(set里的序号,1、2等等是?的顺序)
- 注意占位之后,执行sql语句时,不要将sql参数放入其中,否则会执行带 ? 的sql语句。
public class PreparedStatement_ {
public static void main(String[] args) throws Exception {
//看 PreparedStatement类图
Scanner scanner = new Scanner(System.in);
//让用户输入管理员名和密码
System.out.print("请输入管理员的名字: "); //next(): 当接收到 空格或者 '就是表示结束
String admin_name = scanner.nextLine(); // 老师说明,如果希望看到SQL注入,这里需要用nextLine
System.out.print("请输入管理员的密码: ");
String admin_pwd = scanner.nextLine();
//通过Properties对象获取配置文件的信息
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");
//1. 注册驱动
Class.forName(driver);//建议写上
//2. 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
// =======重要部分=========
//3. 得到PreparedStatement
//3.1 组织SqL , Sql 语句的 ? 就相当于占位符
String sql = "select name , pwd from admin where name =? and pwd = ?"; // ?用于占位
//3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);//关联
//3.3 给 ? 赋值
preparedStatement.setString(1, admin_name);
preparedStatement.setString(2, admin_pwd);
//4. 执行 select 语句使用 executeQuery
// 如果执行的是 dml(update, insert ,delete) executeUpdate()
// 这里执行 executeQuery ,不要在写 sql,因为preparedStatement已经与sql关联
ResultSet resultSet = preparedStatement.executeQuery(sql);
if (resultSet.next()) { //如果查询到一条记录,则说明该管理存在
System.out.println("恭喜, 登录成功");
} else {
System.out.println("对不起,登录失败");
}
//关闭连接
resultSet.close();
preparedStatement.close();
connection.close();
}
}
5.2 预处理DML
预处理DML(数据库操作语言DML:INSERT、UPDATE、DELETE):
//1. 注册驱动
Class.forName(driver);//建议写上
//2. 得到连接
Connection connection = DriverManager.getConnection(url, user, password);
//3. 得到PreparedStatement
//3.1 组织SqL , Sql 语句的 ? 就相当于占位符
//添加记录
//String sql = "insert into admin values(?, ?)";
//String sql = "update admin set pwd = ? where name = ?";
String sql = "delete from admin where name = ?";
//3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//3.3 给 ? 赋值
preparedStatement.setString(1, admin_name);
//preparedStatement.setString(2, admin_name);
//4. 执行 dml 语句使用 executeUpdate
int rows = preparedStatement.executeUpdate();
System.out.println(rows > 0 ? "执行成功" : "执行失败");
第六节 JDBC API小结
第七节 JDBCUtils
7.1 JDBCUtils封装与开发
public class JDBCUtils {
//定义相关的属性(4个), 因为只需要一份,因此,我们做出static
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\\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) {
//1. 将编译异常转成 运行异常
//2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
throw new RuntimeException(e);
}
}
//关闭相关资源
/*
1. ResultSet 结果集
2. Statement 或者 PreparedStatement
3. Connection
4. 如果需要关闭资源,就传入对象,否则传入 null
*/
public static void close(ResultSet set, Statement statement, Connection connection) {
//判断是否为null
try {
if (set != null) {
set.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
//将编译异常转成运行异常抛出
throw new RuntimeException(e);
}
}
}
7.2 JDBCUtils的使用(DML)
@Test
public void testSelect() {
//1. 得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "select * from actor where id = ?";
PreparedStatement preparedStatement = null;
ResultSet set = null;
//3. 创建PreparedStatement 对象
try {
connection = JDBCUtils.getConnection();
System.out.println(connection.getClass()); //com.mysql.jdbc.JDBC4Connection
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, 5);//给?号赋值
//执行, 得到结果集
set = preparedStatement.executeQuery();
//遍历该结果集
while (set.next()) {
int id = set.getInt("id");
String name = set.getString("name");
String sex = set.getString("sex");
Date borndate = set.getDate("borndate");
String phone = set.getString("phone");
System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(set, preparedStatement, connection);
}
}
@Test
public void testDML() {//insert , update, delete
//1. 得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "update actor set name = ? where id = ?";
// 测试 delete 和 insert ,自己玩.
PreparedStatement preparedStatement = null;
//3. 创建PreparedStatement 对象
try {
connection = JDBCUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
//给占位符赋值
preparedStatement.setString(1, "周星驰");
preparedStatement.setInt(2, 4);
//执行
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
}
自己写的测试:
@Test
public void testInsert() throws Exception {
Connection connection = JDBCUtils.getConnection();
// 1. 插入语句:向subject表插入数据
String sqlInsert01 = "INSERT INTO subject(subjectname) VALUES (?),(?)";
PreparedStatement preparedStatement = connection.prepareStatement(sqlInsert01);
preparedStatement.setString(1, "编译原理");
preparedStatement.setString(2, "计算机图形学");
System.out.println(preparedStatement.executeUpdate() > 0 ? "插入成功!": "插入失败!");
// 2. 释放资源
JDBCUtils.close(null, preparedStatement, connection);
}
@Test
public void testDelete() throws Exception {
Connection connection = JDBCUtils.getConnection();
// 1. 删除语句:向subject表删除数据
String sqlDelete01 = "DELETE FROM subject WHERE subjectno BETWEEN 19 AND 22";
PreparedStatement preparedStatement = connection.prepareStatement(sqlDelete01);
System.out.println(preparedStatement.executeUpdate() > 0 ? "删除成功!": "删除失败!");
// 2. 释放资源
JDBCUtils.close(null, preparedStatement, connection);
}
@Test
public void testUpdate() throws Exception {
Connection connection = JDBCUtils.getConnection();
// 1. 修改语句:向subject表修改数据
String sqlUpdate01 = "UPDATE subject set subjectname=\"计算机组成原理\" WHERE subjectname=\"计算机图形学\"";
PreparedStatement preparedStatement = connection.prepareStatement(sqlUpdate01);
System.out.println(preparedStatement.executeUpdate() > 0 ? "修改成功!": "修改失败!");
// 2. 释放资源
JDBCUtils.close(null, preparedStatement, connection);
}
@Test
public void testSelect() throws Exception {
Connection connection = JDBCUtils.getConnection();
// 1. 查询语句:向subject表查询数据
String sqlSelect01 = "SELECT * FROM student";
PreparedStatement preparedStatement = connection.prepareStatement(sqlSelect01);
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
System.out.println(resultSet.getString("studentname") + " 住在 " +
resultSet.getString("address"));
}
// 2. 释放资源
JDBCUtils.close(null, preparedStatement, connection);
}
第八节 事务
注意点:
- connection默认是自动提交事务,也就是执行完SQL语句后,成功就自动提交,数据库就此改变,无法回滚。
- 如果涉及到多条SQL语句组成的事务,为了保证事务执行的正确性,需要将connection的自动提交设置成false。
经典的转账问题,涉及到事务提交:
//没有使用事务. 由于部分SQL语句因为异常而中断执行,因此最后数据库出现了一方扣账,而另一个并没有增加存款金额
@Test
public void noTransaction() {
//操作转账的业务
//1. 得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "update account set balance = balance - 100 where id = 1";
String sql2 = "update account set balance = balance + 100 where id = 2";
PreparedStatement preparedStatement = null;
//3. 创建PreparedStatement 对象
try {
connection = JDBCUtils.getConnection(); // 在默认情况下,connection是默认自动提交
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate(); // 执行第1条sql
int i = 1 / 0; //抛出异常,此时下面的两条语句并不会得到执行
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate(); // 执行第3条sql
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
}
//事务来解决
@Test
public void useTransaction() {
//操作转账的业务
//1. 得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "update account set balance = balance - 100 where id = 1";
String sql2 = "update account set balance = balance + 100 where id = 2";
PreparedStatement preparedStatement = null;
//3. 创建PreparedStatement 对象
try {
connection = JDBCUtils.getConnection(); // 在默认情况下,connection是默认自动提交
//将 connection 设置为不自动提交
connection.setAutoCommit(false); //开启了事务,关闭自动提交事务
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate(); // 执行第1条sql
int i = 1 / 0; //抛出异常
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate(); // 执行第3条sql
//这里提交事务,只有在最后确认所有SQL语句都被成功执行后,才提交事务
connection.commit();
} catch (SQLException e) { // 出现异常后,在这里进行回滚,将之前已经执行了的SQL语句回滚回去
//这里我们可以进行回滚,即撤销执行的SQL
//默认回滚到事务开始的状态.
System.out.println("执行发生了异常,撤销执行的sql");
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
}
第九节 批处理
注解:batch:一批、批
- 效率提升?不再是执行一条语句就提交一次,而是多条语句一同提交处理。
- 减少编译次数体现在:预处理阶段使用占位符,减少编译次数,而不是通过拼接SQL语句。
使用批处理前后效率对比:
@Test
public void noBatch() throws Exception{ // 不使用批处理,一条SQL一条连接
Connection connection = JDBCUtils.getConnection();
String sql = "INSERT INTO test VALUES (null, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long begin = System.currentTimeMillis();
for (int i = 0; i < 500; i++) {
preparedStatement.setString(1, "Jack" + i);
preparedStatement.setString(2, "666");
preparedStatement.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.println("不使用批处理执行时间:" + (end - begin)); // 47342
// 关闭连接
JDBCUtils.close(null, preparedStatement, connection);
}
@Test
public void useBatch() throws Exception{ // 使用批处理,多条SQL才连接一次进行处理
Connection connection = JDBCUtils.getConnection();
String sql = "INSERT INTO test VALUES (null, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
long begin = System.currentTimeMillis();
for (int i = 0; i < 500; i++) {
preparedStatement.setString(1, "Jack" + i);
preparedStatement.setString(2, "666");
preparedStatement.addBatch(); // 添加到批处理
if ((i + 1) % 100 == 0 ) {
preparedStatement.executeBatch(); // 每一百个SQL,执行一次批处理
preparedStatement.clearBatch(); // 清空
}
}
long end = System.currentTimeMillis();
System.out.println("使用批处理执行时间:" + (end - begin)); // 328
// 关闭连接
JDBCUtils.close(null, preparedStatement, connection);
}
addBatch()源码:使用批处理时,一定要在JDBC的url中添加rewriteBatchedStatements=true。
//将sql 语句加入到批处理包中 -> 看源码
/*
//1. //第一就创建 ArrayList - elementData => Object[]
//2. elementData => Object[] 就会存放我们预处理的sql语句
//3. 当elementData满后,就按照1.5扩容(因为底层是用ArrayList临时存放SQL语句)
//4. 当添加到指定的值后,就executeBatch
//5. 批量处理会减少我们发送sql语句的网络开销,而且减少编译次数,因此效率提高
public void addBatch() throws SQLException {
synchronized(this.checkClosed().getConnectionMutex()) {
if (this.batchedArgs == null) {
this.batchedArgs = new ArrayList();
}
for(int i = 0; i < this.parameterValues.length; ++i) {
this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
}
this.batchedArgs.add(new PreparedStatement.BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
}
}
*/
preparedStatement.addBatch();
第十节 数据库连接池
10.1 传统数据库连接的不足
引出问题:如果连接5000次数据库会发生什么
太多连接!!!
即使是连接之后立马关闭,也会造成很大开销!!!
因此,进行传统数据库连接的弊端总结:
10.2 数据库连接池的原理
解释:
- 使用完毕之后再放回去:Java程序与数据库交互完成后,将连接放回连接池中,并不是close 关闭连接,而是改变连接池中一个现有连接的引用。
- Java程序首先在连接池中创建一定数量的连接,这些连接是已经与数据库成功连接上的,已经通过用户、密码等验证。
原理示意图:
- 当连接池中的连接数量是一定的,如果此时Java程序请求的连接数大于连接池中的数量,那么就会进入等待队列进行排队等待。
- 连接池的作用,更多是限制当前与数据库建立连接的数量,以保证数据库能够平稳地运行,不至于发生内存泄露、数据库奔溃重启等现象。
10.3 数据库连接池的种类
注意,JDBC只提供一个数据库连接池的接口,具体接口实现靠第三方,因此诞生了许多数据库连接池。
Druid 很不错!
10.4 C3P0
C3P0实现了JDBC的DataSource接口,并提供了对应的jar包。
主要有两种方式获取连接对象:
//方式1: 相关参数,在程序中指定user, url , password等
@Test
public void c3p0Connect01 () throws Exception{
// 1. 创建一个数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
// 2. 通过配置文件 mysql.properties 获取相关连接的信息
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);
// 4. 设置连接数
// 设置初始化连接数
comboPooledDataSource.setInitialPoolSize(10);
// 设置连接池最大连接数
// 当请求连接数据池的连接数大于50时,就需要进入等队列进行等待
comboPooledDataSource.setMaxPoolSize(50);
// 5. 得到连接对象(其中一个)
Connection connection = comboPooledDataSource.getConnection();
}
//方式2: 使用配置文件模板来完成(xml文件)
@Test
public void c3p0Connect02 () throws Exception{
// 1. 绑定配置文件中的数据源 configName
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("c3p0_test");
Connection connection = comboPooledDataSource.getConnection();
}
方式二中的配置文件:其中xml文件必须放在src目录下
<c3p0-config>
<!-- 数据源名称代表连接池 -->
<named-config name="c3p0_test">
<!-- 驱动类 -->
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<!-- url-->
<property name="jdbcUrl">jdbc:mysql://localhost:3306/school?rewriteBatchedStatements=true&useSSL=FALSE&serverTimezone=UTC</property>
<!-- 用户名 -->
<property name="user">root</property>
<!-- 密码 -->
<property name="password">zouwenhao</property>
<!-- 每次增长的连接数-->
<property name="acquireIncrement">5</property>
<!-- 初始的连接数 -->
<property name="initialPoolSize">10</property>
<!-- 最小连接数 -->
<property name="minPoolSize">5</property>
<!-- 最大连接数 -->
<property name="maxPoolSize">50</property>
<!-- 可连接的最多的命令对象数 -->
<property name="maxStatements">5</property>
<!-- 每个连接对象可连接的最多的命令对象数 -->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config>
10.5 Druid(德鲁伊)
在大数据量面前,性能最好最稳定。
@Test
public void testDruid() throws Exception {
//1. 加入 Druid jar包
//2. 加入 配置文件 druid.properties , 将该文件拷贝项目的src目录
//3. 创建Properties对象, 读取配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
//4. 创建一个指定参数的数据库连接池, Druid连接池
DataSource dataSource =
DruidDataSourceFactory.createDataSource(properties);
//5. 获得连接
Connection connection = dataSource.getConnection();
connection.close();
}
10.5.1 Druid JDBCUtils
基于Druid数据库连接池的工具类:
public class JDBCUtilsByDruid {
private static DataSource ds;
//在静态代码块完成 ds初始化
static {
Properties properties = new Properties();
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();
}
//关闭连接, 老师再次强调: 在数据库连接池技术中,close 不是真的断掉连接
//而是把使用的Connection对象放回连接池
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);
}
}
}
注意点:connection.close()这条语句,是根据connection实现的类,来决定是断开连接还是还原到连接池中,体现了Java多态,根据当前connection接口所实现的连接池类,来调用对应的close方法,是因为connection接口是由不同数据库连接池实现的。
10.6 Apache—DBUtils
10.6.1 引出问题—如何解决数据集的持久化问题
当前的一个问题:如何把结果集(resultSet)进行持久化?因为每当我们从数据库获得结果集ResultSet之后,只能使用一次就关闭了连接,一旦关闭连接,数据库返回的数据就无法再次使用,但我们不能保持与数据库的连接长期有效,这样会造成很大的高并发压力。
解决当前问题的思路:编写一个类,与数据库返回结果集相映射,通过一个类对象对应一条数据库查询记录的方式,通过ArrayList存储,以实现数据库查询结果的相对持久化的记录。
通过类的封装来实现数据集的复用:(土方法)
首先,新建一个Actor类
public class Actor { //Javabean, POJO, Domain对象
private Integer id;
private String name;
private String sex;
private Date borndate;
private String phone;
public Actor() { //一定要给一个无参构造器[反射需要]
}
public Actor(Integer id, String name, String sex, Date borndate, String phone) {
this.id = id;
this.name = name;
this.sex = sex;
this.borndate = borndate;
this.phone = phone;
}
........
然后,看主代码
//解决ResultSet =封装=> Arraylist
@Test
public ArrayList<Actor> testSelectToArrayList() {
......
ArrayList<Actor> list = new ArrayList<>();//创建ArrayList对象,存放actor对象
//3. 创建PreparedStatement 对象
try {
connection = JDBCUtilsByDruid.getConnection();
System.out.println(connection.getClass());//运行类型 com.alibaba.druid.pool.DruidPooledConnection
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, 1);//给?号赋值
//执行, 得到结果集
set = preparedStatement.executeQuery();
//遍历该结果集
while (set.next()) {
int id = set.getInt("id");
String name = set.getString("name");//getName()
String sex = set.getString("sex");//getSex()
Date borndate = set.getDate("borndate");
String phone = set.getString("phone");
//把得到的resultset 的记录,封装到 Actor对象,放入到list集合
list.add(new Actor(id, name, sex, borndate, phone)); // 将查询记录,一条接一条放入list中
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtilsByDruid.close(set, preparedStatement, connection);
}
//因为ArrayList 和 connection 没有任何关联,所以该集合可以复用.
return list;
}
10.6.2 DBUtils基本介绍
在上一小节中,使用了JavaBean方法来进行对查询结果集ResultSet的持久化保存,但存在部分代码是可复用的,于是引出本小节DBUtils
还是以Actor类为例,使用了DBUtils示例如下:
//使用apache-DBUtils 工具类保存查询结果数据集
public void testQueryMany() throws SQLException { //返回结果是多行的情况
//1. 得到 连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
//3. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//4. 就可以执行相关的方法,返回ArrayList 结果集
//String sql = "select * from actor where id >= ?";
// 注意: sql 语句也可以查询部分列
String sql = "select id, name from actor where id >= ?";
// 老韩解读
//(1) query 方法就是执行sql 语句,得到resultset ---封装到 --> ArrayList 集合中
//(2) 返回集合
//(3) connection: 连接
//(4) sql : 执行的sql语句
//(5) new BeanListHandler<>(Actor.class): 在将resultset -> Actor 对象 -> 封装到 ArrayList
// 底层使用反射机制 去获取 Actor 类的属性,然后进行封装
//(6) 1 就是给 sql 语句中的? 赋值,可以有多个值,因为是可变参数Object... params
//(7) 底层得到的resultset ,会在query 关闭, 关闭PreparedStatment
List<Actor> list =
queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
System.out.println("输出集合的信息");
for (Actor actor : list) {
System.out.print(actor);
}
//释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
对query方法的源码解读:从源码中也能得知,在使用DBUtils之后,用户只需要关闭connection即可
/**
* 分析 queryRunner.query方法:
* 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,返回resultset
* result = rsh.handle(rs);//返回的resultset --> arrayList[result] [使用到反射,对传入class对象处理]
* } catch (SQLException var33) {
* this.rethrow(var33, sql, params);
* } finally {
* try {
* this.close(rs);//关闭resultset
* } finally {
* this.close((Statement)stmt);//关闭preparedstatement对象
* }
* }
*
* return result;
* }
*/
List<Actor> list =
queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
10.6.3 DBUtils应用实例
这一小节的要点就是:根据业务逻辑的不同,在queryRunner.query()中选择合适的handle。
10.6.3.1 返回的结果是单行记录(单个对象)
演示 apache-dbutils + druid 完成 返回的结果是单行记录(单个对象)
使用BeanHandler,上一小节中返回多个对象,使用的是BeanListHandler
public void testQuerySingle() throws SQLException {
//1. 得到 连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
//3. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//4. 就可以执行相关的方法,返回单个对象
String sql = "select * from actor where id = ?";
// 老韩解读
// 因为我们返回的单行记录<--->单个对象 , 使用的 Hander 是 BeanHandler , 底层使用的是反射机制
Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 10);
System.out.println(actor);
// 释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
10.6.3.2 完成查询结果是单行单列-返回的就是object
演示apache-dbutils + druid 完成查询结果是单行单列-返回的就是object
使用ScalarHandler
public void testScalar() throws SQLException {
//1. 得到 连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
//3. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//4. 就可以执行相关的方法,返回单行单列 , 返回的就是Object
String sql = "select name from actor where id = ?";
//老师解读: 因为返回的是一个对象, 使用的handler 就是 ScalarHandler
Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 4);
System.out.println(obj);
// 释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
10.6.3.3 完成DML语句(update, insert ,delete)
public void testDML() throws SQLException {
//1. 得到 连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
//3. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//4. 这里组织sql 完成 update, insert delete
//String sql = "update actor set name = ? where id = ?";
//String sql = "insert into actor values(null, ?, ?, ?, ?)";
String sql = "delete from actor where id = ?";
//老韩解读
//(1) 执行dml 操作是 queryRunner.update()
//(2) 返回的值是受影响的行数 (affected: 受影响)
//int affectedRow = queryRunner.update(connection, sql, "林青霞", "女", "1966-10-10", "116");
int affectedRow = queryRunner.update(connection, sql, 1000 );
System.out.println(affectedRow > 0 ? "执行成功" : "执行没有影响到表");
// 释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
10.7 BasicDAO(CRUD的通用方法)
10.7.1 引出BasicDAO
BasicDAO与数据库中表的示意图:
总结:
10.7.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); // 关闭连接
}
}
//返回多个对象(即查询的结果是多行), 针对任意表
/**
*
* @param sql sql 语句,可以有 ?
* @param clazz 传入一个类的Class对象 比如 Actor.class
* @param parameters 传入 ? 的具体的值,可以是多个
* @return 根据Actor.class 返回对应的 ArrayList 集合
*/
public List<T> queryMulti(String sql, Class<T> clazz, Object... parameters) {
Connection connection = null;
try {
connection = JDBCUtilsDruid.getConnection();
// 返回某类型的对象List数组列表
return qr.query(connection, sql, new BeanListHandler<T>(clazz), parameters);
} catch (SQLException e) {
throw new RuntimeException(e); // 将编译异常->运行异常,方便使用者处理(既可以抛出,也可以打印)
} finally {
JDBCUtilsDruid.close(null, null, connection); // 关闭连接
}
}
// 查询单行结果的通用方法
// 以Actor为例,查询结果肯定是返回一个Actor类型的对象
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); // 关闭连接
}
}
// 查询单行单列的方法,即返回单值的方法
// 这里的Object是单行单列的某元素类型(int、string ....),如查询某行的姓名项,此时就是String类型,这里用Object类
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); // 关闭连接
}
}
}
10.7.3 ActorDAO实现(具体实例)
ActorDAO类:
public class ActorDAO extends BasicDAO<Actor>{ // 继承BasicDAO并指定泛型类型为Actor类
//1. 就有 BasicDAO 的方法
//2. 根据业务需求,可以编写特有的方法.
}
测试方法:
public class TestActorDAO {
public static void main(String[] args) {
ActorDAO actorDAO = new ActorDAO();
//1. 查询
List<Actor> actors = actorDAO.queryMulti("select * from actor where id >= ?", Actor.class, 1);
System.out.println("===查询结果===");
for (Actor actor : actors) {
System.out.println(actor);
}
//2. 查询单行记录
Actor actor = actorDAO.querySingle("select * from actor where id = ?", Actor.class, 6);
System.out.println("====查询单行结果====");
System.out.println(actor);
//3. 查询单行单列
Object o = actorDAO.queryScalar("select name from actor where id = ?", 6);
System.out.println("====查询单行单列值===");
System.out.println(o);
//4. dml操作 insert ,update, delete
int update = actorDAO.update("insert into actor values(null, ?, ?, ?, ?)", "张无忌", "男", "2000-11-11", "999");
System.out.println(update > 0 ? "执行成功" : "执行没有影响表");
}
}