JDBC入门


 什么是JDBC

JDBC就是Java数据库连接,就是用Java语言向数据库发送请求。以往我们用cmd输入sql语句,现在我们可以使用java语言实现对数据库的增删改查等一系列操作了。


 连接方式

1.导jar包--驱动

2.加载驱动类 Class.forName("类名");

3.给出url,username,password,其中url需要背下来

4.使用DriverManager类来得到Connection对象


JDBC四大参数

JDBC四大参数分别是

driverClassName:con.mysql.jdbc.Driver

url:jdbc:mysql://localhost:3306/数据库名

username:用户名
password:密码

其中driverClassName需要背下来

其中url,username,password需要传给DriverManager.getConnection()函数,该函数的返回值是Connection类型,Connection类型是MySQL的jar包里带有的一个类,需要额外import

代码示例:

 1 public void fun() throws ClassNotFoundException, SQLException {
 2         /**
 3          * JDBC四大参数:
 4          * driverClassName:com.mysql.jdbc.Driver
 5          * url:jdbc:mysql://localhost:3306/数据库名
 6          * username:root
 7          * password:XXXXX
 8          */
 9         Class.forName("com.mysql.jdbc.Driver");
10         String url="jdbc:mysql://localhost:3306/test";
11         String username="root";
12         String password="20010821";
13         Connection con=DriverManager.getConnection(url,username,password);
14         System.out.println(con);
15     }

其中抛出的两大异常,第一个是ClassNotFoundException,第二个是SQLException,第一个异常针对的是Class.forName()语句,如果出现异常可能有两种情况,一种是没有导入jar包,无法找到该类,另外一种情况是输入的包的名称错误,导致无法找到该类。

第二个异常是SQLException,出现异常的可能原因是用户名或者密码错误,也可能是因为mysql服务器没有打开,或者被禁止登陆,或者是url端口出现错误。


JDBC原理

应用程序实现JDBC的API接口,接口需要实现类,所谓的类就是数据库驱动,驱动类由数据库提供。

连接不同的数据库的区别就是上面提到的四大参数的区别。

所有的java.sql.Driver实现类都提供了静态方法,其方法就是将自己注册到DriverManager中。从而我们只要加载这个类,其中的静态方法就会自行调用,继而实现数据库的注册驱动。

JDBC4.0后,每个驱动jar包中的META-INF目录下都提供了一种名为java.sql.Driver的文件,文件的内容就是该接口的实现类的名称,所以新版本里不用我们再注册驱动,但是为了代码通用性,兼容老版本的jar,我们应该写上注册驱动类的代码。


JDBC基本操作

我们使用JDBC来进行增删改查的基本操作

一.连接数据库,得到Connection

1.准备四大参数

2.加载驱动类

3.得到Connection

二.对数据库进行增删改操作

1.通过Connection对象创建Statement。Statement是一种发射器,它的功能就是向数据库发送操作语句

2.(增删改)调用Statement的int executeUpdate(String sql),它可以发送DML,DDL,得到的返回值是影响的行数

   (查找) 调用Statement的ResultSet executeQuery(String sql),对返回得到的查询“表格”进行解析

解析过程:

ResultSet有一个行光标,通过行光标遍历所有行

ResultSet的next()方法可以让光标下移一行,next返回的布尔值表示当前行是否存在

在第一行前,叫beforeFirst,第一行叫first

在最后一行后,叫afterLast,最后一行叫last

ResultSet提供了一些列getXXX(''列名''/列编号)方法,

1.把行光标移到第一行,可以调用next()方法进行操作

2.获得该行的内容,可以调用getXXX()方法

三.关闭资源

要求:倒关,先打开的后关

1.关闭ResultSet

2.关闭Statement

3.关闭Connection

一定要关闭,有时候占用资源有时候不占用资源,资源不会被释放掉,资源必须要自己关闭。

为了保证代码的规范化,我们通常采用fun3中的方式,利用try块写代码

 1 package Demo2;
 2 
 3 import com.mysql.jdbc.Driver;
 4 
 5 import java.sql.*;
 6 
 7 public class Demo2 {
 8     public void fun1() throws ClassNotFoundException, SQLException {
 9         /**
10          *一.连接数据库,得到connection
11          * 1.准备四大参数
12          * 2.加载驱动类
13          * 3.得到Connection
14          */
15         Class.forName("com.mysql.jdbc.Driver");
16         //url是jdbc协议的格式,jdbc协议格式为 jdbc:工商名:子协议(由工商自己决定)
17         //mysql的子协议是"//主机:端口号/数据库名"
18         String url="jdbc:mysql://localhost:3306/test";
19         String username="root";
20         String password="20010821";
21         Connection con= DriverManager.getConnection(url,username,password);
22         /**
23          * 二.对数据库进行增删改查操作
24          * 1.通过Connection对象创建一个Statament,Statement是一个发射器,用来向数据库发射sql语句
25          * 2.调用Statement的int executeUpdate(String sql),它可以发送DML,DQL语句
26          */
27         Statement stmt=con.createStatement();
28         String sql="INSERT INTO departments VALUES('d007','Studying')";//注意,这里的sql语句不要使用分号,并且只能写入一句语句
29         //x返回值返回的是影响的行数
30         int x=stmt.executeUpdate(sql);
31         System.out.println(x);
32         stmt.close();
33         con.close();
34     }
35     public void fun2() throws ClassNotFoundException, SQLException {
36         Class.forName("com.mysql.jdbc.Driver");
37         String url="jdbc:mysql://localhost:3306/test";
38         String username="root";
39         String password="20010821";
40         Connection con=DriverManager.getConnection(url,username,password);
41         Statement stsm=con.createStatement();
42         ResultSet rst=stsm.executeQuery("SELECT * FROM departments");
43         while(rst.next()){
44             String dept_no=rst.getString(1);
45             String dept_name=rst.getString("dept_name");
46             System.out.println(dept_no+":"+dept_name);
47         }
48         rst.close();
49         stsm.close();
50         con.close();
51     }
52     //规范化
53     public void fun3() throws ClassNotFoundException, SQLException {
54         Connection con = null;
55         Statement stmt = null;
56         ResultSet rst = null;
57         try{
58             Class.forName("com.mysql.jdbc.Driver");
59             String username = "root";
60             String password = "20010821";
61             String url = "jdbc:mysql://localhost:3306/test";
62             con = DriverManager.getConnection(url,username,password);
63             stmt = con.createStatement();
64             String sql = "SELECT * FROM departments";
65             rst = stmt.executeQuery(sql);
66             while (rst.next()){
67                 System.out.println(rst.getString(1)+":"+rst.getString(2));
68             }
69         }catch(Exception e){
70             throw new RuntimeException(e);
71         }finally{
72             if(rst!=null) rst.close();
73             if(stmt!=null) stmt.close();
74             if(con!=null) con.close();
75         }
76 
77     }
78     public static void main(String args[]) throws SQLException, ClassNotFoundException {
79         Demo2 demo2=new Demo2();
80         //demo2.fun1();
81         //demo2.fun2();
82         demo2.fun3();
83     }
84 }

Statement

上面我们提到了statement,statement中有两个重要的方法分别是:

int executeUpdate(String sql):执行更新操作,即执行insert,update,delete操作,其实这个语句也可以执行create table,alter table,drop table等语句,但我们很少用JDBC去执行他们

ResultSet executeQuery(String sql):执行查询操作,返回ResultSet

statement还有一个boolean execute()操作,这个方法可以用来执行增删改查,所有SQL操作,该方法返回的是一个boolean值,表示SQL语句是否有结果集。如果使用execute()方法,就要进行其它语句的配套使用,如果execute是一个更新语句,那么要配合 int getUpdateCount()来获取insert,update,delete影响的行数。如果是一个查询语句,要配合ResultSet getResultSet()语句来获取结果集


结果集的特性

当使用

三大特性:

是否可滚动,是否敏感,是否可更新

而这些特性则要由生成结果集的statement来决定

由con.createStatement()生成的statement,它生成的是一个不可滚动,不敏感,不可更新的结果集

由con.createStatement(int,int)生成的statement,它生成的结果集是否可滚动,是否敏感,由传入的两个参数决定

所谓滚动,指的是,如果不可滚动,则只能用next()移动光标。可滚动,可以使用多种方式,如first(),last(),previoue(),beforeFirst(),afterLast()...来移动光标

第一个参数:

ResultSet.TYPE_FORWARD_ONLY:不滚动结果集

ResultSet.TYPE_SCROLL_INSENSITIVE:滚动结果集,但结果集数据不会随着数据库而变化

ResultSet.TYPE_FORWARD_SENSITIVE:滚动结果集,但结果集数据不会随着数据库而变化!因为太耗费资源所以不会实现“敏感”,没有数据库会实现这个操作,是jdbc空想出来的参数

第二个参数:

CONCUR_READ_ONLY:结果集是只读的,不能通过修改结果集而反向影响数据库

CONCUR_READ_UPDATABLE:结果集是可更新的,对结果集的更新可以反向影响数据库

但是mysql数据库的所有结果集,都是可滚动的,没有不可滚动结果集


 PreparedStatement

它是statement的子接口

强大之处:

  防SQL攻击

  提高代码的可读性和可维护性

  提高效率

 PreparedStatement的使用方式

先来解释一下什么是SQL攻击

我们建立一个存储用户名和密码的数据库,然后我们使用jdbc来进行对用户的查询

 1 package Demo3;
 2 
 3 import java.sql.*;
 4 
 5 /**
 6  * PreparedStatement的用法
 7  * 防SQL攻击
 8  */
 9 public class Demo3 {
10     /**
11      * 使用username和password去查询数据库
12      * 若查询出结果集,说明正确,返回true
13      * 若查不出结果集,说明错误,返回false
14      */
15     public boolean login(String username,String password) throws ClassNotFoundException, SQLException {
16         /**
17          * 1.得到Connection
18          * 2.得到Statement
19          * 3.得到ResultSet
20          */
21         Class.forName("com.mysql.cj.jdbc.Driver");
22         String url="jdbc:mysql://localhost:3306/test";
23         Connection con=DriverManager.getConnection(url,"root","20010821");
24         Statement stmt=con.createStatement();
25         ResultSet rst=stmt.executeQuery("SELECT * FROM t_user WHERE username='"+username+"' AND password='"+password+"'");
26         if(rst.next()){
27             return true;
28         }else{
29             return false;
30         }
31     }
32     public static void main(String[] args) throws SQLException, ClassNotFoundException {
33         Demo3 demo3=new Demo3();
34         System.out.println(demo3.login("zhangsan","123"));
35     }
36 }

到这里都没有问题,可以正常查询。但是如果有人在登录的时候,把用户名写成了  a' or 'a' = 'a' 密码写成了   a' or 'a'='a

此时,正常情况下,用户名密码数据库中是没有这号人物的,可是在查询时竟然能返回true,原因就在于第25行的SQL语句上。

SELECT * FROM t_user WHERE username='"+username+"' AND password='"+password+"'

这时候,这条语句对应的SQL翻译出来是这样的:

SELECT * FROM t_user WHERE username='a' or 'a'='a' AND password='a' or 'a'='a'

最后这条SQL语句查询的就是所有表,自然,在我们的程序中,就会返回true,这就叫SQL攻击

接下来我们学习使用PreparedStatement的方法

  首先是如何得到PreparedStatement对象

    1.给出SQL模板

    2.调用Connection的PreparedStatement prepareStatement(String sql模板);

    3.调用PreparedStatement里面的   setXXX(int 问号所在位置, XXX 值);   方法,为?赋值

    4.调用executeQuery()或者executeUpdate()方法。注意:它的execute方法都没有参数,因为PreparedStatement就是为SQL模板而生的,且模板中的?在第三步中已经赋值,所以不再需要传入SQL语句了

 1 package Demo3;
 2 
 3 import java.sql.*;
 4 
 5 public class Demo3Improved {
 6     public boolean login(String username,String password) throws ClassNotFoundException, SQLException {
 7         /**
 8          * 1.得到Connection
 9          * 2.得到Statement
10          * 3.得到ResultSet
11          */
12         Class.forName("com.mysql.cj.jdbc.Driver");
13         String url="jdbc:mysql://localhost:3306/test";
14         Connection con= DriverManager.getConnection(url,"root","20010821");
15         /**
16          * 一.得到PreparedStatement
17          * 1.得到SQL模板,所有的参数用?替代
18          * 2.调用Connection中的prepareStatement方法,来得到PreparedStatement
19          */
20         String sql="SELECT * FROM t_user WHERE username =? AND password =?";
21         PreparedStatement prestmt=con.prepareStatement(sql);
22         /**
23          * 二.给参数赋值
24          */
25         prestmt.setString(1,username);
26         prestmt.setString(2,password);
27         /**
28          * 三.调用executeXXX函数
29          */
30         ResultSet rst=prestmt.executeQuery();
31         if(rst.next()){
32             return true;
33         }else{
34             return false;
35         }
36     }
37     public static void main(String[] args) throws SQLException, ClassNotFoundException {
38         Demo3Improved demo3=new Demo3Improved();
39         System.out.println(demo3.login("zhangsan","123"));
40     }
41 }

 预处理(预编译)的原理

  服务器的工作:

    1.校验SQL语句的语法

    2.编译:变成一个和函数类似的东西

    3.执行:调用

  PreparedStatement使用的前提:

    1.连接的数据库必须支持预处理

    2.每个PreparedStatement都与SQL语句模板绑定,先把SQL模板传递给服务器,服务器进行校验,编译。执行时只是把参数传递过去而已

    3.若二次执行,就不用再校验语法了,也不用再次编译,直接执行。这是优点之一!

  但是Mysql默认预处理关闭,要想打开mysql的预处理,需要在url后面加一些参数。

  我们的url本来的样子是:

  jdbc:mysql://localhost:3306/test(数据库名)

  现在我们改成:

  jdbc:mysql://localhost:3306/test?userServerPrepStmts=true&cachePrepStmts=true

  第一个参数的意思是让mysql驱动把SQL语句发送给数据库进行编译,用来达到预处理的目的,从而执行executeXXX()的时候只需要传入参数即可,不需要再编译函数了

  第二个参数的意思是缓存编译后函数的key,防止函数二次编译


 Jdbcutils类