Java不可能具体地去操作数据库,因为数据库有许多种,直接操作数据库是一种很低效且复杂的过程。
因此,Java引入JDBC,规定一套操作数据库的接口规范,从而要求数据库厂商去实现JDBC接口。
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();
}
}
属于静态加载,依赖第三方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);
}
相对比上一种方法,现在是通过反射机制获取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);
}
相对比与上一种方法,使用DriverManager来注册,不用写配置文件, 更加方便
//方式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);
}
相对比于上一种方法,直接可以不用注册驱动
@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!");
}
}
*/
}
甚至连反射机制都可以不写,但建议写上!!!
将第四种方法中,把写死的连接数据库的配置信息,通过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);
}
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是接口,而JDBC42ResultSet才是实现接口的实例对象。
不推荐使用 Statement(SQL注入问题很严重) ,而是使用PreparedStatement。
比如,万能密码登入数据库、一些复杂的密码破坏数据库。
使用Statement语句有存在SQL注入的风险,推荐使用PreparedStatement。
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();
if (resultSet.next()) { //如果查询到一条记录,则说明该管理存在
System.out.println("恭喜, 登录成功");
} else {
System.out.println("对不起,登录失败");
}
//关闭连接
resultSet.close();
preparedStatement.close();
connection.close();
}
}
预处理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 ? "执行成功" : "执行失败");
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);
}
}
}
@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);
}
//没有使用事务. 由于部分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:一批、批
使用批处理前后效率对比:
@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);
}
使用批处理时,一定要在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();
引出问题:如果连接5000次数据库会发生什么
因此,进行传统数据库连接的弊端总结:
解释:
- 使用完毕之后再放回去:Java程序与数据库交互完成后,将连接放回连接池中,并不是close 关闭连接,而是改变连接池中一个现有连接的引用。
- Java程序首先在连接池中创建一定数量的连接,这些连接是已经与数据库成功连接上的,已经通过用户、密码等验证。
注意,JDBC只提供一个数据库连接池的接口,具体接口实现靠第三方,因此诞生了许多数据库连接池。
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();
}
com.mysql.cj.jdbc.Driver
jdbc:mysql://localhost:3306/school?rewriteBatchedStatements=true&useSSL=FALSE&serverTimezone=UTC
root
zouwenhao
5
10
5
50
5
2
在大数据量面前,性能最好最稳定
@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();
}
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接口是由不同数据库连接池实现的。
当前的一个问题:如何把结果集(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 testSelectToArrayList() {
......
ArrayList 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;
}
在上一小节中,使用了JavaBean方法来进行对查询结果集ResultSet的持久化保存,但存在部分代码是可复用的,于是引出本小节DBUtils
QueryRunner可以批量处理sql语句
//使用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 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 query(Connection conn, String sql, ResultSetHandler 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 list =
queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
这一小节的要点就是:根据业务逻辑的不同,在queryRunner.query()中选择合适的handle。
演示 apache-dbutils + druid 完成 返回的结果是单行记录(单个对象)
使用BeanHandler,上一小节中返回多个对象,使用的是BeanListHandler
返回单个对象,使用BranHandler
返回单行单列【返回的是一个对象】,使用ScalarHandler
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);
}
演示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);
}
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);
}
总结:
查询多个数据:return qr.query(connection, sql, new BeanListHandler
(clazz), parameters) 查询一个数据:return qr.query(connection, sql, new BeanHandler
(clazz), parameters) 查询单行单例数据:return qr.query(connection, sql, new ScalarHandler(), parameters)
ActorDAO类:
public class ActorDAO extends BasicDAO{ // 继承BasicDAO并指定泛型类型为Actor类
//1. 就有 BasicDAO 的方法
//2. 根据业务需求,可以编写特有的方法.
}
测试方法:
public class TestActorDAO {
public static void main(String[] args) {
ActorDAO actorDAO = new ActorDAO();
//1. 查询
List 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 ? "执行成功" : "执行没有影响表");
}
}