JDBC的使用总结
JDBC是用Java语言操作关系型数据库的一套API,全称(Java DataBase Connectivity)Java数据库连。JDBC本质:
- Java官方定义的一套操作所有关系型数据库的接口
- 各个数据库厂商根据接口去做各自的实现,提供数据库驱动的jar包
- 开发者使用接口(JDBC)提供的方法编程,真正执行的代码是驱动jar包中提供的实现类。
- 这样虽然数据库不同,但由于接口相同,实现的方法相同。就能使用同一套代码操作不同的数据库。只要替换厂商提供的jar包即可,业务代码不用变化
JDBC的基础操作步骤:
public void testJdbc() throws Exception {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
String driverUrl = "jdbc:mysql://localhost:3306/test";
String userName = "root";
String passWord = "zl.950922";
//2.获取连接
Connection conn = DriverManager.getConnection(driverUrl, userName, passWord);
//3.定义SQL语句
String sql = "select * from bs_user";
//4.获取执行SQL的对象,statement
Statement statement = conn.createStatement();
//5.执行SQL
boolean execute = statement.execute(sql);
System.out.println(execute);
//6.释放资源
statement.close();
conn.close();
}
DriverManager(驱动管理类)
它是一个数据库的驱动管理类,定义了一些静态方法。主要用于注册数据库驱动和获取数据库连接:
- 注册驱动 (DriverManager.registerDriver)
Class.forName("com.mysql.cj.jdbc.Driver");
在之前的示例代码中,我们使用Class.forName将指定名称的jar包加载入内存。好像并没有使用到DriverManager的registerDriver方法。但是,我们查看Driver的源码可以看到:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
MySQL官方在Driver类中写了一个静态代码块。静态代码块中使用registerDriver进行了数据库驱动的注册。而静态代码块是随着类的加载自动执行的。所以,我们在使用Class.forName将类加载进内存的时候,已经自动使用DriverManager对驱动进行了注册
- 获取数据库连接(DriverManager.getConnection)
Connection conn = DriverManager.getConnection(driverUrl, userName, passWord);
userName和passWord是连接数据库的用户名和密码,driverUrl是连接数据库的地址,主要用来定义数据库的链接路径、名称、参数等。
//jdbc:mysql://ip地址(域名):端口号/数据库名称?参数键值对1&参数键值对2&参数键值对3 。。。。
String driverUrl = "jdbc:mysql://localhost:3306/test"; //表示连接本机3306端口的名为test的数据库
//如果是在本机的3306端口,可以简写为 String driverUrl = "jdbc:mysql:///test"
Connection(数据库连接对象)
这个类主要用于获取获取执行SQL语句的对象和管理事务
获取执行SQL语句的对象
1.普通执行SQL语句的对象 :Statement Connection.createStatement()
2.预编译SQL的执行SQL语句的对象:PrepareStatement Connection.prepareStatement(sql)
JDBC事务管理:Connection接口中定义了3个对应的方法用于管理事务
1.开启事务:setAutoCommit(boolean autoCommit) :true为自动提交事务,false为手动提交事务,即开启事务
2.提交事务:commit()
3.回滚事务:rollback()
@Test
public void testAutoCommit() throws Exception {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
String driverUrl = "jdbc:mysql://localhost:3306/test";
String userName = "root";
String passWord = "zl.950922";
//2.获取连接
Connection conn = DriverManager.getConnection(driverUrl, userName, passWord);
//3.定义SQL语句
String sql1 = "update account set money = 6000 where id =1";
String sql2 = "update account set money = 6000 where id =2";
//4.获取执行SQL的对象,statement
Statement statement = conn.createStatement();
//开启事务,不自动提交就是手动提交,即开启事务
conn.setAutoCommit(false);
try {
//5.1执行SQL1
int count1 = statement.executeUpdate(sql1);
System.out.println(count1);
int i = 1/0;
//5.2 执行SQL2
int count2 = statement.executeUpdate(sql2);
System.out.println(count2);
} catch (SQLException e) {
//回滚事务,出现异常将刚才执行的sql语句进行回滚
conn.rollback();
}
//提交事务
conn.commit();
//6.释放资源
statement.close();
conn.close();
}
Statement
此类的作用是用来执行SQL语句,根据不同的SQL类型提供了多个执行SQL语句的方法
1.int executeUpdate(sql):执行DDL和DML语句,DML返回执行收影响的行数,DDL即使执行成功返回值也可能为0
//DML语句(数据表中数据的增删改)
String sql = "update account set money = 10000 where id = 1";
//获取执行SQL的对象,statement
Statement statement = conn.createStatement();
//执行SQL
int i = statement.executeUpdate(sql);
System.out.println(i); //数据库中有一行数据被影响,则返回的结果为1
2.ResultSet executeQuery(sql):执行DQL语句,返回的是ResultSet结果集对象
//执行SQL,获取Result结果集
ResultSet resultSet = statement.executeQuery(sql);
/**
* boolean next():将游标从当前位置向前移动一行,并判断当前行是否为有效行
*/
while (resultSet.next()){
/**
* xxx getXxx(参数):获取数据
* 参数:
* 1.int 列的编号,从1开始
* 2.String 列的名称
*/
//参数为字符串,即列的名称
System.out.println(resultSet.getInt("id"));
//参数为整型,即数据表中列的编号,从1开始
System.out.println(resultSet.getString(2));
}
PreparedStatement
预编译SQL语句并执行,可以起到预防SQL注入的问题。
在之前使用Statement执行SQL语句时,如果SQL语句中有参数,我们是通过直接字符串的拼接来设置参数的:
String name ="zlprime";
String pwd = "zlprime";
String sql = "select * from account where name ='"+name +"'and pwd='"+pwd+"'";
//拼接出来的结果就是 select * from account where name ='zlprime' and pwd ='zlprime'
//这时是正常的,如果 恶意用户输入的是 pwd = "' or '1' = '1'"
//那么拼出来的结果就是 select * from account where name ='zlprime' and pwd ='' or '1' = '1'
//这样无论name和pwd是否正确,后面的1 = 1都是恒等式。那么这条sql语句被执行后,肯定能获取到数据。即使name和pwd不正确
//这就是SQL注入
而PreparedStatement就解决了这个问题,在它的底层对敏感字符进行了转义。如果用户输入的是 ‘1’ 经过PreparedStatement转义变为了 \’1\’ ,敏感字符被转译成了SQL中的字符,而不是再作为SQL语句的关键字被对待。这就解决了SQL注入的问题。
PreparedStatement基本使用
- 获取PreparedStatement对象
//SQL语句中的参数值使用 ? 占位符代替
String sql = "select * from account where id = ? and money = ?";
//通过Connection获取PreparedStatement对象,此时将SQL语句作为参数进行传入
PreparedStatement preparedStatement = conn.prepareStatement(sql);
- 设置参数值
//设置参数值
/**
* setXxx(参数1,参数2):用于给 ? 占位符复制
* 参数1:占位符的位置,即 ? 的位置
* 参数2:占位符的值
*/
preparedStatement.setInt(1,1);
preparedStatement.setString(2,"3000");
整体下来的流程就是,先获取PreparedStatement对象,在获取该对象时,传入用 ? 占位符进行占位的SQL语句。然后再调用PreparedStatement对象的setXxx方法,为占位符进行赋值。接着再跟之前一样,执行它的executeXxx方法即可。
//SQL语句中的参数值使用 ? 占位符代替
String sql = "select * from account where id = ? and money = ?";
//通过Connection获取PreparedStatement对象,此时将SQL语句作为参数进行传入
PreparedStatement preparedStatement = conn.prepareStatement(sql);
//设置参数值
/**
* setXxx(参数1,参数2):用于给 ? 占位符复制
* 参数1:占位符的位置,即 ? 的位置
* 参数2:占位符的值
*/
preparedStatement.setInt(1,1);
preparedStatement.setString(2,"3000");
//6.执行SQL语句
ResultSet resultSet = preparedStatement.executeQuery();
PreparedStatement还有一个好处是预编译SQL,使得SQL语句的执行效率更高。通常情况下,一条SQL语句可能会重复执行多次。在这多次的执行过程中,SQL语句的结构是相同的,只是里面的参数值不同。如果没有预编译,每次执行这条SQL语句都要进行校验、解析等操作,比较耗时。因此MySQL提供了预编译功能,即,将需要替换的参数值使用?占位符进行站位,参数提取出来,把SQL语句进行模板化。这样再执行相同的SQL语句时,就不需要再重复解析校验了,只需要把参数带入再执行即可。
PreparedStatement的执行过程:
- 在mysql的driverUrl中加入参数 useServerPrepStmts=true,开启预编译功能
- 设置cachePrepStmts=true开启编译预缓存功能,即使是不同的PreparedStatement对象,如果执行相同的SQL语句,也不会编译两次。只要是同一个Connection,就能实现一次编译随处运行。
- 在获取PreparedStatement对象时,将SQL语句传给mysql服务器进行检查、校验
- 通过PreparedStatement的setXXX方法为占位符赋值,mysql服务器接收到语句时,直接执行不再检查校验。