数据库厂商提供的用来操作数据库的jar包就叫做数据库的驱动。
把某个java项目打包成jar文件,提供给其他人使用。。
使用的时候,解压jar文件,生成一个文件夹,里面存放的就是编译好的每个包下面的类文件。。
JDBC(Java DataBase Connectivity)就是Java数据库连接,说白了就是用Java语言来操作数据库。
由于不同的数据库厂商提供的数据库驱动各不相同,在使用不同数据库时需要学习对应数据库驱动的api,对于开发人员来说学习成本十分的高。
于是sun提供了JDBC的规范,也就是jar包,本质上一大堆的接口,要求不同的数据库厂商提供的驱动都实现这套接口,这样以来开发人员只需要学会JDBC这套接口,所有的数据库驱动作为这套接口的实现,就都会使用了。
JDBC主要是由 java.sql 和javax.sql包组成的,并且这两个包已经被集成到J2SE的规范中了,这意味着,只要一个普通的java程序就可以使用JDBC。
要注意的是,在开发数据库程序时,除了如上的两个包,还需要手动的导入具体的数据库驱动。
注意: jdbc只是一套接口,具体操作数据库的代码都在接口的实现类中,也就是数据库驱动中,开发时还是要导入具体的数据库驱动.
在导入Connection、Statement、ResultSet包的时候,导的都是接口而不是实现类,是为了程序更加通用,不管是mysql还是Oracle数据库或者其他数据库都可以接收住。
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
//1.注册数据库驱动 告诉程序这是个驱动器
DriverManager.registerDriver(new Driver());//一般不推荐使用这种方式
//2.获取数据库的连接
conn=DriverManager.getConnection("jdbc:mysql:///mydb1",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");
Date date = rs.getDate("birthday");
System.out.println(name+":"+password);
}
} 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;}
}
}
使用DriverManager.registerDriver(new Driver())方式注册数据库有两个缺点,首先,通过观察mysql的中Driver接口的实现类发现在静态代码块中注册驱动的逻辑,所以这种方式会造成驱动被注册两次。另外,这种方式导致了程序和具体的数据库驱动绑死在了一起,程序的灵活性比较低。
所以推荐使用:Class.forName(“com.mysql.jdbc.Driver”);的方式注册数据库驱动。 使用这种方式程只是和数据库驱动的全限定名绑死在一起,后期可以将全限定名提取到配置文件中。
获取数据库连接
Connection conn = DriverManager.getConnection(url,name,psw);
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
/*如果是是访问本机的数据库,localhost可以省略不写,如果在配置数据库的时候,端口号默认的是3306,那么3306也可以省略不写。
Mysql的url地址的简写形式: */
jdbc:mysql:///sid
Jdbc程序中的Connection,它用于代表数据库的连接,Connection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过connection对象完成的,这个对象的常用方法:
createStatement() //:创建向数据库发送sql的statement对象。
prepareStatement(sql): //创建向数据库发送预编译sql的PreparedSatement对象。
prepareCall(sql) //:创建执行存储过程的callableStatement对象。
setAutoCommit(boolean autoCommit) //:设置事务是否自动提交。
commit() //:在此连接接上提交事务。
rollback() //:在此连接上回滚事务。 (当事务提交失败的时候可自动回滚事务)
Jdbc程序中的Statement对象用于向数据库发送SQL语句, Statement对象常用方法:
executeQuery(String sql)// :用于向数据库发送查询语句。
executeUpdate(String sql)//:用于向数据库发送insert、update或delete语句
返回一个int值,代表影响的行数。。
execute(String sql)//:用于向数据库发送任意sql语句
//返回一个boolean值,如果sql语句是查询,则会返回true,如果sql语句是更新(增、删、改),则会返回false.
addBatch(String sql)//:把多条sql语句放到一个批处理中。
executeBatch()//:向数据库发送一批sql语句执行。
Jdbc程序中的ResultSet用于代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式。ResultSet 对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next() 方法,可以使游标指向具体的数据行,进行调用方法获取该行的数据。
Result的next()方法。不仅用于判断是否有下一行语句,还有把光标下移的功能
ResultSet既然用于封装执行结果的,所以该对象提供的都是用于获取数据的get方法:
//获取任意类型的数据
getObject(int index) (传入的参数是列的下标)
getObject(string columnName) (传入的参数是列的名称)
获取指定类型的数据,例如:
getString(int index)
getString(String columnName)
getInt(columnIndex)
getInt(columnLabel)
getDouble(columnIndex)
getDouble(columnLabel)
//操作游标的方法,例如:
next():移动到下一行
Previous():移动到前一行
absolute(int row):移动到指定行
beforeFirst():移动resultSet的最前面。(第一行的前面)
afterLast() :移动到resultSet的最后面。 (最后一行的后面)
Jdbc程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResultSet, Statement和Connection对象。
释放资源的时候注意顺序:要按先 ResultSet 结果集,后 Statement,最后 Connection 的顺序关闭资源,因为 Statement 和 ResultSet 是需要连接时才可以使用的,所以在使用结束之后有可能其它的 Statement 还需要连接,所以不能现关闭 Connection。
特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。
为确保资源释放代码能运行,资源释放代码也一定要放在finally语句中。
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;
/**
* JDBC工具类
* @author Administrator
*
*/
public class JDBCUtils {
private static Properties prop = new Properties();
//构造方法私有化,防止其他类在外面new对象
private JDBCUtils(){}
//在程序启动时读取配置文件
static{
//读取配置文件
try {
//获得一个类加载器
ClassLoader classLoader = JDBCUtils.class.getClassLoader();
//类加载器直接从bin目录下去找
String path = classLoader.getResource
("config.properties").getPath();
prop.load(new FileInputStream(path));
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e); //转化成运行时异常抛出
}
}
/*
* 获取连接对象
*/
public static Connection getConnection(){
Connection conn = null;
try {
String driverClass = prop.getProperty("driverClass");
String jdbcurl = prop.getProperty("jdbcurl");
String user = prop.getProperty("user");
String password = prop.getProperty("password");
Class.forName(driverClass);
conn = DriverManager.getConnection(jdbcurl,user,password);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e); //转化成运行时异常抛出
}
return conn;
}
/*
* 释放资源
*/
public static void close(Connection conn,Statement stat,ResultSet 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;}
}
}
}
select * from user where username='张三'#'' and password=''
select * from user where username='张三' or '2=2' and password=''
发现无需输入密码,直接登录,发现无需密码登录了进去。这就是发生了SQL注入问题。
由于jdbc程序在执行的过程中sql语句在拼装时使用了由页面传入参数,如果用户,恶意传入一些sql中的特殊关键字或者特殊符号,会导致sql语句意义发生变化,从而造成一些意外的操作,这种攻击方式就叫做sql注入。
如何防止SQL注入攻击呢?
这时候就需要用到PreparedStatement对象。
PreparedStatement是Statement的子接口,不同的是,PreparedStatement使用预编译机制,在创建PreparedStatement对象时就需要将sql语句传入,传入的过程中参数要用?替代,这个过程回导致传入的sql被进行预编译,然后再调用PreparedStatement的setXXX将参数设置上去,由于sql语句已经经过了预编译,再传入特殊值也不会起作用了。
PreparedStatement使用了预编译机制,sql语句在执行的过程中效率比Statement要高。
PreparedStatement使用了 “?”通配符省去了字符串的拼接,使代码更加优雅。
PreparedStatement的优点:(如果SQL语句有参数)
(1)可以防止sql注入攻击
通过PreparedStatement对象发送SQL,是先把SQL语句的骨架发送给数据库编译并确定下来,后面发送的只能是参数的值,不能影响SQL语句的骨架,即使参数中包含SQL关键字或者特殊符号,也只会当成普通的文本来处理。
(2)通过方法来设置参数,省去了拼接SQL语句的麻烦
(3)可以提高程序的效率:
通过PreparedStatement对象发送的SQL语句(骨架)到数据库编译后会被数据缓存下来,如果下次执行的SQL与缓存中的相匹配,就不用再编译而是直接使用缓存中的语句,可以减少SQL语句编译的次数,提高程序执行的效率。
Statement对象发送的sql语句到数据库之后也会编译,但是Statement对象是先拼接好再发送SQL到数据库,如果每次参数不同,整条sql语句也就不同了,所以每次都需编译。
如果sql语句中没有参数,那么PreparedStatement对象和Statement对象的功能就是相差无几的,不存在注入问题。
Statement对象发送的sql语句到数据库之后也会编译,但是Statement对象是先拼接好再发送SQL到数据库,如果每次参数不同,整条sql语句也就不同了,所以每次都需编译。
如果sql语句中没有参数,那么PreparedStatement对象和Statement对象的功能就是相差无几的,不存在注入问题。
概述:
假设现有一大堆的SQL语句要到数据库中去执行,如果一条一条发送,有多少条就要发送多少次,效率低下。
可以通过批处理提高发送SQL语句的效率:可以将这一大堆的SQL语句添加到一个批中,一次性将批发送给数据库, 数据库收到后打开批, 依次执行其中sql语句, 这样可以减少sql语句发送的次数, 从而提高程序执行的效率!
当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率。
Statement.addBatch(sql)
执行批处理SQL语句
executeBatch()方法:执行批处理命令
clearBatch()方法:清除批处理命令。 将该批中的语句清除,再次放入新的执行的语句
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtil.getConnection();
String sql1 = "insert into per(name,password)values('kk','123')";
String sql2 = "update user set password='123' where id=3";
st = conn.createStatement();
st.addBatch(sql1); //把SQL语句加入到批命令中
st.addBatch(sql2); //把SQL语句加入到批命令中
st.executeBatch(); //执行批处理命令
} finally{
JdbcUtil.free(conn, st, rs);
}
采用Statement.addBatch(sql)方式实现批处理:
优点:
可以在一次批处理中发送多条结构不同的SQL语句。
缺点:
不能防止SQL注入
SQL语句没有预编译,效率较低。
当向数据库发送多条结构相同的语句时,SQL语句的骨架每次都要编写。
PreparedStatement.addBatch()
conn = JdbcUtil.getConnection();
String sql = "insert into person(name,password)values(?,?)";
ps = conn.prepareStatement(sql);
conn.setAutoCommit(false); //关闭自动提交事务
for(int i=0;i<50;i++){
ps.setString(1, "aaa" + i);
ps.setString(2, "123" + i);
ps.addBatch(); //添加到批处理,但是此时没有处理 如果此时没有
} //加到批处里面,执行之后数据库不会有任何改动
} //因为此时相当于在执行一个空的批,所以没有任何sql语句被执行
ps.executeBatch();
conn.commit(); //最后手动提交一次批处理的事务
采用PreparedStatement.addBatch()实现批处理:
优点:
可以防止sql注入。
发送的是预编译后的SQL语句,执行效率高。
当发送多条结构相同的sql时,SQL语句的骨架可以只发送一次。
缺点:
不能在一次批处理中添加结构不同的sql语句。
因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。
用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。(使用连接并没有消耗多少资源,浪费多长时间。)假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、宕机。
频繁的开关连接相当的耗费资源,所以我们可以设置一个连接池,在程序启动时就初始化一批连接,在程序中共享,需要连接时从池中获取,用完再还回池中,通过池共享连接,减少开关连接的次数,提高程序的效率。
Sun公司为连接池提供 javax.sql.DataSource接口,要求连接池去实现,所以连接池也叫数据源。
我们可以自己实现这个接口来实现一个连接池。
import java.io.PrintWriter;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;
import javax.sql.DataSource;
public class MyPool implements DataSource {
private static List<Connection> pool=new LinkedList<Connection>();
static{
try{
Class.forName("com.mysql.jdbc.Driver");
for(int i=0;i<5;i++){
Connection conn = DriverManager.getConnection("jdbc:mysql:///day11","root","root");
pool.add(conn);
}
}catch (Exception e) {e.printStackTrace();
throw new RuntimeException(e);
}
}
//获取一个连接
public Connection getConnection() throws SQLException {
if(pool.isEmpt()){
for(int i=0;i<3;i++){
Connection conn = DriverManager.getConnection("jdbc:mysql:///day11","root","root");
pool.add(conn);
}
}
return pool.remove(0);
}
//将连接返回给连接池
private void retConn(Connection conn){
try {
if(conn!=null && !conn.isClosed()){
pool.add(conn);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public Connection getConnection(String username, String password)
throws SQLException {return null;}
public PrintWriter getLogWriter() throws SQLException {}
public int getLoginTimeout() throws SQLException {return 0;}
public void setLogWriter(PrintWriter out) throws SQLException {}
public void setLoginTimeout(int seconds) throws SQLException {}
public boolean isWrapperFor(Class<?> iface) throws SQLException
{return false;}
public<T> T unwrap(Class<T> iface) throws SQLException
{return null;}
}
当从连接池中获取连接,需要在使用完后不能关闭连接,而是要调用retConn方法将连接还回池中。