Java数据库操作(JDBC)
JDBC
Java数据库连接(Java DataBase Connectivity,JDBC)用于在Java程序中实现数据库操作功能,它提供了执行SQL语句、访问各种数据库的方法,并为各种不同的数据库提供统一的操作接口,java.sql包中包含了JDBC操作数据库的所有类。通过JDBC访问数据库一般有如下几个步骤:
(1)加载JDBC驱动器。将数据库的JDBC驱动加载到classpath中,在基于JavaEE的Web应用开发过程中,通常要把目标数据库产品的JDBC驱动复制到WEB-INF/lib下。
Oracle数据库导入的jar包为:ojdbc6.jar
mysql数据库导入的jar包为:mysql-connector-java-5.1.13-bin.jar
(2)加载JDBC驱动,并将其注册到DriverManager中。一般使用反射Class.forName(String driverName)。
(3)建立数据库连接,取得Connection对象。一般通过DriverManager.getConnection(url,username,password)方法实现,其中url表示连接数据库的字符串,username表示连接数据库的用户名,password表示连接数据库的密码。
(4)建立Statement对象或是PrepareStatement对象。
(5)执行SQL语句
(6)访问结果集ResultSet对象
(7)关闭资源。依次将ResultSet、Statement、PrepareStatement、Connection对象关闭,释放所占用资源。因为JDBC在底层通常都是通过网络IO实现SQL命令与数据传输的。
下面的例子分别测试了Oracle和mysql数据库的连接
程序目录结构
连接Oracle的操作OracleDb.java
package com.ghq.jdbc.oracle;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class OracleDb {
private static String driver = "oracle.jdbc.driver.OracleDriver";
private static String url = "jdbc:oracle:thin:@localhost:1521:orcl";
private static String user = "scott";
private static String password = "itcast";
private static Connection conn;
private static PreparedStatement ps;
private static ResultSet rs;
//加载驱动
static {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获得链接
public static Connection getConn(){
try {
conn = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
//建立PrepareStatement对象
public static PreparedStatement getPs(String sql,Object...args){
try {
getConn();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1, args[i]);
}
} catch (SQLException e) {
e.printStackTrace();
}
return ps;
}
//查询的方法
public static ResultSet executeQuery(String sql,Object...args){
getPs(sql, args);
try {
rs = ps.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
return rs;
}
//增删改的方法
public static int executeUpdate(String sql,Object...args){
getPs(sql, args);
int row = 0;
try {
row = ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
return row;
}
//批处理的方法
public static int[] executeUpdateBathch(String sql,List<List> args){
getConn();
int rows[] = null;
try {
conn.setAutoCommit(false);
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.size(); i++) {
for (int j = 0; j < args.get(i).size(); j++) {
ps.setObject(i+1, args.get(i).get(j));
}
ps.addBatch();
}
rows = ps.executeBatch();
conn.commit();
} catch (SQLException e) {
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
return rows;
}
//关闭资源
public static void closeAll(){
try {
if (rs != null) {
rs.close();
}
if (ps != null) {
ps.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
连接mysql的操作MySqlDb.java
连接mysql和Oracle数据库的操作都是相同的,只需导入各自的jar,更改driver,url,user和password即可。
private static String driver = "com.mysql.jdbc.Driver";
private static String url = "jdbc:mysql://localhost:3306/test";
private static String user = "root";
private static String password = "root";
批处理
使用JDBC访问oracle数据库,做delete操作,因为oracle有自己的锁机制,即使多个线程同时删除,oracle仍然是一条一条删除,不同同时删除多项。
对于大量的数据更新,oracle有建议一些优化措施,就是使用批处理。
(1)首先通过调用setAutoCommit(false)方法来禁止自动提交,因为每删一条数据,oracle就要自动执行一次commit。commit是需要资源的,设置删除多条数据后再执行commit,可以节省资源。
(2)充分利用batch,如果不用batch,每个delete命令都需要从网络上传到oracle,若有1万条删除命令,要有1万次传送,如果将100个删除命令绑在一起送去Oracle执行。那就只要传送100次就可以了。大大缩短所需时间和网络资源。
setAutoCommit
setAutoCommit总的来说是保持数据的完整性循环里连续的进行插入操作,如果在开始时设置了:conn.setAutoCommit(false);,最后才进行conn.commit(),这样你即使插入的时候报错,修改的内容也不会提交到数据库,而如果没有手动的进行setAutoCommit(false);,出错时就会造成,前几条插入,后几条没有会形成脏数据。
setAutoCommit(false)的误用:(设定setAutoCommit(false)没有在catch中进行Connection的rollBack操作,操作的表就会被锁住,造成数据库死锁)
如果设置成非自动提交,在最后一定要调用commit或者rollback方法
测试连接的代码testconn.java
package com.ghq.textconn; import java.sql.ResultSet; import java.sql.SQLException; import org.junit.Test; import com.ghq.jdbc.mysql.MySqlDb; import com.ghq.jdbc.oracle.OracleDb; public class testconn { //测试连接Oracle数据库,访问emp表编号为7369的员工信息 @Test public void testOracle(){ String sql = "select * from emp where empno = ?"; ResultSet rs = OracleDb.executeQuery(sql,7369); try { while (rs.next()) { System.out.println("Oracle连接 "+rs.getObject(1)+" "+rs.getObject(2)+" "+rs.getObject(3)); } } catch (SQLException e) { e.printStackTrace(); } } //测试连接mysql数据库,访问user表中id为1的用户信息 @Test public void testmysql(){ String sql = "select * from user where id = ?"; ResultSet rs = MySqlDb.executeQuery(sql,1); try { while (rs.next()) { System.out.println("mysql连接 "+rs.getObject(1)+" "+rs.getObject(2)+" "+rs.getObject(3)); } } catch (SQLException e) { e.printStackTrace(); } } }
测试的结果为:
JDBC处理事务
一个事务是由一条或多条对数据库操作的SQL语句所组成的一个不可分割的工作单元,只有当事务中的所有操作都正常执行完了,整个事务才会提交给数据库。
在JDBC中一般是通过commit()方法和rollback()方法来结束事务的操作。commit()方法表示事务的提交,rollback()方法表示事务的回滚,多用于在处理事务的过程中出现了异常的情况,这两种方法位于java.sql.Connection类中。一般而言,事务默认操作时自动提交,即操作成功后,系统将自动调用commit方法,否则将调用rollback方法。
commit
平时使用commit和rollback主要在DML(数据操纵语言)insert,delete,update这些语句上,可以设置保存点,然后依次提交。单独commit,是把之前的数据改动都一次提交。正常的关闭,exit是自动提交的,可以不使用commit。直接关闭窗口,属于强制关闭,关闭之前需要commit。
事务具备四个特性(ACID):
(1)原子性(Atomicity)
指事务包含的所有的操作要么全部成功,要么全部失败回滚。(转账)
(2)一致性(Consistency)
指事务必须使数据库从一个一致状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。(转账两者的钱数总和是一致的)
(3)隔离性(Isolation)
指当多个用户并发访问数据库时,比如同时操作一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
(4)持久性(Durability)
指一个事务提交了,那么对数据库中的数据的改变就是永久性的。即便在数据库系统遇到故障的情况下也不会丢失提交事务的操作。
事务的隔离性
不考虑事务的隔离性会发生的问题:
(1)脏读
脏读指在一个事务处理过程里读取了另一个未提交的事务中的数据。
当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。例如:用户A向用户B转账100元,对应SQL命令如下:
update account set money=money+100 where name=’B’; (此时A通知B)
update account set money=money - 100 where name=’A’;
当只执行第一条SQL时,A通知B查看账户,B发现确实钱已到账(此时即发生了脏读),而之后无论第二条SQL是否执行,只要该事务不提交,则所有操作都将回滚,那么当B以后再次查看账户时就会发现钱其实并没有转。
(2)不可重复读
不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。
不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
(3)幻读
幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。
幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
数据库提供的四种隔离级别:
① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
③ Read committed (读已提交):可避免脏读的发生。
④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
级别越高,执行效率越低。Oracle默认为 read committed,MySql为Repeatable read。
Statement、PrepareStatement和CallableStatement区别
Statement用于执行不带参数的简单SQL语句,并返回它所生成结果的对象,每次执行SQL语句时,数据库都要编译该SQL语句。
Statement stmt = conn.getStatement();
stmt.executeUpdate(“insert into user values (2,’Allen’)”);
PreparedStatement表示预编译的SQL语句的对象,用于执行带参数的预编译SQL语句。
CallableStatement则提供了用来调用数据库中存储过程的接口,如果有输出参数要注册,说明是输出参数。
CallableStatement由prepareCall()方法所创建,它为所有DBMS提供了一种以标准形式调用已存储过程的方法。它从PrepareStatement中继承了用于处理输入参数的方法,而且还增加了调用数据库中的存储过程和函数以及设置输出类型参数的功能。
Statement和PrepareStatement对比
虽然Statement对象和PrepareStatement对象能够完成相同的功能,但相比之下,PrepareStatement具有以下的优点:
(1)效率更高。在使用PrepareStatement对象执行SQL命令时,命令会被数据库进行编译和解析,并放到命令缓冲区。然后每当执行同一个PrepareStatement对象时,由于在缓冲区中可以发现预编译的命令,虽然它还是会被再解析一次,但不会被再次编译,是可以重复使用的,能够有效提高系统性能。因此,如果要执行插入、更新、删除等操作,最好使用PrepareStatement来执行SQL语句。
(2)代码的可读性和可维护性更好。
使用Statement执行SQL语句
stmt.executeUpdate(“insert into user values ((2,’Allen’)”);
使用PrepareStatement执行SQL语句
String sql = “insert into user values (? ,?)”;
ps = con.prepareStatement(sql);
ps.setObject(1,2);
ps.setObject(2,”Allen”);
(3)安全性更好。使用PrepareStatement能够预防SQL注入攻击。
Sql注入指的是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器,达到执行恶意SQL命令的目的。注入只对SQL语句的编译过程有破坏作用,而执行阶段只是把输入字符串作为数据处理,不再需要对SQL语句进行解析,因此就避免了类似select * from user where name = ‘aa’ and psssword = ‘bb’ or 1=1的SQL注入问题的发生。
getString()和getObject()方法的区别
JDBC提供了getString()、getInt()和getData()等方法来从ResultSet中获取数据,当查询结果集中的数量量较小时,不用考虑性能,使用这些方法完全能够满足需求,但是当查询结果集中的数据量非常大时,则会抛出异常:OracleException未处理:ORA-01000:maximum open cursors exceeded。而通常情况下,使用getObject()方法就可以解决这个问题。
getString()或getInt()等方法在被调用的时候,程序会一次性地把数据都存放到内存中,然后通过调用ResultSet的next()和getString()等方法来获取数据。当数据量大到内存中放不下时就会抛出异常,而使用getObject()方法就不会出现这样的问题,因为数据不会一次性被读到内存中,每次调用时会直接从数据库中去获取数据。
参考博客
[1] 用JDBC访问ORACLE数据库 关于commit 增快效率 大数据 等的整理
https://www.cnblogs.com/qingxinblog/p/3375147.html
[2] Connection.setAutoCommit使用的注意事项
http://ygsilence.iteye.com/blog/1297762