韩顺平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 ? "执行成功" : "执行没有影响表");
}
}
韩顺平JDBC学习笔记的更多相关文章
- 韩顺平Linux学习笔记
第 一 章 Linux开山篇 1.1 Linux课程的内容介绍 1.2Linux的学习方向 1.2.1. Linux运维工程师:主要做大公司中的电脑系统维护,保证服务器的正常运行,如服务器的优化 ...
- JDBC 学习笔记(十一)—— JDBC 的事务支持
1. 事务 在关系型数据库中,有一个很重要的概念,叫做事务(Transaction).它具有 ACID 四个特性: A(Atomicity):原子性,一个事务是一个不可分割的工作单位,事务中包括的诸操 ...
- JDBC 学习笔记(十)—— 使用 JDBC 搭建一个简易的 ORM 框架
1. 数据映射 当我们获取到 ResultSet 之后,显然这个不是我们想要的数据结构. 数据库中的每一个表,在 Java 代码中,一定会有一个类与之对应,例如: package com.gerrar ...
- JDBC 学习笔记(六)—— PreparedStatement
1. 引入 PreparedStatement PreparedStatement 通过 Connection.createPreparedStatement(String sql) 方法创建,主要用 ...
- JDBC学习笔记二
JDBC学习笔记二 4.execute()方法执行SQL语句 execute几乎可以执行任何SQL语句,当execute执行过SQL语句之后会返回一个布尔类型的值,代表是否返回了ResultSet对象 ...
- JDBC学习笔记一
JDBC学习笔记一 JDBC全称 Java Database Connectivity,即数据库连接,它是一种可以执行SQL语句的Java API. ODBC全称 Open Database Conn ...
- JDBC学习笔记(2)——Statement和ResultSet
Statement执行更新操作 Statement:Statement 是 Java 执行数据库操作的一个重要方法,用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句.Statement ...
- JDBC学习笔记(1)——JDBC概述
JDBC JDBC API是一个Java API,可以访问任何类型表列数据,特别是存储在关系数据库中的数据.JDBC代表Java数据库连接. JDBC库中所包含的API任务通常与数据库使用: 连接到数 ...
- 【转】JDBC学习笔记(2)——Statement和ResultSet
转自:http://www.cnblogs.com/ysw-go/ Statement执行更新操作 Statement:Statement 是 Java 执行数据库操作的一个重要方法,用于在已经建立数 ...
随机推荐
- [bzoj1005]明明的烦恼
根据purfer序列的原理,每一个purfer序列都一一对应了一棵树,每一个点在purfer序列中出现的次数就是它的度数,那么直接用组合数去计算即可,注意要加高精度 1 #include<cst ...
- mybatis-批量操作数据(list对象 )
在实际工作中老是忘记 传入的参数和数据库参数名称要一致还是与实体类型一致导致很多笑话发生. 那我还是做个记录吧! dao层: int addRemark(@Param("list" ...
- 主动扫描之Nmap
主动扫描之Nmap 本文参考于李华峰等人的图书<Kali Linux2 网络渗透测试实践指南>2018年第1版 目录 主动扫描之Nmap 基本用法 主机发现 端口发现 扫描目标操作系统 扫 ...
- Anaconda 安装与卸载
Anaconda是一个免费开源的Python和R语言的发行版本,用于计算科学(数据科学.机器学习.大数据处理和预测分析),Anaconda致力于简化软件包管理系统和部署.Anaconda的包使用软件包 ...
- Hermite WENO 重构格式
Hermite WENO 单元重构 本文主要介绍采用 Hermite WENO 重构方法作为斜率限制器应用于二维或高维单元中. 1.简介[1] ENO格式最早由 Harten 等[2]提出,ENO格式 ...
- Neville 插值方法
简介 wikipedia: Neville's method 在数学上,Neville 算法是一种计算插值多项式方法,由数学家Eric Harold Neville提出.由给定的n+1个节点,存在一个 ...
- SQL-Union、Union ALL合并两个或多个 SELECT 语句的结果集
UNION 操作符用于合并两个或多个 SELECT 语句的结果集. 请注意,UNION 内部的 SELECT 语句必须拥有相同数量的列.列也必须拥有相似的数据类型.同时,每条 SELECT 语句中的列 ...
- 痞子衡嵌入式:利用GPIO模块来测量i.MXRT1xxx的系统中断延迟时间
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT1xxx的系统中断延迟时间. 在 <Cortex-M系统中断延迟及其测量方法简介> 一文里,痞子衡介绍了 Cor ...
- opencv学习(三)——绘图功能
绘图功能 我们将学习以下函数:cv.line(),cv.circle(),cv.rectangle(),cv.ellipse(),cv.putText()等. 在这些功能中,有一些相同的参数: img ...
- MySQL8.0配置文件详解
mysql8.0配置文件一.关键配置1. 配置文件的位置 MySQL配置文件 /etc/my.cnf 或者 /etc/my.cnf.d/server.cnf几个关键的文件:.pid文件,记录了进程id ...