JDBC笔记
-
使用jdbc前需要有mysql驱动,将其放入lib目录中(驱动不需要与mysql版本一致)
-
右键该驱动,点击最下面的 As the libery 即可
练习需要的配置文件
//以下是mysql.properties配置文件的内容
user=root
password=123456
url=jdbc:mysql://localhost:3306/xsp_db02
driver=com.mysql.jdbc.Driver
快速入门
//注册驱动 com.mysql.jdbc是Driver类所在路径
Driver driver = new com.mysql.jdbc.Driver();
//连接数据库
/*
jdbc:mysql:// 规定表示协议,通过jdbc方式连接mysql
localhost 是主机地址,这个位置用来填写ip,表示连接的ip
3306 mysql监听的端口号
xsp_db02 连接到 mysql dbms 的xsp_db02数据库
mysql连接的本质就是socket连接
*/
String url = "jdbc:mysql://localhost:3306/xsp_db02";
/*
将用户名和密码放入properties对象
user是默认的主键,不能修改,root是登录用户名
password是默认的主键,不能修改,123456是登录密码
*/
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","123456");
//进行网络连接,将url和登录信息传入
Connection connect = driver.connect(url, properties);
//要执行的sql语句
String sql = "insert into stu values(1,'mary')";
//用于执行sql语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
//dml语句执行后会返回生效的行数
int rows = statement.executeUpdate(sql);
//如果生效的行数大于0,则说明执行成功
System.out.println(rows > 0 ? "ok" : "no");
//关闭资源
statement.close();
connect.close();
连接数据库的方式
如何选择
-
看完快速入门的详解即可理解以下方式
-
方式五是最简单的方式,其次是方式四
-
方式六通过配置文件对连接需要的数据进行保存读取,使得连接数据库更灵活
-
前三种方式将连接复杂化,不必选择
方式一
//方式一的详解在快速入门中
Driver driver = new com.mysql.jdbc.Driver();
String url = "jdbc:mysql://localhost:3306/xsp_db02";
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","123456");
Connection connect = driver.connect(url, properties);
System.out.println(connect);
connect.close();
方式二
//使用反射加载Driver类,动态加载,更加灵活,减少依赖性
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
//向下转型
Driver driver = (Driver) aClass.newInstance();
//以下跟方式一 一样
String url = "jdbc:mysql://localhost:3306/xsp_db02";
Properties properties = new Properties();
properties.setProperty("user","root");
properties.setProperty("password","123456");
//进行网络连接,将url和登录信息传入
Connection connect = driver.connect(url, properties);
System.out.println(connect);
connect.close();
方式三
//使用DriverManager替代driver进行统一管理
////使用反射加载Driver
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance();
String url = "jdbc:mysql://localhost:3306/xsp_db02";
//注册Driver驱动
DriverManager.registerDriver(driver);
//进行网络连接,将url和登录信息传入
Connection connection = DriverManager.getConnection(url, "root", "123456");
System.out.println(connection);
connection.close();
方式四
//在方式三的基础上减少了DriverManager.registerDriver(driver);
//原因:Driver类的静态代码块有注册驱动的代码,当类加载时自动执行,因此不需要手动注册
//静态代码块中的语句:DriverManager.registerDriver(new Driver());
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xsp_db02", "root", "123456");
System.out.println(connection);
connection.close();
//和以上程序没区别,只是将url、用户名、密码存入字符串中再使用,因此代码量看起来更多
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/xsp_db02";
String user = "root";
String passward = "123456";
Connection connection = DriverManager.getConnection(url,user,passward);
System.out.println(connection);
connection.close();
方式五
//在jkd1.5之后使用了jdbc4,不再需要主动调用class.forName()注册驱动,会自动调用Driver去注册
//因此实际只需要一个语句即可进行数据库连接
//但由于主动调用会比较明显,如果多个数据库,如oracle,mysql等,方便区分,因此韩顺平老师建议我们将注册驱动语句写出来:Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xsp_db02", "root", "123456");
System.out.println(connection);
connection.close();
方式六
//通过配置文件保存url、用户名、密码
//读取配置文件
Properties properties = new Properties();
properties.load(new FileReader("src\\mysql.properties"));
//获取配置文件相关值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
//注册驱动
Class.forName(driver);
//获取网络连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
connection.close();
//以下是mysql.properties配置文件的内容
user=root
password=123456
url=jdbc:mysql://localhost:3306/xsp_db02
driver=com.mysql.jdbc.Driver
ResultSet结果集
-
表示数据库结果集的数据表,通过执行select语句生成
-
ResultSet对象保持一个光标指向当前数据行
-
初始光标位于第一行之前
-
ResultSet有next方法,该方法将光标移动到下一行,当下一行没有内容时,返回false
-
因此可以用while循环配合next方法循环遍历ResultSet结果集
-
ResultSet结果集实际是将数据保存到数组中的
//连接数据库
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xsp_db02", "root", "123456");
//得到Statement
Statement statement = connection.createStatement();
//要执行的select语句
String sql = "select id, name , sex, borndate from actor";
/* 以下是select在数据库中显示的数据
+----+-----------+-----+---------------------+
| id | name | sex | borndate |
+----+-----------+-----+---------------------+-------+
| 4 | 刘德华 | 男 | 1970-12-12 00:00:00 |
| 5 | jack | 男 | 1990-11-11 00:00:00 |
+----+-----------+-----+---------------------+-------+
*/
//执行给定的SQL语句,该语句返回ResultSet对象
//执行select语句需要使用statement.executeQuery
ResultSet resultSet = statement.executeQuery(sql);
//使用while取出数据
while (resultSet.next()) { // 让光标向后移动,如果没有更多行,则返回false
int id = resultSet.getInt(1); //获取该行的第1列,返回的是int
//int id1 = resultSet.getInt("id"); 也可以通过列名来获取值, 推荐
String name = resultSet.getString(2);//获取该行的第2列,返回的是String
String sex = resultSet.getString(3);//获取第3列,返回的是String
Date date = resultSet.getDate(4);//获取第4列,返回的是Date
System.out.println(id + "\t" + name + "\t" + sex + "\t" + date);//将得到的结果拼接
}
//关闭连接
resultSet.close();
statement.close();
connection.close();
//自己写的 stu表的内容在下面的图
//连接数据库
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xsp_db02", "root", "123456");
//得到statement
Statement statement = connection.createStatement();
//执行select语句得到resultSet
ResultSet resultSet = statement.executeQuery("select * from stu");
//遍历取值
while (resultSet.next()){
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
System.out.println(id + "\t" + name);
}
//关流
statement.close();
connection.close();
Statement
-
Statement的使用参照上面的ResultSet程序即可
-
Statement对象用于执行静态sql语句,并返回其生成的结果
-
在数据库连接后,需要执行命令/sql语句,可以通过以下方式
-
Statement 存在sql注入问题
-
PreparedStatement 预处理
-
CallableStatement 存储过程
-
-
Statement对象执行sql语句存在sql注入风险,因此目前已不用Statement
-
sql注入是利用某些系统没有对用户输入的数据进行充分的检查,某些用户输入时输入非法的sql语句段或命令,恶意攻击数据库
-
防范sql注入,使用PrepareStatement替代Statement
PreparedStatement(预处理)
-
基本使用
-
PreparedStatement执行的sql语句中的参数用问号(?)表示
-
PreparedStatement对象有setXXX()方法来设置 ?的参数
-
set方法有两个参数,参数1:第几个问号,参数2:要设置的值
-
一般用preparedStatement.setString(参数1,参数2);来设置参数
-
-
执行select语句,调用executeQuery();返回ResultSet对象
-
执行dml操作(update,delete,insert),调用executeUpdate();调用后返回生效的行数,如果生效行数>0,说明执行成功
-
-
好处
-
不在使用 +拼接sql语句,减少语法的错误
-
有效解决sql注入问题
-
大大减少编译次数,提高效率
-
-
执行select语句使用 executeQuery();
-
Scanner scanner = new Scanner(System.in);
//让用户输入
System.out.print("请输入id: "); //next(): 当接收到空格或者'就是表示结束
String id = scanner.nextLine(); //如果希望看到SQL注入,这里需要用nextLine,nextLine只有按回车才表示结束,即使接收到空格或者'
System.out.print("请输入名字: ");
String name = scanner.nextLine();
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");//建议写上
//2. 得到连接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xsp_db02", "root", "123456");
//3. 得到PreparedStatement
//3.1 sql语句 问号?相当于占位符
String sql = "select id , name from stu where id = ? and name = ?";
//3.2 通过connection获取prepareStatement对象 在获取prepareStatement对象对象时,就需要将sql语句放入
PreparedStatement preparedStatement = connection.prepareStatement(sql);//在这里就需要将sql语句放入
//3.3 给?赋值
preparedStatement.setString(1, id);
preparedStatement.setString(2, name);
//4. 执行 select 语句使用 executeQuery(),会返回ResultSet对象
// 如果执行的是 dml(update, insert ,delete) executeUpdate()
// 这一步不能在括号内写入sql语句
ResultSet resultSet = preparedStatement.executeQuery();//执行sql语句后会返回查询结果
if (resultSet.next()) { //如果能查询到(即有返回数据),则说明该数据存在
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
//关闭连接
resultSet.close();
preparedStatement.close();
connection.close();
-
-
执行dml语句 使用executeUpdate();
-
Scanner scanner = new Scanner(System.in);
//让用户输入
System.out.print("请输入名字: ");
String name = scanner.nextLine();
System.out.print("请输入id: ");
String id = scanner.nextLine();
//连接数据库
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/xsp_db02", "root", "123456");
//设置update语句
String sql = "update stu set name = ? where id = ?";//输入修改后的名字和目标id即可完成修改
PreparedStatement preparedStatement = connection.prepareStatement(sql);//在这里就需要将sql语句放入
preparedStatement.setString(1, name);
preparedStatement.setString(2, id);
//执行update语句
int rows = preparedStatement.executeUpdate();//执行update语句后会返回生效行数
System.out.println(rows>0 ? "执行成功" : "执行失败");//生效行数大于0说明执行成功
//关闭连接
preparedStatement.close();
connection.close();
-
JDBC相关API及方法
-
DriverManager驱动管理类
-
getConnection(url,user,pwd)
获取到连接
-
-
Connection接口
-
createStatement()
创建Statement对象 -
preparedStatement(sql)
创建预处理PreparedStatement对象
-
-
Statement接口
-
executeUpdate(sql)
执行dml语句,返回影响的行数 -
executeQuery(sql)
执行select查询,返回ResultSet对象 -
execute(sql)
执行任意的sql,返回boolean值
-
-
PreparedStatement接口
-
executeUpdate()
执行dml语句,返回影响的行数 -
executeQuery()
执行select查询,返回ResultSet对象 -
execute()
执行任意的sql,返回boolean值 -
setXxx(占位符索引,占位符的值)
设置?的值,解决sql注入 -
setObject(占位符索引,占位符的值)
设置?的值,解决sql注入
-
-
ResultSet结果集
-
next()
向下移动一行,如果没有下一行返回false -
previous()
向上移动一行,如果没有上一行,返回false -
getXxx(列的索引/列名)
返回对应 索引/列名 的值,接收类型是Xxx ,这里的索引是指第几列 -
getObject(列的索引/列名)
返回对应 索引/列名 的值,接收类型是Object
-
自设JDBCUtils
设计类
设计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) {
//将编译异常转成 运行异常
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);
}
}
}
使用
这是要操作的表数据
使用在select
//得到连接 这里先写成null是为了扩大作用域,否则在try-catch中无法找到
Connection connection = null;
//sql语句
String sql = "select * from actor where id = ?";
//这里先写成null是为了扩大作用域,否则在try-catch中无法找到
PreparedStatement preparedStatement = null;
ResultSet resultset = null;
//开始操作
try {
//使用工具类的方法连接数据库
connection = JDBCUtils.getConnection();
//获取prepareStatement并传入sql语句
preparedStatement = connection.prepareStatement(sql);
//给?号赋值
preparedStatement.setInt(1, 5);
//执行, 得到结果集
resultset = preparedStatement.executeQuery();
//遍历该结果集
while (resultset.next()) {
int id = resultset.getInt("id");//获取id列的值
String name = resultset.getString("name");//获取name列的值
String sex = resultset.getString("sex");//获取sex列的值
Date borndate = resultset.getDate("borndate");//获取borndate列的值
String phone = resultset.getString("phone");//获取phone列的值
System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//使用工具类关闭资源
JDBCUtils.close(resultset, preparedStatement, connection);
}
使用在dml
//null为了扩大作用域
Connection connection = null;
//sql
String sql = "update actor set name = ? where id = ?";
//null为了扩大作用域
PreparedStatement preparedStatement = null;
//开始操作
try {
//使用工具类的方法连接数据库
connection = JDBCUtils.getConnection();
//获取prepareStatement并传入sql语句
preparedStatement = connection.prepareStatement(sql);
//给占位符赋值
preparedStatement.setString(1, "周星驰");
preparedStatement.setInt(2, 4);
//执行
preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//使用工具类关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
事务
介绍
-
jdbc中当一个Connection对象创建时,默认情况下自动提交事务,每次执行sql成功数据库就会自动提交,不能回滚
-
jdbc为了让多个sql语句作为整体运行,需要使用事务
-
调用 Connection的setAutoCommit(false);可以取消自动提交事务
-
需要回滚时调用 Connection的rollback(); 回滚事务
-
所有sql语句成功执行后 调用 Connection的commit(); 提交事务
-
可以在两个执行的sql语句之间制造一个异常,模拟没有事务出现的问题
使用
需要的表
转账练习(事务处理)
//操作转账的业务
//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";
//3. 创建PreparedStatement 对象
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection(); // 在默认情况下,connection是默认自动提交
//将 connection 设置为不自动提交
connection.setAutoCommit(false); //即开启了事务
//获取preparedStatement并提交第一条sql
preparedStatement = connection.prepareStatement(sql);
//执行第1条sql
preparedStatement.executeUpdate();
//制造算术异常 出现异常后,后面的语句不再执行,只执行catch和finally的
int i = 1 / 0;
//再次获取preparedStatement并提交第二条sql
preparedStatement = connection.prepareStatement(sql2);
//执行第2条sql
preparedStatement.executeUpdate();
//提交事务
connection.commit();
} catch (Exception e) {
//这里我们可以进行回滚,即撤销执行的SQL
//默认回滚到事务开始的状态.
System.out.println("执行发生了异常,撤销执行的sql");
try {
//回滚 回滚语句有异常需要捕捉
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
转账练习(不使用事务)
//操作转账的业务
//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";
//3. 创建PreparedStatement 对象
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection(); // 在默认情况下,connection是默认自动提交
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate(); // 执行第1条sql
//在抛出异常后 后面的语句都不执行,因此只执行了第1条sql,导致id1扣钱了,id2没有加钱
int i = 1 / 0; //抛出异常
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate(); // 执行第3条sql
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
批处理
介绍
-
需要大量 插入/更新 记录,可以采用java的批量更新机制,该机制允许多条语句一次性提交给数据库批量处理,通常情况下比单独处理更有效率
-
jdbc批处理方法,在 Statement/PreparedStatement对象下
-
addBatch(); 添加需要批量处理的sql语句/参数,放入批处理包中(实际是一个数组)
-
executeBatch();执行批量处理
-
clearBatch();清空批量处理包
-
-
如果使用批处理功能,需要在jdbc连接数据库时,必须在url后加上
?rewriteBatchedStatements=true
-
批处理一般和PreparedStatement搭配,既能减少编译次数,还能减少运行次数,提高效率
演示
批处理的使用
//使用批量方式添加数据
//使用前记得在配置文件的url中加上?rewriteBatchedStatements=true
Connection connection = JDBCUtils.getConnection();
String sql = "insert into admin2 values(null, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行");
long start = System.currentTimeMillis();//开始时间
for (int i = 0; i < 5000; i++) {//5000执行
//每次循环都会重新设定sql语句的?参数内容
preparedStatement.setString(1, "jack" + i);
preparedStatement.setString(2, "666");
//将sql语句加入到批处理包中
preparedStatement.addBatch();
//当有1000条记录时,在批量执行
if((i + 1) % 1000 == 0) {//满1000条sql
//批量执行
preparedStatement.executeBatch();
//清空
preparedStatement.clearBatch();
}
}
long end = System.currentTimeMillis();//结束时间
System.out.println("批量方式 耗时=" + (end - start));//批量方式 耗时=108
//关闭连接
JDBCUtils.close(null, preparedStatement, connection);
//总耗时71毫秒
传统提交sql
//传统方法,添加5000条数据到admin2
Connection connection = JDBCUtils.getConnection();
String sql = "insert into admin2 values(null, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行");
long start = System.currentTimeMillis();//开始时间
for (int i = 0; i < 5000; i++) {//5000执行
preparedStatement.setString(1, "jack" + i);
preparedStatement.setString(2, "666");
preparedStatement.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.println("传统的方式 耗时=" + (end - start));//传统的方式 耗时=10702
//关闭连接
JDBCUtils.close(null, preparedStatement, connection);
//耗时3328毫秒 = 3秒多
addBatch();源码
// 以下是批处理addBatch();方法的源码
//1. 第一就创建 ArrayList,数组名为elementData => Object[]
//2. elementData => Object[] 就会存放我们预处理的sql语句
//3. 当elementData满后,就按照1.5扩容
//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();下断点
-
进入preparedStatement
-
找到batchedArgs
-
进入elementDate,里面的数组存放的就是sql语句
数据库连接池
注意
-
从这里开始需要大量导入外部的jar包,在使用前请记得导入!!!
-
还需要根据不同的数据库和表等情况,导入配置文件并对配置文件进行修改!!!
传统连接弊端
-
传统jdbc数据库连接使用过DriverManager来获取,每次向数据库建立连接都需要将Connection加载到内存中,再验证ip地址、用户名、密码(共需0.05s~1s)。需要数据库连接的时候就向数据库要求一个连接,频繁的进行数据库连接会占用很多系统资源,过多时会造成服务器崩溃
-
每次数据连接,使用完都需要断开,如果程序出现异常没有关闭,会导致数据库内存泄漏,最终导致重启数据库
-
传统的获取连接方式,不能控制创建的连接数量,如果连接过多可能导致内存泄漏,导致mysql崩溃
-
解决传统连接的弊端,可以采用数据库连接池技术
数据库连接池
-
原理:
-
预先在缓冲池中放入一定数量的连接,需要建立数据库连接时,从缓冲池取出一个,使用完再放回缓冲池
-
数据库连接池负责分配、管理、释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立新的连接
-
当应用程序向连接池请求的连接数超过最大的连接数量时,这些请求会加入到等待队列中
-
-
jdbc的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,通常由第三方提供实现
-
分类:
-
C3P0数据库连接池,速度相对较慢,稳定性较好(hibernate,spring中使用到)
-
Druid(德鲁伊)数据库连接池,由阿里提供,集DBCP、C3P0、Proxool优点
-
Proxool数据库连接池,能监控连接池状态,稳定性比C3P0略差
-
BoneCP数据库连接池,速度较快
-
-
在数据库连接池技术中close不是真的断掉连接,只是将连接放回缓冲池
C3P0数据库连接池
-
首先需要有c3p0的jar包,放入lib目录,右键点击add as library
-
方式一
-
//方式1:在程序中指定user, url , password等
//1. 创建一个c3p0数据源对象
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");
//给数据源 comboPooledDataSource 设置相关的参数
//注意:连接管理是由 comboPooledDataSource 来管理
comboPooledDataSource.setDriverClass(driver);//driver = com.mysql.jdbc.Driver
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
//设置初始化连接数
comboPooledDataSource.setInitialPoolSize(10);
//最大连接数 如果超过最大连接数,就会进入等待队列
comboPooledDataSource.setMaxPoolSize(50);
//测试连接池的效率, 测试对mysql 5000次操作
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
//进行连接
//从DataSource接口实现的方法comboPooledDataSource.getConnection()
Connection connection = comboPooledDataSource.getConnection();
//关闭连接
connection.close();
}
long end = System.currentTimeMillis();
//c3p0 5000连接mysql 耗时=391
System.out.println("c3p0 5000连接mysql 耗时=" + (end - start));
-
-
方式二
-
//第二种方式 使用配置文件模板来完成
//1. 将c3p0提供的 c3p0_config.xml 拷贝到 src目录下(文件名必须一样,不能改变)
//2. 该文件指定了连接数据库和连接池的相关参数
//使用c3p0,mysql_xsp是在配置文件中自定义的名字,用来找到配置文件
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("mysql_xsp");
//获取数据库连接
Connection connection = comboPooledDataSource.getConnection();
//关闭数据库连接
connection.close(); -
//测试5000次连接mysql
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("mysql_xsp");
long start = System.currentTimeMillis();
System.out.println("开始执行....");
for (int i = 0; i < 500000; i++) {
Connection connection = comboPooledDataSource.getConnection();
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("c3p0的第二种方式(500000) 耗时=" + (end - start));//1917
-
-
方式二需要的配置文件c3p0_config.xml,内容根据实际情况进行修改,配置文件放于src目录下
-
<!-- 后面有对配置文件的解读 -->
<c3p0-config>
<named-config name="mysql_xsp">
<!-- 驱动类 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- url-->
<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/xsp_db02</property>
<!-- 用户名 -->
<property name="user">root</property>
<!-- 密码 -->
<property name="password">123456</property>
<!-- 每次增长的连接数--><!-- 当超过了当前的连接数,小于最大连接数,就会进行自定的5增长 -->
<property name="acquireIncrement">5</property>
<!-- 初始的连接数 -->
<property name="initialPoolSize">10</property>
<!-- 最小连接数 --><!-- 如果进行一定时间的等待后,连接数太少,就会将连接减少到自定的5 -->
<property name="minPoolSize">5</property>
<!-- 最大连接数 --><!-- 自设最多只有50个连接,如果超过50则进入等待队列 -->
<property name="maxPoolSize">50</property>
<!-- 可连接的最多的命令对象数 -->
<property name="maxStatements">5</property>
<!-- 每个连接对象可连接的最多的命令对象数 -->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config> -
<named-config name="mysql_xsp">
这里的mysql_xsp是自定义的,在方式二中进行c3p0连接时需要此名字来找到配置文件ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("mysql_xsp");
-
<property name="driverClass">com.mysql.jdbc.Driver</property>
中间填写的是注册驱动Driver类的地址,如果是oracel数据库,则替换成<property name="driverClass">com.oracel.jdbc.Driver</property>
-
<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/xsp_db02</property>
中间是url地址,注意xsp_db02是要连接的数据库名字,如果是其它数据库就改成其它的 -
<property name="user">root</property>
root是要登录的数据库用户名 -
<property name="password">123456</property>
123456是要登录的数据库密码 -
后面的较简单的已在配置文件中给出注释
-
Druid数据库连接池
常规使用
-
首先需要在lib目录加入Druid的jar包,右键点击add as library
-
然后将配置文件 druid.properties , 放在src目录,内容根据实际情况进行修改,配置文件在下面的代码块
-
使用:
-
//连接Druid数据连接池和获取连接的操作
Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
//创建一个指定参数的数据库连接池, Druid连接池
DataSource dataSource =
DruidDataSourceFactory.createDataSource(properties);
//获取数据库连接
Connection connection = dataSource.getConnection();
//关闭数据库连接
connection.close(); -
//测试Druid的速度
//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);
//连接断开500000次统计时间
long start = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
//获取数据库连接
Connection connection = dataSource.getConnection();
//System.out.println("连接成功!");
connection.close();
}
long end = System.currentTimeMillis();
//druid连接池 操作500000 耗时=539
System.out.println("druid连接池 操作500000 耗时=" + (end - start));
-
-
需要的配置文件 druid.properties
-
#key=value Driver类的地址
driverClassName=com.mysql.jdbc.Driver
#url=jdbc:mysql://localhost:3306/girls
url=jdbc:mysql://localhost:3306/xsp_db02?rewriteBatchedStatements=true
username=root
password=123456
#initial connection Size 初始连接数
initialSize=10
#min idle connecton size 最小连接数
minIdle=5
#max active connection size 最大连接数
maxActive=20
#max wait time (5000 mil seconds 是5秒)
#最大等待时间(如果超过等待时间,则取消尝试连接)
maxWait=5000
-
设计成工具类
-
功能
-
类加载时创建和读取properties
-
getConnection方法获取数据库连接
-
close方法关闭连接
-
public class JDBCUtilsByDruid {
//定义DataSource,用来接收DruidDataSourceFactory.createDataSource(properties);
private static DataSource ds;
//在静态代码块完成 ds初始化 创建和读取properties
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不是真的断掉连接,只是将连接放回缓冲池
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);
}
}
}
工具类使用
注意:这里使用到的表是actor演员表,有属性:id、name、sex、borndate、phone,请按顺序使用(如果表在不同数据库,记得修改配置文件)
public void testSelect() {
System.out.println("使用druid方式完成");
//1. 得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "select * from actor where id >= ?";
PreparedStatement preparedStatement = null;
ResultSet set = null;
//3. 创建PreparedStatement 对象
try {
//使用工具类获取数据库连接,工具类中的静态代码块已经完成了对配置文件的读取
connection = JDBCUtilsByDruid.getConnection();
//运行类型 com.alibaba.druid.pool.DruidPooledConnection
//System.out.println(connection.getClass());
//将sql语句放入preparedStatement中
preparedStatement = connection.prepareStatement(sql);
//给sql语句中的?号赋值
preparedStatement.setInt(1, 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");
System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//工具类关闭资源
JDBCUtilsByDruid.close(set, preparedStatement, connection);
}
}
apache-DbUtils
为什么有DBUtils
-
关闭connection连接后,resultSet结果集就无法使用
-
resultSet不利于数据的管理
DbUtils介绍
-
commons-dbutils是apache组织提供的开源jdbc工具类库,是对jdbc的封装,使用dbutils能极大简化jdbc编码的工作量
-
DbUtils类
-
QueryRunnber类:封装了sql的执行,线程安全,能实现 增删改查、批处理
-
ResultSetHandler接口:用于处理ResultSet,将数据按要求转换成另外的形式保存
-
方法:
-
-
-
dbutils+druid使用
-
需要先创建对应表的类
-
注意:这里使用到的表是actor演员表,有属性:id、name、sex、borndate、phone,请按顺序使用(如果表在不同数据库,记得修改配置文件)
-
生成javaBean的时候是通过无参构造器构建对象,set方法也需要有(测试发现,如果没有给对应的set,存入的就会是null),如果没有有参构造器,没有影响
//Actor类,对应的是Actor表,所有的属性都是该表的 列
//该类创建的对象用来保存ResultSet结果集的结果
//因此也被叫做Javabean/POJO/Domain对象
public class Actor {
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;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Date getBorndate() {
return borndate;
}
public void setBorndate(Date borndate) {
this.borndate = borndate;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
//对结果集输出时,直接输出该对象,自动调用toString
@Override
public String toString() {
return "\nActor{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", borndate=" + borndate +
", phone='" + phone + '\'' +
'}';
}
}
-
不同返回情况的使用
-
注意
-
需要提前导入dbutils的jar包,一般名字是commons-dbutils-1.7.jar
-
对应的表需要的类也要创建好
-
-
下面的程序体现了不同的情况,但实际只有 String sql = ... 语句后面的
queryRunner.query()
方法是最重要的-
主要是学会根据不同的返回情况 用不同的值接收以及对应需要的参数
-
-
前三个Test 是select不同的返回情况
-
最后一个Test是dml操作的使用
-
先看第一个Test即可明白大部分,注释较详细
//注意:需要提前导入dbutils的jar包,一般名字是commons-dbutils-1.7.jar
// 对应的表需要的**类**也要创建好
//不同返回情况的使用
//返回结果是多行的情况
@Test
public void testQueryMany() throws SQLException {
//1. 通过druid工具类得到连接
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3. 执行QueryRunner相关的方法,返回ArrayList结果集
String sql = "select id, name from actor where id >= ?";
//返回的是多行,因此需要List接收保存,并且指定泛型是Actor类
//返回的是多行,Handler使用new BeanListHandler<>(Actor.class),这里的Actor.class是反射,表示Actor类
//第三个参数:是sql语句中问号的参数,可以写多个,比如1,4,5等,按顺序有多少问号写多少
List<Actor> list =
queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
System.out.println("输出集合的信息");
//list数组中,每一个有效索引对应一个Actor对象
for (Actor actor : list) {
System.out.print(actor);
}
//释放资源
//queryRunner.query将结果返回后,还会将ResultSet和PreparedStatement资源关闭
//因此在它们对应的位置填null即可
JDBCUtilsByDruid.close(null, null, connection);
}
//返回的结果是单行记录(单个对象)
@Test
public void testQuerySingle() throws SQLException {
//1. 得到 连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3. 执行相关的方法,返回单个对象
String sql = "select * from actor where id = ?";
// 因为我们返回的单行记录,即单个对象 , 返回单行使用的Hander 是 BeanHandler
// 只需要使用一个actor对象接收,不需要使用List
Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 10);
System.out.println(actor);
// 释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
//查询结果是单行单列,返回的是object
@Test
public void testScalar() throws SQLException {
//1. 得到 连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3. 执行相关的方法,返回单行单列 , 返回的就是Object
String sql = "select name from actor where id = ?";
//因为返回的是一个Object对象, 返回单行单列使用的handler 就是 ScalarHandler,并且使用Object对象接收
Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 4);
System.out.println(obj);
// 释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
//dml (update, insert ,delete)
@Test
public void testDML() throws SQLException {
//1. 得到 连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3. 这里组织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 = ?";
//执行dml 操作是 queryRunner.update() 返回的值是受影响的行数,因此只需要一个整型接收
//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);
}
对query()
方法的解读
// 老韩解读
//(1) query方法会执行sql 语句,得到resultset 并将其封装到 ArrayList 集合中
//(2) 然后返回集合
//参数 connection: 连接
// sql : 执行的sql语句
// new BeanListHandler<>(Actor.class): 将 resultset 放入 Actor 对象 然后 封装到 ArrayList
// 底层使用反射机制 去获取Actor类的属性,然后进行封装
// 第四个参数 1 是给 sql 语句中的?赋值,可以有多个值,因为是可变参数Object... params
// 底层得到的resultset ,会在query关闭, 以及关闭PreparedStatment
/**
* 分析 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;
* }
*/
DAO(BasicDAO)
引入BasicDAO
-
dbutils+druid开发极大简化jdbc开发,但还有缺陷
-
sql语句固定,不能通过参数传入,通用性不好,需要改进
-
select操作,如果有返回值,返回类型不能固定,需要使用泛型
-
未来的表很多,业务需求复杂,需要多个java类
-
因此有了BasicDAO
-
自下而上的BasicDAO示意图
-
基本说明
-
DAO:data access object 数据访问对象
-
这样的通用类,成为BasicDAO,是专门和数据库交互的,完成对数据库(表)的crud
-
在BasicDao的基础上,实现一张表对应一个DAO,能更好的完成功能
-
比如Custome表对应CustomerDao类
-
这样的类也被称为javabean/domain/pojo
-
使用
-
使用前需要
-
类:Actor类,JDBCUtilsByDruid类,将会放在最后
-
jar包:dbutils包,druid包
-
配置文件:druid(一般下载后压缩包有对应的配置文件)
-
-
BasicDao类是druid连接池进行crud的通用类,一般继承它的类是 对应表 的 表名+DAO类
-
真正进行测试使用的是第三个程序
//BasicDAO实际是一个druid连接池进行crud的通用类,需要则继承即可,就可以使用所有方法了
public class BasicDAO<T> { //泛型指定具体类型
//创建QueryRunner对象
private QueryRunner qr = new QueryRunner();
//开发通用的dml方法, 针对任意的表
public int update(String sql, Object... parameters) {
Connection connection = null;
try {
//用druid工具类获得连接
connection = JDBCUtilsByDruid.getConnection();
int update = qr.update(connection, sql, parameters);
return update;
} catch (SQLException e) {
throw new RuntimeException(e); //将编译异常->运行异常 ,抛出
} finally {
JDBCUtilsByDruid.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 = JDBCUtilsByDruid.getConnection();
return qr.query(connection, sql, new BeanListHandler<T>(clazz), parameters);
} catch (SQLException e) {
throw new RuntimeException(e); //将编译异常->运行异常 ,抛出
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
//查询单行结果 的通用方法
public T querySingle(String sql, Class<T> clazz, Object... parameters) {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
return qr.query(connection, sql, new BeanHandler<T>(clazz), parameters);
} catch (SQLException e) {
throw new RuntimeException(e); //将编译异常->运行异常 ,抛出
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
//查询单行单列的方法,即返回单值的方法
public Object queryScalar(String sql, Object... parameters) {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
return qr.query(connection, sql, new ScalarHandler(), parameters);
} catch (SQLException e) {
throw new RuntimeException(e); //将编译异常->运行异常 ,抛出
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
}
public class ActorDAO extends BasicDAO<Actor> {
//1. 就有 BasicDAO 的方法
//2. 根据业务需求,可以编写特有的方法.
}
public class TestDAO {
//测试ActorDAO 对actor表crud操作
@Test
public void testActorDAO() {
//创建ActorDAO对象,该对象继承了BasicDao,因此具有BasicDao的方法
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 ? "执行成功" : "执行没有影响表");
}
}
数据连接池、DbUtils、DAO总结
-
数据连接池、DbUtils、DAO 这三者是层层递进的
-
DAO基于DbUtils,DbUtils基于数据连接池
-
-
必须从数据连接池开始理解,后面的才能逐步理解
-
三者都是对crud的操作,只是一步一步进行简化
解析示意图
-
图片自下而上分层
-
第一层:domain(javabean)和utils工具类 层
-
一个javabean对应一张sql表
-
javabean是一个类,其中有表的所有列(参照前面使用过的Actor类)
-
-
第二层:DAO层
-
我们将不同的服务内容(表)分成了不同的DAO
-
BasicDAO是所有DAO类的父类
-
其它的DAO类继承BasicDAO后就可以使用其所有方法
-
DAO层主要操作数据库
-
-
第三层:服务层
-
包含了业务需求的代码,并调用DAO层类
-
-
第四次:界面层
-
在界面层调用服务层的类
-
服务层又调用了DAO层
-
DAO层又调用了javabean和utils操作数据库,最后结果一层一层返回到界面层
-
-
posted on 2021-08-23 17:21 snake_skin 阅读(36) 评论(1) 编辑 收藏 举报