JDBC(Java DataBase Connectivity)java数据库连接,是一种用于执行SQL语句的Java API(Application Programming Interface),可以为多种关系型数据库提供统一访问,它由一组用Java语言编写的类和接口组成。
有了JDBC,在我们的程序中向各种关系型数据库发送SQL语句就是一件很容易的事了。也就是说,程序员不必为了访问不同的数据库而编写不同的应用程序了,只需用JDBC API写一个程序就可以了,它可向相应的数据库发送SQL调用。同时,将Java语言和JDBC结合起来使程序员不必为不同的平台编写不同的应用程序,只须写一遍程序就可以让它在任何平台上运行,这也是Java语言“编写一次,到处运行”的优势。
JDBC原理图
在这里驱动程序指的就是各个数据库厂商遵循JDBC规范的,可以访问自己数据库的API。而JDBC是底层接口,各个数据库驱动才是JDBC接口的实现。也就是说,没有驱动就无法完成对数据库的连接。所以,每次要使用JDBC的时候,记得的第一件事就是导包(相应数据库的驱动包)。
JDBC核心接口的介绍
本文主要简单的介绍JDBC一些核心接口(类),想系统了解JDBC整个体系的还是建议阅读官方文档。如果可以的话,希望读者能去了解一下JDBC的历史。我觉得学一门语言或技术,了解其发展史,才能更好地运用,也就能更好把握未来的方向。
JDBC常用的接口(类)有DriverManager、Connection、Statement(PreparedStatement)和ResultSet。这些接口(类)都是在java.sql包下,导包的时候注意不要导错。
DriverManager:在JDBC中,DriverManager主要有这两方面的作用:1、注册驱动,也就是让JDBC知道要使用哪个数据库驱动;2、获取连接(Connection),得到了连接,说明已经和数据库连接上了。说白了,就是可以开始操作数据库了。
Connection:Connection表示连接,JDBC与数据库的通讯都是通过Connection展开的。
Statement:Statement是用来向数据库发送SQL语句的,这样数据库就会执行程序中发送过去的SQL语句。
PreparedStatement:PreparedStatement是Statement的子接口,具有预编译功能的Statement,多次执行的效率比Statement高,也能防止SQL注入。
ResultSet:ResultSet表示查询结果集,只有在执行查询操作后才会有结果集的产生。结果集是一个二维的表,有行有列。
JDBC操作步骤
我把JDBC的操作归结为如下“六部曲”:
1,加载驱动
2,获取连接
3,获取statement
4,执行SQL语句
5,处理结果集
6,释放资源
下面开始解释每一步骤的用法。注意,本文使用的是MySQL的驱动,所以解释也是针对MySQL驱动来解释的。其它数据库是一样的。
加载驱动:前面说过DriverManager类的一个作用是注册驱动,方法是registerDriver,它需要一个参数:java.sql.Driver,而java.sql.Driver是一个接口,JDBC规定每个驱动程序类必须实现该接口,其中MySQL驱动程序中的java.sql.Driver接口实现类是com.mysql.jdbc.Driver,所以使用MySQL数据库注册驱动的代码为DriverManager.registerDriver(new com.mysql.jdbc.Driver()),我们再来看看com.mysql.jdbc.Driver的源码:
package com.mysql.jdbc;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver{
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException { }
}
源码很简单,只有一个无参的构造器和一个static代码块。注意看static代码块的内容,是不是很熟悉,没错,注册驱动。也就是说我们只要加载com.mysql.jdbc.Driver类就会执行static代码块里的内容,从而也就会把com.mysql.jdbc.Driver注册到DriverManager中。加载驱动的代码为:Class.forName("com.mysql.jdbc.Driver")。
获取连接:获取连接的代码为DriverManager.getConnection(url, username, password),其中username和password是登录数据库时的用户名和密码;url就类似于一个网址,用于JDBC找到所连接的数据库。其格式为:jdbc:subprotocol:subname。不管连接什么数据库,Jdbc都是固定的;subprotocol就是各个数据库厂商所注册的JDBC协议名字了,比如oracle,mysql等;而subname只和具体的JDBC驱动提供厂商有关,所以我们需要了解各个数据库厂商的要求,MySQL的subname由数据库服务器IP地址、端口号和数据库名称组成。
常见数据库的URL:
MySQL:jdbc:mysql://dbip:port/databasename
Oracle:jdbc:oracle:thin:@dbip:port:databasename
SQL Server:jdbc:microsoft:sqlserver://dbip:port;DatabaseName=databasename
获取statement:获得连接之后,一个重要的作用就是可以通过连接得到statement对象:connection.createStatement()。
执行SQL语句:我们大费周章得到statement,那么,statement有什么作用呢?statement的int executeUpdate(String sql)方法可以执行INSERT、UPDATE、DELETE操作,ResultSet executeQuery(String sql)可以执行SELECT操作。也就是说,常见的数据库操作,都是statement负责执行的。
处理结果集:从数据库得到数据之后就是怎样处理数据的问题了。ResultSet对象具有指向其当前数据行的光标。最初,光标被置于第一行之前。next方法将光标移动到下一行;因为该方法在 ResultSet对象没有下一行时返回false,所以可以在while循环中使用它来迭代结果集,其中获取数据用getXxx(String columnName)(或getXxx(int columnIndex)列索引下标从1开始),Xxx表示数据类型,当你不确定数据类型的时候可以使用getObject(...))。
释放资源:所有数据库对象使用完之后都要记得关闭以释放资源,关闭的顺序与获取的顺序是相反的,即后获取的先关闭。
下面演示如何具体的操作JDBC,所有操作都是基于stu数据库的student表,所以先创建stu数据库:
/*创建stu数据库*/
CREATE DATABASE stu
/*切换到stu数据库下*/
USE stu
/*创建student表*/
CREATE TABLE student(`name` VARCHAR(20),`password` VARCHAR(20))
/*进入测试数据*/
INSERT INTO student VALUES('zhangsan','3333')
INSERT INTO student VALUES('lisi','4444')
INSERT INTO student VALUES('wangwu','5555')
INSERT INTO student VALUES('zhaoliu','6666')
再次重申一遍,操作之前一定要先导入相应的驱动包。不然的话就会出现ClassNotFoundException 。
为了操作方便,先创建Student实体类用于封装数据。
package com.gk.jdbc.bean;
public class Student {
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "Student [name=" + name + ", password=" + password + "]";
}
}
package com.gk.jdbc.demo;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.gk.jdbc.bean.Student;
public class JdbcDemo {
public static void fun() {
try {
// 加载驱动
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
/*
* MySQL的URL一般格式,也可以在后面带上参数,
* 比如带上与字符编码有关的参数:
* "jdbc:mysql://localhost:3306/stu?useUnicode=true&characterEncoding=utf8"
*/
String url = "jdbc:mysql://localhost:3306/stu";
String username = "root"; // 我机器上MySQL的用户名
String password = "lxc123"; // root用户的密码
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
String sql = "select * from student"; // 查询所有学生
try {
// 获取连接
connection = DriverManager.getConnection(url, username, password);
// 获取statement
statement = connection.createStatement();
// 执行SQL语句
resultSet = statement.executeQuery(sql);
// 处理结果集
while(resultSet.next()){
Student student = new Student();
student.setName(resultSet.getString("name"));
student.setPassword(resultSet.getString("password"));
System.out.println(student);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally{
// 释放资源,后获取的先释放
if (resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (statement != null){
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (connection != null){
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
public static void main(String[] args) {
fun();
}
}
上面程序(使用statement)虽然能得到结果,但是如果要多次运行的话,效率却很低;也不安全,可能会有SQL攻击。于是就有了preparedStatement,preparedStatement是statement接口的子接口。它的强大之处在于能提高效率和防止SQL注入。
什么是SQL注入呢?百度百科给出的解释是:所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。
下面来演示SQL注入,为了演示方便,下面所有例子直接把异常给抛出。因为每次都要获得连接与释放资源,所以把共性的东西抽取出来写一个工具类来方便我们的操作。
package com.gk.jdbc.utils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* JdbcUtils的作用主要是获取连接和释放资源
* @author lxc
*/
public class JdbcUtils {
/**
* 加载驱动,由于驱动只需加载一次,所以放static代码块是比较合适的
*/
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new RuntimeException("Load drive failed...");
}
}
/**
* 获取连接
* @return java.sql.Connection
*/
public static Connection getConnection() {
String url = "jdbc:mysql://localhost:3306/stu?useUnicode=true&characterEncoding=utf8";
String username = "root";
String password = "lxc123";
Connection connection = null;
try {
connection = DriverManager.getConnection(url, username, password);
} catch (SQLException e) {
throw new RuntimeException("Access connection failed...");
}
return connection;
}
/**
* 释放资源
* @param connection
* @param statement
* @param resultSet
*/
public static void close(ResultSet resultSet,
Statement statement,
Connection connection){
if (resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (statement != null){
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (connection != null){
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
SQL注入演示
package com.gk.jdbc.demo;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import com.gk.jdbc.utils.JdbcUtils;
public class SqlInjection {
public static void main(String[] args) throws Exception {
login("1111' or '1'='1","111' or '1'='1");
}
public static void login(String name, String password) throws Exception{
Connection connection= JdbcUtils.getConnection();
String sql = "select * from student where " +
"name='" + name + "'and password='" + password + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
if(resultSet.next()){
System.out.println("Login successful...");
}else{
System.out.println("Logon failed...");
}
JdbcUtils.close(resultSet, statement, connection);
}
}
注意,数据库中并没有name="1111' or '1'='1",password="111' or '1'='1"的学生,但还是登陆成功了,这是为什么呢?我们来看看拼接之后的SQL语句:SELECT * FROM student WHERE NAME='1111' OR '1'='1'AND PASSWORD='111' OR '1'='1',懂SQL语句的一看便知,由于人为的加上OR '1'='1',所以不管怎样,这条SQL语句总能查询成功。使用preparedStatement就能有效的避免这种情况。
package com.gk.jdbc.demo;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import com.gk.jdbc.utils.JdbcUtils;
public class SqlInjection {
public static void main(String[] args) throws Exception {
login2("1111' or '1'='1","111' or '1'='1");
}
public static void login2(String name, String password) throws Exception{
Connection connection = JdbcUtils.getConnection();
// 给出SQL模板
String sql = "select * from student where name=? and password=?";
PreparedStatement preparedStatement =
connection.prepareStatement(sql);
preparedStatement.setString(1, name); // 给sql语句的第一个?赋值
preparedStatement.setString(2, password); // 给sql语句的第二个?赋值
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()){
System.out.println("Login succeed...");
}else{
System.out.println("Logon failed...");
}
JdbcUtils.close(resultSet, statement, connection);
}
}
同样的登录语句,换成了preparedStatement就显示Logon failed...了,也就是说preparedStatement完美的解决了这个问题。除此之外,上面还提到的另一个强大之处是它能提高效率。
其实preparedStatement具有预编译功能,在创建preparedStatement的时候让它与一条SQL模板绑定在一起(connection.prepareStatement(sql)),数据库得到这个SQL模板之后先校验有没有语法错误,如果没有再对其进行编译。然后执行的时候我们再调用prepareStatement的setXxx(...)系列方法给SQL模板中的?赋值。当以后再执行这条SQL语句的时候数据库就不用再次校验语法,也不用再次编译,而是直接执行。由于重复使用了同一个编译过的模板,这就大大节省了运行时间,也是prepareStatement高效的原因了。所以还是建议优先使用prepareStatement而不是statement。
如果要调用存储过程的话使用CallableStatement,这里就不给出具体的例子了,可以参考这篇文章:http://blog.youkuaiyun.com/luanlouis/article/details/31376041。
下面用preparedStatement写一个完整的CRUD(增删改查)供大家参考,所有的代码都经本人测试可以运行。
package com.gk.jdbc.demo;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.gk.jdbc.bean.Student;
import com.gk.jdbc.utils.JdbcUtils;
public class CRUD {
/**
* 查找所有学生
* @return
*/
public static List<Student> findAll() throws SQLException{
List<Student> students = new ArrayList<Student>();
Connection connection = JdbcUtils.getConnection(); // 获取连接
String sql = "select * from student"; // SQL模板
PreparedStatement preparedStatement = connection.prepareStatement(sql); // 获取preparedStatement
ResultSet resultSet = preparedStatement.executeQuery(); // 执行,注意与statement的不同,如果是statement的话,此方法要有一个SQL语句。
while(resultSet.next()){ // 处理结果集
Student student = new Student();
student.setName(resultSet.getString("name"));
student.setPassword(resultSet.getString("password"));
students.add(student);
}
JdbcUtils.close(resultSet, preparedStatement, connection); // 释放资源
return students;
}
/**
* 根据学生名字查找学生
* @param name
* @return
*/
public static Student findByName(String name) throws SQLException{
Connection connection = JdbcUtils.getConnection(); // 获取连接
String sql = "select * from student where name=?"; // SQL模板
PreparedStatement preparedStatement =connection.prepareStatement(sql); // 获取prepareStatement
preparedStatement.setString(1, name); // 给SQL模板中的?号赋值
ResultSet resultSet = preparedStatement.executeQuery(); // 执行
if (resultSet.next()){ // 处理结果集
Student student = new Student();
student.setName(resultSet.getString("name"));
student.setPassword(resultSet.getString("password"));
return student;
}
JdbcUtils.close(resultSet, preparedStatement, connection);
return null;
}
/**
* 添加学生操作
* @param student
* @return
* @throws SQLException
*/
public static int add(Student student) throws SQLException{
int result = 0;
Connection connection = JdbcUtils.getConnection();
String sql = "insert into student values(?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, student.getName()); // 给SQL模板的第一个?赋值
preparedStatement.setString(2, student.getPassword()); // 给SQL模板的第二个?赋值
result = preparedStatement.executeUpdate(); // 注意与statement的不同,如果是statement的话,此方法要有一个SQL语句。
JdbcUtils.close(null, preparedStatement, connection);
return result;
}
/**
* 修改密码
* @param student
* @return
* @throws SQLException
*/
public static int modify(Student student) throws SQLException{
int result = 0;
Connection connection = JdbcUtils.getConnection();
String sql = "update student set password=? where name=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, student.getPassword());
preparedStatement.setString(2, student.getName());
result = preparedStatement.executeUpdate();
JdbcUtils.close(null, preparedStatement, connection);
return result;
}
/**
* 删除指定学生
* @param student
* @return
* @throws SQLException
*/
public static int delete(Student student) throws SQLException{
int result = 0;
Connection connection = JdbcUtils.getConnection();
String sql = "delete from student where name=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, student.getName());
result = preparedStatement.executeUpdate();
JdbcUtils.close(null, preparedStatement, connection);
return result;
}
public static void main(String[] args) throws SQLException{
System.out.println("查找所有学生");
List<Student> list = findAll();
if(list.size() > 0){
for (Student student : list){
System.out.println(student);
}
}
System.out.println("------------------------------------------------");
System.out.println("根据学生名字查找该学生");
Student student = findByName("sunqi");
System.out.println(student);
System.out.println("------------------------------------------------");
/*System.out.println("添加学生");
Student student2 = new Student();
student2.setName("sunqi");
student2.setPassword("7777");
int result2 = add(student2);
System.out.println("add --- result:" + result2);
if (result2 != 0){
System.out.println("添加成功...");
}else{
System.out.println("添加失败...");
}*/
System.out.println("------------------------------------------------");
/*System.out.println("修改学生密码");
Student student3 = new Student();
student3.setName("sunqi");
student3.setPassword("1234567");
int result3 = modify(student3);
System.out.println("modify --- result:" + result3);
if (result3 != 0){
System.out.println("修改成功...");
}else{
System.out.println("修改失败...");
}*/
System.out.println("------------------------------------------------");
/*System.out.println("删除学生");
Student student4 = new Student();
student4.setName("sunqi");
student4.setPassword("1234567");
int result4 = delete(student4);
System.out.println("delete --- result:" + result4);
if (result4 != 0){
System.out.println("删除成功...");
}else{
System.out.println("删除失败...");
}*/
}
}