我们为什么需要JDBC?
任何一款程序,都是在与数据交互的基础上运行的。而有些数据,我们需要持久化得保存起来,所以我们使用了各类数据库,如Mysql、Oracle等。这些数据库,就可以看作是一款单独的程序软件,那么我们编写的Java代码,如何与数据库进行交互呢?
程序与程序的交互,本质上就是进行通讯,JDBC(JAVA Database Connectivity)就是这个通讯的桥梁。SUN公司在Java中提供了接口,然后靠各个数据库厂商实现接口,我们也常把这些实现称作驱动包。通过这种面向接口编程的方式,JAVA程序与不同的数据库交互,只需要使用不同的驱动包即可,主体逻辑不需要大型改动。
下面主要以Mysql数据库为例,介绍JDBC的使用。
第一步:加载驱动
首先获得数据库的驱动jar包,并导入工程。
在编程中要连接数据库,还要先加载数据库驱动程序。不同的数据库有不同的装载方法,但一般都是通过Class类来加载:
//加载Mysql数据库驱动
try {
Class.forName("com.mysql.jdbc.Driver");//路径名是固定的
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//加载Mysql数据库驱动
try {
Class.forName("oracle.jdbc.driver.OracleDriver");//路径名是固定的
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
第二步:获取连接对象
有了接口的实现驱动后,我们需要创建一个连接对象(万物皆对象嘛)来进行通讯的操作。
Java中为连接对象定义了Connection接口,通过DriverManager接口来得到Connection对象:
String url = "jdbc:mysql://localhost:3306/mydb";
//Oracle的url格式为:jdbc:oracle:thin:@host:port:databse
String user = "root";
String password = "1234";
try {
Connection conn =DriverManager.getConnection(url,user,password);
} catch (SQLException e) {
e.printStackTrace();
}
建立连接的过程,包含了许多步骤,包括通讯的建立、登陆身份验证等,所以此步骤是非常耗时的。而且如果每交互一次就获取一个连接对象,交互完成后立马关闭对象,这样的设计明显非常低效。所以我们一般使用连接池的方式管理连接对象。关于连接池,可以看我的这篇博客:《连接池与DBCP、Druid》
第三步:获取SQL执行对象
有了连接Connection对象后,说明已可以与数据库正常通讯,信道已打开。这时候我们希望传sql语句给数据库执行的话,不是使用连接对象直接执行,而是需要创建Statement语句对象,用来执行sql语句。
Statement可以分为三类:
- Statement,使用createStatement()方法创建,sql语句由字符串拼接而成。
- PreparedStatement,使用prepareStatement()方法创建,可以使用?占位符来表示输入参数,可以有效防止sql注入。
- CallableStatement,使用prepareCall()方法创建,继承自PreparedStatement,用于调用存储过程。
三种获取SQL执行对象的代码实例:
String sql = "";
Statement stmt1 = conn.createStatement();//创建时不需要sql,执行时才传入
Statement stmt2 = conn.prepareStatement(sql);//创建时需要sql,参数定义完成后直接执行
Statement stmt3 = conn.prepareCall(sql);
第四步:执行SQL语句
得到Statement对象后,则可以开始编写sql语句来进行执行。不用的语句有不同的执行过程,但都是使用以下三个方法来执行:
- execute():运行语句,返回是否有结果集
- executeQuery():运行select语句,返回ResultSet结果集。
- executeUpdate():运行insert/update/delete操作,返回更新的行数。
下列实例中用到的t_student表结构为:
DML语句
使用Statement执行DML语句:
Statement stmt1 = conn.createStatement();
String name = "小猪";
int age = 18;
Date date = new Date(System.currentTimeMillis());
//拼接sql
String sql = "insert into t_student (name,age,birthday) "
+ "values ('" + name + "'," + age + ",'"+ date +"')";//让人眼花的拼接
//使用execute执行
stmt1.execute(sql);
使用prepareStatement执行DML语句:
String sql = "insert into t_student (name,age,birthday) values (?,?,?)";// ? 占位符
PreparedStatement stmt2 = conn.prepareStatement(sql);
String name = "小虎";
int age = 20;
Date date = new Date(System.currentTimeMillis());
stmt2.setObject(1, name);//设置参数
stmt2.setObject(2, age); //也可以使用对应参数类型,stmt2.setInt(2, age);
stmt2.setObject(3, date);
//使用executeUpdate执行DML语句
stmt2.executeUpdate();
DQL语句
使用Statement执行DQL语句:
Statement stmt1 = conn.createStatement();//获取Statement对象
//拼接sql
String sql = "select * from t_student r where r.id = 1";
//使用executeQuery执行
ResultSet rs = stmt1.executeQuery(sql);//范围结果为ResultSet类型,与集合的迭代器类似
while(rs.next()){//通过get方法获取每一列的值
System.out.println(rs.getString("name")+"--"+rs.getInt("age")+"--"+rs.getTimestamp("birthday"));
}
使用Statement执行DQL语句:
String sql = "select * from t_student r where r.id = ?";// ? 占位符
PreparedStatement stmt2 = conn.prepareStatement(sql);//获取PreparedStatement对象
stmt2.setObject(1, 2);//设置参数
//使用executeUpdate执行DML语句
ResultSet rs = stmt2.executeQuery();
while(rs.next()){
System.out.println(rs.getString("name")+"--"+rs.getInt("age")+"--"+rs.getTimestamp("birthday"));
}
批处理
当需要一次性插入多条数据时,若条数特别多时,一行一行插入且一行行提交,那么效率就会显得相当的慢。这时我们可以使用批处理,取消自动提交,改为手动提交,并通过addBatch()方法将多条sql语句加在一起。实例:
conn.setAutoCommit(false); //取消自动提交,设为手动提交
Statement stmt1 = conn.createStatement();//获取Statement对象
String name = "小猪";
int age = 18;
Date date = new Date(System.currentTimeMillis());
for(int i=0;i<20000;i++){
//addBatch,将多条sql语句放在一起,待一起执行
stmt1.addBatch("insert into t_student (name,age,birthday) "
+ "values ('" + name + i + "'," + age + ",'"+ date +"')");
}
stmt1.executeBatch(); // 使用executeBatch方法,批量执行
conn.commit(); //手动提交事务
操作CLOB与BLOB
在数据库中,还有两个大对象类型,就是CLOB(文本大对象)与BLOB(二进制大对象),可以用来存储占用空间较大的数据,最大可达4G。
操作CLOB读写实例:
//写clob
String sql = "insert into t_student (name,age,info) values (?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setObject(1, "小猪");
ps.setObject(2, 18);
ps.setClob(3, new FileReader(new File("e:/test.txt")));//clob文本类型,与字符流对接
ps.executeUpdate();
//读clob
String sql = "select * from t_student r where r.id = ? ";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setObject(1, 20009);
ResultSet rs = ps.executeQuery();
while(rs.next()) {
Clob c = rs.getClob("info");
Reader r = c.getCharacterStream();//通过getCharacterStream方法,得到字符输出流
int len = 0;
char[] flush = new char[1024];
while((len=r.read(flush))!=-1){ //字符输出流的操作
for(int i=0;i<len;i++) {
System.out.print(flush[i]); //输出为字符
}
}
}
操作BLOB读写实例:
//写blob
String sql = "insert into t_student (name,age,headimage) values (?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setObject(1, "小猪");
ps.setObject(2, 18);
ps.setBlob(3, new FileInputStream(new File("e:/a.jpg")));//blob二进制类型,与字节流对接
ps.executeUpdate();
//读blob
String sql = "select * from t_student r where r.id = ? ";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setObject(1, 20010);
ResultSet rs = ps.executeQuery();
while(rs.next()) {
Blob b = rs.getBlob("headimage");
InputStream is = b.getBinaryStream();//通过getBinaryStream方法,得到字节输出流
int len = 0;
byte[] flush = new byte[1024];
while((len= is.read(flush))!=-1){ //字节输出流的操作
for(int i=0;i<len;i++) {
System.out.print(flush[i]); //输出为字节
}
}
}
调用存储过程
通过prepareCall方法,获得Statement对象,则可以来调用存储过程。需要注意的是输出输出参数的设置与取值,实例:
String call = "{call proc_test(?,?)};"; //调用存储过程语句,第一个参数为输入参数,第二个为输出参数
CallableStatement proc = conn.prepareCall(call); //通过prepareCall方法,获得Statement对象
proc.setString(1,"20020"); //给输入参数传值
proc.registerOutParameter(2,Types.VARCHAR); //声明输出参数是什么类型的
proc.execute(); //执行
String res = proc.getString(2); //获得输出参数
第五步:释放资源
同其他资源释放步骤一样,后使用的,先释放。实例:
try {
//jdbc操作...省略
}catch(e){
//异常捕获与处理
}finally{ //在finally中释放资源,后使用的,先释放。JDBC主要有以下三个资源需要释放
try {
if(rs!=null){ //rs ResultSet 执行结果对象
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(ps!=null){ // ps Statement 执行语句对象
ps.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(conn!=null){ //conn Connection 连接对象
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}