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,防止函数二次编译