Java入门(八)JDBC
目录
????:哈喽~☀️欢迎加入程序????大家庭,快点开始✒️自己的黑客帝国吧 ~????????
内容简述:JDBC概述、封装JDBCUtils工具类、Statement、PreparedStatement、批处理、事务、返回自增主键、JDBC操作分页。
一、JDBC概述、怎样使用JDBC、初步封装
JDBC概述
JDBC是什么
-
JDBC是一套sun公司定义的接口,是一套标准,规定了统一的数据库访问方法
-
各个数据库厂商实现这套标准,让java程序能操作数据库
为什么使用JDBC
- jdbc的好处:我们只学习一套接口的使用和操作,就可以访问任何数据库了。
怎么使用JDBC
-
下载驱动,mysql驱动,去Maven库搜索MySQL connector下载即可;
-
把驱动加载到java环境中,注册驱动;
// 注册驱动 Class.forName("com.mysql.jdbc.Driver");
-
获得连接对象 Connection
public static void main(String[] args) throws Exception { //1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2.获得Connection对象 //url jdbc:mysql://数据库IP:端口号/库名 //username 数据库用户名 //password 数据库密码 Connection conn=DriverManager.getConnection( "jdbc:mysql://localhost:3306/demo1", "root", "root"); //验证是否连接成功 System.out.println(conn); //证明接口实现类来自驱动 System.out.println(conn.getClass()); }
- 补充:
- 如果使用mysql版本为6.0以上的jar包,url 中要添加时区指定。
- “jdbc:mysql://localhost:3306/demo1?serverTimezone=Asia/Shanghai”-------地方时区;
- “jdbc:mysql://localhost:3306/demo1?serverTimezone=UTC”---------国际的标准时区。
-
连接上数据库后,要执行sql语句,使用Statement对象执行sql语句。
4.1. 执行DDL
String ddl="create table demo_2(id int,name varchar(20))"; // true 代表有返回结果集 // false 代表没有返回结果集 boolean flag=sta.execute(ddl); System.out.println(flag);
4.2. 执行DML
// 插入操作 DML String dml="insert into demo_2 values(1,'hanmeimei')"; sta.executeUpdate(dml);
4.3. 执行DQL,处理结果集
//查询 String dql="select 'hello' as a from dual"; //使用execute(dql)执行查询语句 ResultSet rs=sta.executeQuery(dql); while(rs.next()){ String str=rs.getString("a"); System.out.println(str); }
-
关闭连接对象
conn.close();
ResultSet 游标
- ResultSet对象,使用游标控制数据 ;
- 当执行查询语句的时候,resultSet对象用于封装查询结果方法;
- Boolean rs.next()
- 让结果集中的游标往下移动一行;
- 判断该行是否有数据!有的话,返回true,没有就返回false。
- rs.getXXX() 有很多种,分别对应数据库中不同的数据类型;
- 数据库类型 get方法
- char/varchar getString()
- int getInt()
- bigint getLong()
- float/double getFloat()/getDouble()
- datetime/timestamp getDate()
初步封装
-
java面向对象的思想,让我们获得连接对象的代码可以重用;
-
创建一个工具类 DBUtils,封装获取连接对象的静态方法:
public class DBUtils { /** * 1.创建静态方法getConnection,返回值为Connection对象 */ public static Connection getConnection() throws Exception{ // 1.1把4个字符串拆分出来 String driver="com.mysql.jdbc.Driver"; String url="jdbc:mysql://localhost:3306/demo1"; String userName="root"; String password="root"; // 1.2注册驱动 Class.forName(driver); // 1.3获取连接对象 Connection conn=DriverManager.getConnection(url, userName, password); return conn; } public static void main(String[] args) throws Exception { // 调用封装的getConnection方法获得连接对象 Connection conn=DBUtils.getConnection(); String sql="select * from demo_2"; // 获取statement对象 Statement sta=conn.createStatement(); ResultSet rs=sta.executeQuery(sql); while(rs.next()){ int id=rs.getInt(1); String str=rs.getString(2); System.out.println(id); System.out.println(str); } conn.close(); } }
课堂练习
-
完成DBUtils的封装,并且测试成功
-
思考尝试一下,封装conn.close()
封装进阶
-
JDBC可以操作所有的数据库;
-
如果我们现在想要更换数据,或者更换用户/密码/库,需要进入代码去更改;
-
把4个字符串driver,url,username,password,放到一个配置文件中,以便后期维护。
课堂练习
- 完全熟悉jdbc操作数据的库基本流程 (Connection对象,Statement对象,Properties对象);
- 完成进阶封装的代码操作;
- 对emp表,进行3次增删改查操作。
二、连接池技术、优化Statement对象(使用PreparedStatement)
连接池
- DBCP database connection pool—apache
- 缓存中存储了连接对象,可以重用连接对象的技术。
为什么使用连接池
- 重用了数据库连接对象,提高了连接效率:
-
在缓存(内存)中保存了一些connection对象,使用的时候拿出来用,用完了归还给缓存
-
从内存获取和归还connection对象的效率,要远远高于创建和销毁connection对象的效率
-
保护数据库连接数量,避免连接过载;
使用一个数据库的管理员,管理员对数据库连接做管理,导入包,并直接使用。
课堂练习
public static void main(String[] args) {
// 声明四个必要参数
String driver;
String url;
String username;
String password;
// 声明两个策略参数
int initialSize;
int maxActive;
// 声明BasicDataSource对象,赋值为null
BasicDataSource ds=null;
// 静态块中完成读取六个参数的操作
// 创建BasicDataSource对象的操作
// 给BasicDataSource对象设置6个参数的操作
try {
Properties conf=new Properties();
InputStream in = DBUtils1.class.getClassLoader()
.getResourceAsStream("db.properties");
conf.load(in);
driver=conf.getProperty("jdbc.driver");
url=conf.getProperty("jdbc.url");
username=conf.getProperty("jdbc.username");
password=conf.getProperty("jdbc.password");
initialSize=Integer.parseInt(conf.getProperty("initialSize"));
maxActive=Integer.parseInt(conf.getProperty("maxActive"));
//创建BasicDataSource对象
ds=new BasicDataSource();
//给BasicDataSource对象设置6个参数
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
ds.setInitialSize(initialSize);
ds.setMaxActive(maxActive);
Connection conn=ds.getConnection();
} catch (Exception e) {
e.printStackTrace();
}
}
封装连接池,完成查询表内容
多线程演示连接池的等待效果
数据库执行计划
-
目的:优化Statement对象,重用执行计划。
-
sql语句数据库是不认识的,需要进行编译,编译后的结果叫做执行计划。
-
执行一条sql语句,就产生一个执行计划。
-
同样的sql语句,哪怕一丁点的不一样(比如数据不一样,大小写不一样,空格不一样等),就会重新产生一个执行计划。
-
因此,我们要优化statement对象,重用执行计划:
- 优化方法----->使用PreparedStatement
- Statement:一般用于执行不发生改变的sql语句(比如DDL,DCL,TCL等)
- PreparedStatement:一般用于执行发生改变的sql语句(比如DML,DQL等)
- 优化方法----->使用PreparedStatement
示例:
//?是占位符,把可变数据的位置占住
String sql="insert into t9 values(?,?,?)";
//将sql语句发送到数据库,并创建执行计划
//ps就代表执行计划
PreparedStatement ps=conn.prepareStatement(sql);
//替换执行计划中的参数
//按照顺序和数量发送给执行计划
ps.setInt(1, 4);
ps.setString(2, "ddd");
ps.setString(3, "444");
//运行执行计划
ps.executeUpdate();
PreparedStatement的另一个功能是:可以防止sql注入。
注意:PreparedStatement不需要单引号修饰问号,但是Statement需要。
三、批量处理、结果集元数据、事务、返回自增主键、JDBC操作分页
批量处理
问题:Connection是一次连接,一次jdbc和数据库的响应叫一次通讯。当执行多条SQL的时候,就需要进行多次连接通讯,效率低。
Statement 解决方案:
- 在jdbc中设置一个缓存区用于积攒sql语句,积攒一定数量之后执行;
- 因为sql语句是DDL语句,所以执行语句不能省,只是节省通讯次数。
PreparedStatement解决方案:
- 使用批量处理
-
积攒DDL语句,使用Statement对象
sta.addBatch(ddl); sta.executeBatch(); sta.clearBatch();
完整方法:
public static void main(String[] args) { //准备一批sql语句 String ddl1="create table log1(id int,msg varchar(20))"; String ddl2="create table log3(id int,msg varchar(20))"; String ddl3="create table log4(id int,msg varchar(20))"; String ddl4="create table log5(id int,msg varchar(20))"; String ddl5="create table log2(id int,msg varchar(20))"; Connection conn=null; try { conn=DBUtils1.getConnection(); Statement sta=conn.createStatement(); //把一批sql语句添加到缓存中 sta.addBatch(ddl1); sta.addBatch(ddl2); sta.addBatch(ddl3); sta.addBatch(ddl4); sta.addBatch(ddl5); //执行一批SQL语句 int[] arr=sta.executeBatch(); //返回值有3种 //1.>=0 代表成功 //2.代表成功-2 //oracle对executeBatch()并不完全支持,返回-2 //3.代表失败-3 for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } catch (Exception e) { e.printStackTrace(); }finally{ DBUtils1.closeConnection(conn); } }
-
积攒参数,使用PreparedStatement,重用执行计划。
ps.addBatch(); ps.executeBatch(); ps.clearBatch();
完整方法:
public static void main(String[] args) { // 准备dml语句 String dml = "insert into log1 values(?,?)"; Connection conn = null; try { conn=DBUtils1.getConnection(); PreparedStatement ps = conn.prepareStatement(dml); // 把一批参数添加到ps的缓存中 ps.setInt(1, 1); ps.setString(2, "1111"); ps.addBatch(); ps.setInt(1, 2); ps.setString(2, "2222"); ps.addBatch(); ps.setInt(1, 3); ps.setString(2, "3333"); ps.addBatch(); // 批量执行一批参数 int[] arr = ps.executeBatch(); for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } } catch (Exception e) { e.printStackTrace(); }finally { DBUtils1.closeConnection(conn); } }
结果集元数据
- 就是结果集的相关信息。
- 目的:为了获取结果集中其它的描述信息。
-
举个栗子:
public static void main(String[] args) { Connection conn = null; try { conn=DBUtils1.getConnection(); String sql="select * from emp"; Statement sta=conn.createStatement(); ResultSet rs=sta.executeQuery(sql); // 获取元数据 ResultSetMetaData meta = rs.getMetaData(); // 获取列的数量 int n = meta.getColumnCount(); System.out.println(n); // 获取列的名称 String name1=meta.getColumnName(1); String name2=meta.getColumnName(2); System.out.println(name1+"~"+name2); for(int i = 1; i <= meta.getColumnCount(); i ++){ System.out.println(meta.getColumnName(i)); } } catch (Exception e) { e.printStackTrace(); }finally { DBUtils1.closeConnection(conn); } }
课堂练习
-
使用sta对象,批量执行DDL语句 5个
log6 (id int,msg varchar(20)); sta.addBatch(sql); sta.executeBatch()
-
使用PreparedStatement对象,批量更新20条数据。
- 注意控制不要让内存溢出。
事务
sql> set autocommit=0;
sql> start transaction;
sql> ....
sql> commit/rollback;
课堂练习
-
借钱的业务
create table account1( id int, name varchar(20), money double(7,2) ); insert into account1 values(1,'aaa',10000); insert into account1 values(2,'bbb',100);
-
完成转账事务逻辑
-
提高,把这个逻辑封装成方法
- pay(int from,int to,double money)
- from—>减钱的账户ID
- to----->加钱的账户id
- money–>转了多少钱
注意:如果是抛异常一定要自己调用回滚,因为我们在关闭连接对象的时候需要恢复自动提交,如果不自己调用回滚的话,缓存中的sql语句在恢复自动提交的时候也会执行。
返回自增主键
问题:在我们的日常开发过程中,数据的主键我们一般是使用数据库自动生成的序列。
那么如何在新增数据库之后获取到该条数据自动生成的主键序列呢?
解决方案:
- 1.在建表的时候指定主键序列为自增长,auto_increment;
create table post(
id int primary key auto_increment,
content varchar(200),
k_id int
);
create table keywords(
id int primary key auto_increment,
content varchar(20)
);
- 2.java语句:
/**
* 归还连接池中连接对象的方法
*/
public static void closeConnection(Connection conn) {
try {
if(conn != null) {
// 还原自动提交的默认设置
conn.setAutoCommit(true);
// 此处close是归还
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Connection conn=null;
String sql1="insert into keywords values(null,?)";
try {
conn=DBUtils1.**getConnection**();
// 关闭自动提交事务--开启事务
conn.setAutoCommit(false);
// 自动生成***的列名
// 一张表中可以存在多个自增长的列,
// 所以这里使用数组保存
String[] cols= {"id"};
// 获取ps对象
// 第一个参数,生成执行计划的sql语句
// 第二个参数,执行时,返回得到的列值的列名
PreparedStatement ps=conn.prepareStatement(sql1,cols);
ps.setString(1, "雾霾");
// ps一执行,自增的id值,
// 就返回到ps对象中保存着
ps.executeUpdate();
// 获取自增长的值
ResultSet rs=ps.getGeneratedKeys();
while(rs.next()) {
// 注意:这里的getXXX(index)
// 只能写下标不能写列名
int id=rs.getInt(1);
System.out.println("id:"+id);
}
conn.commit();
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtils1.**closeConnection**(conn);
}
}
JDBC操作分页
- MySQL大表查询必须使用分页
select id from keywords limit 1,5;
Java 代码
:
public static void main(String[] args) {
Connection conn = null;
try {
conn=DBUtils1.getConnection();
String sql = "select id from keywords limit ?,?";
PreparedStatement ps = conn.prepareStatement(sql);
// 从0开始,显示5条
ps.setInt(1, 0);
ps.setInt(2, 5);
ResultSet rs=ps.executeQuery();
while(rs.next()){
System.out.println(rs.getInt(1));
}
} catch (Exception e) {
}finally {
DBUtils1.closeConnection(conn);
}
}
为什么MySQL查询大表必须要分页?
- 官方要求查询结果存在数据库的缓存中,直到rs.getXXX,数据才会传到服务器内存;
- 但是MySQL根标准官方建议不一样,对JDBC查询实现的不好;
- MySQL只要一执行查询,就把结果全部存到服务器内存中了;
- MySQL只要查询大表,必须使用分页;
- 所以mysql的分页操作非常简单,算是一种补救。
注意:查询尽量不要使用select *,查询字段冗余,而且如果表结构变更,系统报错不容易排查原因。