JDBC概述
b. JDBC简介:
○ JDBC全称为:Java Data Base Connectivity(java数据库连接),它主要由接口组成。
§ 组成JDBC的2个包:
java.sql包 javax.sql包
§ 开发JDBC应用需要以上2个包的支持外,还需要导入相应JDBC的数据库实现(即数据库驱动)。
○ 不仅需要jdbc接口,还需要驱动这个实现,驱动中就是对jdbc接口的一些实现。
6步实现jdbc
○ 注册数据库驱动
○ 获取数据库连接
○ 创建传输器
○ 传输sql并返回结果
○ 遍历结果
○ 关闭资源
4. 代码实现
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 Demo1 {
public static void main(String[] args) throws SQLException {
//获取数据库驱动
DriverManager.registerDriver(new Driver());
//创建数据库连接
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydb1", "root", "root");
//获取传输器
Statement stat = conn.createStatement();
//利用传输器传输sql,并获取返回结果
ResultSet rs = stat.executeQuery("select * from exam");
//5.遍历结果
while(rs.next()){
int id = rs.getInt("id");
String name = rs.getString(2);
System.out.println("id:"+id+">>name:"+name);
}
//6.关闭资源
//后创建的先关闭
rs.close();
stat.close();
conn.close();
}
}
- 程序详解—DriverManager
• Jdbc程序中的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对象。-
数据库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
常用属性: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语句 -
程序详解—ResultSet
○ Jdbc程序中的ResultSet用于代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式。ResultSet 对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next() 方法,可以使游标指向具体的数据行,进行调用方法获取该行的数据。
○ ResultSet既然用于封装执行结果的,所以该对象提供的都是用于获取数据的get方法:
○ 获取指定类型的数据,例如:
getString(int index)
getString(String columnName)
○ 提问:数据库中列的类型是varchar,获取该列的数据调用什么方法?Int类型呢?bigInt类型呢?Boolean类型? -
常用数据类型转换表
-
ResultSet中的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回收,也能保证资源的释放。 -
工具类JDBCUtils
创建连接和关闭资源代码重复调用,可以封装成方法。import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; 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 JDBCUtils(){ } public static Properties prop = new Properties(); static{ try { prop.load(new FileInputStream(new File( //获取类加载器,JDBCUtils.class.getClassLoader() //通过类加载器获取src目录,getResource() //getResource()会得到从盘符到src目录的路径, //直接在括号中书写文件名称即可得到文件路径。 //getPath()是为了将url转换为String类型的数据 JDBCUtils.class.getClassLoader().getResource("conf.properties").getPath()))); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //创建连接 public static Connection getConnection() throws Exception{ 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; } } } }
-
登录功能实现
在控制台输入用户名和密码,如果与数据库中的数据匹配,则登录,反之不登录。
代码实现:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;
import cn.tedu.utils.JDBCUtils;
//登录功能
public class Login {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
//testLogin(username,password);
PreparedtestLogin(username,password);
}
private static void PreparedtestLogin(String username, String password) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
//发送sql主干
ps = conn.prepareStatement("select * from user where username=? and password=?");
//发送参数
ps.setString(1, username);
ps.setString(2, password);
//通知数据库服务器执行sql
rs = ps.executeQuery();
if(rs.next()){
System.out.println("登录成功");
}else{
System.out.println("登录失败");
}
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCUtils.close(conn, ps, rs);
}
}
private static void testLogin(String username, String password) {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
stat = conn.createStatement();
rs = stat.executeQuery("select * from user where username= '"+username+"' and password='"+password+"'");
if(rs.next()){//如果为true,则证明能够查询到用户名,可以登录
System.out.println("登录成功");
}else{//其他情况都不能登录。
System.out.println("登录失败");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.close(conn, stat, rs);
}
}
}
- SQL注入攻击
由于sql语句是由前台参数与后台语句拼接而来,在用户传入的参数位置可能会输入数据库的关键字。这些关键字可能是sql语句的语义发生改变,从而达到一些特殊的效果,这种操作方式称之为sql注入攻击。
a. 攻击方式:
在参数中添加 '# 改变语义。
b. 解决方案:
i. 利用Statement子接口PreparedStatement可以有效防止sql注入攻击。-
PreparedStatement概述
PreparedStatement是Statement的一个子接口。
具有预编译功能。
a. PreparedStatement发送sql 的步骤:
i. 先将sql语句的主干部分发送到数据库服务器中,参数位置使用"?"预留,sql语句到达服务器中时,会立刻变成一段机器码(二进制数据)这个机器码不能被操作。
ii. 再讲sql语句中的参数发送到数据库服务器,这些参数到达数据库服务器中时只会作为纯文本内容使用。
b. PreparedStatement优势:
参数可以单独传入,避免sql语句的拼接错误。
拥有预编译功能,可以防止sql注入攻击。
c. 拓展:
PreparedStatement 和 statement 谁能传入参数?
两者都可以。 -
批处理机制
a. 在sql语句执行过程中,每个JDBC六步仅操作一个语句,如果有多个sql要执行,则在成很大的代码冗余,书写不便利。
b. 可以将这些sql语句放入一个JDBC的批处理中,一同发送的数据库服务器执行。 -
Statement批处理和PreparedStatement批处理
a. Statement批处理:
stat.addBatch(String sql); 添加sql 到批处理中
stat.addBatch(String sql);
stat.addBatch(String sql);
stat.executeBatch(); 执行批处理
i. 代码实现:import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; import .utils.JDBCUtils; //Statement批处理 /* create table t1(id int,name varchar(20)) insert into t1 values(1,'鸣人') insert into t1 values(2,'佐助') insert into t1 values(3,'小樱') insert into t1 values(4,'蝎子') Statement特点: 优点:1.可以执行不同语义的sql 缺点:1.没有预编译功能 2.每次都会将sql语句完整的发送到数据库服务器。 3.无法预留sql语句在服务器中,执行效率较低。 * */ public class StateBatchDemo1 { public static void main(String[] args) { Connection conn = null; Statement stat = null; ResultSet rs = null; try { conn = JDBCUtils.getConnection(); stat = conn.createStatement(); stat.addBatch("create table t1(id int,name varchar(20))"); stat.addBatch("insert into t1 values(1,'鸣人')"); stat.addBatch("insert into t1 values(2,'佐助')"); stat.addBatch("insert into t1 values(3,'小樱')"); stat.addBatch("insert into t1 values(4,'蝎子')"); //通知数据库服务器 stat.executeBatch(); System.out.println("Statement批处理执行成功"); } catch (Exception e) { e.printStackTrace(); }finally{ JDBCUtils.close(conn, stat, rs); } } }
b. PreparedStatement批处理:
ps = conn.preparedStatement(String sql);
ps.setString(1,5)
ps.addBatch() 添加sql参数 到批处理中
ps.executeBatch() 执行批处理
ps.clearBatch(); 清空批处理
i. 代码实现import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import .utils.JDBCUtils; //PreparedStatement批处理 /* * PreparedStatement特点: 优点:1.有预编译功能。 2.将sql主干预留在数据库服务器中,不必重复发送sql语句。 3.每次仅发送sql参数部分,执行效率较高。 缺点:1.只能执行同一语义的sql语句。 * * */ public class PreparedBatchDemo1 { 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(); if(i%1000 ==0){ ps.executeBatch(); ps.clearBatch(); System.out.println("执行完毕,当前批次数为:"+i/1000); } } //循环可能有不满一千的数据,通过本句来执行。 ps.executeBatch(); System.out.println("PreparedStatement执行完毕"); } catch (Exception e) { e.printStackTrace(); }finally{ JDBCUtils.close(conn, ps, rs); } } }
-
连接池
在使用JDBC过程中,连接的使用占用资源较少,而创建连接和销毁连接占用资源较多。为了减少在服务器和数据库服务器之间,创建连接和销毁连接的过程,可以使用连接池代替原本JDBC有关连接的 操作。
-
连接池原理
连接池在服务器启动的时候,会自动向数据库服务器索要一批连接,这些连接会保留在连接池中,用户需要访问数据库时,可以从连接池中取出连接。使用完成后,可以归还连接,从而省去了创建和销毁的过程。 -
代码实现
package cn.tedu.pool;import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.List;import javax.sql.DataSource;
/**- 手写连接池
- @author 16
*/
public class MyPool implements DataSource{
//定义一个能够存储连接的数据结构,由于经常使用插入和删除操作,所以List较好。
private static List 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/mydb2?user=root&password=root”);
//将创建好的每一个连接对象添加到List中,模拟将连接加入连接池
pool.add(conn);
}
}catch(Exception e){
e.printStackTrace();
throw new RuntimeException(e);
}
}//创建连接(从连接池中取出一个连接) @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/mydb2?user=root&password=root"); pool.add(conn); } } //从List中取出一个连接对象 //此处不能使用get(),get()方法只能读取对应下标的元素,没有将读取到的元素移除,如果是取出连接对象,应将对象移除。 Connection conn = pool.remove(0); ConnDecorate connpool = new ConnDecorate(conn,this); System.out.println("成功获取一个连接,池中还剩:"+pool.size()+"个连接"); return connpool; } //返还连接 //手写一个返还连接的方法 public void retConn(Connection conn){ try { //归还的连接如果已经关闭或者为空,则不允许放入池中。 if(conn!=null&&!conn.isClosed()){ pool.add(conn); System.out.println("成功还回一个连接,池中还剩:"+pool.size()+"个连接"); } } catch (SQLException e) { e.printStackTrace(); } } @Override public PrintWriter getLogWriter() throws SQLException { return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { // TODO Auto-generated method stub } @Override public void setLoginTimeout(int seconds) throws SQLException { // TODO Auto-generated method stub } @Override public int getLoginTimeout() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { // TODO Auto-generated method stub return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { // TODO Auto-generated method stub return false; } @Override public Connection getConnection(String username, String password) throws SQLException { // TODO Auto-generated method stub return null; }
}
-
开源数据库连接池
DBCP和C3P0
a. DBCP
apache组织开源的一个连接池。
i. 实现案例:
package cn.tedu.pool;import java.io.File; import java.io.FileInputStream; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties; import javax.sql.DataSource; import org.apache.commons.dbcp.BasicDataSource; import org.apache.commons.dbcp.BasicDataSourceFactory; import utils.JDBCUtils; //DBCP连接池测试使用 public class DBCPDemo1 { public static void main(String[] args) { Connection conn = null; Statement stat = null; ResultSet rs = null; BasicDataSource source = new BasicDataSource(); source.setDriverClassName("com.mysql.jdbc.Driver"); source.setUrl("jdbc:mysql://localhost:3306/mydb1"); source.setUsername("root"); source.setPassword("root"); //利用工厂生产一个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(e); }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; } } } } } ii. dbcp配置文件: driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql:///mydb1 username=root password=root
b. c3p0
i. c3p0连接池会自动读取,位于当前工程下src目录中的c3p0-config.xml文件或c3p0.properties文件。
ii. 实现案例:import java.beans.PropertyVetoException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import com.mchange.v2.c3p0.ComboPooledDataSource; //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/mydb1"); source.setUser("root"); source.setPassword("root");*/ conn = source.getConnection(); stat = conn.createStatement(); rs = stat.executeQuery("select * from exam"); while(rs.next()){ String name = rs.getString("name"); System.out.println("name:"+name); } } catch (Exception 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; } } } } } iii. c3p0配置文件 c3p0.driverClass=com.mysql.jdbc.Driver c3p0.jdbcUrl=jdbc:mysql://localhost:3306/mydb1 c3p0.user=root c3p0.password=root iv. c3p0配置文件2 <?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/mydb1</property> <property name="user">root</property> <property name="password">root</property> </default-config> </c3p0-config>
-