关于JDBC操作的小小总结

 JDBCJava DataBase Connectivityjava数据库连接,是一种用于执行SQL语句的Java APIApplication Programming Interface),可以为多种关系型数据库提供统一访问,它由一组用Java语言编写的类和接口组成。


 有了JDBC,在我们的程序中向各种关系型数据库发送SQL语句就是一件很容易的事了。也就是说,程序员不必为了访问不同的数据库而编写不同的应用程序了,只需用JDBC API写一个程序就可以了,它可向相应的数据库发送SQL调用。同时,将Java语言和JDBC结合起来使程序员不必为不同的平台编写不同的应用程序,只须写一遍程序就可以让它在任何平台上运行,这也是Java语言“编写一次,到处运行”的优势。


  JDBC原理图 

 

 在这里驱动程序指的就是各个数据库厂商遵循JDBC规范的,可以访问自己数据库的API。而JDBC是底层接口,各个数据库驱动才是JDBC接口的实现。也就是说,没有驱动就无法完成对数据库的连接。所以,每次要使用JDBC的时候,记得的第一件事就是导包(相应数据库的驱动包)。


JDBC核心接口的介绍


 本文主要简单的介绍JDBC一些核心接口(类),想系统了解JDBC整个体系的还是建议阅读官方文档。如果可以的话,希望读者能去了解一下JDBC的历史。我觉得学一门语言或技术,了解其发展史,才能更好地运用,也就能更好把握未来的方向。


 JDBC常用的接口(类)有DriverManagerConnectionStatement(PreparedStatement)ResultSet。这些接口(类)都是在java.sql包下,导包的时候注意不要导错。


 DriverManager:在JDBC中,DriverManager主要有这两方面的作用:1、注册驱动,也就是让JDBC知道要使用哪个数据库驱动;2、获取连接(Connection),得到了连接,说明已经和数据库连接上了。说白了,就是可以开始操作数据库了。


 ConnectionConnection表示连接,JDBC与数据库的通讯都是通过Connection展开的。


 StatementStatement是用来向数据库发送SQL语句的,这样数据库就会执行程序中发送过去的SQL语句。 


 PreparedStatementPreparedStatementStatement的子接口,具有预编译功能的Statement,多次执行的效率比Statement高,也能防止SQL注入。


 ResultSetResultSet表示查询结果集,只有在执行查询操作后才会有结果集的产生。结果集是一个二维的表,有行有列。


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),其中usernamepassword是登录数据库时的用户名和密码;url就类似于一个网址,用于JDBC找到所连接的数据库。其格式为:jdbc:subprotocol:subname。不管连接什么数据库,Jdbc都是固定的;subprotocol就是各个数据库厂商所注册的JDBC协议名字了,比如oraclemysql等;而subname只和具体的JDBC驱动提供厂商有关,所以我们需要了解各个数据库厂商的要求,MySQLsubname由数据库服务器IP地址、端口号和数据库名称组成。

 常见数据库的URL

 MySQLjdbc:mysql://dbip:port/databasename

 Oraclejdbc:oracle:thin:@dbip:port:databasename

 SQL Serverjdbc:microsoft:sqlserver://dbip:port;DatabaseName=databasename


 获取statement获得连接之后,一个重要的作用就是可以通过连接得到statement对象:connection.createStatement()


 执行SQL语句我们大费周章得到statement,那么,statement有什么作用呢?statementint executeUpdate(String sql)方法可以执行INSERTUPDATEDELETE操作,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攻击。于是就有了preparedStatementpreparedStatementstatement接口的子接口。它的强大之处在于能提高效率和防止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模板之后先校验有没有语法错误,如果没有再对其进行编译。然后执行的时候我们再调用prepareStatementsetXxx(...)系列方法给SQL模板中的?赋值。当以后再执行这条SQL语句的时候数据库就不用再次校验语法,也不用再次编译,而是直接执行。由于重复使用了同一个编译过的模板,这就大大节省了运行时间,也是prepareStatement高效的原因了。所以还是建议优先使用prepareStatement而不是statement

 如果要调用存储过程的话使用CallableStatement,这里就不给出具体的例子了,可以参考这篇文章:http://blog.csdn.net/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("删除失败...");
		}*/
	}

}




























 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值