SQL学习笔记之数据库课题(四):浅谈JDBC用法

SQL学习笔记之数据库专题(四):浅谈JDBC用法

数据库厂商提供的用来操作数据库用的jar包就是数据库驱动。各个厂商如果提供各自的数据库驱动的话会导致开发人员学习成本太高,所以sun公司提供了一套数据库驱动应该遵循的接口规范,这套规范就叫做JDBC,本质上是很多的接口。简而言之,JDBC就是一套操作数据库的接口规范,由于所有的数据库驱动都遵循JDBC规范,我们在学习和使用数据库时只要学习JDBC中的接口就可以了。

组成JDBC的2个包:java.sql,javax.sql,开发JDBC应用需要以上2个包的支持外,还需要导入相应JDBC的数据库实现(即数据库驱动)。

我们先看看JDCB的使用步骤:

*在数据库中建立好表
*在程序中导入数据库驱动包
1.注册数据库驱动
DriverManager.registerDriver(new Driver());//缺点一:观察mysqlDriver源码发现此方法导致了数据库驱动被注册了两次。缺点二:整个程序域mysql数据库驱动绑定增加了耦合性
Class.forName(“com.mysql.jdbc.Driver”);
2.获取连接
DriverManager.getConnection(url, user, password);
~url的写法:
Oracle写法:jdbc:oracle:thin:@localhost:1521:sid
SqlServer—jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=sid
MySql—jdbc:mysql://localhost:3306/sid
~url可以接的参数
user、password
useUnicode=true&characterEncoding=UTF-8


3.获取传输器
createStatement():创建向数据库发送sql的statement对象。
prepareStatement(sql) :创建向数据库发送预编译sql的PrepareSatement对象。
4.利用传输器执行sql语句获取结果集
executeQuery(String sql) :用于向数据发送查询语句。
executeUpdate(String sql):用于向数据库发送insert、update或delete语句
execute(String sql):用于向数据库发送任意sql语句


5.遍历结果集取出结构
ResultSet以表的样式在内存中保存了查询结果,其中还维护了一个游标,最开始的时候游标在第一行之前,每调用一次next()方法就试图下移一行,如果移动成功返回true;
ResultSet还提供了很多个Get方法,用来获取查询结果中的不同类型的数据
除了next方法,还有以下方法可以用来遍历结果集:
next():移动到下一行
Previous():移动到前一行
absolute(int row):移动到指定行
beforeFirst():移动resultSet的最前面。
afterLast() :移动到resultSet的最后面。
6.释放资源
conn是一个有限的资源,用完立即要释放表
stat占用内存,所以使用完后也要释放
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;
}
}


再看一个简单的例子:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class FreedomJDBCDemo1 {
	public static void main(String[] args){
		Connection conn = null;
		Statement stat = null;
		ResultSet rs = null;
		try{
			//1.注册数据库驱动
			//--由于mysql在Driver类的实现中自己注册了一次,而我们又注册了一次,于是会导致MySql驱动被注册两次
			//--创建MySql的Driver对象时,导致了程序和具体的Mysql驱动绑死在了一起,在切换数据库时需要改动java代码
			//DriverManager.registerDriver(new Driver());
			Class.forName("com.mysql.jdbc.Driver");
			//2.获取数据库连接,这里使用简洁写法,可以省略掉localhost和端口
			conn = DriverManager.getConnection("jdbc:mysql:///freedom?user=root&password=root");
			//3.获取传输器对象
			stat = conn.createStatement();
			//4.利用传输器传输sql语句到数据库中执行,获取结果集对象
			rs = stat.executeQuery("select * from user");
			//5.遍历结果集获取查询结果
			while(rs.next()){
				String name = rs.getString("name");
				System.out.println(name);
			}
		}catch (Exception e) {
			e.printStackTrace();
		}finally{
			//6.关闭资源
			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;
				}
			}
		}
	}
}

我们可以将上述例子封装一下,将公共部分比如链接数据库,关闭资源等操作,封装起来作为一个工具类。我们可以将路径、用户民和密码用适配好。以后只需要修改配置文件即可。

配置文件内容如下:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql:///freedom
user=root
password=root


看工具类:

import java.io.FileReader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class JDBCUtils {
	private static Properties prop = null;
	private JDBCUtils() {
	}
	static{
		try{
			prop = new Properties();
			prop.load(new FileReader(JDBCUtils.class.getClassLoader().getResource("config.properties").getPath()));
		}catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 获取连接
	 * @throws ClassNotFoundException 
	 * @throws SQLException 
	 */
	public static Connection getConn() throws ClassNotFoundException, SQLException{
		// 1.注册数据库驱动
		Class.forName(prop.getProperty("driver"));
		// 2.获取连接
		return DriverManager.getConnection(prop.getProperty("url"), prop.getProperty("user"), prop.getProperty("password"));
		
	}
	/**
	 * 关闭连接
	 */
	public static void close(ResultSet rs, Statement stat,Connection conn){
		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;
			}
		}
	
	}
}

我们再看使用工具类简化后的操作例子:

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;

import org.junit.Test;

import com.itheima.util.JDBCUtils;

public class FreedomJDBC {

	/**
	 * @Title: add
	 * @Description: 增加
	 * @throws
	 */
	@Test
	public void add() {
		Connection conn = null;
		Statement stat = null;
		try {
			// 1.注册数据库驱动
			// 2.获取连接
			conn = JDBCUtils.getConn();
			// 3.获取传输器对象
			stat = conn.createStatement();
			// 4.执行sql语句
			int count = stat
					.executeUpdate("insert into user values (null,'freedom','123456','freedom@qq.com','2012-01-01')");
			// 5.处理结果
			if (count > 0) {
				System.out.println("执行成功!影响到的行数为" + count);
			} else {
				System.out.println("执行失败!!");
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 6.关闭资源
			JDBCUtils.close(null, stat, conn);
		}
	}

	/**
	 * @Title: delete
	 * @Description:删除
	 * @throws
	 */
	@Test
	public void delete() {
		Connection conn = null;
		Statement stat = null;
		ResultSet rs = null;
		try {
			conn = JDBCUtils.getConn();
			stat = conn.createStatement();
			stat.executeUpdate("delete from user where name='freedom'");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtils.close(rs, stat, conn);
		}
	}

	/**
	 * @Title: update
	 * @Description: 更新
	 * @throws
	 */
	@Test
	public void update() {
		Connection conn = null;
		Statement stat = null;
		try {
			conn = JDBCUtils.getConn();
			stat = conn.createStatement();
			stat
					.executeUpdate("update user set password=666 where name='freedom'");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtils.close(null, stat, conn);
		}
	}

	/**
	 * @Title: find
	 * @Description: 查询
	 * @throws
	 */
	@Test
	public void find() {
		Connection conn = null;
		Statement stat = null;
		ResultSet rs = null;
		try {
			conn = JDBCUtils.getConn();
			stat = conn.createStatement();
			rs = stat.executeQuery("select * from user where name='freedom'");
			while (rs.next()) {
				String name = rs.getString("name");
				String password = rs.getString("password");
				System.out.println(name + ":" + password);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtils.close(rs, stat, conn);
		}
	}
}

SQL注入攻击:
由于dao中执行的SQL语句是拼接出来的,其中有一部分内容是由用户从客户端传入,所以当用户传入的数据中包含sql关键字时,就有可能通过这些关键字改变sql语句的语义,从而执行一些特殊的操作,这样的攻击方式就叫做sql注入攻击

PreparedStatement利用预编译的机制将sql语句的主干和参数分别传输给数据库服务器,从而使数据库分辨的出哪些是sql语句的主干哪些是参数,这样一来即使参数中带了sql的关键字,数据库服务器也仅仅将他当作参数值使用,关键字不会起作用,从而从原理上防止了sql注入的问题


PreparedStatement主要有如下的三个优点:
~1.可以防止sql注入
~2.由于使用了预编译机制,执行的效率要高于Statement
~3.sql语句使用?形式替代参数,然后再用方法设置?的值,比起拼接字符串,代码更加优雅.

看使用代码:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

import com.itheima.util.JDBCUtils;

public class JDBCDemo3 {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			conn = JDBCUtils.getConn();
			ps = conn
					.prepareStatement("select * from user where name=? and password=?");
			ps.setString(1, "freedom");
			ps.setString(2, "666");

			rs = ps.executeQuery();
			while (rs.next()) {
				System.out.println(rs.getString("email"));
			}

		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtils.close(rs, ps, conn);
		}
	}
}

JDBC其实还支持大文本和大二进制的处理,但是实际开发中我们很少使用到,一般情况下,处理这些大数据可能还需要修改虚拟机的启动内存大小。这里就讲讲大二进制数据组的存储和读取吧。

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

import org.junit.Test;

import com.itheima.util.JDBCUtils;

/*
 * 数据库中创建一个表
 create table blobdemo(
 id int primary key auto_increment,
 name varchar(100),
 content MEDIUMBLOB
 );
 */
public class BlobDemo1 {

	@Test
	public void addBlob() {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			conn = JDBCUtils.getConn();
			ps = conn
					.prepareStatement("insert into blobdemo values (null,?,?)");
			ps.setString(1, "高达.mp3");
			File file = new File("1.mp3");
			ps.setBinaryStream(2, new FileInputStream(file), (int) file
					.length());
			ps.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtils.close(rs, ps, conn);
		}
	}

	@Test
	public void findBlob() {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			conn = JDBCUtils.getConn();
			ps = conn.prepareStatement("select * from blobdemo");
			rs = ps.executeQuery();
			while (rs.next()) {
				String name = rs.getString("name");
				InputStream in = rs.getBinaryStream("content");
				OutputStream out = new FileOutputStream(name);

				byte[] bs = new byte[1024];
				int i = 0;
				while ((i = in.read(bs)) != -1) {
					out.write(bs, 0, i);
				}
				in.close();
				out.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			JDBCUtils.close(rs, ps, conn);
		}
	}
}

有时候,当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率。JDBC的批处理有两种方式,各有各的优缺点。我们先看第一种方式:

import java.sql.Connection;
import java.sql.Statement;

import com.itheima.util.JDBCUtils;

/*
	create database day10batch;
	use day10batch;
	create table batchDemo(
		id int primary key auto_increment,
		name varchar(20)
	);
	insert into batchDemo values(null,'aaaa');
	insert into batchDemo values(null,'bbb');
	insert into batchDemo values(null,'cc');
	insert into batchDemo values(null,'d');
 */
/*
	Statement方式执行批处理:
		优点:可以执行多条不同结构的sql语句
		缺点:没有使用预编译机制,效率低下,如果要执行多条结构相同仅仅参数不同的sql时,仍然需要写多次sql语句的主干
 */
public class StatementBatch {
	public static void main(String[] args) {
		Connection conn = null;
		Statement stat = null;
		try{
			conn = JDBCUtils.getConn();
			stat = conn.createStatement();
			stat.addBatch("create database day10batch");
			stat.addBatch("use day10batch");
			stat.addBatch("create table batchDemo("+
								"id int primary key auto_increment,"+
								"name varchar(20)"+
							")");
			stat.addBatch("insert into batchDemo values(null,'aaaa')");
			stat.addBatch("insert into batchDemo values(null,'bbb')");
			stat.addBatch("insert into batchDemo values(null,'cc')");
			stat.addBatch("insert into batchDemo values(null,'d')");
			
			stat.executeBatch();
		}catch (Exception e) {
			e.printStackTrace();
		}finally{
			JDBCUtils.close(null, stat, conn);
		}
	}
}

再看第二种方式:

import java.sql.Connection;
import java.sql.PreparedStatement;

import com.itheima.util.JDBCUtils;
/*
 	create table psbatch(
 		id int primary key auto_increment,
 		name varchar(30)
 	);
 */
/*
	prparedStatement 方式实现的批处理:
		优点:有预编译机制,效率比较高.执行多条结构相同,参数不同的sql时,不需要重复写sql的主干
		缺点:只能执行主干相同参数不同的sql,没有办法在一个批中加入结构不同的sql
 */
public class PSBatch {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement ps = null;
		try{
			conn = JDBCUtils.getConn();
			ps = conn.prepareStatement("insert into psbatch values(null,?)");
			
			for(int i=1;i<=100000;i++){
				ps.setString(1, "name"+i);
				ps.addBatch();
				
				if(i%1000==0){
					ps.executeBatch();
					ps.clearBatch();
				}
			}
			ps.executeBatch();
			
		}catch (Exception e) {
			e.printStackTrace();
		}finally{
			JDBCUtils.close(null, ps, conn);
		}
	}
}

好了,JDBC的基础用法到此讲解完毕,希望能够帮助到看到此文的人。