
目录
1.JDBC概述及配置
1.1 概述
1.2 配置
2.JDBC的开发
2.1 JDBC编程六步概述
2.2 具体代码实现
3.SQL注入
3.1 SQL注入现象
3.2 Statrment和PreparedStatement的对比
3.3 PreparedStatement实现增删改
4.JDBC的事务自动提交机制
4.1 JDBC的事务机制
5.JDBC的模糊查询
6.设置行级锁
JDBC:Java Database Connectivity。java语言连接数据库。
JDBC的本质:是SUN公司编写的一套接口interface。在java.sql.*软件包下有很多接口。不同的数据库的底层实现原理都不一样,而不同的数据库如mysql、oracle都去实现这套接口,不同的数据库有不同的这些接口的实现类。
接口都有调用者和实现者,面向接口调用、面向接口写是实现类都属于面向接口编程(为了解耦合,降低程序的耦合度,提高程序的扩展力)。
JDBC开发前的准备工作,先从官网下载对应的驱动jar包,然后将其配置到环境变量classpath当中,配置classpath时开头必须有".;"(.表示当前路径)。配置jar包就是为了获得数据库对于JDBC接口的实现类。当前配置的版本是5.1.23。
以上配置针对于文本编辑器的开发方式,使用IDEA工具的时候,不需要配置以上的环境变量,IDEA有自己的配置方式。
IDEA配置JDBC:
①注册驱动:告诉JVM即将要连接的是哪个数据库
②获取连接:表示JVM的进程和数据库进程之间的通道打开了。属于进程间的通信,是重量级的,使用完之后一定要关闭通道。
③获取数据库操作对象(专门执行sql语句的对象):有个该对象,才可以执行sql语句。
④执行sql语句:DQL、DML语句。
⑤处理查询结果集:只有当第四步执行的是select语句的时候,才有这一步。
⑥释放资源:使用完资源之后一定要关闭资源。
import java.sql.*;
public class JDBCTest01{
public static void main(String[] args){
Connection conn = null;
Statement stmt = null;
try{
//1.注册驱动
Driver driver = new com.mysql.jdbc.Driver();//注意:前面的Driver是一个接口,属于java.sql包下的,后面的Driver是接口Driver的一个Mysql实现类
//实现了多态,父类型引用指向子类型对象
DriverManager.registerDriver(driver);
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//2.获取连接
String url = "jdbc:mysql://127.0.0.1:3306/bjpowernode";//当前电脑上的数据库bjpowernode
String user = "root";//用户名
String password = "Axxyneymar123";//密码
conn = DriverManager.getConnection(url,user,password);
//conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","Axxyneymar123");
System.out.println("数据库连接对象 = " + conn);
//3.获取数据库操作对象
stmt = conn.createStatement();
//4.执行sql语句
String sql1 = "insert into dept(deptno,dname,loc) values(50,'人事部','北京')";//插入
int count1 = stmt.executeUpdate(sql1);//专门执行DML语句的函数(insert,delete,update),返回值是“该DML语句影响数据库中的记录条数”
System.out.println(count1 == 1 ? "保存成功":"保存失败");
}catch(SQLException e){
e.printStackTrace();
}finally{
//6.释放资源
//为了保证资源一定释放,在finally语句块中关闭资源,并且要遵循从小到大依次关闭。
//分别对其try...catch
if(stmt != null){
try{
stmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(conn != null){
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
关于上述代码中的url:
URL:统一资源定位符(网络中某个资源的绝对路径)。
URL包括哪几部分:
①协议:通信协议是通信之前就提前定好的数据传送格式,数据包具体怎么传数据。
②IP
③端口PORT
④资源名
比如:http://182.61.200.7:80/index.html http://是通信协议,182.61.200.7是服务器IP地址,
80是服务器上软件的端口,index.html是服务器上某个资源名。
注意:localhost和127.0.0.1都是本机IP地址。
Oracle的URL:jdbc:oracle:thin:@localhost:1521:orcl
JDBC中的sql语句不需要分号;结尾,加上了分号会报错。
利用反射使用类加载的方式注册驱动:
import java.sql.*;
public class JDBCTest03{
public static void main(String[] args){
try{
//注册驱动的第二种方式,常用的。
//为什么常用?因为参数是一个字符串,字符串可以写到xxx.properties文件中。
//以下方法不需要返回值,因为我们只想用它的类加载动作
Class.forName("com.mysql.jdbc.Driver");//mysql的实现类的源代码内有一个静态代码块来实现驱动的注册。
//直接利用反射机制在类加载的时候就会执行静态代码块。
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","Axxyneymar123");
System.out.println("数据库连接对象 = " + conn);
}catch(SQLException e){
e.printStackTrace();
}catch(ClassNotFoundException e){
e.printStackTrace();
}
}
}
com.mysql.jdbc.Driver的源码中有一个静态代码块,该代码块中调用了DriverManager.registerDriver()
使用资源绑定器来绑定配置文件jdbc.properties,这样子不用把java代码写死,之后只要修改配置文件就行。
//将连接数据库的所有信息配置到配置文件中
import java.sql.*;
import java.util.*;
public class JDBCTest04{
public static void main(String[] args){
//使用资源绑定器绑定配置文件
ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
Connection conn = null;
Statement stmt = null;
try{
Class.forName(driver);
//2.获取连接
conn = DriverManager.getConnection(url,user,password);
System.out.println("数据库连接对象 = " + conn);
//3.获取数据库操作对象
stmt = conn.createStatement();
//4.执行sql语句
String sql1 = "update dept set dname = '销售部',loc = '天津' where deptno = 20";
int count1 = stmt.executeUpdate(sql1);
System.out.println(count1 == 1 ? "修改成功":"修改失败");
}catch(SQLException e){
e.printStackTrace();
}catch(ClassNotFoundException e){
e.printStackTrace();
}finally{
//6.释放资源
if(stmt != null){
try{
stmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(conn != null){
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
关于处理查询到的数据集:java.sql.ResultSet接口。
import java.sql.*;
import java.util.*;
public class JDBCTest05{
public static void main(String[] args){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;//结果集
try{
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","Axxyneymar123");
//3.获取数据库操作对象
stmt = conn.createStatement();
//4.执行sql语句
String sql = "select empno as a,ename,sal from emp";
rs = stmt.executeQuery(sql);//专门执行DQL语句的方法
//5.处理查询结果集
while(rs.next()){//返回true代表这一行有数据
//String empno = rs.getString(1);//1,2,3代表第几列,但不建议使用这种方式
//String ename = rs.getString(2);
//String sal = rs.getString(3);
String empno = rs.getString("a");//以列的名字获取,若重命名,必须写重命名后的名字。
//重点注意:列名称不是表中的列名称,是查询结果集的列名称。
String ename = rs.getString("ename");
String sal = rs.getString("sal");
System.out.println(empno + "," + ename + "," + sal);
//除了可以以String类型取出之外,还可以以特定的类型取出
int empno = rs.getInt("a");
String ename = rs.getString("ename");
double sal = rs.getDouble("sal");
System.out.println(empno + "," + ename + "," + (sal + 100));//若使用double,可以直接加法运算
}
}catch(Exception e){
e.printStackTrace();
}finally{
//释放资源
if(rs != null){
try{
rs.close();
}catch(Exception e){
e.printStackTrace();
}
}
if(stmt != null){
try{
stmt.close();
}catch(Exception e){
e.printStackTrace();
}
}
if(conn != null){
try{
conn.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
}
}
比如下面这个sql语句:
//模拟用户登录 String sql = "select * from t_user where loginName = '"+loginName+"' and loginPwd = '"+loginPwd+"'"; //只有在数据库中能找到一条记录,才说明登录成功
此时如果输入:
数据库中没有该用户,但是仍显示登录成功。
此时sql语句相当于:'1'='1'恒成立,且是or表示或者,因此where后面的条件一直成立,所以
就能查出数据记录了,而判断逻辑就是有结果数据就说明登录成功,因此显示登录成功。本质
上就是正好完成了sql语句的拼接,使其可以组成一个正确的sql语句。
这种现象称为SQL注入。
导致SQL注入的原因:用户输入的信息中含有sql语句的关键字,并且这些关键字会参与sql语句的编译过程,导致sql语句的原意被扭曲,进而达到sql注入。
解决办法:
只要用户提供的信息不参与sql语句的编译过程,问题就解决了。即使用户提供的信息中含有
sql语句的关键字,但是没有参与编译,不起作用。要想用户信息不参与sql语句的编译,那么
必须使用java.sql.PreparedStatement接口,该接口继承了java.sql.Statement。
PreparedStatement是属于预编译的数据库操作对象,其原理是预先对sql语句的框架进行编
译,然后再给sql语句传"值"。?作为值的占位符。
代码改为:
//JDBC代码
Connection conn = null;
PreparedStatement ps = null;//这里使用PreparedStatement(预编译的数据库操作对象)
ResultSet rs = null;
try{
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","Axxyneymar123");
//3.获取预编译的数据库操作对象
//SQL语句的框子,其中一个?表示一个占位符,一个?将来接收一个“值”,注意:占位符不能使用单引号括起来。
String sql = "select * from t_user where loginName = ? and loginPwd = ?";//SQL语句的框架
//程序执行到此处,会发送sql语句框子给DBMS,然后DBMS进行sql语句的预先编译
ps = conn.prepareStatement(sql);
//给占位符?传值(第1个问号小标是1,第2个问号下标是2,JDBC中所有下标从1开始)
ps.setString(1,loginName);
ps.setString(2,loginPwd);
//4.执行sql
rs = ps.executeQuery();//此处参数不能再加sql了,加上了会再进行一次编译。
//5.处理结果集
if(rs.next()){
//如果rs.next()返回true说明查找到了记录,说明登录成功,标记置为true
loginSuccess = true;
}
对比:
①Statement存在sql注入问题;PreparedStatement解决了sql注入问题。
②Statement是编译一次执行一次,PreparedStatement是编译一次可执行N次,只要传值就
行,因此PreparedStatement效率较高一些。
③PreparedStatement会在编译阶段做类型的安全检查,传入的值的类型是否与要求一致。
综上所述:PreparedStatement使用较多,只有极少数的情况下会使用Statement。
什么情况下必须使用Statement?
业务方面要求必须支持SQL注入的时候。比如需要升序降序时,如果使用setString进行传值
给占位符,会给升序降序关键字加上单引号'',导致sql语句不正确了,此时需要使用到拼接。
//用户在控制台输入desc就是降序,输入asc就是升序
Scanner s = new Scanner(System.in);
System.out.println("输入desc或asc,desc表示降序,asc表示升序");
System.out.print("请输入:");
String keyWords = s.nextLine();
//执行sql语句
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","Axxyneymar123");
//获取数据库操作对象
stmt = conn.createStatement();
String sql = "select ename from emp order by ename "+keyWords+"";
//或者String sql = "select ename from emp order by ename" + keyWords;
rs = stmt.executeQuery(sql);
while(rs.next()){
System.out.println(rs.getString("ename"));
}
Connection conn = null;
PreparedStatement ps = null;
try{
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","Axxyneymar123");
//3.获取预编译的数据库操作对象
String sql = "delete from dept where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1,60);
//4.执行sql语句
int count = ps.executeUpdate();
System.out.println(count);
JDBC中的事务是自动提交的,即只要执行任意一条DML语句,则自动提交一次,这是JDBC默认的事务行为。但是在实际的业务当中,通常都是N条DML语句共同联合才能完成的,必须保证他们这些DML语句在同一个事务中同时成功或者同时失败。
用一个账户转账的例子来实现事务:
public class JDBCTest11 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try{
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","Axxyneymar123");
//将自动提交机制修改为手动提交
conn.setAutoCommit(false);//开启事务
//3.获取预编译的数据库操作对象
String sql = "update t_act set balance = ? where actno = ?";
ps = conn.prepareStatement(sql);
ps.setDouble(1,10000);
ps.setInt(2,111);
int count = ps.executeUpdate();
String s = null;
s.toString();
ps.setDouble(1,10000);
ps.setInt(2,222);
count += ps.executeUpdate();
//程序能够走到这里说明以上程序没有异常,事务结束,手动提交数据
conn.commit();//手动提交事务
}catch(Exception e){
//出现了异常,需要回滚事务
if(conn != null){
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
}finally{
if(ps != null){
try{
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try{
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
重点代码:
Connection对象conn。
conn.setAutoCommit(false);
conn.commit();
conn.rollback();
JDBC模糊查询的格式:
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs= null;
//错误写法,问号旁边不应该有符号
String sql = "select ename from emp where ename like ?";
ps = conn.prepareStatement(sql);
ps.setString(1,"_A%");
rs = ps.executeQuery();
while (rs.next()){
System.out.println(rs.getString("ename"));
}
在select语句中末尾加上for update就会把这几条记录锁住,即加上了行级锁(悲观锁)。下面用两个事务来举个例子,debug后发现两个事务必须排队进行。
public class JDBCTest13 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnertion();
//开启事务
conn.setAutoCommit(false);
//在sql语句后面加上for update就会加上行级锁
String sql = "select ename,job,sal from emp where job = ? for update";
ps = conn.prepareStatement(sql);
ps.setString(1,"MANAGER");
rs = ps.executeQuery();
while (rs.next()){
System.out.println(rs.getString("ename") + " " + rs.getString("job") + " " + rs.getDouble("sal"));
}
//提交事务(事务结束)
conn.commit();
} catch (Exception e) {
if(conn != null){
try {
//回滚事务(事务结束)
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally{
DBUtil.close(conn,ps,rs);
}
}
}
public class JDBCTest14 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps =null;
try {
conn = DBUtil.getConnertion();
conn.setAutoCommit(false);
String sql = "update emp set sal = sal*1.1 where job = ?";
ps = conn.prepareStatement(sql);
ps.setString(1,"MANAGER");
int count = ps.executeUpdate();
System.out.println(count);
conn.commit();
} catch (Exception e) {
if(conn != null){
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
}finally {
DBUtil.close(conn,ps,null);//没有这个参数就写null
}
}
}
PS:根据动力节点课程整理,如有侵权,联系删除。