JDBC编程
JDBC即java数据库互连,是java语言和数据库之间独立于数据库的连接标准API,JDBC从根本上来说是一种规范,具体的实现需要依赖于具体数据库生产商提供jar包【驱动】,提供了统一的接口用于访问不同的底层数据库,允许使用java语言编写不同的应用程序以访问数据库。
-
为Java语言定义了一个SQL调用级别的统一界面
-
为访问关系型数据库提供了一个标准的界面
-
所谓的JDBC实际上就是一种用于执行SQL语句的API,可以采用一致的方式连接不同的额数据库系统,从而使用标准的SQL语言来存取数据库中的数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SXXFXyDJ-1657335309051)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220603171919325.png)]
具体的数据库访问采用的都是c/s架构
-
支持多种不同的数据库服务器
-
主要的业务逻辑集中在客户端中
-
服务器端的逻辑侧重于数据库的操作
-
客户端将频繁访问远程数据库,可能会导致网络流量增大问题
JDBC的体系结构
Java应用程序可以通过Java API与数据库连接,JavaAPI中的一组用Java语言编写的类和接口位于java.sql和javax.sql包中,实际动作则是由JDBC驱动管理器通过数据库生产厂商提供的JDBC驱动程序与数据库管理系统进行连接
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MsZ9TCcl-1657335309052)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220603172018306.png)]
每个JDBC应用程序至少需要有一个JDBC驱动程序,JDBC驱动是Driver接口的实现类
-
Driver接口是驱动程序需要实现的接口,Driver使DriverManager和JDBC应用可以独立于具体的数据库系统
-
在不同类型的应用中添加驱动的方式不同
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gda27bIX-1657335309052)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220603172053084.png)]
JDBC API
-
java.sql.DriverManager负责加载、拆除驱动程序,负责获取和数据库管理系统的连接
-
java.sql.Connection实现对某个数据库系统的连接
一个数据库管理系统能够提供的连接数是有限的,必须保证连接及时关闭
- java.sql.Statement用于实现向数据库系统提交SQL语句
java.sql.PreparedStatement用于执行预编译的SQL语句
java.sql.CallableStatement用于提交执行存储过程
- java.sql.ResultSet是数据库系统返回的查询结果集
JDBC基本操作步骤
-
加载驱动程序
-
创建数据库连接,必须保证及时关闭
-
提交执行SQL语句
-
接收并处理SQL的执行结果
-
关闭释放资源
1、加载驱动
首先将驱动jar包从数据库系统官方网站上下载下来,并将驱动jar包添加到构建路径中
Class.forName(“驱动串”)。这里的驱动串就是需要加载的JDBC驱动程序的名称
早期没有很多数据库提供驱动,所以java提供了通过微软的ODBC连接数据库的方法,这个驱动已经包含在java中,叫做jdbc-odbc桥接方式。例如访问micorosoft office中的access数据库的方法为
Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”);
JDBC的驱动方式可以分为4种类型:JDBC-ODBC桥接方式、Native-API驱动、JDBC-Net驱动和Native-protocol驱动
//受检型异常 ClassNotFoundException
Class.forName("com.mysql.cj.jdbc.Driver");
//从反射机制的角度上说new A()等价于Class.forName("com.yan.A").newInstance(),所以加 载操作也可以简化
new Driver();
如果使用JDBC高版本驱动时实际上还可以省略加载步骤
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NZV6n0IX-1657335309053)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20220603172433288.png)]
2、获取连接
可以通过驱动管理器DriverManager类获取和数据库系统的连接
Connection conn=DriverManager.getConnection("连接串","连接数据库的用户名称","对应的 口令")
- 连接串用于代表需要连接的数据库,连接串的标准 jdbc主协议:子协议:其它部分 ,但是不同的数据库系统其对应的连接串不同。例如连接oracle则驱动串为 oracle.jdbc.OracleDriver ,连接串为 jdbc:oracle:thin:@localhost:1521:test
// 受检型异常 ClassNotFoundException
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test? serverTimezone=UTC", "root", "123456");
System.out.println(conn); //com.mysql.cj.jdbc.ConnectionImpl@365185bd
注意:数据库连接属于稀有资源,所以需要使用时进行创建,使用完毕必须结束关闭,不能依赖于垃圾回收。建议使用try/finally结构
3、构建语句对象
在与特定数据库建立连接后,就可以发送SQL语句,在发送SQL语句之前需要创建一个语句对象
最古老的方式是使用Statement对象
Statement stmt=conn.createStatement();
ResultSet res=stmt.executeQuery("select * from tb_student");
一般不建议使用Statement对象,这个对象用于提交静态sql语句。有sql注入漏洞,建议使用Statement的子接口PreparedStatement
-
Statement对象中提供了executeQuery方法来执行查询操作,这个方法会返回查询结果集ResultSet类对象,其中包含SQL语句的查询结果
-
Statement对象中提供了executeUpdate方法来执行修改操作,这个方法会返回一个int值,表示sql的增删改操作所影响的数据行数
需要查询tb_student表
//注意导入包时不要出现错误
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class Test1 {
public static void main(String[] args) throws Exception {
// 受检型异常
ClassNotFoundException Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test? serverTimezone=UTC", "root","123456");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from tb_student");
}
}
4、接收查询结果集
JDBC中使用ResultSet对象来表示查询结果集,可以理解为一个指向满足查询结果的行指针,其中并不存放实际数据,所以在使用结果集之前不能关闭连接
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from tb_student");
5、遍历处理结果集
注意:连接不能关闭
ResultSet对象包含了执行查询后满足条件的所有行,提供了对应的访问方法,有一组对应的getXxx方法获取指定列的值
while(rs.next()){ //用于移动行指针,同时判断是否有数据,如果有数据则返回为true,否则 false
/** | tb_student | CREATE TABLE `tb_student` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL,
`age` int(11) DEFAULT '16',
`sex` tinyint(1) DEFAULT '1',
`dept` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8 |
*/ //由于历史原因,列序号从1开始
long id=rs.getLong(1);//指定列时可以使用序号,也可以使用列名称,一般建议使用列名 称,因为数据表的结构会有变动
String name=rs.getString("name");
int age=rs.getInt("age");
boolean sex=rs.getBoolean("sex");
System.out.println(id+":"+name+":"+age+":"+sex);
}
一般列是什么数据类,则应该使用对应类型的getXxx方法,实际上使用getString可以获取任意类型的列中的数据
如果执行修改操作,则返回的是int类型数据,表示受影响行数
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///test? serverTimezone=UTC", "root", "123456");
Statement stmt = conn.createStatement();
int len=stmt.executeUpdate("update tb_student set dept='材料系' where id=8");
if(len>0)
System.out.println("修改成功!");
else
System.out.println("修改失败!");
6、关闭对象
手动关闭ResultSet、Statement和Connection,注意这里必须保证Connection对象关闭
建议使用try/finally结构
正向打开对象,逆向关闭对象
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Test3 {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
String sql="select * from tb_student";
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql:///test? serverTimezone=UTC", "root", "123456");
stmt = conn.createStatement(); rs=stmt.executeQuery(sql);
while(rs.next()){
long id=rs.getLong("id");
String name=rs.getString("name");
boolean sex=rs.getBoolean("sex");
System.out.println(id+"\t"+name+"\t"+(sex?"男":"女"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
} catch (SQLException e2) {
e2.printStackTrace();
}try {
if (stmt != null)
stmt.close();
} catch (SQLException e1) {
e1.printStackTrace();
}try {
if (conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
JDBC主要编程接口对象
JDBC即java数据库互连,是java语言和数据库之间独立于数据库的连接标准API,JDBC从根本上来说是一种规范,具体的实现需要依赖于具体数据库生产商提供jar包【驱动】,提供了统一的接口用于访问不同的底层数据库,允许使用java语言编写不同的应用程序以访问数据库。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Test3 {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
String sql="select * from tb_student";
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql:///test? serverTimezone=UTC", "root", "123456");
stmt = conn.createStatement(); rs=stmt.executeQuery(sql);
while(rs.next()){
long id=rs.getLong("id");
String name=rs.getString("name");
boolean sex=rs.getBoolean("sex");
System.out.println(id+"\t"+name+"\t"+(sex?"男":"女"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
} catch (SQLException e2) {
e2.printStackTrace();
}try {
if (stmt != null)
stmt.close();
} catch (SQLException e1) {
e1.printStackTrace();
}try {
if (conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Connection接口
Connection接口用于表示应用和数据库系统之间的静态连接,提供了针对事务处理的方法以及创建执行sql语句和存储过程的方法,同时提供了一些基本的错误处理方法
public interface Connection extends Wrapper, AutoCloseable
-
createStatement():Statement 创建用于执行sql语句的语句对象
-
prepareStatement(String sql):PreparedStatement 创建PreparedStatement对象,用于实现数据库的动态访问
-
prepareCall(String sql):CallableStatement 用于创建执行存储过程的CallableStatement对象
-
close():void结束Connection对象数据库连接,务必注意:数据库连接属于稀有资源,必须保证及时关闭
-
isClose():boolean测试是否已经关闭数据库连接
-
setAutoCommit(boolean):void设置事务是否自动提交
-
commit()在连接上提交事务
-
rollback()回滚撤销事务
Statement接口
Statement接口用于提交执行静态的SQL语句,并返回SQL的执行结果
public interface Statement extends Wrapper, AutoCloseable
-
executeQuery(String查询语句):ResultSet 执行sql语句,并返回满足查询条件的结果集
-
executeUpdate(String增删改语句):int 执行增删改语句,并返回sql语句的影响行数
-
execute(String sql):boolean 执行任意的SQL语句,如果第一个执行结果是ResultSet则会返回true,如果返回整数则返回false
public class Test2 {
public static void main(String[] args) throws Exception {
new Driver();
try (Connection conn = DriverManager.getConnection("jdbc:mysql:///test? serverTimezone=Asia/Shanghai", "root","123456");
Statement stmt = conn.createStatement();
) {
String sql = "create table if not exists tb_users(id int)";
boolean res = stmt.execute(sql);
System.out.println(res);
}
}
}
-
close():void 关闭语句对象
-
addBatch(String sql)将多条sql语句放入到一个批处理中执行
-
executeBatch()向数据库发送一批sql语句,并执行
三种语句对象
在JDBC种提供了三种语句对象Statement、PreparedStatement和CallableStatement
- Statement用于发送简单的静态SQL语句,不带参数。不支持预编译、有sql注入的风险。不允许使用
mysql> create table tb_users(
-> id bigint primary key auto_increment,
-> username varchar(20) not null,
-> password varchar(20) not null);
Query OK, 0 rows affected (0.03 sec)
所谓的用户登录就是提交用户名称和口令到应用中,应用按照用户名username和口令password,到数 据库tb_users表中进行查询,如果能够查询到数据,则登录成功,否则登录失败
String username = "yanjun";
String password = "123456";
boolean success = false;
try ( Connection conn = DriverManager.getConnection("jdbc:mysql:///test? serverTimezone=Asia/Shanghai", "root", "123456");
Statement stmt = conn.createStatement();
) {
// 通过字符串拼接生成对应的sql语句
String sql = "select * from tb_users where username='" + username + "' and password='" + password + "'";
success = stmt.executeQuery(sql).next();
}
if (success)
System.out.println("登录成功!");
else
System.out.println("登录失败!");
- PreparedStatement是Statement的子接口,用于发送含有一个或者多个参数的sql语句。PreparedStatement比Statement执行效率高,因为它支持预编译功能,同时在一定程度上可以防止SQL注意,一般使用使用这个接口
public class Test4 {
public static void main(String[] args) throws Exception {
String username = "yanjun";
String password = "123456";
try (Connection conn = DriverManager.getConnection("jdbc:mysql:///test? serverTimezone=Asia/Shanghai", "root", "123456");
) {
String sql="select * from tb_users where username=? and password=?";
PreparedStatement ps=conn.prepareStatement(sql);
//针对?参数ps对象提供了一组对应的setXxx方法,用于给参数赋值
ps.setString(1, username);
ps.setString(2, password);
ResultSet rs=ps.executeQuery();
while(rs.next()){
String upwd=rs.getString("password");
long id=rs.getLong("id");
String uname=rs.getString("username") System.out.println(id+"\t"+uname+"\t"+upwd);
}
}
}
}
- CallableStatement接口继承于PreparedStatement接口,主要用于调用执行存储过程一般在具体应用开发中不使用Statement,基本上都是使用PreparedStatement,主要原因就是PreparedStatement具有Statement的所有功能,同时支持预编译,还有避免SQL注入风险
Statement和PreparedStatement的区别
-
Statement只能处理静态SQL,PreparedStatement既能处理静态SQL,也能处理动态SQL,它继承于Statement接口
-
PreparedStatement支持预编译,所以适合执行连续多次相同结构的SQL语句,Statement不适合
SQL注入
客户端利用jdbc statement的缺点,传入非法参数,从而使jdbc返回不合法的值,通常称为sql注入
String username = "yanjun";
String password = "12345' or '1'='1";
boolean success = false;
try (Connection conn = DriverManager.getConnection("jdbc:mysql:///test? serverTimezone=Asia/Shanghai", "root","123456");
Statement stmt = conn.createStatement();
) {
// 通过字符串拼接生成对应的sql语句
String sql = "select * from tb_users where username='" + username + "' and password='" + password + "'";
System.out.println(sql);
success = stmt.executeQuery(sql).next();
}
if (success)
System.out.println("登录成功!");
else
System.out.println("登录失败!");
ResultSet接口
ResultSet接口用于得到包含了执行SQL查询结果的结果集,要获取表中任何一个字段项都要先找到该字段项所处于的行,再获取对应的列
-
获取数据要依靠一个指向当前行的指针,开始时指向满足条件的第一行之前,每次执行next方法,则指针后移,到达目标行后就可以使用getXxx方法获取对应列的值
-
boolean next()指针后移,如果有数据则返回true,否则false
-
getXxx(int索引序号/String列名称):Xxx 获取对应列值
时间类型处理
java.util.Date类型用于封装日期时间【具体实现实际上保存的是从1970-1-1 0:0:0到指定时刻的毫秒
值】;在java.sql中针对日期时间类型提供了3个java.util.Date类型的子类
-
java.sql.Date只有日期
-
java.sql.Time只有时间
-
java.sql.Timestamp具有日期时间
传入日期类型数据
String sql="select * from tb_users where birth=?"; //假设birth是日期类型
PreparedStatement ps = conn.prepareStatement(sql);
//setDate方法要求参数必须是java.sql.Date类型,不能是java.util.Date类型
java.sql.Date dd=new java.sql.Date(now.getTime());//now中实际包含年月日时分秒,dd 中只有年月日
ps.setDate(1, dd); ResultSet rs = ps.executeQuery();
接收日期类型数据
while (rs.next()) {
long id = rs.getLong("id");
java.util.Date birth=rs.getDate("birth");
//rs.getDate获取的数据类型是 java.sql.Date,所以只有年月日,如果需要时分秒则使用getTime,如果需要年月日时分秒则使用 getTimestamp
System.out.println(id + "\t" + birth);
}
tatement(sql);
//setDate方法要求参数必须是java.sql.Date类型,不能是java.util.Date类型
java.sql.Date dd=new java.sql.Date(now.getTime());//now中实际包含年月日时分秒,dd 中只有年月日
ps.setDate(1, dd); ResultSet rs = ps.executeQuery();
接收日期类型数据
```java
while (rs.next()) {
long id = rs.getLong("id");
java.util.Date birth=rs.getDate("birth");
//rs.getDate获取的数据类型是 java.sql.Date,所以只有年月日,如果需要时分秒则使用getTime,如果需要年月日时分秒则使用 getTimestamp
System.out.println(id + "\t" + birth);
}