JDBC数据库接口(Java)
1、阅前浏览:
1.1 JAR包定义
JAR 文件就是 Java Archive File,顾名思意,它的应用是与 Java 息息相关的,是 Java 的一种文档格式。JAR 文件非常类似 ZIP 文件——准确的说,它就是 ZIP 文件,所以叫它文件包。JAR 文件与 ZIP 文件唯一的区别就是在 JAR 文件的内容中,包含了一个 META-INF/MANIFEST.MF 文件,这个文件是在生成 JAR 文件的时候自动创建的。
1.2 接口的作用
接口是一种特殊类。用于抽象出行为特征与常量属性。接口即可以强化规则又可以有效地降低类与类之间的耦合度,在未来业务变更时降低项目修改的难度
比如有一个汽车工厂【CarFactory】,负责生产汽车。但是不负责生产发动,因此汽车【CarFactory】可以设置发动机引擎规则,即为【Engine接口】。然后交给跑车发动机研发商【SprotEngineFactory】和越野车发动机研发商【SuvEngineFactory】进行研发。研发成功后可以直接组装到汽车上即可。
1.3 JAVA规范
1.3.1 JAVASE规范
JAVASE(Java Platform,Standard Edition)是由Sun Microsystems公司于1995年5月推出的Java程序设计语言和Java平台的总称.。指定了JAVA编程语言开发时规范。指Java类在单独执行,在单独处理业务时需要遵守语法规则。比如 继承,访问权限,接口实现,控制语句,标识符命名
1.3.2 JAVAEE规范
JAVAEE规范基于JAVASE规范。制定了Java与不同服务器之间开发规则。Java在进行商业开发时遵守开发规则共有13中规范。在商业开发过程中,往往需要Java类与不同服务器进行沟通来解决当前业务。由于在商业开发过程中,Java需要与13种不同服务器进行沟通,因此SUN公司根据13种服务器特征指定13套接口,这13套接口统称为JAVAEE规范。SUN公司相当于【汽车制造工厂】负责提供接口(Engine),但是不负责提供接口中实现类。接口中实现类由不同服务器厂商来提供。服务器厂商将完成接口实现类以JAR包形式提供。Java程序员通过jar包得到接口中实现类,从而实现与指定服务器之间交流。
1.4 API :
应用程序编程接口(这里的接口不是狭义的interface)
API包括:API源码、API字节码、API帮助文档。
使用API的时候,以上三元素必须保证版本一致。
2、 JDBC简介
- Java Database Connectivity:Java语言连接数据库
- JDBC是SUN公司做的关于Java与数据库的接口
- 具体实现是由各大数据库公司来制作,做出jar包称为连接数据库的驱动
- 向对应数据库官网下载该jar包,导入到项目中
思考:MySQL数据库的原理和Oracle数据库的原理不同,那我们的Java程序应该怎么写呢?MySQL数据库一套原理,应该对应一套Java程序 Oracle数据库是另一套原理,也应该对应另一套Java程序 如果采用以上这种机制,Java程序的扩展力很差。
解决方案:SUN公司制定好一套接口,这套接口就是一套连接数据库的API,这套API叫做:JDBC 换句话说:Java程序员根本不需要关系底层的数据库是哪种类型,编写一套Java代码就可以连接所有的数据库。 可见我们Java程序员是面向JDBC接口调用相关的方法来完成数据库的CRUD(增删改查)操作。
3、JDBC开发前的准备工作
3.1 下载驱动
第一:需要从官网下载数据库对应版本的驱动程序,驱动是xxx.jar,例如MySQL的:mysql-connector-java-5.1.26-bin.jar
第二:以上的jar包下载之后,要想让JVM能够进行加载,必须将上面的jar包配置到环境变量classpath当中。
3.2 配置环境变量classpath
classpath=.;D:\course\05-JDBC\resource\mysql-connector-java-5.1.26\mysql-connector-java-5.1.26-bin.jar
4、认识JDBC实现的接口
实现功能 | 对应接口 |
驱动管理 | DriverManager |
连接接口 | Connection DatabaseMetaData |
语句对象接口 | Statement PreparedStatement CallableStatement |
结果集接口 | ResultSet ResultSetMetaData |
MySQL与Oracle实现
Oracle实现
- ojdbc6.jar / ojdbc14.jar
- Class.forName(“oracle.jdbc.OracleDriver”)
MySQL实现 - mysql-connector-java-5.1.8-bin.jar
- Maven可搜索jar包
mysql-connector-java
- Class.forName(“com.mysql.cj.jdbc.Driver”)
URL连接地址后缀
在连接数据库的时候,可以在 url
地址字符串后添加后缀参数,设置或开启某些功能,拼接规则同 web
的 get
请求方式,使用 ?
和 &
。如:jdbc:mysql:///testdb?a=1&b=2
功能 | 参数 | 备注 |
指定系统时区 |
| mysql 8x 必须指定 |
定义写入字符集 |
| 储存中文时指定 |
多SQL执行 |
| 允许一次执行多条SQL |
sqlYog mycat 都默认开启多SQL执行
5、JDBC开发(五)六部曲
- 一般情况下我们在整个连接中都需要try,catch,其中1~5步在try中,第6步在finally中
- 错误类型为SQLException,finally中close()或者使用java新语法try()定义三者
try{
//1.注册驱动(两种方式,以下是注册驱动的第一种方式,后期再讲解第二种注册驱动的方式。)
//创建驱动对象
java.sql.Driver driver = new com.mysql.jdbc.Driver(); //多态
//完成驱动注册
DriverManager.registerDriver(driver);
}catch(SQLException e){
e.printStackTrace();
}
//2.获取连接对象
//url可以添加各种功能后缀,其中8x的mysql必须指定时区
String url = "jdbc:mysql://localhost:3306/database";
String user = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url,user,password);
//3.获取数据库操作对象
/*
通过连接对象Connection获取数据库操作对象Statement代码:
一个数据库连接对象可以创建多个数据库操作对象。
*/
Statement stmt = conn.createStatement();
Statement stmt2 = conn.createStatement();
//4.执行SQL语句[DQL]
ResultSet rs = stmt.excuteQuery("DQL语句select");
//执行SQL语句[DML]
int count = stmt.executeUpdate("DML语句insert update delete");
//执行SQL语句
boolean flag = stmt.excute(sql);
//SQL语句结尾不用加分号,注意空格干扰语义
//(5.处理查询结果,执行DQL语句才有第五步,执行DML语句不用处理查询结果集)
while(rs.next()){
String a1 = rs.getString(1); //以列的下标,JDBC下标都以1开始
String a2 = rs.getString("col2"); //以列的名称
}
//next方法如果当前行有数据返回true,否则false.getXXX()取得字段内容
//6.释放资源
Connection、Statement、ResultSet都会占用一定的资源,它们需要全部释放/关闭
* 释放资源的注意事项:
- 为了保证每一个资源都可以关闭,建议将关闭资源的代码写到finally语句块当中。
- 先关闭ResultSet、再关闭Statement、最后关闭Connection。
- 在finally语句块当中关闭这些资源的话必须保证这些变量在finally语句块当中是可以访问的,需要将try语句块当中的局部变量提出到try语句块之外。
- 关闭这些资源的时候统一调用close方法
- 关闭资源之前最好判断这些资源是否为空,避免空指针异常的发生。
- 关闭的close方法有异常,需要编写代码的时候处理这些异常,分别对其进行异常处理,不要一块try进行处理。
try {
if (rs!=null) rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (ps!=null) ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn!=null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
6、两种不同驱动注册方式
- 第一种:
Driver driver = new com.mysql.jdbc.Driver();
DriverManager.registerDriver(driver); // 这行代码可以忽略不写 - 第二种:(这种方式使用较多)
Class.forName(“com.mysql.jdbc.Driver”);
Class.forName(“com.mysql.jdbc.Driver”);
作用是:将com.mysql.jdbc.Driver这个类装载到JVM当中。装载过程中会自动执行静态代码块完成驱动的注册。
7、将数据库的配置信息写到文件中,通过IO+Propertie形式读取信息
1、driver、url、user、password
以上信息应该是可配置的,不应该写死到java程序当中。写到程序中不容易扩展。
2、应该将以上的配置信息写到属性配置文件当中,以后要想连接其它数据库,直接让用户修改配置文件即可。**
补充:什么是属性配置文件?
- 属性配置文件通常扩展名是:xx.properties,但不是必须的
- 属性配置文件当中配置了大量的key和value,通常key和value之间使用“=”隔开,但不是必须的
- 属性配置文件当中的key不能重复,重复的话value会覆盖
- java.util.Properties属性类是Map集合,key和value都是String类型,并且是线程安全的
- 属性配置文件当中的key一旦确定,不要修改,一般都是修改value,因为key是写死在程序中的。
8、关于SQL注入
8.1 你怎么看待SQL注入?
SQL注入不一定是一种危害,但大多数情况下会危及系统的安全,有一些系统中的特殊功能可能还需要SQL注入功能, 此时为了支持SQL注入,需要使用java.sql.Statement接口。
8.2 怎么解决SQL注入,或者说怎么防止SQL注入?
java.sql.PreparedStatement叫做预编译的数据库操作对象,可以防止SQL注入,
原理:对SQL语句框架进行预先编译,再接收用户提供的信息,即使提供的信息当中含有SQL语句的
关键字,但是这些关键字并没有参与SQL语句的编译,那么它也只是一个普通的字符串,SQL语句原意不会被扭曲,防止了SQL注入。PreparedStatement是Statement的子接口大多数情况下还是需要防止SQL注入的,所以PreparedStatement使用较多。
8.3 Statement和PreparedStatement区别
- Statement是先进行SQL语句拼接,再进行SQL语句的编译,存在SQL注入问题;
- PreparedStatement是预先编译SQL语句框架(带有占位符的SQL),然后再给占位符赋值,防止SQL注入;
- Statement编译一次,执行一次,编译一次,执行一次,PreparedStatement编译一次框架,执行N次,
- 所以PreparedStatement效率较高;PreparedStatement使用较多。
9、使用PreparedStatemet实现CRUD
public class JDBCTest{
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、获取连接
conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root", "root");
//3、获取预编译的数据库操作对象
String sql = "insert into dept(deptno,dname,loc) values(?,?,?)";
ps = conn.prepareStatement(sql);
ps.setInt(1, int);
ps.setString(2, "String");
ps.setString(3, "String");
//3、获取预编译的数据库操作对象
String sql = "update dept set dname = ? , loc = ? where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setString(1, "String");
ps.setString(2, "String");
ps.setInt(3, int);
//3、获取预编译的数据库操作对象
String sql = "delete from dept where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1, int);
//4、执行SQL语句
int count = ps.executeUpdate();
System.out.println(count);
} catch (Exception e) {
e.printStackTrace();
} finally{
//5、释放资源
if(ps != null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
10、最终使用
- 驱动自动注册,不再注册
- 通过properties文件获取连接器
- 根据需求,有参数的SQL语句用预编译的数据库操作对象,无参可以用普通的
- 执行语句并处理结果
- 一定记得关闭数据库连接,可以通过jdk1.7特性,在try小括弧中定义
11、自用工具类-创建JDBCUtils
由于数据库的连接与关闭代码比较类似,所以我们常自己书写一个工具类去实现操作,减少代码冗余
工具类常定义为一个静态类,以下是一个工具类的案例,实现了通过配置文件,获取连接对象与关闭连接
public class JDBCUtils {
//工具类不能够产生对象,只能通过类名.的方式调用方法。
private JDBCUtils(){
}
//通过配置文件获取数据
private static Properties prop = null;
static{
prop = new Properties();
try {
File file = new File(JDBCUtils.class.getClassLoader().getResource("conf.properties").getPath());
FileInputStream fis = new FileInputStream(file);
//FileInputStream fis=JDBCUtils.class.getClassLoader().getResourceAsStream("conf.properties");
prop.load(fis);
} catch (IOException e) {
e.printStackTrace();
}
}
//PUBLIC返回连接对象
public static Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName(prop.getProperty("driver"));
return DriverManager.getConnection(
prop.getProperty("url"),
prop.getProperty("user"),
prop.getProperty("password"));
}
//PUBLIC关闭资源方法
public static void close(Connection conn, Statement stat, ResultSet rs){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}finally{
rs = null;
}
}
if(stat != null){
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
}finally{
stat = null;
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}finally{
conn = null;
}
}
}
}
12、更多数据操作
12.1 数据库元数据
- 通过数据库连接对象的
getMetaData()
方法返回数据库元数据对象 - 数据库名称:
String dbmd.getDatabaseProductName()
- 数据库驱动版本:
String dbmd.getDriverVersion()
- 数据库用户名:
String dbmd.getUserName()
- 其他各种方法
12.2表元数据
- 通过结果集的
getMetaData()
方法返回结果集表元数据对象 - 表中字段的总数:
int rsmd.getColumnCount()
- 表中第i个字段的名称:
String rsmd.getColumnName(i)
- 表中第i个字段的类型:
String rsmd.getColumnTypeName(i)
- 注意SQL中以1开始
- 其他各种方法
12.3 获取自增主键值
//PreparedStatement对象获取自增是通过创建时,第二个参数指明可以获取自增键
PreparedStatement preparedStatement = con.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
//PreparedStatement对象获取自增的第二种方法,第二个参数传入GeneratedKeys的字段名列表,字符串数组
PreparedStatement preparedStatement = con.prepareStatement(sql,new String[]{"id"});
//Statement对象获取自动是通过执行语句时,第二个参数指明可以获取自增键
statement.executeUpdate(sql,Statement.RETURN_GENERATED_KEYS);
//获取自增键结果集
ResultSet rs = preparedStatement/statement.getGeneratedKeys();
//游标下移,获取数据
rs.next();
int id = rs.getInt(1)12345678910
12.4批量更新
12.4.1 批量更新的引入
- 批处理:发送到数据库作为一个单元执行的一组更新语句
- 批处理降低了应用程序和数据库之间的网络调用
- 相比单个SQL语句的处理,批处理更为有效
12.4.2批量更新API
- 添加SQL语句到数据库操作对象中
Statement.addBatch(String sql)
PreparedStatement.addBatch()
- 把对象中语句列表中的所有SQL语句发送给数据库进行处理
executeBatch()
- 清空当前SQL语句列表
clearBatch()
12.4.3使用批量更新注意
- 防止OutOfMemory错误
- 如果操作对象中待处理的SQL语句过多,可能会产生内存溢出错误
- 一定要及时处理SQL语句列表
13、 JDBC实现事务处理
13.1 默认情况下JDBC是如何处理事务的**?
JDBC默认情况下采用自动提交机制,所谓的自动提交机制是只要在Java程序当中执行一条DML语句则提交一次。这种事务处理机制有的时候不满足我们实际的需求,实际的业务当中可能要求多条DML语句组合起来才能完成整个流程。
所以JDBC的这种自动提交机制必须改成手动提交。
13.2 什么时候提交?什么时候回滚?
当在这个业务当中所有的DML语句都执行成功的时候提交,只要其中任意一条DML语句执行失败,则回滚事务。 但无论是提交事务还是回滚事务,事务都会结束。
JDBC自动提交改为手动提交,事务处理包括以下三行核心程序?
将自动提交修改为手动提交
connection.setAutoCommit(false); //false表示关闭自动提交机制,默认值是true
等待整个业务的执行结束,正常结束手动提交
connection.commit();
等待整个业务的执行结束,假设其中一个DML语句出现了异常,为了保证数据的安全,
建议手动回滚,在catch语句块当中捕获异常之后,执行以下代码进行回滚:
connection.rollback();
注意:事务只和DML语句有关系。
13.3 JDBC事务实现
Connection conn = null;
try {
conn = DriverManager.getConnection(".", "", "");
boolean autoCommit = conn.getAutoCommit();//获取自动提交状态
conn.setAutoCommit(false);//设定自动提交
//...执行SQL语句
conn.commit();//提交事务
conn.setAutoCommit(autoCommit);//还原自动提交状态
} catch (SQLException e) {
if (conn == null) {
try {
conn.rollback();//当发生错误,回滚事务
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
13.4 设置隔离级别
JDBC中通过Connection提供的方法设置事务隔离级别
Connection. setTransactionIsolation(int level)
//可选参数
Connection.TRANSACTION_READ_UNCOMMITTED //1(读未提交数据)
Connection.TRANSACTION_READ_COMMITTED //2(读已提交数据)
Connection.TRANSACTION_REPEATABLE_READ //4(可重复读)
Connection.TRANSACTION_SERIALIZABLE //8(串行化)
Connection.TRANSACTION_NONE //0(不使用事务)
14、行级锁的使用
1. 行级锁
* 行级锁又被称为悲观锁
* 在当前事务没有结束之前,被锁定的记录(精确到表中的行)其它事务是无法修改的
* 当然,没有被锁定的行,是可以被其它事务修改的
* 行级锁仅对索引列有作用,否则为表级锁。
怎么实现行级锁呢?
conn.setAutoCommit(false);
....
String sql = "select ename,sal from emp where job='MANAGER' for update";
....
conn.commit();
conn.rollback();