目录
1.JDBC
Java Database Connectivity(java语言连接数据库)
2.JDBC的本质
JDBC是SUN公司制定的一套接口(interface)
import java.sql.* (有很多接口)
接口都有调用者和实现者
面向接口调用、面向接口实现类,这都属于面向接口编程
为什么要面向接口编程?
解耦合:降低程序的耦合度,提高程序的扩展力。
多态机制就是经典的:面向抽象编程(不要面向具体编程)。
模拟JDBC
上代码
JDBC接口
package 模拟JDBC本质;
/*
SUN公司负责制定这套JDBC接口
*/
public interface JDBC {
//连接数据库的方法
void getConnection();
}
Mysql实现类
package 模拟JDBC本质;
/*
MySQL的数据库厂家负责编写JDBC接口的实现类
*/
public class Mysql implements JDBC{
public void getConnection() {
//具体这里的代码涉及到mysql底层数据库的实现原理
System.out.println("连接MySQL数据库成功!");
}
}
//实现类被称为驱动。(MySQL驱动)
//xxx.jar 当中有很多.class,都是对JDBC接口进行的实现
程序员角色
package 模拟JDBC本质;
import java.util.ResourceBundle;
/*
Java程序员角色
不需要关心具体是那个品牌的数据库,只需要面向JDBC接口写代码
面向接口编程,面向抽象编程,不要面向具体编程
*/
public class JavaProgrammer {
public static void main(String[] args) throws Exception
{
//JDBC jdbc=new MySQL()
//JDBC jdbc=new Oracle()
//创建对象可以通过反射机制
ResourceBundle bundle=ResourceBundle.getBundle("模拟JDBC本质.jdbc");
String className="模拟JDBC本质."+bundle.getString("className");
Class c=Class.forName(className);
JDBC jdbc=(JDBC)c.newInstance();
//以下代码是面向接口调用方法,不需要修改
jdbc.getConnection();
}
}
以上代码中的配置文件 jdbc.properties
className=SqlServer
Oracle
package 模拟JDBC本质;
/*
Oracle的数据库厂家负责编写JDBC接口的实现类
*/
public class Oracle implements JDBC {
public void getConnection() {
//具体这里的代码涉及到Oracle底层数据库的实现原理
System.out.println("连接Oracle数据库成功!");
}
}
//实现类被称为驱动。(Oracle驱动)
//xxx.jar 当中有很多.class,都是对JDBC接口进行的实现
sqlServer
package 模拟JDBC本质;
/*
SqlServer的数据库厂家负责编写JDBC接口的实现类
*/
public class SqlServer implements JDBC {
public void getConnection() {
//具体这里的代码涉及到SqlServer底层数据库的实现原理
System.out.println("连接SqlServer数据库成功!");
}
}
//实现类被称为驱动。(SqlServer驱动)
//xxx.jar 当中有很多.class,都是对JDBC接口进行的实现
3.导入jar包
mysql-connector-java-5.1.7-bin.jar
mysql数据库驱动包,为了以下JDBC编程第一步需要注册。
4.JDBC编程六步
第一步:注册驱动(告诉JAVA连接哪个数据库)
//注册驱动的弟一种写法
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//注册驱动的第二种方式(常用)
//因为该方法的参数是一个字符串,字符串可以写到xxx.properties文件中
//以下方法不需要接受返回值,因为只需要类加载 加载其中的静态方法块
Class.forName("com.mysql.jdbc.Driver");
第二步:获取连接(表示JVM的进程和数据库进程之间的通道打开,属于进程间的通讯,重量级的,使用后关闭)
//Connection conn=null;
conn=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test","root","105105");
第三步:获取数据库操作对象(专门执行sql语句的对象)
//3.获取数据库操作对象
//Statement stmt=null;
stmt=conn.createStatement();
第四步:执行SQL语句(DQL DML…)
String sql="select * from emp order by ename "+keyWords+"";//keyWords 自定义字符串
rs=stmt.executeQuery(sql);
** 第五步:处理查询结果集(没有可以不处理)**
while(rs.next()) {
System.out.println(rs.getString("ename"));
}
第六步:释放资源。
if(rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt!=null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
用户登录验证
package Test;
import java.util.*;
import java.sql.*;
/**-
实现功能:
1.需求:模拟用户登录功能的实现
2.业务描述:
程序运行的时候。提供一个输入的入口,可以让用户输入用户名和密码
用户输入用户名和密码之后。提交信息,java程序搜集到用户信息
java程序连接数据库验证用户名和密码是否合法
合法:显示登陆成功
不合法:显示登录失败
3.数据的准备
在实际开发中,表的设计会使用专业的建模工具,PowerDesigner
4.当前程序存在的问题:
用户名:fdsa
密码:fdsa' or '1'='1
登陆成功
这种现象称为SQL注入(安全隐患)(黑客技术)
5.导致SQL注入的根本原因
用户输入的信息中含有sql语句的关键字,并且
*/
public class JDBCTest06 {
public static void main(String[] args) {
//1.初始化一个界面
Map<String,String> userLoginInfo=initUI();
//2.验证用户名和密码
boolean LoginSuccess=Login(userLoginInfo);
//3.最后输出结果
System.out.println(LoginSuccess ?"登陆成功" :"登陆失败");
}
/**
* 用户登录
* @param userLoginInfo 用户登陆信息
* @return false表示失败,true表示成功
*/
private static boolean Login(Map<String, String> userLoginInfo) {
//JDBC代码
Connection conn =null;
Statement stmt=null;
ResultSet rs=null;
Boolean LoginSucess=null;
try {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
conn=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test","root","105105");
//3.获取数据库操作对象
stmt=conn.createStatement();
//4.执行sql
String sql="select * from t_user where LoginName='"+userLoginInfo.get("LoginName")+"' and LoginPwd='"+userLoginInfo.get("LoginPwd")+"' ";
rs=stmt.executeQuery(sql);
//5.处理结果集
if(rs.next()) {
LoginSucess=true;
}
else
LoginSucess =false;
} catch (SQLException | ClassNotFoundException e1) {
e1.printStackTrace();
}finally {
//6.关闭资源
if(rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt!=null) {
try {
stmt.close();
} catch (SQLException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
if(conn!=null) {
try {
conn.close();
} catch (SQLException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
return LoginSucess;
}
/**
* 初始化用户界面
* @return 用户输入的用户名和密码登录信息
*/
private static Map<String, String> initUI() {
Scanner sr=new Scanner(System.in);
System.out.print("用户名:");
String LoginName=sr.next();
System.out.print("密码:");
String LoginPwd=sr.next();
Map<String,String> userLoginInfo =new HashMap<String,String>();
userLoginInfo.put("LoginName",LoginName);
userLoginInfo.put("LoginPwd",LoginPwd);
return userLoginInfo;
}
}
存在SQL注入的问题
5.解决SQL注入的问题
只要用户提供的信息不参与SQL语句的编译过程,问题就解决了
- 此时使用java.sql.PrepareStatment
- PrepareStatement接口继承了Statement
- PrepareStatement属于预编译的数据库操作对象
- PrepareStatement的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传值
用户登录改进后
package Test;
import java.util.*;
import java.sql.*;
/**
* 1.解决SQL注入问题
* 只要用户提供的信息不参与SQL语句的编译过程,问题就解决了
* 此时使用java.sql.PrepareStatment
* PrepareStatement接口继承了Statement
* PrepareStatement属于预编译的数据库操作对象
* PrepareStatement的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传值
*/
public class JDBCTest07 {
public static void main(String[] args) {
//1.初始化一个界面
Map<String,String> userLoginInfo=initUI();
//2.验证用户名和密码
boolean LoginSuccess=Login(userLoginInfo);
//3.最后输出结果
System.out.println(LoginSuccess ? "登陆成功" :"登陆失败");
}
/**
* 用户登录
* @param userLoginInfo 用户登陆信息
* @return false表示失败,true表示成功
*/
private static boolean Login(Map<String, String> userLoginInfo) {
//JDBC代码
Connection conn =null;
PreparedStatement ps=null;//这里使用PreParedStatement(预编译的数据库操作对象)
ResultSet rs=null;
Boolean LoginSucess=null;
String LoginName=userLoginInfo.get("LoginName");
String LoginPwd=userLoginInfo.get("LoginPwd");
try {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
conn=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test","root","105105");
//3.获取预编译数据库操作对象
//SQL语句的框子,其中一个?表示一个占位符,用来接受一个"值"
String sql="select * from t_user where LoginName= ? and LoginPwd= ?";
ps=conn.prepareStatement(sql);//发送框子给DBMS,进行sql语句的预编译
//给占位符?传值 (下标从1开始)
ps.setString(1, LoginName);
ps.setString(2, LoginPwd);
//4.执行sql
rs=ps.executeQuery();
//5.处理结果集
if(rs.next()) {
LoginSucess=true;
}
else
LoginSucess =false;
} catch (SQLException | ClassNotFoundException e1) {
e1.printStackTrace();
}finally {
//6.关闭资源
if(rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps!=null) {
try {
ps.close();
} catch (SQLException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
if(conn!=null) {
try {
conn.close();
} catch (SQLException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
return LoginSucess;
}
/**
* 初始化用户界面
* @return 用户输入的用户名和密码登录信息
*/
private static Map<String, String> initUI() {
Scanner sr=new Scanner(System.in);
System.out.print("用户名:");
String LoginName=sr.next();
System.out.print("密码:");
String LoginPwd=sr.next();
Map<String,String> userLoginInfo =new HashMap<String,String>();
userLoginInfo.put("LoginName",LoginName);
userLoginInfo.put("LoginPwd",LoginPwd);
return userLoginInfo;
}
}
6.对比Statement和PreparedStatement
- Statement存在SQL注入问题,PreparedStatement解决了SQL注入问题
- Statement是编译一次执行一次,PreparedStatement是编译一次,可执行n次。PreparedStatement效率比较高
- PreparedStatement会在编译阶段做类型的安全检查。
- Statement可以传递SQL语句PreparedStatement只传值
综上所述:PreparedStatement使用较多,只有极少数的情况下需要Statement(当业务需要进行sql语句拼接的时候)。
7.演示只能使用Statement对象不能使用PreparedStatement的业务需求
用户输入sql语句
package Test;
import java.sql.*;
import java.util.*;
/**
* 演示只能使用Statement对象不能使用PreparedStatement的业务需求
*/
public class JDBCTest08 {
public static void main(String[] args) {
//用户在控制台输入desc就是降序,输入asc就是升序。
Scanner sr=new Scanner(System.in);
System.out.println("desc-降序 asc-升序");
System.out.print("请输入:");
String keyWords=sr.next();
//执行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/company","root","105105");
//获取数据库预编译操作对象
stmt=conn.createStatement();
//执行sql语句
String sql="select * from emp order by ename "+keyWords+"";
rs=stmt.executeQuery(sql);
while(rs.next()) {
System.out.println(rs.getString("ename"));
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
if(rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt!=null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
* 使用PreparedStatement
*/
// public static void main(String[] args) {
// //用户在控制台输入desc就是降序,输入asc就是升序。
// Scanner sr=new Scanner(System.in);
// System.out.println("desc-降序 asc-升序");
// System.out.print("请输入:");
// String keyWords=sr.next();
//
// //执行SQL
// Connection conn=null;
// PreparedStatement ps=null;
// ResultSet rs=null;
// try {
// //注册驱动
// Class.forName("com.mysql.jdbc.Driver");
// //获取连接
// conn=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/company","root","105105");
// //获取数据库预编译操作对象
// String sql="select * from emp order by ename ?";
// ps=conn.prepareStatement(sql);
// ps.setString(1, keyWords);
// //执行sql语句
// rs=ps.executeQuery();
// while(rs.next()) {
// System.out.println(rs.getString("ename"));
// }
// } catch (ClassNotFoundException | SQLException e) {
// e.printStackTrace();
// }finally {
// if(rs!=null) {
// try {
// rs.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
//
// }
// if(ps!=null) {
// try {
// ps.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
//
// }
// if(conn!=null) {
// try {
// conn.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }
//
// }
//
// }
//
// }// 报错显示SQL语法有误
}
8.JDBC事务控制
JDBC事务机制
-
JDBC中的事务是自动提交的
-
只要执行任意一条DML语句,则自动提交一次,这是JDBC默认的事务行为
-
但是在实际业务中,通常是N条DML语句共同联合才能完成的。
-
必须保证这些DML在同一个事物中同时成功或者同时失败。
三段重要代码
conn.setAutoCommit(false);//关闭自动提交
conn.commit;//提交事务
catch(EXception e){//程序异常
if(conn!=null){
conn.rollback;//手动回滚
}
}
应用于数据库用户之间的转账
package Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* JDBC事物的控制
*/
public class JDBCTest11 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test","root","105105");
conn.setAutoCommit(false);//开启事务
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();
ps.setDouble(1,10000);
ps.setInt(2, 222);
count+=ps.executeUpdate();
System.out.println(count==2 ? "转账成功":"转账失败");
conn.commit();//提交事务
} catch (Exception e) {
//如果出现异常,
if(conn!=null) {
try {
conn.rollback();//回滚事务
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
}finally {
if(ps!=null) {
try {
ps.close();
} catch (SQLException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
if(conn!=null) {
try {
conn.close();
} catch (SQLException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
}
9.悲观锁和乐观锁机制
悲观锁:事务必须排队执行,数据锁住了,不允许并发(行级锁:select后面添加for update)
乐观锁:支持并发,事务也不需要排队,只不过需要一个版本号。
举栗子说明
第一个事务使用悲观锁,锁定相关数据
package Test;
/**
* 这个程序开启一个事务,这个事务专门进行查询,并且使用行级锁/悲观锁,锁住相关的数据
*/
import java.sql.*;
import utils.DBUtil;
public class JDBCTest13 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
conn=DBUtil.getConnection();
//开启事物
conn.setAutoCommit(false);
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 e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
}finally {
DBUtil.close(conn, ps, rs);
}
}
}
第二个事务负责修改被锁定的记录
package Test;
/**
*这个程序负责修改被锁定的记录
*/
import java.sql.*;
import utils.DBUtil;
public class JDBCTest14 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
try {
conn=DBUtil.getConnection();
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 e1) {
// TODO 自动生成的 catch 块
e1.printStackTrace();
}
}
e.printStackTrace();
}
}
}
总结:第一个事务没有结束,第二个事务需要排队