JDBC基础
通过Java Database Connectivity可以实现Java程序对后端数据库的访问
一个完整的数据库部署架构,通过是客户端和服务端两部分组成
客户端封装数据库请求,并发送给服务器,服务器执行完毕后将结果返回客户端
常见客户端工具:
Mysql Workbench(图形化工具操作简单,只能进行简单查询)
JDBC (驱动程序jar包)
命令行工具(直接敲入SQL语句进行查询)
市面上多种数据库不尽相同,学习成本高,数据库迁移的移植性好的工具非常有必要
-- JDBC (普通的Java类库):应用程序通过统一的接口,即可实现对任意数据库的访问
-- 对于数据库厂商来说,JDBC就是一套接口规范,每一个数据库都要实现JDBC定义的接口,用户通过接口访问数据库即可。
JDBC的优势:对数据库的访问简单,开发快捷,省时间,面向不同的数据库时的移植性强,在JDBC上可以定制强大的框架(如MyBatis)
体系架构:
上层API层负责与Java web程序之间的通信
JDBC API:
Driver & DriverManager:
Driver是驱动程序的抽象,通过操作Driver接口,可以实现对各个驱动程序的操作
DriverManager是驱动程序的管理类,用户通过Class.forname(DriverName)向DriverManager注册一个驱动程序
之后通过DriverManager的getConnection方法调用该驱动程序建立到后端数据库之间的物理连接。
DriverManager.getConnection(DB_URL,USER,PASS);
//USER和PASS在部署数据库时获得
//DB_URL是后端数据库实例的唯一标识符
i.e.
jdbc:mysql://10.163.173.30:3306/cloud_study
协议 子协议 子名称(主机,端口,数据库名)
(NB:子协议不同,自协议的格式也有不同)
Connection:通过DriverManager的getConnection方法获得的到后端数据库的物理连接
Java应用程序对后端数据库的一条物理线路
通过这些连接,可以执行一些SQL语句:
Statement stmt = conn.createStatement();//sql statement
Statement: sql语句的容器,用于承载sql 语句,在该容器中,可以进行增删改查等操作。
通过executeQuery方法,执行数据库查询并得到返回结果的集合,以ResultSet类的对象来表示:
ResultSet rs = stms.executeQuery(“select userName from user”);
通过executeUpdate方法,执行数据库更新,删除语句,返回的是int值的对象,代表被影响的数据库记录数量
ResultSet对象表示一个SQL语句查询的结果
关系型数据库:二元表 -- ResultSet对象也是由行和列组成的
ResultSet对象内部有一个指针,指向当前对应的行记录(默认指向第一行记录)
.next(): 将指针移动到下一行
.previous(): 将指针移动到上一行
.absolute(): 将指针定位在某一行
.beforeFirst(): 将指针移动到第一行的之前(通过.next()移动到第一行)
.afterLast(): 将指针移动到最后一行之后
.getString(ColumnName/Index): (index从0开始)获取对应列的值
.getInt(ColumnName/Index):
.getObject(ColumnName/Index):
3个get最好使用ColumnName,因为列可能会变化,而且使用columnName会更直观。
SQLException: 在执行过程中MySQL可能会抛出一些异常
通过SQLException对象来进行异常的处理
下层Driver API层负责与具体的数据库建立连接,一般而言下层的driver都是由数据库厂商提供的。
安装JDBC:
JDBC已经继承在JDK中,可以直接引用,无需安装。
需要安装的是数据库的驱动程序
对于MySQL数据库:
登录Oracle账号,下载jar包,添加到Java web project中。
http://dev.mysql.com/downloads/connector/j
构建完整的Java web程序:
NB: 使用JDBC之前,需要准备一个数据库的后端实例,创建1个user表
构建步骤:
装在驱动程序:向DriverManager注册一个驱动程序Driver
建立数据库连接:DriverManager.getConnection()
执行SQL语句:Statement.execute…()
获取执行结果:ResultSet对象
清理环境:关闭Connection,Statement,ResultSet对象
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class HelloJDBC {
static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/cloud_study";
static final String USER = "root";
static final String PASSWORD = "nidongde";
public static void helloworld() throws ClassNotFoundException {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
// 1、装载驱动程序
Class.forName(JDBC_DRIVER);
// 2、建立数据库连接
try {
conn = DriverManager.getConnection(DB_URL, USER, PASSWORD);
// 3、创建Statement
stmt = conn.createStatement();
// 4、执行SQL语句
rs = stmt.executeQuery("select userName from user");
// 5、获取执行结果
while (rs.next()) {
System.out.println("Hello" + rs.getString("userName"));
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) throws ClassNotFoundException{
helloworld();
}
}
代码找茬:(下面这段代码中,有什么问题?)
public static void test() throws ClassNotFoundException, SQLException {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
ResultSet rs1 = null;
// 1. 装载驱动程序
Class.forName(JDBC_DRIVER);
// 2. 建立数据库连接
conn = DriverManager.getConnection(DB_URL, USER, PASSWORD);
// 3. 创建statement
stmt = conn.createStatement();
// 4. 执行SQL语句
rs = stmt.executeQuery("select userName from user");
// 5. 获取执行结果
while(rs.next()) {
System.out.println("Hello" + rs.getString("userName"));
}
}
1. try-catch block
2、close the connection to the database in finally block with another try-catch block
JDBC进阶
业务场景一:过滤条件弱,一次可能读出较多数据
业务场景二:需要海量数据读取
产生结果:Java内存的溢出异常
原因分析:Java程序是运行在JVM中的,而JVM有内存大小限制,当我们把数据库中的数据一次性全部读取到内存中,必须考虑内存中是否放得下这些数据。
解决方法:将数据分批次读取到内存并处理。
游标:提供一种客户端读取部分服务器端结果集的机制
游标的使用:
1. 开启游标:在DB_URL中加入参数useCursorFetch=true
DB_URL:jbdc:mysql://<IP>:<PORT>/<database>?useCursorFetch=true
2. PreparedStatement接口(继承自Statement)
使用PreparedStatement对象替换原来的Statement对象
需要在创建时传入sql语句(sql语句是参数格式化的,即用?表示参数)
后续通过setString()等来设置这些参数
SetFetchSize()接口可以帮助实现游标的功能:设置客户端每次从服务器取回的记录数量
public static void helloworld() throws ClassNotFoundException {
Connection conn = null;
PreparedStatement pmpt = null;
ResultSet rs = null;
// 1、装载驱动程序
Class.forName(JDBC_DRIVER);
// 2、建立数据库连接
try {
DB_URL = DB_URL + "?useCursorFetch=true";
conn = DriverManager.getConnection(DB_URL, USER, PASSWORD);
// 3、创建Statement
String sql = "select userName from user where sex = ?";
pmpt = conn.prepareStatement(sql);
pmpt.setFetchSize(2);
pmpt.setString(1, "男");
// 4、执行SQL语句
rs = pmpt.executeQuery();
// 5、获取执行结果
while (rs.next()) {
System.out.println("Hello" + rs.getString("userName"));
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if (rs != null) {
rs.close();
}
if (pmpt != null) {
pmpt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
业务场景三:某一条记录的数据为大字段
产生结果:和多条记录读取相同,出现Java内存的溢出问题
解决方法:
流方式(与游标类似):将大字段的数据按照二进制流的方式进行划分
流方式的使用:ResultSet.getBinaryStream();
// 1、装载驱动程序
Class.forName(JDBC_DRIVER);
// 2、建立数据库连接
try {
conn = DriverManager.getConnection(DB_URL, USER, PASSWORD);
// 3、创建Statement
String sql = "select * from user_note";
pmpt = conn.prepareStatement(sql);
// 4、执行SQL语句
rs = pmpt.executeQuery();
while (rs.next()) {
// 5、获取对象流
InputStream in = rs.getBinaryStream("blog");
// 6、将对象流写入文件
File f = new File("/Users/liujingjie/test.txt");
OutputStream out = null;
try {
out = new FileOutputStream(f);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
int temp = 0;
try {
while ((temp = in.read()) != -1) { // 边读边写
out.write(temp);
}
in.close();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("ok"); // 假如没异常,这个ok会打印出来,没其他特殊含义。
}
业务场景四:数据录入:大量数据的插入操作
产生结果:插入数据的速度太慢
原因分析:每次进行executeUpdata()或executeQuery()操作时,都是一次客户端到服务端发送sql的过程,发送和接受SQL浪费了很多时间,降低了效率。
解决方法:一次发送多条SQL语句
批处理:一次提交多条SQL语句,节省网络开销
批处理的使用:
Statement:.addBatch(); .executeBatch(); .clearBatch();
addBatch(): batch指执行sql的一个单元,addBatch即将一条条sql加入到batch这个单元中
executeBatch(): 执行这个单元的一批SQL语句
clearBatch(): 执行完毕,即可清空Batch中的SQL语句。
public class HelloJDBC4 {
static final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost:3306/cloud_study";
static final String USER = "root";
static final String PASSWORD = "xjj5211314";
private static void insertUsers(Set<String> users) throws ClassNotFoundException {
Connection conn = null;
Statement stmt = null;
// 1. 装载驱动程序
Class.forName(JDBC_DRIVER);
// 2. 建立数据库连接
try {
conn = DriverManager.getConnection(DB_URL, USER, PASSWORD);
// 3. 执行SQL语句
stmt = conn.createStatement();
// add Batch
for (String user : users) {
stmt.addBatch("insert into User values (null,'" + user + "',null);");
}
// execute Batch
stmt.executeBatch();
// clear Batch
stmt.clearBatch();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 4. 清理环境
try {
if (conn != null) {
conn.close();
}
if (stmt != null) {
stmt.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws ClassNotFoundException {
Set<String> users = new HashSet<String>();
users.add("GuoYi");
users.add("ZhangSi");
users.add("LiSan");
insertUsers(users);
}
}
业务场景五:中文字符集
JDBC字符集需要和数据库的字符集相同
数据库的内部编码:
mysql> show variables like ‘%character%’;
里面有个character_set_server: server级别的编码
character_set_database: database级别的编码
另外在创建Table以及Column的时候这2个级别的编码
优先级:Server-->Database-->Table-->Column
意思就是加入table与Database编码不同,以table为准。
设置JDBC的编码:
DB_URL = DB_URL + “characterEncoding=utf-8”;
作业:
现有一个在线交易系统,有一张Product商品表,表中共有3个字段:
-
Id: auto_increment,自增主键,商品唯一标识;
-
ProductName:varchar(100),商品名称;
-
Inventory: int 商品库存;
表中已经插入了一些商品,请编写一段Java程序,尝试读取商品ID为1的商品记录,输出商品名称和库存数量。
注意:程序中已经定义了一些常量,DRIVER_NAME、DB_URL、DB_USER_NAME、DB_PASSWORD,大家在编写程序时,可以直接引用。
Id | ProductName | Inventory |
1 |
bread | 11 |
2 |
milk | 8 |
// 1. 装载驱动程序
Class.forName(JDBC_DRIVER);
// 2. 建立数据库连接
try {
conn = DriverManager.getConnection(DB_URL, USER, PASSWORD);
// 3. 执行SQL语句
String sql = "select ProductName,Inventory from Product where id = ?";
pmpt = conn.prepareStatement(sql);
pmpt.setInt(1, i);
rs = pmpt.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("ProductName")+":"+rs.getString("Inventory"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 4. 清理环境
try {