当前位置:Gxlcms > 数据库问题 > jdbc详解

jdbc详解

时间:2021-07-01 10:21:17 帮助过:10人阅读

Java Data Base Connectivity),Java数据库连接。是一种用于执行SQL语句的Java API简单来说就是通过Java去操作数据库,数据库发送SQL语句,执行增删改查等操作。
  • Sun公司Java技术开发人员很早就意识到了Java在数据库应用方面的巨大潜力。从1995开始,他们就致力扩展Java标准类库,是之可以应用SQL访问数据库。他们最初希望,通过扩展Java,人就可以用“纯”Java语言与任何数据库进行通信。但是他们很快就发现这是一项无法完成的任务:因为市场上存在有很多不同的数据库,而他们使用的协议也各不相同每一个数据库公司都希望Sun公司可以使用他们自己的协议。但是显然是不现实的。
  • 所有的数据库供应商和工具开发商都认为,如果SUN公司能够为SQL访问提供一套纯”Java API,同时提供一个驱动管理器,以允许第三方驱动程序可以连接到指定的数据库,那它们会显得非常有用。这样,数据库供应商就可以提供自己的驱动程序,并插入到驱动管理器中。另外需要一套简单的机会,以使得第三方驱动程序可以向驱动管理器注册。因此,Sun公司制定了两套接口。应用程序开发者使用JDBC API,而数据库供应商和工具开发商使用JDBC驱动API
  • 这种接口组织方式遵循了微软公司非常成功的ODBC模式,ODBCC语言访问数据库提供了一套编程接口。JDBC和ODBC都基于同一个思想:根据API编写的程序都可以与驱动管理器进行通信,而驱动管理器则通过驱动程序实际数据库进行通信。
  •  技术分享

     

    • 这样做的好处就是,我们在使用JDBC的时候只需要导入相应厂商提供的驱动包然后直接调用JDBC API即可。实际使用JDBC不同的数据库除了SQL语句以及一些配置有区别以为,大部分代码都是相同的,也就是说我们使用JDBC操作MySQL和操作Oracle代码上没有什么区别的。

    核心

    java.sql.DriverManager

    n 用来管理JDBC驱动和获取Connection对象的类

    n static Connection getConnection(String url, String user, String password)  

    u 建立一个到指定数据库的连接,并返回一个Connection对象

    java.sql.Connection

    n 表示和指定数据库建立的数据库连接对象

    n Statement createStatement()  

    u 获取一个Statement对象,用来执行没有占位符的SQL语句

    n void close()  

    u 关闭当前的数据库连接,释放它所创建的JDBC资源

    java.sql.Statement

    n SQL执行器,用于执行一个静态的SQL语句返回它产生的结果

    u ResultSet executeQuery(String sql)  

    l 执行给定的SQL语句,并返回一个用于查询结果的ResultSet对象

    u int executeUpdate(String sql)  

    l 执行给定的SQL语句用于更新数据库数据返回发生变化数据行数

    u void close()  

    l 关闭Statement对象以及它对应的结果集。

    java.sql.ResultSet

    n 代表通过查询数据库获取到的结果集

    u boolean next()  

    l 将结果集的当前向前移动一行。如果以及到达最后一行的后面,则返回false

    u Xxx getXxx(int 列)

    u Xxx getXxx(int 列名)

    l Xxx指数据类型,例如int、doubleStringDate

    l 用给定的列号或列名返回该列的值,并将值转换为指定类型。

    u void close()

    l 关闭当前的结果集

    DEMO

    • 下边我们来开始编写我们的第一个JDBC程序
    • 导入数据库厂商提供的驱动jar包,我们这里使用MySQL,所以导入MySQL驱动

    1. 导入驱动jar包

    n mysql-connector-java-5.1.7-bin.jar

    2. 注册数据库驱动

    • 在程序中注册导入的驱动程序,每一个驱动都是java.sql.Driver实现类,而每个实现类中都会有一个静态代码,具体如下

    static {

    try {

    java.sql.DriverManager.registerDriver(new Driver());

    } catch (SQLException E) {

    throw new RuntimeException("Can‘t register driver!");

    }

    }

    n 在驱动程序的静态代码中,会自动将驱动程序通过DriverManager进行注册,所以我们要做的只是将驱动类加载进JDK中,静态代码块将会自动执行。

    //加载数据库驱动

    Class.forName("com.mysql.jdbc.Driver");

    3. 获取数据库连接

    • 数据库连接也就是我们上边所说的Connection对象,获取数据库连接只需要调用DriverManagergetConnection()方法即可,但是这个方法需要传递三个String类型参数:

    n String url

    数据库URL

    l 在连接数据库时,我们必须使用各种与数据库类型相关的参数,例如主机名、端口号和数据库名

    l 语法jdbc:协议:厂商内容

    l 例如

    n MySQL

    u jdbc:mysql://主机地址:端口号/库名

    u jdbc:mysql://localhost:3306/test

    n Oracle

    u jdbc:oracle:thin:@主机地址:端口号:数据库

    u jdbc:oracle:thin:@localhost:1521:atguigu

    n SQLServer

    u jdbc:microsoft:sqlserver//主机地址:端口; DatabaseName=库

    u jdbc:microsoft:sqlserver//localhost:1433; DatabaseName=sid

    n String user

    u 连接数据库使用的用户的用户名我们这里MySQL的用户名就是root

    n String password

    u 连接数据库使用的用户的密码,我们这里同样也是root

    //设置url地址

    String url = "jdbc:mysql://localhost:3306/test";

    //设置用户名

    String username = "root";

    //设置用户密码

    String password = "root";

    //获取数据库连接

    Connection conn = DriverManager.getConnection(url,username,password);

    4. 获取Statement对象

    • 获取Statement对象用来执行SQL语句,通过Connection对象的相关方法即可。

    //获取Statement对象

    Statement stat = conn.createStatement();

    5. 执行SQL语句

    • SQL语句可以通过Statement对象的executeUpdate()或executeQuery()方法即可

    n executeUpdate()用于执行增、删、相关的方法,它可以返回一个int值,表示执行SQL语句后影响到的行数

    //创建SQL语句

    String sql = "INSERT INTO tb_user VALUES(null,‘sunwukong‘)";

    //执行SQL语句

    int count = stat.executeUpdate(sql);

    n executeQuery()用于执行查询的方法,该方法会返回一个ResultSet对象,对象中封装了查询到的数据。

    //创建SQL语句

    String sql = "SELECT * FROM tb_user";

    //执行SQL语句查询信息

    ResultSet rs = stat.executeQuery(sql);

    6. 读取ResultSet中的数据

    • 通过JDBC做查询操作时,会返回一个ResultSet对象,我们通过数据库查询到的数据都封装在该类实例中。
    • ResultSet中实际上就是一个二维的表格,结构和我们从数据库中查询到的表格一样,在ResultSet中维护着一个光标,这个光标会指向表格的某一行,当光指向一行时,我们就可以读取当前行的数据。
    • ResultSet光标默认是指向表格外的,也就是最初的ResultSet的光标,并没有指向某一行,我们需要调用next()方法使光标下移。next()方法返回一个boolean值,如果返回值为true则代表当前有可读数据,如果为false则表示数据已经全部读完了。
    • ResultSet中给我提供了一系列的getXxx()方法用于读取表中的数据,我们使用ResultSet时,迭代器类似:

    //创建一个while循环用于读取ResultSet中的数据

    while(rs.next()){

    //获取第一列的数据

    int id = rs.getInt(1);

    //获取第二列的数据

    String um = rs.getString(2);

    //输出数据

    System.out.println(id+"--"+um);

    }

    n 上边的代码我是通过列的序号来获取列信息的getInt(1)获取第一列的数据并返回一个int值,getString(2)获取第二列的信息并返回一个String注意这里的列是从1开始的,而不是0

    n 同样我们也可以通过列名来获取指定属性,和上边的用法类似,只不过不在传int行的列序号,而是传String型的列名。如getInt(“id”) 获取int型id的,getString(“username”) 获取String型的username值。注意这里idusername都是表的列名,也就是表头中的内容,如果没有使用别名则以中的名称为准,如果使用了别名则以别名为准。

    7. 释放资源

    • 使用完ResultSetStatementConnection对象时,应立即调用close方法这些对象都使用了规模较大的数据结构,所以我们不应该等待垃圾回收期来处理它们。
    • Statement对象close()方法会自动关闭它已经打开的结果集ResultSetConnection对象的close()方法会自动关闭和它管理的Statement对象所以当我们关闭Connection对象时,StatementResultSet对象也会自动关闭,但是如果关闭时StatementResultSet还没有使用完毕的话,那么就会出问题了,所以我们在关闭这些对象时,一定要注意关闭顺序:先关ResultSetStatement、最后关Connection。

    //关闭ResultSet

    rs.close();

    //关闭Statement

    stat.close();

    //关闭Connection

    conn.close();

    8. 标准代码

    //设置四个参数

    String username = "root";

    String password = "root";

    String driverClass = "com.mysql.jdbc.Driver";

    String url = "jdbc:mysql://localhost:3306/test";

     

    //加载驱动

    Class.forName(driverClass);

     

    //分别声明Connection Statement ResultSet三个变量

    Connection conn = null;

    Statement stat = null;

    ResultSet rs = null;

     

    try {

    //获取Connection对象

    conn = DriverManager.getConnection(url, username, password);

     

    //获取Statement对象

    stat = conn.createStatement();

     

    //执行sql获取ResultSet对象

    rs = stat.executeQuery("");

     

    //读取rs对象中的内容

     

    } catch (SQLException e) {

    e.printStackTrace();

    } finally{

    //关闭rs

    if(rs!=null){

      try {

      rs.close();

      } catch (SQLException e) {

      // TODO Auto-generated catch block

      e.printStackTrace();

      }

    }

     

    //关闭stat

    if(stat != null){

    try {

    stat.close();

    } catch (SQLException e) {

    // TODO Auto-generated catch block

    e.printStackTrace();

    }

    }

     

    //关闭conn

    if(conn != null){

    try {

    conn.close();

    } catch (SQLException e) {

    e.printStackTrace();

    }

    }

    }

    PreparedStatement

    1. SQL注入攻击

    • SQL注入攻击是黑客对数据库进行攻击的一个常用手段之一所谓SQL注入指的就是通过向服务器发送一些特殊的参数,从而使原本的SQL语句语义发生变化从而骗过程序一些验证
    • 典型的SQL攻击就要说我们登录验证的功能,我们在做登录时都需要这样一条SQL语句:SELECT * FROM tb_user WHERE username=‘参数1‘ AND PASSWORD=‘参数2‘,这条SQL语句实际上就是根据用户名和密码查询一个用户信息,如果用户名和密码正确则可以查询到用户,则认定登录成功。用户名密码错误,则不能查询到用户,则认定登录失败。
    • 这里的参数1参数2实际上都是需要用户来传递进来的,但是当用户传过来的是一一些恶意参数可能就会突破我们的验证
    • 比如:用户名传一个 a’ OR ‘1’=’1 密码传一个 a’ OR ‘1’=’1 ,这个用户名和密码显然是不正确的,但是如果将这两个参数设置进SQL语句中,我们的SQL语句就会变成这个样子:SELECT * FROM tb_user WHERE username=‘a‘ OR ‘1‘=‘1‘ AND PASSWORD=‘a‘ OR ‘1‘=‘1‘。显然这条SQL语句无论怎么查都可以查到用户,于是即使用户不知道用户和密码也可以登录系统,这样可能会给我们的用户或者网站带来不可预估的损失。
    • 处理SQL注入有多种方式:

    2. 防止SQL注入

    n 对用户输入进行校验,检查是否包含非法字符

    n 分两次检查用户名和密码,先通过SQL查询用户对象,检查密码是否正确

    n 使用PreparedStatement

    3. PreparedStatement

    • PreparedStatement即预处理SQL语句
    • PreparedStatement已经实现Statement接口并且和Statement有两方面的不同:

    n PreparedStatement中已经包含了编译好的SQL语句在这条SQL语句中可以设置一个过多个参数,这些参数使用?作为占位符,我们只需要在SQL语句执行之前使用对应的参数替换占位符即可。PreparedStatement中为我们提供了很多的setXxx()方法用来替换占位符。

    n 由于PreparedStatement中的SQL语句已经预编译过了,其执行效率要高于Statement

    n PreparedStatement继承Statement的所有功能,但是在这个基础上还添加了多个setXxx()方法用来设置占位符同时个方法execute()、executeUpdate()executeQuery()都已经添加了不需要参数的版本,而之前需要参数的方法我们不应该在调用。

    • 使用

    n 获取PreparedStatement

    u Connection.prepareStatement(String sql);

    PreparedStatement pstmt = conn.prepareStatement("SELECt * FROM tb_user WHERE username=? AND password=?");

    u 我们可以通过Connection对象的prepareStatement方法获取一个PreparedStatement对象,调用这个方法我们必须要传一个SQL语句,这个条SQL语句就是我们的预编的SQL语句,我们会发现SQL语句中出现两个?,这两个问号就是我们所说的占位符,这些占位符在SQL语句执行之前必须要被替换成有效的值。

    n 替换占位符:

    u 在PreparedStatement中提供了很多的setXxx()方法用来替换占位符。所谓的Xxx就是指我们Java中的数据类型,如int、Stringlongdouble等。

    u 例如setString(占位符位置 , 占位符的值)setInt(占位符位置 , 占位符的值)setObject(占位符位置 , 占位符的值)

    pstmt.setString(1, username);

    pstmt.setString(2, password); 

    n 执行SQL

    u 执行SQL也很简单,上边也说了PreparedStatement提供类三个无参的execute()、executeUpdate()、executeQuery()三个方法,根据要操作的SQL的数据类型不同执行相应的方法即可。

    rs = pstmt.executeQuery();

    • 注意:通常我们在使用JDBC是不会使用Statement而是使用PreparedStatement,所以时刻注意我们以后应该使用的PreparedStatement
    • 所谓数据就是占用空间比较大的数据,比如一个比较长的文本,一个视频文件,一个MP3音乐文件这些都是我们所说的大数据
    • 大数据我们SQL中也称为LOB(large object)大对象,一般标准数据库提供两种类型存储字节型数据的BLOB,存储字符型大数据的CLOB
    • 如下表示SQL标准提供的LOB类型:

    大数据

    类型

    长度

    tinyblob

    28--1B256B

    blob

    216-1B64K

    mediumblob

    224-1B16M

    longblob

    232-1B4G

    tinyclob

    28--1B256B

    clob

    216-1B64K

    mediumclob

    224-1B16M

    longclob

    232-1B4G

    • 但是MySQL中的类型稍有不同它并没有提供tinyclob、clobmediumcloblongclob四种类型,而是使用的相关的text代替

    类型

    长度

    tinytext

    28--1B256B

    text

    216-1B64K

    mediumtext

    224-1B16M

    longtext

    232-1B4G

     

    批处理

    • 批处理指的是批量的将部分数据同时插入进数据库。想想一些这样一个场景,我们如20SQL语句需要执行,那么在我们执行,是20语句一个一个执行的快,还是将20语句一起执行来的快?显然一起执行要更快一些,那批处理也就是多条SQL语句一起执行。
    • 批处理主要是来对增删改来说的,查询时我们不需要进行批处理操作。
    • 我们可以使用Statement或者PreparedStatement来进行批量操作。
    • 我们主要介绍PreparedStatement使用批处理的方式使用PreparedStatement做批处理主要有两个方法:

    n addBatch()

    u 将一组已经设置好的参数,添加到批处理中

    n executeBatch()

    u 执行批处理

    • 示例

    //获取connection

    conn = JDBCUtils.getConnection();

    //设置sql语句

    String sql = "INSERT INTO customer (name,age,gender) VALUES(?,?,?)";

    //获取PreparedStatement对象

    pstmt = conn.prepareStatement(sql);

    //使用for循环批量设置参数

    for (int i = 0; i < 10000; i++) {

    pstmt.setString(1, NameUtils.getName());

    pstmt.setInt(2, NameUtils.getAge());

    pstmt.setString(3, NameUtils.getGender());

    //每设置完一组参数,将参数添加到批量操作中

    pstmt.addBatch();

    }

    //执行批量操作

    pstmt.executeBatch();

    • 注意MySQL默认是不支持批量处理的,需要在URL地址加上如下参数:

    n rewriteBatchedStatements=true

    事务

    什么是事务

    • 数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。
    • 举例来说当我们做一个银行转账的业务时,A账户向B账户转账1000钱,这个就是一个事务。A向B1000如果我们简单来考虑的话,首先需要从A的账户中减掉1000,然后在向B的账户加上1000。也就是我们一个业务需要操作两次数据库,那么这两次操作必须同时成功,或者同时失败,也就是说要不然A账户减了1000同时B账户增加了1000,要不就是两个账户没有发生变化,像这种操作我们称为一次事务
    • 原子性(atomicity

    事务的四个特性ACID

    n 一个事务是一个不可分割的工作单位,事务中包括的诸操作要么都做,要么都不做。

    • 一致性(consistency

    n 事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

    • 隔离性(isolation)。

    n 一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

    • 持久性(durability)。

    持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

    MySQL中操作事务

    • MySQL的事务主要是三个语句:

    n START TRANSACTION

    u 开启事务,开启事务后我们在MySQL中输出的每个语句将不会自动提交修改

    n COMMIT

    u 提交事务,提交刚才对数据库做的修改操作

    n ROLLBACK

    u 回滚事务,取消刚才对数据库做的所有操作

    JDBC中操作事务

    • JDBC事务的操作主要通过Connection的三个方法来进行的

    n conn.setAutoCommit(false)

    u 设置事务不自动提交,开启事务

    n conn.commit();

    u 提交事务

    n conn.rollback();

    u 回滚事务

    数据库连接池

    • 之前我们的学习使用数据库连接对象Connection都是通过DriverManager获取到的,也就是每次获取到的都是一个新的对象。数据库连接对象使用完毕以后我们需要关闭数据库连接,这时Connection对象就没有用,会等待被GC所回收。
    • 样反复创建新的连接,关闭连接,回收连接,创建新的连接。这样来回的创建连接释放连接系统的资源的销毁很大的。
    • 所以为了解决这个问题,我们提出了一个数据库连接池的概念所谓数据库连接池实际上就是一个集合,这个集合中保存的就是我们的Connection对象当我们需要使用数据库连接时,直接从集合中获取,而不用再创建的对象,等我们使用完了再将对象放回到集合中,供以后使用,这样实际上就使我们Connection对象达到了复用的目的。
    • 数据库连接池的常见设置:

    n 初始连接数量

    n 最小空闲连接数

    n 每次增加连接数

    n 最大空闲连接数

    n 最大连接数

    n 最大等待时间

    • javax.sql.DataSource

    n JDBC为我们提供了DataSource接口,所有的数据库连接池都需要实现该接口,有了该接口以后,我们在获取数据库连接就不需要使用DriverManager了。

    n Connection getConnection()

    u 从数据库连接池中获取一个连接

    DBCP

    • DBCPApache出品的一款数据库连接池
    • 使用DBCP需要导入两个jar包:

    n commons-dbcp-1.4.jar

    n commons-pool-1.5.5.jar

    • DBCP的核心类为:

    n BasicDataSource

    n BasicDataSourceFactory

    • 示例代码:

    //创建连接池对象

    BasicDataSource dataSource = new BasicDataSource();

     

    //设置连接信息

    dataSource.setUrl("jdbc:mysql://localhost:3306/test");

    dataSource.setUsername("root");

    dataSource.setPassword("root");

    dataSource.setDriverClassName("com.mysql.jdbc.Driver");

     

    //指定初始化连接的个数

    dataSource.setInitialSize(10);

    //指定最大的连接数,数据连接池的容量

    dataSource.setMaxActive(50);

    //指定连接池中的最小连接数

    dataSource.setMinIdle(5);

    //设置最大等待时间

    dataSource.setMaxWait(1000);

     

    //获取数据库连接

    Connection connection = dataSource.getConnection();

    • 配置信息

    n initialSize

    u 初始化连接的个数,默认值为0

    n maxActive

    u 连接池中的最大连接数默认值为8若设置为负数,表示无限制

    n maxIdle

    u 最大空闲连接数,默认值为8若设置为负数,表示无限制

    n minIdle

    u 最小空闲连接数,默认值为0

    n maxWait

    u 最大等待时间,默认为-1,无限等待

    n connectionProperties

    u 连接属性,指定url地址后边的参数

    n defaultAutoCommit

    u 是否自动提交事务,默认为true

    C3P0

    • C3P0一款开源的免费的数据库连接池
    • 相比于DBCP来说,C3P0更加的稳定
    • 使用C3P0需要导入一个jar包:

    n c3p0-0.9.1.2.jar

    • 核心类:

    n ComboPooledDataSource

    • 示例代码:

    //获取连接池对象

    ComboPooledDataSource dataSource = new ComboPooledDataSource();

     

    //设置连接信息

    dataSource.setDriverClass("com.mysql.jdbc.Driver");

    dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");

    dataSource.setUser("root");

    dataSource.setPassword("root");

     

    //设置连接池信息

    dataSource.setAcquireIncrement(5);

    dataSource.setInitialPoolSize(10);

    dataSource.setMinPoolSize(2);

    dataSource.setMaxPoolSize(50);

     

    //获取数据库连接

    Connection connection = dataSource.getConnection();

     

    • 上边这种方式是直接在程序中编码的方式,但是在实际开发中很少使用这种硬编码的方式,而是使用配置文件的方式来配置这些信息。
    • c3p0使用xml作为配置文件

    文件的名称必须为c3p0-config.xml

    n 文件必须放到项目的类路径下

    n 格式

    <?xml version="1.0" encoding="UTF-8"?>

    <c3p0-config>

    <default-config>

    <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>

    <property name="driverClass">com.mysql.jdbc.Driver</property>

    <property name="user">root</property>

    <property name="password">root</property>

    <!-- 最小的连接数 -->

    <property name="minPoolSize">5</property>

    <!-- 最大的连接数 -->

    <property name="maxPoolSize">30</property>

    <!-- 初始化的连接数 -->

    <property name="initialPoolSize">10</property>

    <!-- 需要时一次性创建的连接数 -->

    <property name="acquireIncrement">5</property>

    <!-- 缓存多少个Statement对象 -->

    <property name="maxStatements">15</property>

    </default-config>

    <named-config name="oracle-config">

    <property name="jdbcUrl">jdbc:oracle:thin:@localhost:1521:orcl</property>

    <property name="driverClass">oracle.jdbc.driver.OracleDriver</property>

    <property name="user">orcl</property>

    <property name="password">orcl</property>

     

    <!-- 最小的连接数 -->

    <property name="minPoolSize">5</property>

    <!-- 最大的连接数 -->

    <property name="maxPoolSize">30</property>

    <!-- 初始化的连接数 <

    人气教程排行