JDBC简介
数据库驱动
数据库厂商为了方便开发人员从程序中操作数据库而提供的一套jar包,导入这个jar包就可以调用其中的方法操作数据库,这样的jar包就叫做数据库驱动。
JDBC
SUN公司为了简化、统一对数据库的操作,定义了一套Java操作数据库的规范,称之为JDBC。
JDBC全称为:Java DataBase Connectivity(java数据库连接),它主要由接口组成。
组成JDBC的2个包(均包含在J2SE里):
java.sql
javax.sql
开发JDBC应用除了需要以上2个包的支持外,还需要导入相应JDBC的数据库实现(即数据库驱动)。
JDBC快速入门
首先利用cmd命令建立一个数据库,新建一个user表并写入三条数据。代码如下:
create table user(
id int primary key auto_increment,
name varchar(40),
password varchar(40),
email varchar(60),
birthday date
)character set utf8 collate utf8_general_ci;
insert into user(name,password,email,birthday) values('zs','123456','zs@sina.com','1980-12-04');
insert into user(name,password,email,birthday) values('lisi','123456','lisi@sina.com','1981-12-04');
insert into user(name,password,email,birthday) values('wangwu','123456','wangwu@sina.com','1979-12-04');
建好数据库和表之后,再新建一个Java工程,工程建好后需要新建一个lib文件夹导入MySQL的驱动包mysql-connector-java-5.0.8-bin.jar
,并添加到BuildPath中。
准备工作完成以后,新建一个类JDBCDemo1.java
,用来实现对user表的查询。基本的代码如下:
package me.zipstream.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.mysql.jdbc.Driver;
public class JDBCDemo1 {
public static void main(String[] args) throws SQLException {
//1,注册数据库驱动
DriverManager.registerDriver(new Driver());
//2,获取数据库连接
Connection conn =
DriverManager.getConnection("jdbc:mysql://localhost:3306/day10", "root", "root");
//3,获取传输器对象
Statement statement = conn.createStatement();
//4,利用传输器对象传输SQL语句到数据库中执行,获取结果集对象
ResultSet rs = statement.executeQuery("select * from user");
//5,遍历结果集获取查询结果
while (rs.next()){
String name = rs.getString("name");
System.out.println(name);
}
//6,关闭资源
rs.close();
statement.close();
conn.close();
}
}
程序的细节和优化
DriverManager
JDBC程序中的DriverManager用于加载驱动,并创建与数据库的连接,这个API的常用方法有:
DriverManager.registerDriver(new Driver());
DriverManager.getConnection(String url, String user, String password);
注意:在实际开发中并不推荐采用registerDriver方法注册驱动。原因有二:
1、查看Driver的源代码可以看到,如果采用此种方式,会导致驱动程序注册两次,也就是在内存中会有两个Driver对象。
2、程序依赖mysql的api,脱离mysql的jar包,程序将无法编译,将来程序切换底层数据库将会非常麻烦。
推荐方式:
Class.forName(“com.mysql.jdbc.Driver”);
采用此种方式不会导致驱动对象在内存中重复出现,并且程序仅仅只需要一个字符串,不需要依赖具体的驱动,使程序的灵活性更高。
同样,在开发中也不建议采用具体的驱动类型指向getConnection方法返回的connection对象。正所谓面向接口编程,在导包的时候尽量不要导入与具体数据库有关的实现类,而是要导入接口。
附: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 {
// Required for Class.forName().newInstance()
}
}
数据库URL
URL用于标识数据库的位置,程序员通过URL地址告诉JDBC程序连接哪个数据库,URL的写法为:
jdbc:mysql:[]//localhost:3306/test ?参数名:参数值
常用数据库URL地址的写法:
Oracle写法:jdbc:oracle:thin:@localhost:1521:sid
SqlServer写法:jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=sid
MySql写法:jdbc:mysql://localhost:3306/sid
Mysql的url地址的简写形式: jdbc:mysql:///sid
url可以接的参数
user
、password
useUnicode=true&characterEncoding=UTF-8
Connection
JDBC程序中的Connection,代表数据库的连接,Connection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过Connection对象完成的,这个对象的常用方法有:
createStatement()
:创建向数据库发送sql的statement对象。prepareStatement(sql)
:创建向数据库发送预编译sql的PrepareSatement对象。prepareCall(sql)
:创建执行存储过程的callableStatement对象。setAutoCommit(boolean.autoCommit)
:设置事务是否自动提交。commit()
:在链接上提交事务。rollback()
:在此链接上回滚事务。
Statement
JDBC程序中的Statement对象用于向数据库发送SQL语句,Statement对象常用方法有:
- executeQuery(String.sql)
:用于向数据发送查询语句。
- executeUpdate(String.sql)
:用于向数据库发送insert、update或delete语句
- execute(String sql)
:用于向数据库发送任意sql语句
- addBatch(String.sql)
:把多条sql语句放到一个批处理中。
- executeBatch()
:向数据库发送一批sql语句执行。
ResultSet
JDBC程序中的ResultSet用于代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式。ResultSet对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next()
方法,可以使游标指向具体的数据行,进行调用方法获取该行的数据。
ResultSet用于封装执行结果,所以该对象提供的都是用于获取数据的get方法:
获取任意类型的数据:
getObject(int index)
getObject(string columnName)
其实以上两个方法主要是给框架设计者使用的。
获取指定类型的数据,例如:
getString(int index)
getString(String columnName)
常用的数据类型转换表:
ResultSet还提供了对结果集进行滚动的方法(操作游标):
- next()
:移动到下一行
- Previous()
:移动到前一行
- absolute(int row)
:移动到指定行
- beforeFirst()
:移动resultSet的最前面。
- afterLast()
:移动到resultSet的最后面。
释放资源
JDBC程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResultSet, Statement和Connection对象。
特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
为确保资源释放代码能运行,资源释放代码一定要放在finally语句中。
释放时后创建的先释放。
最终优化后的代码
package me.zipstream.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBCDemo1 {
public static void main(String[] args) {
Connection conn = null;
Statement statement = null;
ResultSet rs = null;
try{
//1,注册数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//2,获取数据库连接
conn =
DriverManager.getConnection("jdbc:mysql:///day10?user=root&password=root");
//3,获取传输器对象
statement = conn.createStatement();
//4,利用传输器对象传输SQL语句到数据库中执行,获取结果集对象
rs = statement.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 (statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
} finally{
statement = null;
}
}
if (conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
} finally{
conn = null;
}
}
}
}
}
使用JDBC对数据库进行CRUD
准备工作
为了提高程序的复用性,减少书写重复代码,提高代码的灵活性。新建一个配置文件config.properties
:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql:///day10
user=root
password=root
再新建一个工具类JDBCUtils.java
,实现获取连接和关闭资源的功能。
package me.zipstream.utils;
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{
prop = new Properties();
try {
prop.load(new FileReader(JDBCUtils.class.getClassLoader().getResource("config.properties").getPath()));
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 获取连接
* @return 对数据库的连接
* @throws ClassNotFoundException
* @throws SQLException
*/
public static Connection getConn() throws ClassNotFoundException, SQLException{
Class.forName(prop.getProperty("driver"));
return DriverManager.getConnection(prop.getProperty("url"), prop.getProperty("user"), prop.getProperty("password"));
}
/**
* 关闭资源
* @param rs
* @param st
* @param conn
*/
public static void close(ResultSet rs, Statement st, Connection conn){
if (rs != null){
try {
rs.close();
} catch (Exception e) {
e.printStackTrace();
} finally{
rs = null;
}
}
if (st != null){
try {
st.close();
} catch (Exception e) {
e.printStackTrace();
} finally{
st = null;
}
}
if (conn != null){
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
} finally{
conn = null;
}
}
}
}
CRUD操作-create
想user表中增加一个记录。代码如下:
@Test
public void add(){
Connection conn = null;
Statement st = null;
try {
conn = JDBCUtils.getConn();
st = conn.createStatement();
int count =
st.executeUpdate("insert into user values(null,'zhaoliu','123456','zhaoliu@qq.com','1999-09-09')");
if (count > 0){
System.out.println("操作成功!影响到"+ count +"行数据!");
} else{
System.out.println("操作失败!");
}
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCUtils.close(null, st, conn);
}
}
CRUD操作-updata
@Test
public void update(){
Connection conn = null;
Statement st = null;
try{
conn = JDBCUtils.getConn();
st = conn.createStatement();
st.executeUpdate("update user set password=999 where name='zhaoliu'");
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCUtils.close(null, st, conn);
}
}
CRUD操作-read
@Test
public void find(){
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try{
conn = JDBCUtils.getConn();
st = conn.createStatement();
rs = st.executeQuery("select * from user where name='zhaoliu'");
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, st, conn);
}
}
CRUD操作-delete
@Test
public void delete(){
Connection conn = null;
Statement st = null;
try{
conn = JDBCUtils.getConn();
st = conn.createStatement();
st.executeUpdate("delete from user where name='zhaoliu'");
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCUtils.close(null, st, conn);
}
}
PreparedStatement
SQL注入攻击
SQL 注入是用户利用某些系统没有对输入数据进行充分的检查,从而进行恶意破坏的行为。由于dao中执行的SQL语句是拼接出来的,其中有一部分内容是由用户从客户端传入,所以当用户传入的数据中包含sql关键字时,就有可能通过这些关键字改变sql语句的语义,从而执行一些特殊的操作,这样的攻击方式就叫做sql注入攻击。典型的手段例如:
username' or '1=1
username'#
就可以不输入密码而登录了。
PreparedStatement的使用
PreparedStatement
是Statement
的孩子,不同的是,PreparedStatement
使用预编译机制,在创建PreparedStatement
对象时就需要将sql语句传入,传入的过程中参数要用?
替代,这个过程回导致传入的sql被进行预编译,然后再调用PreparedStatement
的setXXX
将参数设置上去,由于sql语句已经经过了预编译,再传入特殊值也不会起作用了。从而有效防止SQL注入攻击。
代码样例:
package me.zipstream.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import me.zipstream.utils.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, "zs");
ps.setString(2, "123456");
rs = ps.executeQuery();
while (rs.next()){
System.out.println(rs.getString("email"));
}
} catch (Exception e) {
e.printStackTrace();
} finally{
JDBCUtils.close(rs, ps, conn);
}
}
}
使用JDBC处理大数据
在实际开发中,程序需要把大文本Text
或二进制数据Blob
保存到数据库。
注:Text
是mysql叫法,Oracle中叫Clob
。
基本概念:大数据也称之为LOB(Large Objects),LOB又分为:
Clob和Blob
Clob
用于存储大文本。
Blob
用于存储二进制数据,例如图像、声音、二进制文等。
对MySQL而言只有Blob
,而没有Clob
,mysql存储大文本采用的是Text
。
Text
分为:
TINYTEXT
(255)、TEXT
(64k)、MEDIUMTEXT
(16M)和LONGTEXT
(4G)
Blob
分为:
TINYBLOB
(255)、BLOB
(64k)、MEDIUMBLOB
(16M)和LONGBLOB
(4G)
使用JDBC处理大文本
首先在数据库中新建一个表,用来存储一个10.3M的文本数据。代码如下:
create table textdemo(
id int primary key auto_increment,
name varchar(100),
content MEDIUMTEXT
);
这里还需要修改执行程序虚拟机的最大和最小内存以及MySQL数据库存储文件的大小。在IDE的Run Dialog窗口的Argument选项卡中添加:
-Xms64m
-Xmx256m
来修改虚拟机内存。
在MySQL的my.ini文件的[mysqld]
下添加:
max_allowed_packet=64M
修改MySQL存储文件的大小。
大文本的存储
@Test
public void addText(){
Connection conn = null;
PreparedStatement ps = null;
try{
conn = JDBCUtils.getConn();
ps = conn.prepareStatement("insert into textdemo values(null, ?, ?)");
ps.setString(1, "藏地密码");
File file = new File("1.txt");
ps.setCharacterStream(2, new FileReader(file), (int) file.length());
ps.executeUpdate();
} catch (Exception e){
e.printStackTrace();
} finally{
JDBCUtils.close(null, ps, conn);
}
}
大文本的查询
@Test
public void findText(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
conn = JDBCUtils.getConn();
ps = conn.prepareStatement("select * from textdemo where id=?");
ps.setInt(1, 1);
rs = ps.executeQuery();
while (rs.next()){
String filename = rs.getString("name");
Reader reader = rs.getCharacterStream("content");
Writer writer = new FileWriter(filename);
char[] cs = new char[1024];
int i = 0;
while ((i=reader.read(cs))!=-1){
writer.write(cs, 0, i);
}
reader.close();
writer.close();
}
} catch (Exception e){
e.printStackTrace();
} finally{
JDBCUtils.close(rs, ps, conn);
}
}
使用JDBC处理大二进制数据
方法与处理大文本并无两样。只要把setCharacterStream
和getCharacterStream
替换为setBinaryStream
和getBinaryStream
就可以了。
使用JDBC进行批处理
业务场景:当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率。
Statement方式执行批处理
优点:可以执行多条不同结构的sql语句
缺点:没有使用预编译机制,效率低下,如果要执行多条结构相同仅仅参数不同的sql时,仍然需要写多次sql语句的主干
例子:一段代码完成创建数据库新建表和插入数据的操作
package me.zipstream.batch;
import java.sql.Connection;
import java.sql.Statement;
import me.zipstream.utils.JDBCUtils;
public class StatementBatch {
public static void main(String[] args) {
Connection conn = null;
Statement st = null;
try{
conn = JDBCUtils.getConn();
st = conn.createStatement();
st.addBatch("create database day10batch");
st.addBatch("use day10batch");
st.addBatch("create table batchDemo("+
"id int primary key auto_increment,"+
"name varchar(20)"+
")");
st.addBatch("insert into batchDemo values(null,'aaaa')");
st.addBatch("insert into batchDemo values(null,'bbb')");
st.addBatch("insert into batchDemo values(null,'cc')");
st.addBatch("insert into batchDemo values(null,'d')");
st.executeBatch();
} catch (Exception e){
e.printStackTrace();
} finally{
JDBCUtils.close(null, st, conn);
}
}
}
prparedStatement方式执行批处理
优点:有预编译机制,效率比较高.执行多条结构相同,参数不同的sql时,不需要重复写sql的主干
缺点:只能执行主干相同参数不同的sql,没有办法在一个批中加入结构不同的sql
例子:向psbatch表中存储10000条数据。
package me.zipstream.batch;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import me.zipstream.utils.JDBCUtils;
public class PreparedStatementBatch {
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<=10000; 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);
}
}
}