JDBC 基础入门

由于我也是初学参考的是网上的或者是培训机构的资料所以可能会有错误的信息,仅供参考

一、什么是JDBC(Java Data Base Connectivity)?

java程序连接数据库,JDBC是由SUN公司提出的一组规范,这组规范主要由一组接口构成,主要作用就是访问数据库。

 

二、JDBC核心思想【思想重要】

   

 

三、核心API重点】

     

Java.sql.DriverManager[C]

工具类,帮助我们管理不同数据库厂商的驱动,我们只需要将驱动jar包交给它,他会为我们创建一个数据库连接对象。

Java.sql.Connection[I]

如果得到了一个connection对象,就意味着连接到了数据库。

Java.sql.Statement[I]

发送sql命令到数据库执行。

Java.sql.PreparedStatement[I]

statement功能一样,是statement的子接口。

Java.sql.ResultSet[I]

接收数据库返回的结果集(针对于DQL

 

四、使用JDBC进行开发

1.搭建开发环境

 创建一个java projectà引入驱动jar包到项目中

第一种方式:右键—>build path àconfigure build path à[Libraries]àaddExternal jar

第二种方式:右键—new folderàlibàjar放入文件夹add build path[建议使用]

 ==========================================================================================

==========================================================================================

首先这张图介绍了jdbc的作用 提供了一个接口,这样做 可以有两个作用 1.可以方便的切换数据库 只需要加载不同的驱动也就是jdbc的实现类

第二个作用是避免了 数据库厂商提供原码。

 

所以在这里我们需要获取3个对象

Connection  数据库连接

PreparedStatement/Statement 发送sql

ResultSet  结果集DQL

 

代码实现重要的6步 

 

        //1.加载驱动-->将实现类加载到虚拟机中
         Class.forName("oracle.jdbc.OracleDriver");
        //2.获取连接 url:统一资源标识符,互联网中获取某一资源的方式
         Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "hr", "hr");
        //3.书写sql语句
         String sql="insert into t_User(userid,username,ages) values(1000,'张翰',100)";
        //4.发送sql语句--创建一个发送sql语句的对象
         Statement stmt  = conn.createStatement();
         
         int count = stmt.executeUpdate(sql);
         System.out.println("受影响行数="+count);
        
        //5.关闭资源 先打开的后关闭
         if(stmt != null){
             stmt.close();
         }
         if(conn != null){
             conn.close();
         }

不知道小伙伴们看了这个代码是不是也和小编我一样充满了疑问,于是我就上网查找了一些资料了解一下

1.为什么只需要写这一个步骤就可以了,就完成了将驱动加载到虚拟机中 

        //1.加载驱动-->将实现类加载到虚拟机中
         Class.forName("oracle.jdbc.OracleDriver");

首先要先了解一下类加载 ,类加载是什么

类的信息: 父类是谁? 有什么属性?有什么方法? 有什么构造方法? 是否还实现了其他接口?

类加载: 当JVM第一次使用一个类时, 找到这个类对应的字节码文件, 通过IO流读入这个文件中的类的信息, 并保存起来. 一个类只会加载一次

类加载时机:

  • 第一次创建对象

  • 第一次调用类的静态方法, 或访问类的静态成员

  • 加载子类时, 会先加载父类

类加载的过程:

  • 如果需要, 先加载父类

  • 按代码顺序, 初始化静态属性, 或是运行静态初始代码块

类加载的产物:

类对象 : 储存了一个类的信息 Class类的对象

由于驱动本质上还是一个class,将驱动加载到内存和加载普通的class原理是一样的:使用Class.forName("driverName")。以下是将常用的数据库驱动加载到内存中的代码:

参考了https://blog.csdn.net/b_h_l/article/details/7555016

1:Class.forName在这里的作用是将驱动类加载到JVM中,实际上就是执行这个驱动类中的一段static静态块。因为Class.forName后该类的静态块会被执行,所以这么写。而实际上你直接将这个驱动类new对象实例化也可以达到这个效果。但这样不利于动态读取配置文件创建连接,因此一般都使用Class.forName的形式。

所有的jdbc驱动都是通过静态代码块来调用DriverManager工具类的registerDriver方法,来向DriverManager这个驱动管理器进行注册,但类文件在被类加载器加载时并不会马上执行static静态代码块,而是在必须要初始化时才执行(比如访问其静态变量、实例化等操作)

反射,可以动态获取一个类的方法、属性、构造方法等信息,可以动态创建类

所有Driver代码的实现类中都采用了 
    static {
        try {
            DriverManager.registerDriver(new ProxoolDriver());
        } catch (SQLException e) {
            System.out.println(e.toString());
        }
    }
这种方式进行注册,这样在Class.forName的时候就会执行上面的代码,也就想系统注册驱动程序,注册驱动程序就是他会组装DriverInfo后缓存到DriverManager中

 

 

解释一下每个意思

 //2.获取连接 url:统一资源标识符,互联网中获取某一资源的方式
        jdbc:oracle:thin:@localhost:1521:xe", "hr", "hr"

 这里有个博主写的很详细https://blog.csdn.net/u013046610/article/details/80928399

jdbc:oracle:thin:@localhost:1521:XE
jdbc:表示采用jdbc方式连接数据库  也就是一种协议
oracle:表示连接的是oracle数据库
thin:表示连接时采用thin模式(oracle中有两种模式)

jdbc:oralce:thin:是一个jni方式的命名

@表示地址
1521和XE表示端口和数据库名

@localhost:1521:orcl整个是一块
也就是说是这样[jdbc]:[oracle]:[thin]:[@192.168.3.98:1521:orcl]

 oracle的jdbc连接方式:oci和thin

 

    oci和thin是Oracle提供的两套Java访问Oracle数据库方式。

    thin是一种瘦客户端的连接方式,即采用这种连接方式不需要安装oracle客户端,只要求classpath中包含jdbc驱动的jar包就行。thin就是纯粹用Java写的ORACLE数据库访问接口。
oci是一种胖客户端的连接方式,即采用这种连接方式需要安装oracle客户端。oci是Oracle Call Interface的首字母缩写,是ORACLE公司提供了访问接口,就是使用Java来调用本机的Oracle客户端,然后再访问数据库,优点是速度 快,但是需要安装和配置数据库。

  
 它们分别是不同的驱动类别,oci是二类驱动, thin是四类驱动,但它们在功能上并无差异。

 重点:

oracle定义了自己应该接收什么类型的URL,自己能打开什么类型的URL连接(注意:这里acceptsURL(url)只会校验url是否符合协议,不会尝试连接判断url是否有效) 。connect(String url,Properties info)方法,创建Connection对象,用来和数据库的数据操作和交互,而Connection则是真正数据库操作的开始(在此方法中,没有规定是否要进行acceptsURL()进行校验)。

DriverManager 内部持有这些注册进来的驱动 Driver,由于这些驱动都是 java.sql.Driver 类型,那么怎样才能获得指定厂商的驱动Driver呢?答案就在于:java.sql.Driver接口规定了厂商实现该接口,并且定义自己的URL协议。厂商们实现的Driver接口通过acceptsURL(String url)来判断此url是否符合自己的协议(这段话是作者所说,实际不一定是通过acceptURL方法判断,但逻辑顺序是对的,即先判断是否符合协议格式,再试图创建Connection实例),如果符合自己的协议,则可以使用本驱动进行数据库连接操作,查询驱动程序是否认为它可以打开到给定 URL 的连接。

 

  对于驱动加载后,如何获取指定的驱动程序呢?这里,DriverManager的静态方法getDriver(String url)可以通过传递给的URL,返回可以打开此URL连接的Driver。
     比如,我想获取oracle的数据库驱动,只需要传递形如jdbc:oracle:thin:@<host>:<port>:<SID>或者jdbc:oracle:thin:@//<host>:<port>/ServiceName的参数给DriverManager.getDriver(String url)即可:

  Driver oracleDriver =DriverManager.getDriver("jdbc:oracle:thin:@<host>:<port>:<SID>");  

     实际上,DriverManager.getDriver(String url)方法是根据传递过来的URL,遍历它维护的驱动Driver,依次调用驱动的Driver的acceptsURL(url),如果返回acceptsURL(url)返回true,则返回对应的Driver:

 

   创建 Connection 连接对象,可以使用驱动Driver的 connect(url,props),也可以使用 DriverManager 提供的getConnection()方法,此方法通过url自动匹配对应的驱动Driver实例,然后调用对应的connect方法返回Connection对象实例。

 

DriverManager 作为 Driver 的管理器,它在第一次被使用的过程中(即在代码中第一次用到的时候),它会被加载到内存中,
然后执行其定义的static静态代码段,在静态代码段中,有一个 loadInitialDrivers() 静态方法,
用于加载配置在jdbc.drivers 系统属性内的驱动Driver,配置在jdbc.drivers 中的驱动driver将会首先被加载:
请记住:一定要在第一次使用DriverManager之前设置jdbc.drivers,因为DriverManager中的static静态代码段只会被执行一次!
这个jdbc.driver 就是
Class.forName("oracle.jdbc.OracleDriver"); 我们需要的驱动

 

 

Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe","hr","root");

我参考了这个博主的博客

 https://blog.csdn.net/yumenshizhongjingjie/article/details/81036357

 这里不做深入的研究毕竟时间有限。但是作为一个热爱编程的程序员探索之路不会停止,我先记住简单的在进行深入我

觉得效果会好一点。

 

DriverManager.getConnection一共有四个重载方法,前三个由public修饰,用来获取不同类型的参数,这三个getConnection实际相当于一个入口,他们最终都会return第四个私有化的getConnection方法,最终向第四个私有化方法的传入参数都是url,java.util.Properties,以及Reflection.getCallerClass(),这个方法是native的  其中Reflection.getCallerClass()是反射中的一个方法,这个方法用来返回他的调用类,也就说是哪个类调用了这个方法

在这里每个getConnection都是用CallerSensitive修饰的,调用getCallerClass应该是获取外面使用DriverManager.getConnection()的类的名称,即在class A中调用了DriverManager.getConnection(),则返回class A。                                                                                                        哇,真是惊呆我了还能有这种操作!

这里面有一篇关于类加载器的博客建议看一下

https://blog.csdn.net/briblue/article/details/54973413    

 

 Java语言系统自带有三个类加载器:

 Bootstrap ClassLoader 最顶层的加载类,主要加载核心类库

 Extention ClassLoader 扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。

Appclass Loader也称为SystemAppClass 加载当前应用的classpath的所有类。

 三个类加载器的加载顺序

  1. Bootstrap CLassloder
  2. Extention ClassLoader
  3. AppClassLoader

sun.misc.Launcher,它是一个java虚拟机的入口应用。

 每个类加载器都有一个父加载器,比如加载Test.class是由AppClassLoader完成,那么AppClassLoader也有一个父加载器,

    AppClassLoader的parent是ExtClassLoader,ExtClassLoader的parent是null。这符合我们之前编写的测试代码。

一个类加载器查找class和resource时,是通过“委托模式”进行的,它首先判断这个class是不是已经加载成功,如果没有的话它并不是自己进行查找,而是先通过父加载器,然后递归下去,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果没有找到,则一级一级返回,最后到达自身去查找这些对象。这种机制就叫做双亲委托。

 

一个AppClassLoader查找资源时,先看看缓存是否有,缓存有从缓存中获取,否则委托给父加载器。
递归,重复第1部的操作。
如果ExtClassLoader也没有加载过,则由Bootstrap ClassLoader出面,它首先查找缓存,如果没有找到的话,就去找自己的规定的路径下,也就是sun.mic.boot.class下面的路径。找到就返回,没有找到,让子加载器自己去找。
Bootstrap ClassLoader如果没有查找成功,则ExtClassLoader自己在java.ext.dirs路径中去查找,查找成功就返回,查找不成功,再向下让子加载器找。
ExtClassLoader查找不成功,AppClassLoader就自己查找,在java.class.path路径下查找。找到就返回。如果没有找到就让子类找,如果没有子类会怎么样?抛出各种异常。

总结:

  1. ClassLoader用来加载class文件的。
  2. 系统内置的ClassLoader通过双亲委托来加载指定路径下的class和资源。

==========================================================================================

===========================================================================================

 

  2.编码 :JDBC六部【重点】

      1)加载驱动

//1.加载驱动-->将实现类加载到虚拟机中

Class.forName("oracle.jdbc.OracleDriver");

      2)获取连接

     //2.获取连接 url:统一资源标识符,互联网中获取某一资源的方式  Connectionconn=DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "hr", "hr");

       3)书写sql语句

     String sql="insert into t_User(userid,username,ages) values(1000,'张翰',100)";

      4)创建发送sql语句对象

Statement stmt  = conn.createStatement();

  5)ResultSet 结果集处理

  6) 关闭资源—先打开的后关闭

if(stmt != null){

 stmt.close();

 }

 if(conn != null){

 conn.close();

 }

 

五、PreparedStatement[重点]

    1.PreparedStatement Statement的子接口,作用与Statement一样,都是用来发送sql语句。

    2.特点:使用PreparedStatement可以发送参数化sql(半成品sql);

    比如:

使用:

PreparedStatement Statement区别:

 1sql注入:一些非法分子或黑客,将一些特殊的字符通过字符串拼接的方式注入到系统原有的sql命令当中,改变sql原有的逻辑,从而威胁数据库数据安全,这种现象称之为sql注入。

 --使用PreparedStatement 的好处:

    1)使用pstmt可以避免sql注入

    2)可读性比较高,维护性也好。

 看到这里会不会很好奇到底PreparedStatement 的好处为什么

具体的内容请参考这个链接 https://blog.csdn.net/Marvel__Dead/article/details/69486947 我主要是看到那个这个博主写的那个吐槽太逗了然后就

点进去了,有意思


当客户发送一条SQL语句给服务器后,服务器总是需要校验SQL语句的语法格式是否正确,然后把SQL语句编译成可执行的函数,最后才是执行SQL语句。其中校验语法,和编译所花的时间可能比执行SQL语句花的时间还要多。
注意:可执行函数存储在MySQL服务器中,并且当前连接断开后,MySQL服务器会清除已经存储的可执行函数。
如果我们需要执行多次insert语句,但只是每次插入的值不同,MySQL服务器也是需要每次都去校验SQL语句的语法格式,以及编译,这就浪费了太多的时间。如果使用预编译功能,那么只对SQL语句进行一次语法校验和编译,所以效率要高。

 

注意: 防止sql注入攻击的实现是在PreparedStatement中实现的,和服务器无关。笔者在源码中看到,PreparedStatement对敏感字符已经转义过了。

总结:

1. PreparedStatement的预编译是数据库进行的编译后的函数key是缓存在PreparedStatement中的,编译后的函数是缓存在 数据库服务器中的。预编译前有检查sql语句语法是否正确的操作。只有数据库服务器支持预编译功能时,JDBC驱动才能够使用数据库的预编译功能,否则会报错。预编译在比较新的JDBC驱动版本中默认是关闭的,需要配置连接参数才能够打开。在已经配置好了数据库连接参数的情况下,Statement对于MySQL数据库是不会对编译后的函数进行缓存的,数据库不会缓存函数,Statement也不会缓存函数的key,所以多次执行相同的一条sql语句的时候,还是会先检查sql语句语法是否正确,然后编译sql语句成函数,最后执行函数。

 

2. 对于PreparedStatement在设置参数的时候会对参数进行转义处理

3. 因为PreparedStatement已经对sql模板进行了编译,并且存储了函数,所以PreparedStatement做的就是把参数进行转义后直接传入参数到数据库,然后让函数执行。这就是为什么PreparedStatement能够防止sql注入攻击的原因了。

4. PreparedStatement的预编译还有注意的问题,在数据库端存储的函数和在PreparedStatement中存储的key值,都是建立在数据库连接的基础上的如果当前数据库连接断开了,数据库端的函数会清空,建立在连接上的PreparedStatement里面的函数key也会被清空,各个连接之间的预编译都是互相独立的

 

 ResultSet 的是如何执行的      

      ResultSet:结果集,封装了使用JDBC进行查询的结果
      1.调用Statement对象的excuteQuery(sql)方法可以得到结果集
      2.ResultSet返回的实际上就是一张数据表,有一个指针
       指向数据表的第一样的前面,可以调用next()方法检测下一行是否有效,若有效则返回true
        ,并且指针下移,相当于迭代器对象的hasNext()和next()的结合体
      3.当指针对位到确定的一行时,可以通过调用getXxx(index)或者getXxx(columnName)
         获取每一列的值,例如:getInt(1),getString("name")
      4.ResultSet当然也需要进行关闭
扩展:

 1、最基本的ResultSet。

          ResultSet他起到的作用就是完成了查询结果的存储功能,而且只能读取一次,不能够来回的滚动读取。

2、可滚动的ResultSet类型。

        这个类型支持前后滚动取得纪录next()、previous(),回到第一行first(),同时还支持要去的ResultSet中的第几行absolute(int n),以及移动到相对当前行的     第几行relative(int n),要实现这样的ResultSet在创建Statement时用如下的方法。 

3、可更新的ResultSet 
  这样的ResultSet对象可以完成对数据库中表的修改,但是我知道ResultSet只是相当于数据库中表的视图,所以并不时所有的ResultSet只要设置了可更新就能够完成更新的,能够完成更新的ResultSet的SQL语句必须要具备如下的属性: 

  a、只引用了单个表。 

  b、不含有join或者group by子句。 

  c、那些列中要包含主关键字。 

 

4、可保持的ResultSet 

  正常情况下如果使用Statement执行完一个查询,又去执行另一个查询时这时候第一个查询的结果集就会被关闭,也就是说,所有的Statement的查询对应的结果集是一个,如果调用Connection的commit()方法也会关闭结果集。可保持性就是指当ResultSet的结果被提交时,是被关闭还是不被关闭。JDBC2.0和1.0提供的都是提交后ResultSet就会被关闭。不过在JDBC3.0中,我们可以通过设置JDBC Connection接口中的ResultSet是否关闭。要完成这样的ResultSet的对象的创建,要使用的Statement的创建要具有三个参数,这个Statement的创建方式也就是,我所说的Statement的第三种创建方式。

 

 

 有参考这个博主的文章  https://www.cnblogs.com/ysw-go/p/5453194.html

    https://www.iteye.com/blog/bioubiou-1775129

 

 

 

 

 

posted @ 2019-08-20 13:32  纳兰容若♫  阅读(186)  评论(0编辑  收藏  举报