-
JDBC
目录
-
JDBC引入
为了能让程序员利用java程序操作数据库,数据库厂商提供了一套jar包,通过导入这个jar 包就可以直接调用其中的方法操作数据库,这个jar包称之为驱动。
mysql-connector-java-5.0.8-bin.jar
行业中有很多种的数据库,要使用这么多数据库需要学习很多数据库驱动,对于程序员来说,学习成本非常高, 想要让java程序兼容数据库,所有的数据库驱动都实现了jdbc这套接口。
-
JDBC实现
- 注册数据库驱动
- 获取数据库连接
- 创建传输器
- 传输sql并返回结果
- 遍历结果
- 关闭资源
package cn.zyj.jdbc;
import com.mysql.jdbc.Driver;
import java.sql.*;
public class fxDemo1 {
public static void main(String[] args) {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
//注册驱动
DriverManager.registerDriver(new Driver());
//获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb5", "root", "");
//创建传输器
stat = conn.createStatement();
//传输sql并返回结果
rs = stat.executeQuery("select * from exam");
//遍历结果
while (rs.next()) {
int id = rs.getInt(1);
String name = rs.getString(2);
System.out.println("id=" + id + "name=" + name);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
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 (rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
rs=null;
}
}
}
}
}
-
程序详解
-
DriverManager:
用于加载驱动,并创建与数据库的连接,这个API的常用方法:
DriverManager.registerDriver(new Driver());
DriverManager.getConnection(url,user,password);
注意(在实际开发中并不推荐采用registerDriver方法注册驱动)原因有二:
- 查看Driver的源代码可以看到,如果采用此种方式,会导致驱动程序注册两次,也就是在内存中会有两个Driver对象。
- 程序依赖mysql的api,脱离mysql的jar包,程序将无法编译,将来程序切换底层数据库将会非常麻烦。
推荐方式:Class.forName("com.mysql.jdbc.Driver");
- 采用此种方式不会导致驱动对象在内存中重复出现,并且采用此种方式,程序仅仅只需要一个字符串,不需要依赖具体的驱动,使程序的灵活性更高。
- 同样,在开发中也不建议采用具体的驱动类型指向getConnection方法返回的connection对象。
package cn.zyj.jdbc;
import java.sql.*;
public class fxDemo2 {
public static void main(String[] args) {
//定义
Connection conn=null;
Statement stat=null;
ResultSet rs= null;
try {
//加载注册驱动
Class.forName("com.mysql.jdbc.Driver");
//获取数据库连接
conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb5","root","");
//创建传输器
stat=conn.createStatement();
//传输sql并返回结果
rs=stat.executeQuery("select * from exam");
while (rs.next()){
int id = rs.getInt(1);
String name = rs.getString(2);
System.out.println("id="+id+"name="+name);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
//这边写了一个工具类进行资源关闭,关闭方法和之前的代码相似
JDBCutils.close(conn,stat,rs);
}
}
}
-
数据库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
常用属性:useUnicode=true&characterEncoding=UTF-8
-
Connection
Jdbc程序中的Connection,它用于代表数据库的链接,Connection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过connection对象完成的,这个对象的常用方法:
createStatement():创建向数据库发送sql的statement对象。
prepareStatement(sql) :创建向数据库发送预编译sql的PrepareSatement对象。
prepareCall(sql):创建执行存储过程的callableStatement对象。
setAutoCommit(boolean autoCommit):设置事务是否自动提交。
setAutoCommit(boolean autoCommit):设置事务是否自动提交。
commit() :在链接上提交事务。rollback() :在此链接上回滚事务。
-
Statement
Jdbc程序中的Statement对象用于向数据库发送SQL语句, Statement对象常用方法:
executeQuery(String sql) :用于向数据发送查询语句。
executeUpdate(String sql):用于向数据库发送insert、update或delete语句
execute(String sql):用于向数据库发送任意sql语句
-
ResultSet
○ Jdbc程序中的ResultSet用于代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式。ResultSet 对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next() 方法,可以使游标指向具体的数据行,进行调用方法获取该行的数据。
○ ResultSet既然用于封装执行结果的,所以该对象提供的都是用于获取数据的get方法:
○ 获取指定类型的数据,例如:getString(int index)getString(String columnName)
常用数据类型转换表
API:
ResultSet还提供了对结果集进行滚动的方法:
next():移动到下一行
Previous():移动到前一行
absolute(int row):移动到指定行
beforeFirst():移动resultSet的最前面。
afterLast() :移动到resultSet的最后面。
-
释放资源
为什么要关闭资源?
在安装数据库的时候,设置过最大连接数量,如果用了不还连接,别人就无法使用了。
rs对象中可能包含很大的一个数据,对象保存在内存中,这样就十分占用内存。需要将他关闭。最晚创建的对象,最先关闭。
○ Jdbc程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResultSet, Statement和Connection对象。
○ 特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
○ 为确保资源释放代码能运行,资源释放代码也一定要放在finally语句中
释放资源
○ 在关闭过程中可能会出现异常,为了能够关闭资源,需要将资源在finally中关闭。
○ 如果在finally中关闭资源则需要将conn,stat,rs三个对象定义成全局的变量
○ 在conn,stat,rs三个变量出现异常的时候可能会关闭不成功,我们需要将他们在finally中置为null。conn,stat,rs这三个对象是引用,将引用置为null,它引用的对象就会被JVM回收,也能保证资源的释放。
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 (rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
rs=null;
}
}
-
JDBC工具类
package cn.zyj.jdbc;
import java.io.File;
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;
//工具类(工厂模式)
public class JDBCutils {
//类中的方法只能通过类名.的方式来调用
public static Properties prop= new Properties();
private JDBCutils(){}
//创建连接
public static Connection getConnection() throws Exception {
//获取类加载器JDBCutils.class.getClassLoader()
// 通过类加载器获取src目录getResource()
//直接在括号中书写文件名称即可得到文件路径
//getPath()是为了将url转换为String类型的数据
prop.load(new FileInputStream(new File(JDBCutils.class.getClassLoader().getResource("conf.properties").getPath())));
Class.forName(prop.getProperty("driver"));
//创建数据库连接
return DriverManager.getConnection(prop.getProperty("url"),
prop.getProperty("user"),prop.getProperty("password"));
}
//关闭资源
public static void close(Connection conn, Statement stat, ResultSet rs){
//关闭资源,后创建的先关闭
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
rs = null;
}
}
if (stat != null) {
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
stat = null;
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
conn = null;
}
}
}
}
//con.properties文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb5
user=root
password=
-
SQL注入攻击
由于sql语句是由前台参数与后台语句拼接而来,在用户传入的参数位置可能会输入数据库的关键字。这些关键字可能是sql语句的语义发生改变,从而达到一些特殊的效果,这种操作方式称之为sql注入攻击。
a. 攻击方式:在参数中添加 '# 改变语义。
b. 解决方案:i. 利用Statement子接口PreparedStatement可以有效防止sql注入攻击。
-
PreparedStatement概述
PreparedStatement是Statement的一个子接口,具有预编译功能
PreparedStatment发送sql的步骤:
1、先将sql主干语句部分发送到数据库服务器中,把参数位置用?来预留,sql语句到达服务器之后会变成一段二进制的机器码,这段机器码不能被操作
2、再将sql语句中的参数发送到数据库服务器中,这些参数以存文本的形式进行发送。也就是加上了单引号‘’
PreparedStatment的优势:
参数可以单独传入,避免sql语句的拼接错误,用有预编译功能防止sql注入攻击
-
批处理机制
在sql语句执行过程中,每个JDBC六步仅操作一个语句,如果有多个sql要执行,则在成很大的代码冗余,书写不便利。可以将这些sql语句放入一个JDBC的批处理中,一同发送的数据库服务器执行。
-
statement批处理
stat.addBatch(String sql); 添加sql 到批处理中
stat.addBatch(String sql);
stat.addBatch(String sql);
stat.executeBatch(); 执行批处理
package cn.zyj.batch;
import cn.zyj.jdbc.JDBCutils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class sbdfx1 {
public static void main(String[] args) {
//定义
Connection conn =null;
Statement stat=null;
ResultSet rs = null;
try {
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
//获取数据库连接
conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb5","root","");
//创建传输器
stat=conn.createStatement();
stat.addBatch("create table t2(id int,name varchar(20))");
stat.addBatch("insert into t2 values(1,'鸣人')");
stat.addBatch("insert into t2 values(2,'小樱')");
stat.addBatch("insert into t2 values(3,'做主')");
stat.addBatch("insert into t2 values(4,'hh')");
//执行批处理
stat.executeBatch();
System.out.println("插入成功");
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCutils.close(conn,stat,null);
}
}
}
ps = conn.preparedStatement(String sql);
ps.setString(1,5)
ps.addBatch() 添加sql参数 到批处理中
ps.executeBatch() 执行批处理
ps.clearBatch(); 清空批处理
- PreparedStatement批处理
package cn.zyj.batch;
//PreparedStatement批处理
/*
PreparedStatement特点
优点:
有预编译功能
将sql主干预留在数据库服务器中,不必重复发送sql语句
每次仅发送sql参数部分,执行效率较高
缺点:
只能执行同一语义的sql
**/
import cn.zyj.jdbc.JDBCutils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
public class PBD1 {
public static void main(String[] args) {
Connection conn =null;
PreparedStatement ps = null;
ResultSet rs=null;
try {
conn= JDBCutils.getConnection();
ps=conn.prepareStatement("insert into t1 values(?,?)");
for (int i = 0; i < 100000; i++) {
ps.setInt(1,i);
ps.setString(2,"name"+i);
ps.addBatch(); //添加sql参数到批处理中
if (i%1000==0){
ps.executeBatch(); //执行批处理
ps.clearBatch(); //清空批处理
System.out.println("执行完毕,当前批次数为"+i/1000);
}
}
//循环可能有不慢1000的数据,通过本句来执行
ps.executeBatch();
System.out.println("PreparedStatement执行完毕");
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCutils.close(conn,ps,rs);
}
}
}
-
连接池
在使用JDBC过程中,连接的使用占用资源较少,而创建连接和销毁连接占用资源较多。为了减少在服务器和数据库服务器之间,创建连接和销毁连接的过程,可以使用连接池代替原本JDBC有关连接的操作 。
原理:连接池在服务器启动的时候,会自动向数据库服务器索要一批连接,这些连接会保留在连接池中,用户需要访问数据库时,可以从连接池中取出连接。使用完成后,可以归还连接,从而省去了创建和销毁的过程。
-
代码实现
package cn.zyj.pool;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.*;
import java.util.LinkedList;
import java.util.logging.Logger;
public class MyPool implements DataSource {
//当前类加载时初始化一批连接
public static LinkedList<Connection> pool = new LinkedList();
static {
try {
Class.forName("com.mysql.jdbc.Driver");
for (int i = 0; i < 5; i++) {
Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb5","root","");
//每次创建对象的地址不同
pool.add(conn);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Connection getConnection() throws SQLException {
//获取连接
if (pool.size()==0){
for (int i = 0; i < 5; i++) {
Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb5","root","");
//每次创建对象的地址不同
pool.add(conn);
}
}
Connection conn=pool.remove(0);
System.out.println("从池中取出连接,池中还剩"+pool.size()+"个连接");
return conn;
}
public void returnConnection(Connection conn) throws SQLException {
if (conn!=null&&!conn.isClosed()){
pool.add(conn);
System.out.println("归还连接,池中还剩"+pool.size()+"个连接");
}
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
package cn.zyj.pool;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/*测试*/
public class TestPool {
public static void main(String[] args) {
Connection conn =null;
PreparedStatement ps = null;
ResultSet rs = null;
MyPool pool = new MyPool();
try {
conn=pool.getConnection();
ps=conn.prepareStatement("select * from exam where id=?");
ps.setInt(1,1);
rs=ps.executeQuery();
while (rs.next()){
String name= rs.getString("name");
System.out.println("name"+name);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//关闭资源,后创建的先关闭
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
rs = null;
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
ps = null;
}
}
if (conn != null) {
try {
pool.returnConnection(conn);
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
conn = null;
}
}
}
}
}
console
从池中取出连接,池中还剩4个连接
name关羽
归还连接,池中还剩5个连接
-
开源数据库连接池
-
DBCP(apache开源的一个连接池)
package cn.zyj.pool;
import cn.zyj.jdbc.JDBCutils;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
public class DBCPDemo1 {
public static void main(String[] args) throws Exception {
Connection conn = null;
Statement stat=null;
ResultSet rs= null;
/* BasicDataSource source = new BasicDataSource();
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUrl("");
source.setUsername("");
source.setPassword("");*/
//利用工厂生产一个dbcp数据源对象
try {
Properties prop = new Properties();
prop.load(new FileInputStream(new File(DBCPDemo1.class.getClassLoader().getResource("dbcp.properties").getPath())));
BasicDataSourceFactory factory = new BasicDataSourceFactory();
DataSource source = factory.createDataSource(prop);
conn = source.getConnection();
stat = conn.createStatement();
rs=stat.executeQuery("select * from exam");
while (rs.next()){
int id = rs.getInt(1);
String name = rs.getString(2);
System.out.println("id="+id+"name="+name);
}
}catch (Exception e){
throw new RuntimeException();
}finally {
//conn.close() dbcp重写 是归还连接
JDBCutils.close(conn,stat,rs);
}
}
}
dbcp.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///mydb5
username=root
password=
-
c3p0开源连接池
package cn.zyj.pool;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/*
C3P0连接池测试使用
* */
public class c3p0Demo1 {
public static void main(String[] args) {
Connection conn = null;
Statement stat=null;
ResultSet rs= null;
ComboPooledDataSource source = new ComboPooledDataSource();
try {
/*source.setDriverClass("com.mysql.jdbc.Driver");
source.setJdbcUrl("jdbc:mysql://localhost:3306/mydb5");
source.setUser("root");
source.setPassword("");*/
conn=source.getConnection();
stat=conn.createStatement();
rs=stat.executeQuery("select * from exam");
while(rs.next()){
String name = rs.getString(2);
int id = rs.getInt(1);
System.out.println("id="+id+"name="+name);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//关闭资源,后创建的先关闭
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
rs = null;
}
}
if (stat != null) {
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
stat = null;
}
}
if (conn != null) {
try {
//归还连接
conn.close();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
} finally {
conn = null;
}
}
}
}
}
c3p0-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb5</property>
<property name="user">root</property>
<property name="password"></property>
</default-config>
</c3p0-config>