简介
刚开始介绍了mysql基本语句,但是你会觉得好像不会知道怎么用,它的用途在什么地方,所以为了提高兴趣今天我们来介绍一下JDBC,以后会和MySQL一起更新。
JDBC,到底jdbc是什么东西呢?
JDBC(Java Data Base Connectivity,java数据库连接),是由一些接口和类构成的API。是J2SE的一部分,由java.sql,javax.sql包组成。
概述
JDBC是JAVA与数据的连接。因为ODBC是完全用C语言编写的,而JAVA中实现与C语言程序的通信是比较困难的,因此就产生了由JAVA语言编写的用于JAVA程序与数据库连接的接口技术。JDBC与具体的某种数据库连接,是通过由数据库厂商提供的驱动来作为中间桥梁实现的。在JDBC API类库一般在java.sql包中,它包含了用于实现与数据库连接的其它功能的类,包括与数据库建立连接、传送查询和接受查询结果。
建立数据库
为了演示JDBC我们先来创建一个数据库,为下面的做铺垫,之前装的数据库客户端是英文版的,今天我们使用的是版本较低的中文yog客户端,这样看起来比较方便。
下载驱动
既然java和数据库建立连接的桥梁是驱动,那当然首先我们得有驱动,我们今天说的的mysql,所以先去mysql官网下载驱动然后解压,你不想去下载的话今天的文件可以关注公众号 代码黑洞 后台获取一下,里面这些都有了。官网下载地址:https://dev.mysql.com/downloads/connector/j/
下载好之后我们要去把这个jar包添加到我们创建的项目的Build Path中,如下图。
JDBC实例
实现jdbc有这么五个步骤:
1.注册驱动
2.创建连接
3.创建语句
4.执行语句
5.处理结果
6.关闭资源
我们一个一个来看。
1.注册驱动
注册驱动 有三种方式:
方式一:
/*DriverManager中的registerDriver(new Drivers());参数传的就是一个Drivers
* 可以注册很多驱动,比如SQL sever,MySQL,Oracle等
* 通过查看源码我们可以只知道这个Drivers的存储方式其实是一个vector
* 在程序运行时给定了url就会在这个vector里进行比对看能不能建立连接
* 如果可以就返回结果,如果把vector遍历完了都不能建立连接那就会报错
*
* 使用这种方式去注册驱动会造成DriverManager中产生两个一样的
* 驱动,并会对具体的驱动类产生依赖。不利于移植,和扩展。
*/
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
方式一:
/*
* 使用键值对的方式registerDriver里边有一个loadInitialDrivers方法 * 会去找通过键值对注册的驱动信息,但是分割方式是以:而不是=的方式
* 所以用这个键值对的方式去注册很多个驱动的时候中间使用:分割的
* 如:System.setProperty("jdbc.drivers", "com.mysql.jdbc.Driver:com.oracle.jdbc.Driver");
* 就注册了mysql和oracle的两个驱动
* 虽然不会对具体的驱动类产生依赖;但注册不太方便,所以很少使用。
*/
System.setProperty("jdbc.drivers", "com.mysql.jdbc.Driver");
方式三:
/*
* 通过这种Class.forName的方式去找这个文件并装载到虚拟机中来
* 即Java反射机制,会根据这个名称去找编译好的.class文件
* com.mysql.jdbc这是包名,这个跟导不导包是没有关系的
* 因为它只是一个字符串,这个根据这个classPath只是找到了包,
* 包这是个文件夹而并不会接着往包里边去找所以要写上包名
* 告诉虚拟机去这个路径下找然后装载到jvm虚拟机里来
* 所以异常就是ClassNotFoundException,class文件没有找到异常
* 推荐这种方式,不会对具体的驱动类产生依赖。
* 根据Java反射的特性就可以知道用反射是极大的提高了扩展性的
*/
Class.forName("com.mysql.jdbc.Driver");
2.建立连接
/*
* 2.建立连接
* Connection conn = DriverManager.getConnection(url, user, password);
* 参数是:url,user,password
* 为什么是url呢因为一半请求的都是网络主机
* 所以要指定网络主机数据库的url然后带着用户名和密码去
* 建立连接,告诉服务器你要连接哪个用户的数据库
* 如果没有密码那就是password为空字符
*
* url格式:(这不需要去记百度都能找到)
* JDBC:子协议:子名称//主机名:端口/数据库名?属性名=属性值&…
* User,password可以用“属性名=属性值”方式告诉数据库;
* 其他参数如:useUnicode=true&characterEncoding=GBK
* 这个其他参数就是指定解码格式,用来解析返回来的数据
* 如果返回回来的是utf-8编码方式的数据,你用GBK的方式去
* 解析反馈回来的数据,那就会出现乱码的情况
* 所以如果节码方式不指定,虚拟机程序时会给出警告
*/
String url = "jdbc:mysql://localhost:3306/jdbc";
String user = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, user, password);
3.创建语句
Statement 对象
一旦我们获得了数据库的连接,我们就可以和数据库进行交互。JDBC 的 Statement,CallableStatement 和 PreparedStatement 接口定义的方法和属性,可以让你发送 SQL 命令或 PL/SQL 命令到数据库,并从你的数据库接收数据。
在数据库中,它们还定义了帮助 Java 和 SQL 数据类型之间转换数据差异的方法。
下表提供了每个接口的用途概要,根据实际目的决定使用哪个接口。
接口推荐使用
Statement可以正常访问数据库,适用于运行静态 SQL 语句。 Statement 接口不接受参数。
PreparedStatement计划多次使用 SQL 语句, PreparedStatement 接口运行时接受输入的参数。
CallableStatement适用于当你要访问数据库存储过程的时候, CallableStatement 接口运行时也接受输入的参数。
我们先演示的是Statement
Statement st = conn.createStatement();
4.执行数据库语句
当你创建了一个 Statement 对象之后,你可以用它的三个执行方法的任一方法来执行 SQL 语句。
boolean execute(String SQL) : 如果 ResultSet 对象可以被检索,则返回的布尔值为 true ,否则返回 false 。当你需要使用真正的动态 SQL 时,可以使用这个方法来执行 SQL DDL 语句。
int executeUpdate(String SQL) : 返回执行 SQL 语句影响的行的数目。使用该方法来执行 SQL 语句,是希望得到一些受影响的行的数目,例如,INSERT,UPDATE 或 DELETE 语句。
ResultSet executeQuery(String SQL) : 返回一个 ResultSet 对象。当你希望得到一个结果集时使用该方法,就像你使用一个 SELECT 语句。
我们就用ResultSet来执行数据库语句:
ResultSet rs = st.executeQuery("select * from user");
5.处理结果
/*
* 5.处理结果
* 学过集合框架都知道迭代器的使用,比如:iterator所以这个rs.next()
* 就是去查找下一行数据。按行遍历的所以我们只需要按字段去拿
* While(rs.next()){
* rs.getString(“col_name”);//col_name字段(列名)
* rs.getInt(“col_name”);
* ....
* }
*/
while (rs.next()) {
//这里时也是去拿字段只不过更加能体现面向对象的思想,
//每一个字段都是一个对象
//和getString,getInt等,是一样的
System.out.println(rs.getObject(1) + "\t" + rs.getObject(2) + "\t"+ rs.getObject(3) + "\t" + rs.getObject(4));
}
6.释放资源
/*
* 6.释放资源
* 释放ResultSet, Statement,Connection.
* 数据库连接(Connection)是非常稀有的资源,用完后必须马上释放
* 如果Connection不能及时正确的关闭将导致系统宕机。
* Connection的使用原则是尽量晚的去建立连接,尽量早的释放。
* 也就是尽量减少占用数据库的时间,减轻数据库的压力
*/
rs.close();
st.close();
conn.close();
改进
上述所示例子有很多问题需要处理,不够严谨。我们就需来对它一步一步的优化。
首先为了提高扩展性,我们可以把建立连接时Connection参数定义为private static final
我们发现finally关闭资源的时候处理异常太麻烦,每次都要嵌套些这么多就很烦,所以还是需要优化。于是我们打算写一个名叫JdbcUtils工具类来完成这件事,同样的先把参数定义为全局常量
把构造方法私有化,避免产生对象,我们写这个工具类是直接使用的不需要创建对象
/*
*注册驱动。将其声明为static代码块即静态代码块,是在类中独立于类成员的static语句块当JVM加载类时就会执行
*它不依赖类特定的实例,被类的所有实例共享。
*
* 在这里就非常有用了,使用jdbc第一个步骤就是要先注册驱动
* 把注册驱动写成静态代码块,当类加载时就会被执行达到注册的目的无需单独写成在方法再去调用,那样麻烦!!
*/
/*
* 建立连接,还是一样,把参数抽取出来,定义为全局变量,提高其扩展性,比如当你改了
* 数据库密码的时候,就不用在去修改源代码。
* 当然还有更好的方法是用Properties集合把这些信息保存到配置文件中去,大大提高了扩展性
* 当信息改动只需要更改配置文件,而不需要去改源代码。这里就不再演示Properties。
*/
/*
*将关闭资源单独分装到自己写的工具类中,提高复用性,因为关闭资源需要处理的异常写起来很麻烦
*而又是必须要处理,所以单独封装,可以节省时间和空间
*/
当我们需要的去连接到数据库的时候,就可以很方便的进行,如下所示:
测试改进效果
这样代码就会简洁很多。运行结果如下,这就查询到了数据库里的信息
继续优化
既然我们这种方式是需要私有化构造函数的,那我们就想到了,能用单类来完成它
/*
* 单类模式实现工具类 创建方法一 饿汉式(即时加载模式)
* 特点:
* 1、单例类只能有一个实例。
* 2、单例类必须自己创建自己的唯一实例。
* 3、单例类必须给所有其他对象提供这一实例。
* 单例模式保证了全局对象的唯一性
*/
/* 思路:
* 单类:通过将构造方法限定为private避免了类在外部被实例化,
* 在同一个虚拟机范围内,JdbcUtilsSingle的唯一实例
* 只能通过getInstance()方法访问。
*
* 第一步:创建自己的唯一实例
* 第二部:对外提供getInstance()方法获取实例
*/
然后其他地方就和第一次改进后一样
注册驱动
后面的一样那就不在赘述。
测试单类工具类
懒汉式单类设计模式
单类实现二:有时候把对象构造出来但是不一定会用的时候用这种延时加载(也叫惰性加载)的方式,我们什么时候需要再去实例化对象
创建getInstance()方法去让其他类拿到这个对象
/*
* 但是对于以上的getInstance()方法来说,还是有些问题到,如果此时有两个线程,线程A执行到1处,读取了instance为null,
* 然后cpu就被线程B抢去了,此时,线程A还没有对instance进行实例化。
* 因此,线程B读取instance时仍然为null,于是,它对instance进行实例化了。
* 然后,cpu就被线程A抢去了。此时,线程A由于已经读取了instance的值并且认为它为null
* 所以,再次对instance进行实例化。所以,线程A和线程B返回的不是同一个实例。
* 这就出现了线程安全问题
*/
/*
* 那么线程安全问题怎么解决呢?
* 方法一:在方法前面加synchronized修饰。这样肯定不会再有线程安全问题。
* 但是,这种解决方式,假如有100个线程同时执行,那么,每次去
* 执行getInstance方法时都要先获得锁再去执行方法体,如果没有锁,
* 就要等待,耗时长,效率就很低。因此,还是需要改进
*/
/* 解决方法改进:
* 加同步代码块,减少锁的颗粒大小,即能用局部锁就不用函数锁。我们发现,只有第一次instance为null的
* 时候,才去创建实例,而判断instance是否为null是读的操作,不可能存在线程安全
* 问题,所以,我们只需要对创建实例的代码进行同步代码块的处理,也就是所谓的对可
* 能出现线程安全的代码进行同步代码块的处理。
*/
/*但是我们这样处理就没有问题了吗?
* 同理我们先来做个分析,假设有两个线程,线程A和线程B,
* 首先线程A读取instance值为null,然后cpu就被线程B抢了去获得执行权,
* 线程B再来判断instance值为null,接着线程B依然持有执行权往下执行了同
* 步代码块中的代码,对instance进行实例化。
* 然后,线程A获得cpu的执行权,由于线程A之前已经判断instance值为null,
* 于是线程A直接就执行了它后面的同步代码块代码,对instance进行实例化。
*这样就实例化了两个对象,实则只用的到一个对象,造成资源浪费。
*
*所以从上面的分析来看,我们需要继续改进
*那就是加双重if判断
*/
/*但是。。。哈哈尽管这样还是不能保证代码百分百一定没有线程安全问题了
* 因为,这里会涉及到一个指令重排序问题,虽然几率很小,但是还是有存在的可能
* 所以再来一步把之前那句private static JdbcUtilsSingle1 instance = null;
* 加上volatile关键字,因为volatile可以禁止指令重排序。如下:
* private static volatile JdbcUtilsSingle1 instance = null;
*/
现在可以保证是完全没有问题了,后面的结果处理,关闭资源和之前是一样的那就不在赘述,如何使用,也是和单类设计模式一的测试一样的也就不再说了。
那么通过上述示例,我们就把,jdbc,多线程中线程安全问题以及单类的两种设计模式(懒汉式,饿汉式)融合贯通起来,不禁让我感叹代码之美。
如果有什么问题可以到公众号咨询,一般是机器人回复,但我看到后会和你一起讨论。
如果这中有什么问题欢迎在评论区留言指正,共同进步。
欢迎关注公众号 代码黑洞