JDBC剩余部分
ResultSet(结果集)底层
返回ResultSet可以认为是返回一张表
ResultSet相当于集合中的迭代器,刚开始光标指向数据之外,next方法光标往后移动一行(每次使用一次next光标往后移动一行),当光标的指向处没有数据时,next函数返回false
package com.hsp.edu;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
//演示Select语句返回ResultSet,并取出结果
public class ResultSetTest {
public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
//获取配置文件信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql1.properties"));
final String url = properties.getProperty("url");
final String user = properties.getProperty("user");
final String password = properties.getProperty("password");
final String driver = properties.getProperty("driver");
//1.注册驱动
Class.forName(driver);
//2.获取连接
final Connection connection = DriverManager.getConnection(url, user, password);
//3.执行sql
String sql="select * from actor";
final Statement statement = connection.createStatement();
final ResultSet resultSet = statement.executeQuery(sql);//执行查询语句,返回一个结果集
//初始时,光标指向表外
while (resultSet.next()){//让光标向后移动,如果没有更多行,返回false
final int id = resultSet.getInt(1);//得到该行第一列
final String name = resultSet.getString(2);//获取改行第二列
final String sex = resultSet.getString(3);//获取改行第三列
final String time = resultSet.getString(4);//第四列
System.out.println("id"+id + "name"+name +"sex"+sex+ " time"+time);
}
//4.关闭资源
resultSet.close();
statement.close();
connection.close();
}
}
SQL注入
sql注入演示
-- 演示sql注入
-- 建立一张表
CREATE TABLE admin(
`name` VARCHAR(32) NOT NULL UNIQUE,
psd VARCHAR(32)NOT NULL DEFAULT'')CHARACTER SET utf8;
-- 添加数据
INSERT INTO admin VALUES('tom','123'),('jack','456');
-- 查询某个管理员是否存在(正常写法)
SELECT * FROM admin
WHERE `name` ='tom' AND psd='123'
-- sql注入
-- 输入用户名:1'or
-- 输入密码为:or '1'='1
SELECT * FROM admin
WHERE `name` ='1'OR' AND psd='OR '1'='1'
上面的代码中,当数据库没有对输入的信息进行检查。通过输入的非法数据:通过输入的非法的信息,where子句直接为真,将会直接执行查找整个管理员表。将会导致非法分子获取了数据库的用户和密码,从而导致数据丢失
演示java程序中的sql注入
package com.hsp.edu;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
//演示java程序中的sql注入问题
public class StatementTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException, IOException {
//模拟一个在java执行sql实现登录数据库
//前置工作:获取配置文件中的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql1.properties"));//读取信息到集合中
final String driver = properties.getProperty("driver");//获取全类名
final String url = properties.getProperty("url");//获取url
final String user = properties.getProperty("user");//获取用户名
final String password = properties.getProperty("password");//获取密码
//1.注册驱动
Class.forName(driver);
//2.获取连接
final Connection connection = DriverManager.getConnection(url, user, password);
//3.执行sql
Scanner sc = new Scanner(System.in);
//定义用户
String admin_name;
String admin_psw;
System.out.println("请输入用户名:");
admin_name = sc.nextLine();
System.out.println("请输入密码:");
admin_psw= sc.nextLine();
//定义sql
String sql ="SELECT * FROM admin WHERE NAME ='" +admin_name+ "' AND psd='" +admin_psw+ "'";//注意中间不要有空格
System.out.println(sql);
final Statement statement = connection.createStatement();//获取statement对象
final ResultSet resultSet = statement.executeQuery(sql);//执行sql
if(resultSet.next()){
System.out.println("登录成功");
}else {
System.out.println("登录失败");
}
}
}
真实的管理员表的信息
- 正确的用户密码登录
- 使用sql注入非法登录
此时就通过sql注入非法获取了数据库的管理员的信息
预处理查询
- PreparedStatement的继承结构
PreparedStatement是Statement的一个子接口
案例演示(使用PreparedStatement解决sql注入问题)
package com.hsp.edu;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
//演示ProparedStatement的使用
public class ProparedStatementTest {
//模拟一个在java执行sql实现非法获取数据库管理员的信息
public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
//准备工作
String admin_name;//查询的管理员姓名
String admin_psw;//查询的密码
Scanner sc = new Scanner(System.in);
//键盘录入姓名和密码
System.out.println("请输入姓名:");
admin_name = sc.nextLine();
System.out.println("请输入密码:");
admin_psw= sc.nextLine();
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
final String user = properties.getProperty("user");//得到用户名
final String password = properties.getProperty("password");//得到密码
final String url = properties.getProperty("url");//得到url
final String driver = properties.getProperty("driver");//获取驱动类全类名
//1.注册驱动
Class.forName(driver);
//2.获取连接
final Connection connection = DriverManager.getConnection(url, user, password);
//3.执行sql(使用proparedStatement)
//3.1定义sql sql语句的?相对于占位符
String sql = "select * from admin where name=? and psd=?";
//preparedStatement为PreparedStatement实现类的对象
//3.2
final PreparedStatement preparedStatement = connection.prepareStatement(sql);//获取对象
//3.3 给?赋值(依次给sql中的?赋值)
//第一个参数表示第几个?
preparedStatement.setString(1,admin_name);
preparedStatement.setString(2,admin_psw);
//3.4执行sql
final ResultSet resultSet = preparedStatement.executeQuery();//在创建PreparedStatement时已经绑定sql,这里不能填写sql了
if(resultSet.next()){
System.out.println("登录成功");
}else {
System.out.println("登录失败");
}
//4.关闭资源
resultSet.close();
preparedStatement.close();
connection.close();
}
}
这里成功的控制了sql注入问题
通过测试,当我们执行的是静态sql语句 final ResultSet resultSet = preparedStatement.executeQuery(sql)处的executeQuery()参数sql写不写都可以,
但是如果执行的是动态的sql则该处不能写
预处理DML
在判断sql语句是否执行成功的时候(使用返回多少行受到影响)时要注意有些情况下可能有0行受到影响,但是此时程序是执行成功的
- 以insert语句为例
package com.hsp.edu;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.Properties;
import java.util.Scanner;
//演示使用ProparedStatement处理SQL的DML语句
public class ProparedStatementTest2 {
public static void main(String[] args) throws Exception{
//前置工作
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
final String url = properties.getProperty("url");
final String user = properties.getProperty("user");
final String password = properties.getProperty("password");
final String driver = properties.getProperty("driver");//驱动类全类名
System.out.println(driver);
//1.注册驱动
Class.forName(driver);
//2.获取连接
final Connection connection = DriverManager.getConnection(url, user, password);
//3.执行sql
//3.1 定义sql
String sql = "insert into admin values(?,?)";
//3.2获取ProparedStatement的对象
final PreparedStatement preparedStatement =connection.prepareStatement(sql);
//3.3给?赋值
Scanner sc = new Scanner(System.in);
System.out.println("输入用户名:");
String admin_user = sc.nextLine();
System.out.println("输入密码");
String admin_psw =sc.nextLine();
preparedStatement.setString(1,admin_user);
preparedStatement.setString(2,admin_psw);
final int row = preparedStatement.executeUpdate();//执行sql
if(row!=0){
System.out.println("执行成功");
}else {
System.out.println("执行失败");
}
//4.关闭资源
preparedStatement.close();
connection.close();
}
}
个人见解
**在使用Statement处理sql时,将可能回导致sql注入问题。在使用PreparedSteatement时,将一些数据通过?占位符代替,?数据通过set方法统一录入。java在set方法中对?的内容单独进行了检查,从而避免了sql注入问题
预处理练习
package com.hsp.edu;
import jdk.jshell.spi.SPIResolutionException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;
/*
预处理练习:
1.创建admin表
2.使用prearedStatement添加5条记录
3.修改tom的记录,将name改成king
4.删除一条记录
5.查询全部记录,并显示在控制台
*/
public class PreparedStatementPractice {
public static void main(String[] args) throws Exception {
//准备工作
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
final String url = properties.getProperty("url");
final String user = properties.getProperty("user");
final String driver = properties.getProperty("driver");
final String password = properties.getProperty("password");
//1.注册驱动
Class.forName(driver);
//2.获取连接
final Connection connection = DriverManager.getConnection(url, user, password);
//3.执行sql
//定义sql
String sql1 = "insert into admin values (?,?),(?,?),(?,?)";//添加记录
String sql2 = "update admin set name=? where name=?";
String sql3 = "select * from admin";
final PreparedStatement preparedStatement = connection.prepareStatement(sql1);
//给?赋值
preparedStatement.setString(1,"tom");
preparedStatement.setInt(2,1);
preparedStatement.setString(3,"jack");
preparedStatement.setInt(4,2);
preparedStatement.setString(5,"bob");
preparedStatement.setInt(6,3);
final PreparedStatement preparedStatement1 = connection.prepareStatement(sql2);
preparedStatement1.setString(1,"king");
preparedStatement1.setString(2,"tom");
final int row = preparedStatement.executeUpdate();
final int row1 = preparedStatement1.executeUpdate();
final PreparedStatement preparedStatement2 = connection.prepareStatement(sql3);
final ResultSet resultSet = preparedStatement2.executeQuery();
while (resultSet.next()){//光标不为false
final String name = resultSet.getString(1);//该行第一列
final int id = resultSet.getInt(2);//该行第2列
System.out.println(name+" "+id);
}
//4关闭资源
resultSet.close();
preparedStatement1.close();
preparedStatement2.close();
preparedStatement.close();
connection.close();
}
}
没有解决或者该代码存在的问题:
1.无法执行执行多条sql,而是每一条sql需要创建一个PreparedStatement对象
2.当我们给?赋值的时候,无法一下给一个sql的所有?赋值,而是需要一个一个?赋值,很麻烦
JDBC API
在给?赋值时,可以统一使用setObject()更简单。在获取结果集的值的时候,可以统一使用getObject()方法,更简单
封装JDBC utils
将获取连接和释放资源封装到工具类中
- 封装成工具类的的原理图
如果我们有多个java程序操作我们的mysql,他们所执行的sql语句可能是不同的,但是操作sql中的1.连接数据库2.关闭资源可能是相同的。我们将连接数据库和关闭资源封装在一个工具类JDBCUiils中,可以有效的减少代码的冗余
**测试类--调用前面的JDBCUtils工具类 **
package com.hsp.edu;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/*
这是一个工具类,完成mysql的连接和关闭资源
*/
public class JDBCUtils {
private static String url;//url
private static String user;//用户名
private static String password;//密码
private static String driver;//驱动全类名
static {//初始化
Properties properties = null;
try {
properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
} catch (IOException e) {
//在实际开发中 将编译异常转化为运行时异常,并抛给调用者
//这时调用者,可以选择捕获该异常,也可以使用默认方式处理
throw new RuntimeException(e);
}
url = properties.getProperty("url");
user = properties.getProperty("name");
password = properties.getProperty("password");
driver = properties.getProperty("driver");
}
//连接数据库
public static Connection getConnection(){
final Connection connection;
try {
connection = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return connection;
}
//关闭资源
/*
那些资源可能需要关闭:
1.ResultSet
2.Statement或PreparedStatement
3.Connection
4.如果需要关闭资源就传入对象,否则传入null
*/
public static void close(ResultSet set, Statement statement,Connection connection){
//Statement为PreparedStatement的父接口,两者都可以通过Staatement接收
//这里当多个资源需要关闭时,应该每个资源都使用一个try...catch
//从而避免了当有一个关闭出现异常而导致其他的资源无法关闭的情况
try {
if(set!=null){
set.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
if(statement!=null){
statement.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
1.对于是否要进行注册驱动不是很清楚?
2.对于资源关闭的异常处理问题还需要优化
JDBCUtilsDML(演示使用前面的JDBC工具类)
注册驱动可以自动执行,写不写都行,会Mysql的版本有关
package com.hsp.edu;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
//演示使用JDBCUtils工具类
public class JDBCUtils_use {
public static void testDML(){//执行DML
//1.获取连接(注册驱动在高版本的mysql中可以自动执行,这里省略)
Connection connection = null;
//2.执行sql
//2.1定义sql
String sql = "update actor set name=? where id=?";
PreparedStatement preparedStatement=null;//避免被finally访问不到
//2.2 获取PreparedStatement对象
try {
connection=JDBCUtils.getConnection();//该处需要处理getConnection可能抛出的异常,需要处理
preparedStatement = connection.prepareStatement(sql);
//2.3给?赋值
preparedStatement.setObject(1,"周星驰");
preparedStatement.setObject(2,1);
//2.4执行
final int row = preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement,connection );
}
}
public static void main(String[] args) {
testDML();
}
}
JDBCUtil 查询
package com.hsp.edu;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//演示使用JDBCUtils工具类
public class JDBCUtils_use {
public static void testSelect(){
//1.获取连接(注册驱动在高版本的mysql中可以自动执行,这里省略)
Connection connection = null;
//2.执行sql
//2.1定义sql
String sql = "select * from actor";
PreparedStatement preparedStatement=null;//避免被finally访问不到
ResultSet set = null;//避免被finally访问不到
//2.2 获取PreparedStatement对象
try {
connection=JDBCUtils.getConnection();//该处需要处理getConnection可能抛出的异常,需要处理
preparedStatement = connection.prepareStatement(sql);
//2.3给?赋值
//该sql不需要赋值
//2.4执行
set = preparedStatement.executeQuery();
//2.5遍历结果集
while (set.next()){
final Object column1 = set.getObject(1);//该行第一列
final Object column2 = set.getObject(2);//第二列
System.out.println("姓名\t"+column1.toString()+" id\t"+column2.toString());
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(set, preparedStatement,connection );//此时结果集也要关闭
}
}
public static void main(String[] args) {
testSelect();
}
}
和上面类似,只是在执行sql的步骤中,有点不同
事务概念
当我们的获取一个连接,并且有了一个Statement/PreparedSatment对象将sql发送给Mysql执行后,将会自动提交Commit,不能进行回滚
当我们执行多条语句时,为了防止有的语句执行成功,有的语句执行失败,从而导致的数据不一致的问题,需要使用事务来解决这一问题。此时这些语句要能全部执行成功,如果有一条执行失败,也将全部执行失败
通过Connect 的setAutoCommit(false)可以将事务设置为不自动提交。这样的化,sql将会自动,但是 不会自动提交
实例模拟事务的使用
模拟转账将马云的100块转给马化腾
- 准备
在jdbc数据库中创建创建一张账户表,并添加马云和马化腾的信息 - 最初状态已经实现
不使用事务的情况
JDBC工具类
package com.hsp.edu;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/*
这是一个工具类,完成mysql的连接和关闭资源
*/
public class JDBCUtils {
private static String url;//url
private static String user;//用户名
private static String password;//密码
private static String driver;//驱动全类名
static {//初始化
Properties properties = null;
try {
properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
} catch (IOException e) {
//在实际开发中 将编译异常转化为运行时异常,并抛给调用者
//这时调用者,可以选择捕获该异常,也可以使用默认方式处理
throw new RuntimeException(e);
}
url = properties.getProperty("url");
user = properties.getProperty("user");
password = properties.getProperty("password");
driver = properties.getProperty("driver");
}
//连接数据库
public static Connection getConnection() {
final Connection connection;
try {
connection = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return connection;
}
//关闭资源
/*
那些资源可能需要关闭:
1.ResultSet
2.Statement或PreparedStatement
3.Connection
4.如果需要关闭资源就传入对象,否则传入null
*/
public static void close(ResultSet set, Statement statement,Connection connection){
//Statement为PreparedStatement的父接口,两者都可以通过Staatement接收
try {
if(set!=null){
set.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
if(statement!=null){
statement.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
try {
if(connection!=null){
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
测试类
package com.hsp.edu.transaction;
import com.hsp.edu.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//演示jdbc中如何使用事务
//此时将会执行2条sql语句
public class Transaction {
//不使用事务处理
public static void noTransaction(){
//1.获取连接(注册驱动在高版本的mysql中可以自动执行,这里省略)
Connection connection = null;
//2.执行sql
//2.1定义sql
String sql1 = "update account set balance=? where id=?";//马云-100
String sql2 = "update account set balance =? where id=?";//马化腾+100
PreparedStatement preparedStatement1=null;//避免被finally访问不到
PreparedStatement preparedStatement2 = null;
//2.2 获取PreparedStatement对象
try {
connection= JDBCUtils.getConnection();//该处需要处理getConnection可能抛出的异常,需要处理
preparedStatement1 = connection.prepareStatement(sql1);
preparedStatement2 = connection.prepareStatement(sql2);
//2.3给?赋值
preparedStatement1.setObject(1,2900);
preparedStatement1.setObject(2,1);
preparedStatement2.setObject(1,10100);
preparedStatement2.setObject(2,2);
//2.4执行
preparedStatement1.executeUpdate();//执行第一条sql
//假如在该处抛出异常,此时下面的第二条sql将不会执行
int c = 2/0;
preparedStatement2.executeUpdate();//执行第二条sql
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement1,connection );//此时结果集也要关闭
}
}
public static void main(String[] args) {
noTransaction();
}
}
在这2个sql执行过程中,当第一个sql执行后,程序出现了异常(使用了2/0来模拟),此时的程序将不会执行后面的sql2,然后程序停止运行
这样将会导致严重的后果,即马云被减去 了100元,马化腾却没有多100元,100元此时不翼而飞
在默认的情况下,connection对象,是默认自动提交事务的
非事务操作----》sql操作将会自动提交--》无法保证数据的一致性
事务---》保证数据的一致性----》如果有的语句执行失败----》可以通过回滚撤销操作----》从而保证的数据的一致性 当操作都没有问题时,手动提交事务,数据才生效
使用事务处理
package com.hsp.edu.transaction;
import com.hsp.edu.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//演示jdbc中如何使用事务
//此时将会执行2条sql语句
public class Transaction {
public static void useTransaction() {
//1.获取连接(注册驱动在高版本的mysql中可以自动执行,这里省略)
Connection connection = null;
//2.执行sql
//2.1定义sql
String sql1 = "update account set balance=? where id=?";//马云-100
String sql2 = "update account set balance =? where id=?";//马化腾+100
PreparedStatement preparedStatement1 = null;//避免被finally访问不到
PreparedStatement preparedStatement2 = null;
//2.2 获取PreparedStatement对象
try {
connection = JDBCUtils.getConnection();//该处需要处理getConnection可能抛出的异常,需要处理
connection.setAutoCommit(false);//设置为不自动提交事务(其实也就是开启了事务)
preparedStatement1 = connection.prepareStatement(sql1);
preparedStatement2 = connection.prepareStatement(sql2);
//2.3给?赋值
preparedStatement1.setObject(1, 2900);
preparedStatement1.setObject(2, 1);
preparedStatement2.setObject(1, 10100);
preparedStatement2.setObject(2, 2);
//2.4执行
preparedStatement1.executeUpdate();//执行第一条sql
//假如在该处抛出异常,此时下面的第二条sql将不会执行
int c = 2 / 0;//此处抛出异常,进入catch
preparedStatement2.executeUpdate();//执行第二条sql
//当前面的sql执行的都没有问题的时候,提交事务
connection.commit();
} catch (SQLException e) {
//在catch里面可以进行回滚(撤销执行的sql)
System.out.println("执行发生了异常,撤销执行的sql(执行回滚操作)");
try {
connection.rollback();//参数可以写保持点/无参数则是默认回滚到事务开始的状态
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement1, connection);//此时结果集也要关闭
}
}
public static void main(String[] args) {
useTransaction();
}
}
此时我们查询表,发现表中的数据并没有发生变化。说明此时我们的事务已经成功的回滚,保证了数据的一致性
批处理
多条sql语句,需要发送给mysql处理,需要发送多次,而如果将多条sql语句打包到一个集合中进行发送,则只需要发送一次,效率更高
Statement和PreparedStatement可以用于执行sql语句,可以同一条sql语句执行多次,但是不同执行多条sql语句
- 已经创建了空表admin
使用传统的方式添加500条记录
package com.hsp.edu.batch;
import com.hsp.edu.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
//演示使用批处理处理大量sql和不使用批处理处理大量sql的效率
public class Batch_ {
public static void noBatch() throws SQLException {
//1.获得连接
final Connection connection = JDBCUtils.getConnection();
//2.执行sql
//2.1定义sql
String sql = "insert into admin values(null,?,?)";
final PreparedStatement preparedStatement = connection.prepareStatement(sql);
final long start = System.currentTimeMillis();//开始时间
for (int i = 0; i < 5000; i++) {//执行5000次sql
//2.2.给?赋值
preparedStatement.setObject(1,"jack"+i);//为了和前面区分
preparedStatement.setObject(2,666);
preparedStatement.executeUpdate();//执行sql
}
final long end = System.currentTimeMillis();//结束时间
System.out.println("传统做法耗时:"+(end-start));
//3.关闭资源
JDBCUtils.close(null,preparedStatement,connection);
}
public static void main(String[] args) throws SQLException {
noBatch();
}
}
- 时间花销
批量处理
注意经过实验:如果我们进行批量处理,但是url里面的参数没有改变,此时程序还是可以执行的(程序不会抛出异常),此时我们的程序的并没有进行批处理,以至于我们的执行的时间被没有缩短
- 此时我们在Url中添加&rewriteBachedStatements=true
- url
package com.hsp.edu.batch;
import com.hsp.edu.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
//演示使用批处理处理大量sql和不使用批处理处理大量sql的效率
public class Batch_ {
//使用批量处理
public static void useBatch () throws Exception {
//1.获得连接
final Connection connection = JDBCUtils.getConnection();
//2.执行sql
//2.1定义sql
String sql = "insert into admin values(null,?,?)";
final PreparedStatement preparedStatement = connection.prepareStatement(sql);
final long start = System.currentTimeMillis();//开始时间
for (int i = 0; i < 5000; i++) {//执行5000次sql
//2.2.给?赋值
preparedStatement.setObject(1,"jack"+i);//为了和前面区分
preparedStatement.setObject(2,666);
//此时不立马执行,先将sql加入到批处理包中--->里面具体参照源码
preparedStatement.addBatch();
//当包有1000条记录时,批量执行
if((i+1)%1000==0){
preparedStatement.executeBatch();//将包中的sql批量执行
//将包中的sql清空
preparedStatement.clearBatch();
}
}
final long end = System.currentTimeMillis();//结束时间
System.out.println("批处理做法耗时:"+(end-start));
//3.关闭资源
JDBCUtils.close(null,preparedStatement,connection);
}
public static void main(String[] args) throws Exception {
useBatch();
}
}
可以看出我们的速度有很大的飞跃
批处理源码分析(源码理解不够详尽,以后再议)
传统连接方式的弊端
在我们的实际开发中,我们可能会进行多次连接mysql,如果我们是并发程序,我们可能有多个程序分别多次连接Mysql,这样一来,如果我们的mysql的连接数量达到峰值,mysql可能会崩溃。而且多个连接我们需要进行频繁的进行连接和释放资源,将会耗费大量的时间,将造成效率问题
- 严重当mysql被大量连接而没有被释放出现的问题
package com.hsp.edu;
import java.sql.Connection;
public class ConQuestion {
public static void testCon(){
//模拟进行5000次连接还没有来得及释放
for (int i = 0; i < 500000000; i++) {
//使用传统的jdbc方式,得到连接
final Connection connection = JDBCUtils.getConnection();
//后面将会执行操作...
}
}
public static void main(String[] args) {
}
}
此时将可能会出现以上的异常情况,我们的Mysql直接罢工了
进行频繁连接并且释放将耗费大量时间
package com.hsp.edu;
import java.sql.Connection;
public class ConQuestion {
//问题2:频繁的进行连接然后释放mysql,将会耗费大量的时间
public static void testCon1(){
final long start = System.currentTimeMillis();
System.out.println("开始进行连接");
for (int i = 0; i < 50000; i++) {
//使用传统的jdbc方式,得到连接
final Connection connection = JDBCUtils.getConnection();
//释放资源
JDBCUtils.close(null,null,connection);
}
final long end = System.currentTimeMillis();
System.out.println("总共耗时为:"+(end-start));
}
public static void main(String[] args) {
testCon1();
}
}
可以发现这也耗费了大量的时间
- 数据库连接池的引出
传统的的连接方式,只要想连接mysql,就会让其连接(没有安检员检查,连接是否达到上限),而mysql的连接是有上限的,当达到上限,将会导致mysql崩溃,也会有内存泄漏的风险
数据库连接池的原理
预先创建多个连接,当一个Java程序使用完了,将连接放回去,不会断掉连接,避免了释放连接的耗时
放回去:是java程序将该连接的引用断开了,而不是关闭了该连接,该连接的通道还在
- 数据库连接池原理示意图
用的比较多的就是德鲁伊和C3P0
C3P0方式一:相关参数在程序中指定
java官方对数据库连接池定义的是一个接口,即javax.sql.DataSource,该接口由第三方提供实现【通过提供jar包】
在用C3p0进行操作之前,需要首先配置c3p0.jar包,并将其添加进我们的项目当中
ComboPooledDataSource继承结构
我们可以发现ComboPooledDataSource类间接的实现了jdk提供的datasource接口
ComboPooledDataSource体现出的就是连接池,以后将有数据库连接池帮我们连接,我们需要提供url,user,password给他
注意:在使用c3p0的时候还要另外导入:mchange-comman-java的jar包才能执行
控制台报的信息并不是异常
package com.hsp.edu.datasource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
//演示C3p0的使用
public class C3p0_ {
//1.方式一:在程序中指定参数(uer,url,password)
public static void testC3p0_01() throws Exception{
//1.创建一个数据源(连接池)
//此时的combopoolledDataSource相当于一个连接池
final ComboPooledDataSource comboPooledDataSource
= new ComboPooledDataSource();
//2.通过配置文件mysql.properities获取连接信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
//3.给连接池comboPooledDataSource设置相关的参数
comboPooledDataSource.setDriverClass(driver);//设置驱动类
comboPooledDataSource.setJdbcUrl(url);//设置url
comboPooledDataSource.setPassword(password);//设置密码
comboPooledDataSource.setUser(user);//设置用户名
//设置初始化连接数
comboPooledDataSource.setInitialPoolSize(10);//初始连接为10个
comboPooledDataSource.setMaxPoolSize(50);//设置最大连接数为50,当连接>50将进入等待队列
//4.取出连接
final Connection connection = comboPooledDataSource.getConnection();//相当于拿出一个连接
System.out.println("连接成功");
connection.close();//归还连接
System.out.println("hello");
}
public static void main(String[] args) throws Exception {
testC3p0_01();
}
}
测试连接池的效率---连接5000次
plaintext
package com.hsp.edu.datasource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
//演示C3p0的使用
public class C3p0_ {
//1.方式一:在程序中指定参数(uer,url,password)
public static void testC3p0_01() throws Exception{
//1.创建一个数据源(连接池)
//此时的combopoolledDataSource相当于一个连接池
final ComboPooledDataSource comboPooledDataSource
= new ComboPooledDataSource();
//2.通过配置文件mysql.properities获取连接信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
String url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
//3.给连接池comboPooledDataSource设置相关的参数
comboPooledDataSource.setDriverClass(driver);//设置驱动类
comboPooledDataSource.setJdbcUrl(url);//设置url
comboPooledDataSource.setPassword(password);//设置密码
comboPooledDataSource.setUser(user);//设置用户名
//设置初始化连接数
comboPooledDataSource.setInitialPoolSize(10);//初始连接为10个
comboPooledDataSource.setMaxPoolSize(50);//设置最大连接数为50,当连接>50将进入等待队列
//4.取出连接
final long start = System.currentTimeMillis();
for (int i = 0; i <5000 ; i++) {
final Connection connection = comboPooledDataSource.getConnection();//相当于拿出一个连接
connection.close();//归还连接
}
final long end = System.currentTimeMillis();
System.out.println("耗费时间为:"+(end-start));
}
public static void main(String[] args) throws Exception {
testC3p0_01();
}
}
可以发现我们的效率有一个很大的飞跃
对此现象的解释:整个流程是刚刚开始已经建立好了,并连接好了10个连接。然后需要连接是取过来,用完了然后放回去,而不是频繁的真正的去连接数据库。
真正连接最多连接50次(初始时10次+后面可能不够在连接40次)
C3p0方式二
最小连接数:当我们的连接池发现初始化连接数数据过多,在一定的时间内,还有很多没有使用过,就会把多的进行回收,达到最小连接数
如果我们写了.xml的配置文件,可以自动读取,而不用去使用properties的配置文件了
每次增长数5:当我们定义的初始化连接数,不够用,每次5个
package com.hsp.edu.datasource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
//演示C3p0的使用
public class C3p0_ {
//方式二:使用配置文件模板完成
public static void testC3p0_02() throws SQLException {
//1.获取连接池
final ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("swt");
//2.获取连接
final Connection connection = comboPooledDataSource.getConnection();
System.out.println("连接成功");
connection.close();
System.out.println("hello");
}
public static void main(String[] args) throws Exception {
testC3p0_02();
}
}
- 配置文件信息
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/jdbc</property>
<property name="user">root</property>
<property name="password">888888</property>
<property name="initialPoolSize">10</property>
<property name="maxPoolSize">20</property>
<property name="minPoolSize">10</property>
<property name="acquireIncrement">10</property>
<property name="checkoutTimeout">3000</property>
<property name="maxStatements">100</property>
<property name="autoCommitOnClose">true</property>
<property name="maxStatementsPerConnection">20</property>
<property name="numHelperThreads">8</property>
<property name="acquireRetryAttempts">10</property>
<property name="acquireRetryDelay">3000</property>
<property name="testConnectionOnCheckin">true</property>
<property name="preferredTestQuery">SELECT 1</property>
<property name="idleConnectionTestPeriod">6000</property>
<property name="maxIdleTime">7000</property>
<property name="maxAdministrativeTaskTime">300</property>
</default-config>
<named-config name="swt">
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/jdbc</property>
<property name="user">root</property>
<property name="password">888888</property>
<property name="initialPoolSize">10</property>
<property name="maxPoolSize">20</property>
<property name="minPoolSize">10</property>
<property name="acquireIncrement">10</property>
<property name="checkoutTimeout">3000</property>
<property name="maxStatements">100</property>
<property name="autoCommitOnClose">true</property>
<property name="maxStatementsPerConnection">20</property>
<property name="numHelperThreads">8</property>
<property name="acquireRetryAttempts">10</property>
<property name="acquireRetryDelay">3000</property>
<property name="testConnectionOnCheckin">true</property>
<property name="preferredTestQuery">SELECT 1</property>
<property name="idleConnectionTestPeriod">6000</property>
<property name="maxIdleTime">7000</property>
<property name="maxAdministrativeTaskTime">300</property>
</named-config>
</c3p0-config>
在这种方式中,我们通过手动的配置一个c3p0-config.xml的配置文件(放在src下面),将我们需要提供给数据库连接池的一些信息写入到这个配置文件中。我们的程序可以自动的帮我们读取这个里面的信息给数据库连接池,在这个文件里面我们一般需要指定数据源(连接池)名称(和我们的程序匹配时使用),用户名 密码 url 最大连接数据 最小连接数量等信息。
而且我们在这个文件里面可以配置多个连接信息,而且可以配置默认的的配置信息,当我们的连接池信息没有匹配到的时候,将会使用默认配置信息(上图第一个配置信息则为默认信息)
德鲁伊数据库连接池
1.首先也是需要将Dluid实现的jar包加入到我们的项目当中去
需要将官方提供的配置文件(用于提供给我们的连接池连接数据库),加入到src目录下
最大等待时间5:如果5s没有从等待池中拿到连接就放弃
数据源只是帮你管理连接,还是要用到驱动
他实现的原理和我们的连接池的基本原理一样
- dluid的使用
package com.hsp.edu.datasource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.util.Properties;
//测试德鲁伊的使用
public class Druid_ {
//1.导入deluid jar包
//2.加入配置文件 deluid.properties 到src目录
public static void deluid01() throws Exception {
//3.创建Properties对象,并读取配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\deluid.properities"));
//4.创建一个指定参数的Druid数据库连接池
final DataSource dataSource =
DruidDataSourceFactory.createDataSource(properties);
//5.拿到一个连接
final Connection connection = dataSource.getConnection();
System.out.println("连接成功");
//6.归还连接
connection.close();
}
public static void main(String[] args) throws Exception {
deluid01();
}
}
德鲁伊工具类(将JDBC工具类改成使用Dluid的工具类)
Connection接口是由java定义的由第三方实现,对于实现的第三方不一样,Connection 里面定义的方法实现不一样(实现类不同,里面具体实现也不同)(其他的也是如此),比如close的实现中在使用数据库连接池中的含义就是归还连接(实际上只是断开了该连接的引用)而不是真的断开连接
- 通过druid的JDBC工具类
package com.hsp.edu.datasource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
//通过Dluid实现JDBC连接池
public class JDBCUtilsByDluid {
private static DataSource ds;//定义一个连接池
//准备初始化连接池
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\deluid.properities"));
ds = DruidDataSourceFactory.createDataSource(properties);//初始化连接池
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//1.编写获取连接的方法(得到一个连接)
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//关闭资源
/*
*注意:在数据库连接池技术中,close不是真的断开连接
* 而是把使用的Connection对象放回连接池
*
*
*/
public static void close(ResultSet set , Statement statement,Connection connection){
try {
if (set!=null){
set.close();
}
if (statement!=null){
statement.close();
}
if(connection!=null){
connection.close();//这里不是真正的关闭连接
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
实践德鲁伊实现的工具类
package com.hsp.edu.datasource;
import com.hsp.edu.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCByDruid_use {
public static void testSelectByDruid(){
//使用Druid实现的JDBC工具类
//1.获取连接(注册驱动在高版本的mysql中可以自动执行,这里省略)
System.out.println("使用druid完成");
Connection connection = null;
//2.执行sql
//2.1定义sql
String sql = "select * from actor";
PreparedStatement preparedStatement=null;//避免被finally访问不到
ResultSet set = null;//避免被finally访问不到
//2.2 获取PreparedStatement对象
try {
connection= JDBCUtilsByDluid.getConnection();//该处需要处理getConnection可能抛出的异常,需要处理
preparedStatement = connection.prepareStatement(sql);
//2.3给?赋值
//该sql不需要赋值
//2.4执行
set = preparedStatement.executeQuery();
//2.5遍历结果集
while (set.next()){
final Object column1 = set.getObject(1);//该行第一列
final Object column2 = set.getObject(2);//第二列
System.out.println("姓名\t"+column1.toString()+" id\t"+column2.toString());
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtilsByDluid.close(set, preparedStatement,connection );//此时结果集也要关闭
}
}
public static void main(String[] args) {
testSelectByDruid();
}
}
在这个程序中我们将使用的Connection preparedStatement RessultSet的类名打印出来,可以发现这些接口都是由阿里巴巴的Druid进行实现的
上图是通过0使用mysql官方提供的jar包来实现的jdbc工具类的测试程序。通过打印Connection ResultSet PreparedStatement实现类的类名,可以发现这些接口都是由mysql实现的
本质上这些接口有不同的实现类,而不同的实现类,对于相同的方法的实现细节不同(造成相同的方法功能不同)
Apache-DBUtils
为什么需要DBUtils?
- 问题一:关闭Connection后resultSet将无法使用
可以用完结果集再关闭,但是再某些情况下我们希望结果集可以复用,可能需要在多个地方都需要使用,如果此时等到结果集用完在关闭,将会很麻烦 - ResultSet不利于数据的管理
如果拿到这个连接迟迟不关闭,造成别到需要连接的等待时间增加(在高并发的情况下更明显)
当我们使用resultSet之前关闭了Connection,将会抛出一下异常。即提示在Connection之后resultSet也将会被关闭
resultSet存在的问题以及解决问题的原理图
针对resultSet存在的问题(1.如果connection关闭,result也会随之关闭2.resultset只能使用一次connection关闭将不能使用3.ResultSet提供的返回数据的方法不能见名知意)创建一个类来概括这张表,每一个对象表示表的每条记录。并且将每个对象装到ArrayList集合中。这样就解决了连接关闭了不能进行查询的问题,查询的时候1.找到ArrayList的对象2.通过getter,setter方法找到具体的属性(列)
- 可以看到(黑线)我们的数据库直接和ArrayList集合间接的关联了起来
用土方法完成封装
- Actor存储表中的每条记录(每条记录对应每个Actor对象)
此时Collect关闭不关闭无所谓,我们已经将所有的记录都存储到了ArrayList集合中了
package com.hsp.edu.datasource;
import java.util.Date;
//Actor对象用于和actor表的记录对应
public class Actor {
private Integer id;//这里用对象比较好
private String name;
private String sex;
private Date borndate;//注意这里导入util下的Date
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;
}
/**
* 获取
* @return id
*/
public Integer getId() {
return id;
}
/**
* 设置
* @param id
*/
public void setId(Integer id) {
this.id = id;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return sex
*/
public String getSex() {
return sex;
}
/**
* 设置
* @param sex
*/
public void setSex(String sex) {
this.sex = sex;
}
/**
* 获取
* @return borndate
*/
public Date getBorndate() {
return borndate;
}
/**
* 设置
* @param borndate
*/
public void setBorndate(Date borndate) {
this.borndate = borndate;
}
/**
* 获取
* @return phone
*/
public String getPhone() {
return phone;
}
/**
* 设置
* @param phone
*/
public void setPhone(String phone) {
this.phone = phone;
}
public String toString() {
return "Actor{id = " + id + ", name = " + name + ", sex = " + sex + ", borndate = " + borndate + ", phone = " + phone + "}";
}
}
封装的过程
plaintext
package com.hsp.edu.datasource;
import com.hsp.edu.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
public class JDBCByDruid_use {
public static void testSelectByDruidToArrayList() {
//使用Druid实现的JDBC工具类
//1.获取连接(注册驱动在高版本的mysql中可以自动执行,这里省略)
System.out.println("使用druid完成");
Connection connection = null;
//2.执行sql
//2.1定义sql
String sql = "select * from actor";
PreparedStatement preparedStatement = null;//避免被finally访问不到
ResultSet set = null;//避免被finally访问不到
ArrayList<Actor> list = new ArrayList<>();//创建ArrayList对象存放actor对象
//2.2 获取PreparedStatement对象
try {
connection = JDBCUtilsByDluid.getConnection();//该处需要处理getConnection可能抛出的异常,需要处理
preparedStatement = connection.prepareStatement(sql);
//2.3给?赋值
//该sql不需要赋值
//2.4执行
set = preparedStatement.executeQuery();
//2.5遍历结果集
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");
//**将得到的resultSet记录,封装到Actor对象,并放入到ArrayList集合中
list.add(new Actor(id,name,sex,borndate,phone));
}
//取出数据的简便
for (Actor actor : list) {//循环一次可以取出一条记录
System.out.println(actor.getId());//取出id
System.out.println(actor.getName());//取出姓名
System.out.println(actor.getSex());//取出性别
System.out.println(actor.getBorndate());//取出出生日期
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtilsByDluid.close(set, preparedStatement, connection);//此时结果集也要关闭
}
}
public static void main(String[] args) {
testSelectByDruidToArrayList();
}
}
Ap--DBUtil查询(返回多行记录)
actor表已经存在的数据如下
package com.hsp.edu.datasource;
//使用apaache-dbtuils工具类+druid完成对表的cruid的操作
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class UBUtils_use {
public static void testQueryMany() throws SQLException {
//1.得到连接(Druid)
final Connection connection = JDBCUtilsByDluid.getConnection();
//2.使用DBUtils类和接口,先引入DBUtils相关的jar,并添加进入本project
//3.创建QreryRunner
QueryRunner queryRunner = new QueryRunner();
//4.就可以执行相关的sql,并返回ArrayList结果集
String sql = "select * from actor where id>=?";//也可以查询部分列,也是可以的(没有查询的列,默认返回null)
/*函数作用:
*1.query方法就是执行sql语句,得到resultSet结果集---》并将其封装到ArrayList集合中
* 2.并返回ArrayList集合
* 函数参数:
*connection:连接
* sql:执行的sql语句
* new BeanListHander<>(Actor.class):将resultSet--->Actor对象-->封装到ArrayList
* 底层使用了反射机制去获取Actor类的属性,然后进行封装
* 参数1:这个1就是给sql语句中?赋值的,使用的是可变参数
* 在该方法底层会得到resultSet,也会得到preparedStatement,并且在改方法中都会进行关闭
*
*/
final List <Actor>list =
queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
for (Actor actor : list) {
System.out.println(actor);
}
//释放资源
JDBCUtilsByDluid.close(null,null,connection);
}
public static void main(String[] args) throws SQLException {
testQueryMany();
}
}
Actor类
package com.hsp.edu.datasource;
import java.util.Date;
//Actor对象用于和actor表的记录对应
public class Actor {
private Integer id;//这里用对象比较好
private String name;
private String sex;
private Date borndate;//注意这里导入util下的Date
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;
}
/**
* 获取
* @return id
*/
public Integer getId() {
return id;
}
/**
* 设置
* @param id
*/
public void setId(Integer id) {
this.id = id;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return sex
*/
public String getSex() {
return sex;
}
/**
* 设置
* @param sex
*/
public void setSex(String sex) {
this.sex = sex;
}
/**
* 获取
* @return borndate
*/
public Date getBorndate() {
return borndate;
}
/**
* 设置
* @param borndate
*/
public void setBorndate(Date borndate) {
this.borndate = borndate;
}
/**
* 获取
* @return phone
*/
public String getPhone() {
return phone;
}
/**
* 设置
* @param phone
*/
public void setPhone(String phone) {
this.phone = phone;
}
public String toString() {
return "Actor{id = " + id + ", name = " + name + ", sex = " + sex + ", borndate = " + borndate + ", phone = " + phone + "}";
}
}
此外改程序中还使用到了JDBC工具类(使用Druid封装)
DBUtil源码分析
/*
public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
return this.query(conn, false, sql, rsh, params);
}
下面是this.query的一段
} else {
PreparedStatement stmt = null;//定义PreparedStatement
ResultSet rs = null;//接收返沪的ResultSet
T result = null;//接收返回的ArrayList
try {
stmt = this.prepareStatement(conn, sql);//创建PreaparedStatement
this.fillStatement(stmt, params);//对sql进行?赋值
rs = this.wrap(stmt.executeQuery());//执行sql,返回resultset
result = rsh.handle(rs);//将返回的resultSet--->ArraryList[使用返回,对传入的Class对象进行分析]
} catch (SQLException var33) {
this.rethrow(var33, sql, params);
} finally {
try {
this.close(rs);//关闭resultset
} finally {
this.close(stmt);//关闭PrepredStatement
if (closeConn) {
this.close(conn);//关闭connection
}
}
}
return result;
}
}
*/
}
(通过源码分析)在qury方法的底层,会使用到resultset结果集 PreparedStatement预处理,至于他使用的这些接口的实现类是什么,主要取决于他传入的Connection接口是谁实现的
DBUtils查询2(返回单行记录)
注意:在DButil的底层完全封装了对PerparedStatement resultSet Connection的关闭。不需要我们进行手动关闭
package com.hsp.edu.datasource;
//使用apaache-dbtuils工具类+druid完成对表的cruid的操作
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class UBUtils_use {
//演示 DBUtils+druid完成返回的结果是单行记录(对应的是单行对象)
public static void testQuerySingle() throws SQLException {
//1.得到连接(Druid)
final Connection connection = JDBCUtilsByDluid.getConnection();
//2.使用DBUtils类和接口,先引入DBUtils相关的jar,并添加进入本project
//3.创建QreryRunner
QueryRunner queryRunner = new QueryRunner();
//4.执行相关的sql,返回单个对象(此时只会返回一条记录)
String sql = "select * from actor where id=?";//也可以查询部分列,也是可以的(没有查询的列,默认返回null)
//因为我们返回的是单行记录-->单个对象(在底层只会封装程Actor对象而不会封装程ArrayList集合) 即我们使用的处理器是BeanHandler
//我们怎么知道我们返回的是一条记录:因为id不可重复
final Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 2);
//当我们所有查询的数据不存在,query方法将会返回null
System.out.println(actor);
//此时我们的Connection (query方法中会创建PerparedStatement resultSet,并且在方法内部关闭了)query方法中关闭了,不需要手动进行关闭
}
public static void main(String[] args) throws SQLException {
//testQueryMany();
testQuerySingle();
}
}
DButil查询(返回单行单列记录)
package com.hsp.edu.datasource;
//使用apaache-dbtuils工具类+druid完成对表的cruid的操作
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class UBUtils_use {
//scalar单一的
//DButils+druid完成查询结果是单行单列--返回的就是Object
public static void testScalar() throws SQLException {
//1.得到连接(Druid)
final Connection connection = JDBCUtilsByDluid.getConnection();
//2.使用DBUtils类和接口,先引入DBUtils相关的jar,并添加进入本project
//3.创建QreryRunner
QueryRunner queryRunner = new QueryRunner();
//4.执行相关的的方法,返回单行单列,返回的就是Object
String sql = "select name from actor where id=?";
//因为返回的是一个对象,使用的handler就是ScalarHandler
final Object obj = queryRunner.query(connection, sql, new ScalarHandler<>(), 2);//不存在将返回Null
System.out.println(obj);
}
public static void main(String[] args) throws SQLException {
testScalar();
}
}
DButils--DML
经验:在sql中表的列在设置的时候使用自增,在传入数据的时候一般传入null,将胡自动自增
package com.hsp.edu.datasource;
//使用apaache-dbtuils工具类+druid完成对表的cruid的操作
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class UBUtils_use {
//演示DButils+druid 完成dml(update ,insert,delete)
public static void testDML() throws SQLException {
//1.得到连接(Druid)
final Connection connection = JDBCUtilsByDluid.getConnection();
//2.使用DBUtils类和接口,先引入DBUtils相关的jar,并添加进入本project
//3.创建QreryRunner
QueryRunner queryRunner = new QueryRunner();
//4.这里组织sql,完成update,insert ,delete
//String sql = "update actor set name=? where id=?"; //update
//String sql= "insert into actor values(?,?,?,?,?)";//inseri
String sql = "delete from actor where id=?";
//这里表在设计的时候没有将Id设成主键和自增长(实际中需要设置)
/*
1.queryRunner.update()方法不仅可以执行update,还可以执行insert和delect(DML都可以执行)
2.update的返回值是受影响的行数(你的这条sql对我们的表影响了多少行)
3.如果返回0,不代表就一定执行失败,比如delete表中就没有这个记录,则返回0,但是执行成功了。即返回0只代表对表中的数据没有影响,
而不一定执行失败
*/
// final int affectedRow = queryRunner.update(connection, sql, "张三丰", 2);//update
// int affectedRow = queryRunner.update(connection,sql,4,"林青霞","男","1990-10-10","110");//insert
int affectedRow = queryRunner.update(connection,sql,4);//delete
System.out.println(affectedRow>0?"执行成功":"执行没有影响表中的数据");
}
public static void main(String[] args) throws SQLException {
testDML();
}
}
表中的列的数据类型和javaBean中的各个属性的数据类型是一一对应的
1.mysql中的基础数据类型,java中一律要用他们的包装类
注意点:1.mysql中用 char,java中也要用String
2.mysql中用Date,java中用Date或者String
BasicDAO问题
- 原理图
JAVABean部分:每一个数据库的表映射出一个javaBean类,DAO部分:将对每一个表的操作都封装到一个DAO对象中,并将各个DAO类的公共部分作为一个父类BasicDAO中3.我们在测试类中调用Dao类已完成对表的操作
1.将各种DAO共有的部分提取出来,作为BasicDAO作为父类
2.我们需要操作xx表只需要调用xxDAO中的方法即可
DAO 全称:data access object (数据访问对象)可以理解为访问数据的对象
BasicDAO分析
- 加上工具类的的原理图
BasicDAO实现1
实际上就是将前面使用的DButils的使用进行了封装。以达到对不同的表进行可以进行统一操作
bao包中的ActorDAO和BasicDAO类
- BasicDAO
package com.hsp.edu.dao.dao;
import com.hsp.edu.dao.utils.JDBCUtilsByDluid;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
//开发BasicDAO,是其他DAO的父类
public class BasicDAO<T> {//使用泛型类表示
//定义属性
private QueryRunner queryRunner = new QueryRunner();//用于执行sql
//通用的DML方法(返回所影响的行数)
//适用于任意表
//parames:表示传入的?的具体值
public int update(String sql, Object... parames) {//这里连接的获取放在在函数里面比较好
Connection connection = null;
try {
connection = JDBCUtilsByDluid.getConnection();
final int affectedRow = queryRunner.update(connection, sql, parames);//可变参数本质是数组
return affectedRow;
//执行sql
} catch (SQLException e) {
throw new RuntimeException(e);
}
//在update方法的内部,connection resultSet preparedStatement都已经被关闭
}
//查询的结果返回多个对象(即返回多行结果,使用List包装)
//适用于任意表
/*
参数解释:
clazz:表示传入一个类的Class对象,比如Actor.class
params:传入?的具体值
返回对应的ArrayList集合
*/
public List<T> queryMulti(String sql, Class<T> clazz, Object... params) {
Connection connection = null;
try {
connection = JDBCUtilsByDluid.getConnection();
final List<T> list = queryRunner.query(connection, sql, new BeanListHandler<T>(clazz), params);
return list;//返回一个List集合
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//返回单行结果的通用方法(返回一个javaBean对象 )
public T querySingle(String sql, Class<T> clazz, Object... params) {
Connection connection = null;
try {
connection = JDBCUtilsByDluid.getConnection();
T t = queryRunner.query(connection, sql, new BeanHandler<T>(clazz), params);
return t;//返回一个javaBean对象
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
//返回单行单列,返回一个单值
//因为是返回一个单值不需要封装,所有不用传入Class对象
public Object queryScalar(String sql,Object...parames){
Connection connection = null;
try {
connection = JDBCUtilsByDluid.getConnection();
Object object = queryRunner.query(connection, sql, new ScalarHandler<>(), parames);
return object;//返回任意一个单值
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
- ActorDAO
package com.hsp.edu.dao.dao;
import com.hsp.edu.dao.domain.Actor;
public class ActorDAO extends BasicDAO<Actor>{//我们的子类在这里指定泛型
//1.已经继承了BasicDAO中的所有方法和属性
//2.根据业务需求,可以编写他特有的方法
}
domain包中存储javaBean类Actor
- Actor
package com.hsp.edu.dao.domain;
import java.util.Date;
//Actor对象用于和actor表的记录对应
public class Actor {
private Integer id;//这里用对象比较好
private String name;
private String sex;
private Date borndate;//注意这里导入util下的Date
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;
}
/**
* 获取
* @return id
*/
public Integer getId() {
return id;
}
/**
* 设置
* @param id
*/
public void setId(Integer id) {
this.id = id;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return sex
*/
public String getSex() {
return sex;
}
/**
* 设置
* @param sex
*/
public void setSex(String sex) {
this.sex = sex;
}
/**
* 获取
* @return borndate
*/
public Date getBorndate() {
return borndate;
}
/**
* 设置
* @param borndate
*/
public void setBorndate(Date borndate) {
this.borndate = borndate;
}
/**
* 获取
* @return phone
*/
public String getPhone() {
return phone;
}
/**
* 设置
* @param phone
*/
public void setPhone(String phone) {
this.phone = phone;
}
public String toString() {
return "Actor{id = " + id + ", name = " + name + ", sex = " + sex + ", borndate = " + borndate + ", phone = " + phone + "}";
}
}
util包中存储JDBC工具类
- JDBCUtilsByDruid
package com.hsp.edu.dao.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
//通过Dluid实现JDBC连接池
public class JDBCUtilsByDluid {
private static DataSource ds;//定义一个连接池
//准备初始化连接池
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\deluid.properities"));
ds = DruidDataSourceFactory.createDataSource(properties);//初始化连接池
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//1.编写获取连接的方法(得到一个连接)
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//关闭资源
/*
*注意:在数据库连接池技术中,close不是真的断开连接
* 而是把使用的Connection对象放回连接池
*
*
*/
public static void close(ResultSet set , Statement statement,Connection connection){
try {
if (set!=null){
set.close();
}
if (statement!=null){
statement.close();
}
if(connection!=null){
connection.close();//这里不是真正的关闭连接
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
test包中存储测试类
- testDAO
package com.hsp.edu.dao.test;
import com.hsp.edu.dao.dao.ActorDAO;
import com.hsp.edu.dao.domain.Actor;
import java.util.List;
//测试ActorDAO对actor表crud操作
public class testDAO {
//1.返回多行记录
public static void testActorQuery1(){
ActorDAO actorDAO = new ActorDAO();
String sql = "select * from actor where id>=?";
final List <Actor>actors = actorDAO.queryMulti(sql, Actor.class, 1);//返回一个集合
for (Actor actor : actors) {
System.out.println(actor);
}
}
//2.查询单行记录
public static void testActorQuery2(){
ActorDAO actorDAO = new ActorDAO();
final Actor actor = actorDAO.querySingle("select * from actor where id=?", Actor.class, 2);
System.out.println(actor);
}
//3.返回单行单列记录
public static void testActorQuery3(){
ActorDAO actorDAO = new ActorDAO();
final Object o = actorDAO.queryScalar("select name from actor where id=?", 2);
System.out.println(o);
}
public static void testActorDML(){
ActorDAO actorDAO = new ActorDAO();
final int affectedRow = actorDAO.update("insert into actor values(?,?,?,?,?)", 4, "刘涛", "男", "2001-4-24", "2345");
System.out.println(affectedRow>0?"执行成功":"执行的所影响的航行为0");
}
public static void main(String[] args) {
// testActorQuery1();
testActorQuery1();
// testActorQuery2();
//testActorQuery3();
//testActorDML();
}
}
该封装结构对事务的处理可能存在问题
实际工作中的三层结构
- 作业