目录
(5)PreparedStatement(执行sql的对象)
1.JDBC的介绍
(1)是什么?
概念:Java DataBase Connectivity Java 数据库连接, Java语言操作数据库
JDBC本质:其实是官方定义的一套操作所有关系型数据库的规则,即接口。每个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
(2)怎么用
步骤:
- 1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
- 1)复制mysql-connector-java-5.1.37-bin.jar到项目的libs目录下
- 2)右键-->Add As Library
- 2. 注册驱动
- 3. 获取数据库连接对象 Connection
- 4. 定义sql
- 5. 获取执行sql语句的对象 Statement
- 6. 执行sql,接受返回结果
- 7. 处理结果
- 8. 释放资源
使用示例:
准备好驱动jar包
- 新建一个项目,在项目下新建一个libs文件夹,专门用于保存要用的库的包
- 直接将要用mysql的jar包复制到libs目录下,然后右键单击该文件夹libs------>Add As Library
示例代码:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class Main {
public static void main(String[] args) throws Exception {
//1. 导入驱动jar包
//2.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//3.获取数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb","root","");
//4.定义sql语句
String sql = "update sc set grade = 90 where sid = 'S1'";
//5.获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//6.执行sql
int count = stmt.executeUpdate(sql);
//7.处理结果
System.out.println("处理结果:"+count);
//8.释放资源
stmt.close();
conn.close();
}
}
代码执行前,mydb数据库中的sc表的数据如下:
代码执行后的结果:
2.各个类对象的详解
(1)DriverManager(驱动管理对象)
功能:
- 1.注册驱动:告诉程序该使用哪一个数据库驱动jar
查看JDK的API文档,可以发现它有如下方法
而写代码时使用下述代码?
Class.forName("com.mysql.jdbc.Driver"); //把com.mysql.jdbc.Driver这个类加载进内存
通过查看源码发现:在com.mysql.jdbc.Driver类中存在静态代码块(随着类的加载会被执行),其中调用了registerDriver方法
注意:mysql5之后的驱动jar包可以省略注册驱动的步骤,因为在mysql5之后的jar包中的java.sql.Driver文件中已经帮我们把com.mysql.jdbc.Driver类写到该文件中,写到该文件它就可以帮我们自动地注册驱动,不过建议写上此段代码
- 2. 获取数据库连接:
方法:
参数说明:
- url:指定连接的路径
语法:jdbc:mysql://ip地址(域名):端口号/数据库名称 例子:jdbc:mysql://localhost:3306/mydb 细节:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称
- user:用户名
- password:密码
(2)Connection(数据库连接对象)
功能:
- 1. 获取执行sql 的对象
- 2. 管理事务:见下文JDBC事务管理
(3) Statement(执行sql的对象)
功能:
执行sql
- 1)执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句
- 返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功,如果返回值>0的则执行成功,反之,则失败。
- 2)执行DQL(select语句)
练习:为sc表 添加一条记录
添加前:
代码:
import java.sql.*; public class Main { public static void main(String[] args){ Connection conn=null; Statement state=null; try { //1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2.定义sql String sql="insert into sc values('S6','C2',93)"; //3.获取connection对象 conn= DriverManager.getConnection("jdbc:mysql:///mydb","root",""); //4.获取执行sql的对象 state=conn.createStatement(); //5.执行sql int count=state.executeUpdate(sql); //影响的行数 //6.处理结果 if(count>0) { System.out.println("添加成功"); } else{ System.out.println("添加失败"); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); }finally { //7.释放资源 //如果conn或state在没被初始化时,就出现异常,则此处如果直接释放资源,会报空指针异常 //conn.close(); //state.close(); //为了防止空指针异常需要判断是否为空 if(conn!=null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } if(state!=null) { try { state.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
添加后:
(4)ResultSet(结果集对象)
功能:封装查询结果
- 1)光标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回true
- 2)getXxx(参数):获取数据 Xxx:代表数据类型 如: int getInt() , String getString()
- 参数:
- 1. int:代表列的编号,从1开始 如: getString(1)代表获取结果集的第一列
- 2. String:代表列名称。 如: getDouble("balance")代表获取结果集中的balance列
注意:
使用步骤:
- next函数完成:
- 1. 游标向下移动一行
- 2. 判断是否有数据
- getXxx函数完成:
- 3. 获取数据
使用演示:
import java.sql.*; public class Main { public static void main(String[] args){ Connection conn=null; Statement state=null; try { //1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2.定义sql String sql="select * from sc"; //3.获取connection对象 conn= DriverManager.getConnection("jdbc:mysql:///mydb","root",""); //4.获取执行sql的对象 state=conn.createStatement(); //5.执行sql ResultSet result=state.executeQuery(sql); //影响的行数 //6.处理结果 //6.1 循环判断游标是否是最后一行末尾。 while(result.next()){ //6.2 获取数据 String sid = result.getString(1); String cid = result.getString("cid"); int grade = result.getInt(3); System.out.println(sid + "---" + cid + "---" + grade); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); }finally { //7.释放资源 //如果conn或state在没被初始化时,就出现异常,则此处如果直接释放资源,会报空指针异常 //conn.close(); //state.close(); //为了防止空指针异常需要判断是否为空 //释放conn对象 if(conn!=null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } //释放state对象 if(state!=null) { try { state.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
运行结果:
select查询练习:定义一个方法,查询sc表的数据将其封装为对象,然后装载集合,返回。
- 1. 定义Sc类
package com.test.jdbc; public class Sc { private String sid; private String cid; private int grade; public String getSid() { return sid; } public void setSid(String sid) { this.sid = sid; } public String getCid() { return cid; } public void setCid(String cid) { this.cid = cid; } public int getGrade() { return grade; } public void setGrade(int grade) { this.grade = grade; } @Override public String toString() { return "Sc{" + "sid='" + sid + '\'' + ", cid='" + cid + '\'' + ", grade=" + grade + '}'+"\n"; } }
- 2. 定义方法 public List<Sc> findAll(){}
- 3. 实现方法 select * from sc;
package com.test.jdbc; import com.test.utils.JDBCUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List; public class JDBC_test8 { public static void main(String[] args) { List<Sc> list=new JDBC_test8().findAll(); //打印list,此处打印是按照Sc的toString方法打印 System.out.println(list); System.out.println(); System.out.println(list.size()); } /** * 查询所有Sc对象 */ public List<Sc> findAll() { Connection conn=null; Statement state=null; ResultSet result=null; List<Sc> list=null; try { //1.注册驱动 //2.获取connection对象 conn= JDBCUtils.getConnection(); //此处使用了博客下文中JDBCUtils类 //3.定义sql String sql="select * from sc"; //4.获取执行sql的对象 state=conn.createStatement(); //5.执行sql result=state.executeQuery(sql); //影响的行数 //6.处理结果 //6.1 循环判断游标是否是最后一行末尾。 Sc sc=null; list=new ArrayList<Sc>(); while(result.next()){ //6.2 获取数据 String sid = result.getString(1); String cid = result.getString("cid"); int grade = result.getInt(3); sc=new Sc(); sc.setCid(cid); sc.setSid(sid); sc.setGrade(grade); //装载集合 list.add(sc); } }catch (SQLException e) { e.printStackTrace(); }finally { //7.释放资源 JDBCUtils.close(result,state,conn); //此处使用了博客下文中JDBCUtils类 } return list; } }
注意:此处的代码使用了后面封装的JDBCUtils类
运行结果:
(5)PreparedStatement(执行sql的对象)
1)SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题
演示:
在SQLyog中使用如下语句建表:
需求:
- 1.通过键盘录入用户名和密码
- 2.判断用户是否登录成功
代码:
package com.test.jdbc; import com.test.utils.JDBCUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Scanner; public class JDBC_test9 { public static void main(String[] args) { //1.键盘登录,接受用户名和密码 Scanner scan=new Scanner(System.in); System.out.print("请输入用户名:"); String user=scan.nextLine(); System.out.print("\n请输入密码:"); String password=scan.nextLine(); //2.调用登录方法 boolean flag=new JDBC_test9().login(user,password); if(flag) System.out.println("登录成功"); else System.out.println("登录失败"); } /** * 登录方法 */ public boolean login(String username,String password) { if(username==null || password==null) return false; Connection conn=null; Statement state=null; ResultSet rs=null; try { //1.注册驱动 //2.获取connection对象 conn= JDBCUtils.getConnection(); //3.定义sql String sql="select * from user where username='"+username+"' and password='"+password+"'"; System.out.println(sql); //4.获取执行sql的对象 state=conn.createStatement(); //5.执行查询 rs=state.executeQuery(sql); //6.处理结果 return rs.next(); } catch (SQLException e) { e.printStackTrace(); }finally { //8.释放资源 JDBCUtils.close(rs,state,conn); } return false; } }
正常执行结果:
1.
2.
3.
但是当随便输入一个表中不存在的用户,输入密码:a' or 'a ' = 'a时:
这样的密码输入使得sql为:select * from user where username = 'zhang' and password = 'a' or 'a' = 'a' ,
where后的结果恒为true,该sql等价于:select * from user,只要表中有数据,就会返回true
2)解决sql注入问题:使用PreparedStatement对象来解决
PrepareStatement是Statement的子接口,也用来执行sql对象
3)静态SQL和预编译SQL
静态SQL:所有的sql语句在执行前都是拼接好的
预编译的SQL:参数使用?作为占位符,在执行sql的时候,为它赋值就可以了,示例如下:
4)使用步骤:
- 1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar
- 2. 注册驱动
- 3. 获取数据库连接对象 Connection
- 4. 定义sql
- 注意:sql的参数使用?作为占位符。 如:select * from user where username = ? and password = ?;
- 5. 获取执行sql语句的对象 PreparedStatement Connection.prepareStatement(String sql)
- 6. 给?赋值:
- 方法: setXxx(参数1,参数2)
- 参数1:?的位置编号 从1 开始
- 参数2:?的值
- 7. 执行sql,接受返回结果,不需要传递sql语句
- 8. 处理结果
- 9. 释放资源
上述登录代码改造:
package com.test.jdbc; import com.test.utils.JDBCUtils; import java.sql.*; import java.util.Scanner; public class JDBC_test9 { public static void main(String[] args) { //1.键盘登录,接受用户名和密码 Scanner scan=new Scanner(System.in); System.out.print("请输入用户名:"); String user=scan.nextLine(); System.out.print("\n请输入密码:"); String password=scan.nextLine(); //2.调用登录方法 boolean flag=new JDBC_test9().login(user,password); if(flag) System.out.println("登录成功"); else System.out.println("登录失败"); } /** * 登录方法 */ public boolean login(String username,String password) { if(username==null || password==null) return false; Connection conn=null; PreparedStatement pstate=null; ResultSet rs=null; try { //1.注册驱动 //2.获取connection对象 conn= JDBCUtils.getConnection(); //3.定义sql String sql="select * from user where username = ? and password = ?"; //4.获取执行sql的对象 pstate=conn.prepareStatement(sql); //5.给?赋值 pstate.setString(1,username); pstate.setString(2,password); //6.执行查询 rs=pstate.executeQuery(); //7.处理结果 return rs.next(); } catch (SQLException e) { e.printStackTrace(); }finally { //8.释放资源 JDBCUtils.close(rs,pstate,conn); } return false; } }
注意:后期都会使用PreparedStatement来完成增删改查的所有操作
- 1. 可以防止SQL注入
- 2. 效率更高
3.抽取JDBC工具类 ——JDBCUtils
上述的代码存在一个问题,就是每次要连接数据库,我们都会写挺过重复的数据库连接和资源释放的重复代码,我们可以将这些代码抽取出来,进行复用
目的:简化书写,代码复用
分析:
- 1. 注册驱动也抽取(不会每次都来注册,写在静态代码块中随着类的加载进行)
- 2. 抽取一个方法获取连接对象
- 需求:不想传递参数(麻烦),还得保证工具类的通用性。
- 解决:配置文件:jdbc.properties
- url=
- user=
- password=
- 3. 抽取一个方法释放资源
(1)新建一个utils包来存放工具类,在utils中新建JDBCUtil类:
(2)JDBCUtil类的代码:
package com.test.utils;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.sql.*;
import java.util.Properties;
/**
*JDBC的工具类
* */
public class JDBCUtils {
private static String url;
private static String user;
private static String password;
private static String driver;
/**
* 配置文件的读取,只需要读取一次即可拿到这些值,使用静态代码块
*/
static{
//读取资源文件,获取值
try {
//通过Properties读取
//1.创建Properties集合类
Properties pro = new Properties();
//2.加载文件
//获取src路径下的文件
ClassLoader classLoader=JDBCUtils.class.getClassLoader();
URL res=classLoader.getResource("jdbc.properties");
String path=res.getPath();
System.out.println(path);
pro.load(new FileReader(path));
//3.获取属性,赋值
url=pro.getProperty("url");
user=pro.getProperty("user");
password=pro.getProperty("password");
driver=pro.getProperty("driver");
//4.注册驱动
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
*获取连接
* @return 连接对象
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,user,password);
}
/**
* 释放资源
* @param state 要释放的执行sql的对象
* @param connection 要释放的连接对象
*/
public static void close(Statement state,Connection connection)
{
//为了防止空指针异常需要判断是否为空
if(connection!=null)
{
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(state!=null)
{
try {
state.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 释放资源
* @param result 要释放的结果集合
* @param state 要释放的执行sql的对象
* @param connection 要释放的连接对象
*/
public static void close(ResultSet result,Statement state, Connection connection)
{
//为了防止空指针异常需要判断是否为空
if(result!=null)
{
try {
result.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
close(state,connection);
}
}
(3)在src目录下定义配置文件jdbc.properties:
(4)连接数据库执行sql的Main类中代码:
package com.test.jdbc;
import com.test.utils.JDBCUtils;
import java.sql.*;
public class Main {
public static void main(String[] args){
Connection conn=null;
Statement state=null;
ResultSet result=null;
try {
//1.注册驱动
//2.获取connection对象
conn=JDBCUtils.getConnection();
//3.定义sql
String sql="select * from sc";
//4.获取执行sql的对象
state=conn.createStatement();
//5.执行sql
result=state.executeQuery(sql); //影响的行数
//6.处理结果
//6.1 循环判断游标是否是最后一行末尾。
while(result.next()){
//6.2 获取数据
String sid = result.getString(1);
String cid = result.getString("cid");
int grade = result.getInt(3);
System.out.println(sid + "---" + cid + "---" + grade);
}
}catch (SQLException e) {
e.printStackTrace();
}finally {
//7.释放资源
JDBCUtils.close(result,state,conn);
}
}
}
(5)运行结果:
4.JDBC事务管理
- 1. 事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。
- 2.事务的三个核心操作:
- 1) 开启事务
- 2)提交事务
- 3)回滚事务
- 3.使用Connection对象来管理事务
- 1)开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务
- 2)提交事务:commit()
- 3)回滚事务:rollback()
代码演示:
package com.test.jdbc;
import com.test.utils.JDBCUtils;
import java.sql.*;
public class JDBC_test9 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement pstate1=null;
PreparedStatement pstate2=null;
try {
//1.注册驱动
//2.获取connection对象
conn= JDBCUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
//3.定义sql
String sql1="update account set balance =balance + ? where id = ?";
String sql2="update account set balance =balance - ? where id = ?";
//4.获取执行sql的对象
pstate1=conn.prepareStatement(sql1);
pstate2=conn.prepareStatement(sql2);
//5.给?赋值
pstate1.setInt(1,500);
pstate1.setInt(2,1);
pstate2.setInt(1,500);
pstate2.setInt(2,2);
//6.执行查询
int count1=pstate1.executeUpdate();
//制造一个异常,如果不使用事务,这里就会造成数据不一致
int error=3/0;
int count2=pstate2.executeUpdate();
//7.处理结果
System.out.println(count1);
System.out.println(count2);
//处理完提交事务
//提交事务
conn.commit();
} catch (Exception e) { //不管出现什么异常,都应该回滚,所以这里应该抓取Exception
//事务回滚
try {
if(conn!=null)
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}finally {
//8.释放资源
JDBCUtils.close(pstate1,conn);
JDBCUtils.close(pstate2,null);
}
}
}
代码执行前的表:
如果将代码中使用事务的代码注释:在执行一条语句后,发生异常后出现数据不一致
使用了事务:在执行一条语句后,发生异常后,会回滚到事务执行前的状态
事务使用步骤总结:
- 1.在连接建立执行之后执行sql之前开启事务
- 2.在执行完sql之后关闭事务
- 3.一旦出现不管任何异常(Exception),即在catch中回滚事务