JDBC总结
0 概述
JDBC是一组能够执行SQL语句的API
1. 连接数据库的步骤:
1) 注册驱动
JDBC类库向DriverManager注册数据库驱动·
2) 建立连接
使用DriverManager提供的getConnection()方法连接到数据库
3) 创建语句
通过数据库的连接对象的createStatement方法建立SQL语句对象
4) 执行语句
执行SQL语句,并将结果集合返回到ResultSet中
5) 处理结果
使用while循环读取结果
6) 释放资源(注意关闭的顺序)
demo
DriverManager.registerDriver(newcom.mysql.jdbc.Driver());
System.setProperty("jdbc.drivers", "com.mysql.jdbc.Driver");
//加载驱动方式
Class.forName("com.mysql.jdbc.Driver");//推荐方式
//2.建立连接
String url = "jdbc:mysql:localhost:3306/jdbc";
String user = "root";
String password = "mysql";
Connection conn = DriverManager.getConnection(url,user,password);
//3.创建语句
Statement st = conn.createStatement();
//4.执行语句
ResultSet rs = st.executeQuery("select * from user");
//5.处理结果
while(rs.next()){//按行来遍历
System.out.println(rs.getObject(1) + " " + rs.getObject(2) + " " +rs.getObject(3) + " " + rs.getObject(4) );
}
//6.释放资源(注意关闭的顺序)
rs.close();
st.close();
conn.close();
一JDBC的使用方法
1.建立连接:
建立连接分为两个关键步骤:①加载驱动程序;②建立连接。此处所涉及到的Java类有:Class、Driver、DriverManager。
建立连接,需要提供3个必不可少的参数:①数据库名称;②数据库账号;③数据库密码。
关键代码:
① 加载驱动程序:Class.forName(“com.mysql.jdbc.Driver”);
② 建立连接:
Connection conn=DriverManager.getConnection(url,name,pwd);
此处,String url=”jdbc:mysql://localhost:3306/”+数据库名称
String name=数据库账号
String pwd=数据库密码
需要说明的是,这两句关键代码所用的方法都会抛出异常,故应将这两句放在try-catch块里。
import java.sql.*; public class TestConnection{ static { try{ //加载驱动程序 Class.forName(“com.mysql.jdbc.Driver”); } catch(Exeception e){ e.printStackTrace(); } }
public static void main(String[] args){ String url=”jdbc:mysql://localhost:3306/lt_atm”; String name=”admin”; String pwd=”lt11235”; Connection conn=null; try{ //建立数据库连接 conn=DriverManager.getConnection(url,name,pwd); } catch(SQLExeception e){ e.printStackTrace(); } finally{ try{ conn.close(); } catch(SQLException e){ e.printStackTrace(); }
} } } |
一般,我们在静态代码块里加载驱动程序,不需要每次调用时都重新加载一次驱动。
建立连接过程——流程图总结:
2.执行SQL命令:
①在建立起来的连接线上创建Statement对象;②用新建的Statement对象传送SQL命令;③接收Statement对象返回过来的结果。
管理系统将所接受的SQL命令分为两类:①写,具体来说就是增删改;②读,即查。对于写性质的SQL命令,管理系统会将受到影响的记录条数交给信使Statement;对于读性质的SQL命令,管理系统会将满足条件的所有记录封装在一个对象里交给信使Statement,而这个对象就是ResultSet类的对象。现在我们来看一些这些动作的关键代码:
增删改:stmt.executeUpdate(sql),返回修改的记录条数
查:stmt.executeQuery,返回结果对象
关键代码:
创建信使
Statement stmt=conn.createStatement();
传送SQL命令&接收返回结果
l 添加记录:
String sql=”INSERT INTO account VALUES (‘1132’,’金龙’,’男’,10000) ”;
int count=stmt.executeUpdate(sql);
修改记录:
String sql=”UPDATE account SET name=’无花’ WHERE accId=’1132’”;
int count=stmt.executeUpdate(sql);
删除记录:
String sql=”DELETE FROM account WHERE accId=’1132’”;
int count=stmt.executeUpdate(sql);
l 读性质的命令:
查询记录:
String sql=”SELECT * FROM account”;
ResultSet rs=stmt.executeQuery(sql);
接下来,我们以具体的程序例子来说明传送SQL命令的过程;再以流程图来总结该过程:
传送SQL命令过程——代码示例:
import java.sql.*;
public class TestExecute{
static {
try{
//加载驱动程序
Class.forName(“com.mysql.jdbc.Driver”);
System.out.println(“Success loading Driver!”);
}
catch(Exeception e){
e.printStackTrace();
}
}
public static void main(String[] args){
String url=”jdbc:mysql://localhost:3306/lt_atm”;
String name=”admin”;
String pwd=”lt11235”;
Connection conn=null;
try{
//建立数据库连接
conn=DriverManager.getConnection(url,name,pwd);
Statement stmt=conn.createStatement();
//添加记录的SQL命令
String sql_1=”INSERT INTO account VALUES
(‘1132’,’金龙’,’男’,10000) ”;
//修改记录的SQL命令
String sql_2=”UPDATE account SET name=’无花’
WHERE accId=’1132’”;
//删除记录的SQL命令
String sql_3=”DELETE FROM account WHERE accId=’1132’”;
//查询记录的SQL命令
String sql_4=”SELECT * FROM account”;
//传送SQL命令
int count_1=stmt.executeUpdate(sql_1);
int count_2= stmt.executeUpdate(sql_2);
int count_3= stmt.executeUpdate(sql_3);
ResultSet rs=stmt.executeQuery(sql_4);
}
catch(SQLExeception e){
e.printStackTrace();
}
finally{
try{
conn.close();
}
catch(SQLException e){
e.printStackTrace();
}
}
}
}
传送SQL命令过程——流程图总结:
3.从返回的对象(结果集)中读取数据:
当是查找数据时,返回的是结果对象集合。
如何从返回的ResultSet对象中读取数据呢?
ResultSet对象中,除了每一条记录各占用一个位置,还有一些特殊作用的位置,其中有两个分别是:beforeFirst、afterLast。他们分别是第一条记录的前一个位置、最后一条记录的下一个位置。当我们用ResultSet对象封装了查询到的记录,游标初始位置置为beforeFirst。
ResultSet类中有一个next()方法,其作用就是移动游标,使其指向下一个位置。该方法的返回值是一个boolean类型变量,当移动游标后能指向一个有效记录,返回true,否则(比如说移动到了afterLast位置)返回false。ResultSet类还有一系列方法,可以统一写成getXXX()。其作用是从游标指向的记录中读取数据。其中XXX是String、Int、Double类的变量类型,取决于所要读取的字段的数据类型。
思路:①用得到的ResultSet对象调用next()方法,若返回true转向②,否则转向③;②调用getXXX方法,从游标当前指向的记录中读取各个字段的数值;然后调用next方法,若返回true,转向②,否则转向③。③读取过程完成,正常结束。
读取数据的过程——代码示例:
import java.sql.*;
public class TestExecute{
static {
try{
//加载驱动程序
Class.forName(“com.mysql.jdbc.Driver”);
System.out.println(“Success loading Driver!”);
}
catch(Exeception e){
e.printStackTrace();
}
}
public static void main(String[] args){
String url=”jdbc:mysql://localhost:3306/lt_atm”;
String name=”admin”;
String pwd=”lt11235”;
Connection conn=null;
try{
//建立数据库连接
conn=DriverManager.getConnection(url,name,pwd);
System.out.println(“Success eastablishing the Connection…”);
Statement stmt=conn.createStatement();
//查询记录的SQL命令
String sql=”SELECT * FROM account”;
//传送SQL命令
ResultSet rs=stmt.executeQuery(sql);
//读取数据
while(rs.next()){
String accId=rs.getString(1); //或者 rs.getString(“accId”);
String name=rs.getString(2);//或者 rs.getString(“name”);
Char sex=rs.getChar(3);//或者 rs.getChar(“sex”);
int count=rs.getInt(4);//或者 rs.getInt(“count”);
System.out.println(accId+”/t”+name+”/t”+sex+”/t”+count);
}
}
catch(SQLExeception e){
e.printStackTrace();
}
finally{
try{
conn.close();
}
catch(SQLException e){
e.printStackTrace();
}
}
}
}
读取数据的过程——流程图总结:
二 动态执行SQL命令
动态SQL:程序运行时根据条件执行具体的内容。
1、条件动态化:
编写一个方法,他从外界接收一个参数,并用这个参数使方法内部的SQL条件具体化,然后return一个结果。具体代码如下:
public static ResultSet sqlQuery(String accName){
String sql=”SELECT * FROM account WHERE name=’”+ accName +”’”;
.............................
return ResultSet;
}
2、字段动态化:
承上所述,此处所谓的字段动态化,就是我们不仅能按姓名来查询记录,还能根据其他字段来查询:比如说根据编号,根据身份,甚至是根据性别。在上文sqlQuery方法的基础上继续扩展,其代码如下:
public static ResultSet sqlQuery(String column,String value){
String sql=”SELECT * FROM account WHERE
’”+ column +”’=’”+ value +”’”;
.............................
return ResultSet;
}
3、数据表动态化:
目标是使他能不受具体数据表的限制(传入一个数据表参数),而是根据我们不同的需要,访问不同的数据表。代码:
public static ResultSet sqlQuery(String table,String column,String value){
String sql=”SELECT * FROM ’”+ table +”’ WHERE
’”+ column +”’=’”+ value +”’”;
.............................
.............................
return ResultSet;
}
4、PreparedStatement类的介绍:
PreparedStatement是继承自Statement的类
PreparedStatement拥有Statement所有的特性和方法。但是与Statement不同在于PreparedStatement可以将SQL命令事先编译,并存储在PreparedStatement对象中,当需要执行多次相似的SQL命令时,能够比较高效地执行。
另外,PreparedStatement最主要的功能就是能够输入条件式的SQL命令
import java.sql.*;
public class VisitDB{
static {
try{
//加载驱动程序
Class.forName(“com.mysql.jdbc.Driver”);
}
catch(Exeception e){
e.printStackTrace();
}
}
//负责建立与数据库管理系统之间的连接
public static Connection getCon(){
String url=”jdbc:mysql://localhost:3306/lt_atm”;
String name=”admin”;
String pwd=”lt11235”;
Connection conn=null;
try{
//建立数据库连接
conn=DriverManager.getConnection(url,name,pwd);
}
catch(SQLExeception e){
conn=null;
e.printStackTrace();
}
return conn;
}
//负责动态执行查询语句
public static ResultSet sqlQuery(String table,String column,String value){
Connection conn= VisitDB.getCon();
//条件式SQL命令,?表示待定参数
String sql=”SELECT * FROM ? WHERE ?=?”;
try{
//创建pstmt时就将sql命令传过去,使其预先编译
PreparedStatement pstmt=conn.prepareStatement(sql);
//表示第1个?的值为String类型的table
pstmt.setString(1,table);
//表示第2个?的值为String类型的column
pstmt.setString(2, column);
//表示第3个?的值为String类型的value
pstmt.setString(3, value);
//让pstmt对象执行将编译好的SQL命令传过去,并接收结果
ResultSet rs=pstmt.executeQuery();
return rs;
}
catch(Exception e){
return null;
e.printStackTrace();
}
}
//负责关闭不再用的对象,释放系统资源
public static void close(ReaultSet rs,Statement stmt,Connection conn){
try{
rs.close();
stmt.close();
conn.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}
好了,此时我们的三个动态扩展也用PreparedStatement实现了。不得不提的是PreparedStatement类的setXXX系列的方法并不是只有setString一种,只是上例中我们只用到了这一种而已
三 批处理SQL命令
1、Statement的批处理功能
Statement提供了批处理的功能,批处理多用在执行写操作的SQL命令时,如果一次要增删改很多条记录,使用批处理会减少对象在程序与数据库系统之间的交互,从而提高其性能。批处理的流程如下所示:
方法说明:
1、public void clearBatch() throws SQLException
作用:将Statement对象的Batch中的所有SQL命令清空
2、public void addBatch(String sql) throws SQLException
作用:插入一个SQL命令到Statemen对象中的Batch中
3、public int[] executeBatch() throws SQLException
作用:将Statement对象的Batch中的所有SQL命令传给数据库系统
代码示例:
Import java.sql.*;
public class TestBatch{
String[] sqls={“INSERT INTO account VALUES(‘13579’ , ’云飞’ , ‘男’ , 100)”,
”DELETE FROM account WHERE name=’无花’”,
”UPDATE account SET name=’铁手’ WHERE accId=’2468’”,}
public void testBatch(){
Connection conn=null;
try{
conn= VisitDB.getCon();
Statement stmt=conn.createStatement();
stmt.clearBatch();
for(int i=0;i<sqls.length;i++){
stmt.addBatch(sqls[i]);
}
stmt.executeBatch();
}
catch(Exception e){
e.printStackTrace();
}
}
}
2、PreparedStatement的批处理功能:
PreparedStatement是Statement的子类,所以他也有批处理功能,其流程如下所示:
图中所涉及到的方法都已说过,就不再赘述了。现在我们直接来看一下代码示例:
public void testPreBatch(){
try{
Connection conn= VisitDB.getCon();
PreparedStatement pstmt=conn.prepareStatement(
“DELETE FROM account WHERE name=?”);
pstmt.clearBatch();
pstmt.setString(“金龙”);
pstmt.addBatch();
pstmt.setString(“无花”);
pstmt.addBatch();
pstmt.setString(“楚香帅”);
pstmt.addBatch();
pstmt.executeBatch();
}
catch(Exception e){
e.printStachTrace();
}
}
四 事务机制
事务最主要的功能是用来确保多个连续的数据库操作,能作为一个整体被对待。即在执行时,要么全部执行成功,否则全部执行失败——回到最初状态。
流程说明:
1、Connection对象调用setAutoCommit()方法,将AutoCommit设置为false,以取消自动提交事务机制(自动提交事务机制将每一条SQL命令看做是一个事务,每做一次数据库操作,会自动提交一次)。
2、当一个逻辑事务所包含的SQL命令都被执行后,调用commit()方法,向数据库系统提交事务;这条语句通常是try块里最后一句。
3、中途如果发生任何错误或不明原因导致操作中断,调用rollback()方法,要求数据库系统执行Rollback操作。
方法说明:
public void setAutoCommit(boolean autoCommit) throws SQLException
说明:设置是否自动提交事务。当true时,每执行一条SQL命令,数据库系统自动提交一次事务,即对数据库里的数据进行操作;当为false时,数据库系统不自动提交事务,知道遇到调用的commit()方法。
public boolean getAutoCommit() throws SQLException
说明:获得当前的Commit方式
public void commit() throws SQLException
说明:指示数据库系统提交事务,将结果写进数据库
public void rollback() throws SQLException
说明:指示数据库执行恢复操作,将数据恢复到最初状态
流程图:
代码示例:
public void testTransaction(){
try{
Connection conn= VisitDB.getCon();
conn.setAutoCommit(false);
Statement stmt=con.createStatement();
String sql_1=”UPDATE account SET balance=balance-1000
WHERE accId=’A’”;
String sql_1=”UPDATE account SET balance=balance+1000
WHERE accId=’B’”;
conn.commit();
}
catch(SQLException ex){
ex.printStackTrace();
try{
conn.rollback();
}
catch(SQLException e){
e.printStackTrace();
}
}
}
SQL注入PreparedStatement和Statement
PreparedStatement(从Statement扩展而来)相对Statement的优点:
1、没有SQL注入的问题。
2、Statement会使数据库频繁编译SQL,可能会造成数据库缓冲区溢出。
3、数据库和驱动可以对PreparedStatement进行优化(只有在相关联的数据库连接没有关闭的情况下有效)。
为什么要始终使用PreparedStatement代替Statement?
1 代码可维护性
(辨别: Statement:使用 + PreparedStatement:使用?)
stmt.executeUpdate("insert into tb_name (col1,col2,col2,col4) values ('"+var1+"','"+var2+"',"+var3+",'"+var4+"')");
注意Statement和PreparedStatement语句上的区别:Statement是在执行的时候才传入SQL语句,而PreparedStatement在创建的时候就已经传入了SQL语句。
perstmt = con.prepareStatement("insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)");
perstmt.setString(1,var1);
perstmt.setString(2,var2);
perstmt.setString(3,var3);
perstmt.setString(4,var4);
perstmt.executeUpdate();
2 防止SQL注入
对于JDBC而言,SQL注入攻击只对Statement有效,对PreparedStatement是无效的,这是因为PreparedStatement不允许在插入时改变查询的逻辑结构.
SQL注射原理
SQL 注射能使攻击者绕过认证机制,完全控制远程服务器上的数据库
登陆验证
假如后台的sql语句时这样拼接的
select id from test where username='"+myname+"' and password='"+mypasswd+"' ";
表面上看,如果用户名和口令对匹配,那么该用户通过认证;否则,该用户不会通过认证——但是,事实果真如此吗?这里并没有对SQL命令进行设防,所以攻击者完全能够在用户名或者口令字段中注入SQL语句,从而改变SQL查询 。为此,我们仔细研究一下上面的SQL查询字符串:
上述代码认为字符串username和password都是数据,不过,攻击者却可以随心所欲地输入任何字符 。如果一位攻击者输入的用户名为
‘’OR 1=1—
而口令为
x
双划符号--告诉SQL解析器,右边的东西全部是注释,所以不必理会。这样,查询字符串相当于:
select id from test where username='' or 1=1;
因为现在只要用户名为长度为零的字符串''或1=1这两个条件中一个为真,就返回用户标识符ID——我们知 道,1=1是恒为真的。所以这个语句将返回user_table中的所有ID。在此种情况下,攻击者在username字段放入的是SQL指令 'OR1=1--而非数据。
更为严重的情况是当username对应的是'OR1=1;DROPTABLEuser_table;--
数据库中执行的sql语句就变成了:
select id from test where username='' or 1=1;drop table test
这个语句将执行句法上完全正确的SELECT语句,并利用drop命令清空test表。
应对策略
问题的关键就是不要用string构造sql语句,这样就不会利用输入的参数构造sql语句了。所以要用PreparedStatement替换Statement,即用占位符作为实参定义sql语句,从而避免sql注入攻击。
不管什么框架,还是纯JDBC,只用Preparedstatement,一定要用占位符作为实参来构造sql(或hql)语句。
String sql= "select * from test where usernmae=? and password=? " ;
PreparedStatement psm=conn.preparedStatement(sql);
psm.setString(1,myname);
psm.setString(2,mypasswd);
Result rs=psm.executeQuery();
if (rs.next){
rs.close();
con.close();
return false ;
}
else {
rs.close();
con.close();
return true ;
}