此笔记基于动力节点课程
1、JDBC是什么?
Java DataBase Connectivity(Java语言连接数据库)
2、JDBC的本质是什么?
JDBC是SUN公司制定的一套接口(interface)
java.sql.*; (这个软件包下有很多接口。)
接口都有调用者和实现者。
面向接口调用、面向接口写实现类,这都属于面向接口编程。
为什么要面向接口编程?
解耦合:降低程序的耦合度,提高程序的扩展力。
多态机制就是非常典型的:面向抽象编程。(不要面向具体编程)
建议:
Animal a = new Cat();
Animal a = new Dog();
// 喂养的方法
public void feed(Animal a){ // 面向父类型编程。
}
不建议:
Dog d = new Dog();
Cat c = new Cat();
思考:为什么SUN制定一套JDBC接口呢?
因为每一个数据库的底层实现原理都不一样。
Oracle数据库有自己的原理。
MySQL数据库也有自己的原理。
MS SqlServer数据库也有自己的原理。
....
每一个数据库产品都有自己独特的实现原理。
JDBC的本质到底是什么?
一套接口。
3、JDBC开发前的准备工作,先从官网下载对应的驱动jar包,然后将其配置到环境变量classpath当中。
classpath=.;D:\course\06-JDBC\resources\MySql Connector Java 5.1.23\mysql-connector-java-5.1.23-bin.jar
以上的配置是针对于文本编辑器的方式开发,使用IDEA工具的时候,不需要配置以上的环境变量。
IDEA有自己的配置方式,将jdbc的jar包导入到项目库文件中即可。
4、JDBC编程六步
第一步:注册驱动(作用:告诉Java程序,即将要连接的是哪个品牌的数据库)
第二步:获取连接(表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用完之后一定要关闭通道。)
第三步:获取数据库操作对象(专门执行sql语句的对象)
第四步:执行SQL语句(DQL DML....)
第五步:处理查询结果集(只有当第四步执行的是select语句的时候,才有这第五步处理查询结果集。)
第六步:释放资源(使用完资源之后一定要关闭资源。Java和数据库属于进程间的通信,开启之后一定要关闭。)
package com.jdbc;
import java.sql.*;
public class TestJdbc01 {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
//1、注册驱动
//常用类加载的方法加载驱动,因为参数是一个字符串,字符串可以写到.properties文件中
//不需要接受返回值,因为我们只想用到他的类加载的动作
Class.forName("com.mysql.cj.jdbc.Driver");
//第二中加载驱动的方法
//Driver driver = new com.mysql.cj.jdbc.Driver();//多态
//DriverManager.registerDriver(driver);
//2、获取连接
String url = "jdbc:mysql://localhost/powernode";
String user = "root";
String password = "8888";
connection = DriverManager.getConnection(url, user, password);
// com.mysql.cj.jdbc.ConnectionImpl@ffaa6af
System.out.println("数据库连接对象 = " + connection);
//3、获取数据库操作对象
statement = connection.createStatement();
//4、执行sql
String sql = "insert into dept (deptno,dname,loc)values(50,'PERSONNEL','BEIJING')";
//executeUpdate(String sql)专门执行DML语句(INSERT,UPDATE,DELETE)
//返回值是"影响数据库中的记录条数"
int count = statement.executeUpdate(sql);
System.out.println(count==1?"保存成功":"保存失败");
sql = "delete from dept where deptno = 50";
count = statement.executeUpdate(sql);
System.out.println(count==1?"删除成功":"删除失败");
sql = "update dept set loc = 'NANJING' where deptno = 40";
count = statement.executeUpdate(sql);
System.out.println(count==1?"更新成功":"更新失败");
sql = "select * from dept";
//5、处理查询结果集
resultSet = statement.executeQuery(sql);
while(resultSet.next()) {
//列名称不是表中的列名称,是查询结果集中的列名称,即起别名后要用别名
String deptno = resultSet.getString("deptno");
String dname = resultSet.getString("dname");
String loc = resultSet.getString("loc");
System.out.println(deptno+","+dname+","+loc);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//6、释放资源
//为保证资源 一定释放,在finally语句块中关闭资源
//从小到大依次关闭,并分别try...catch...
try {
if(resultSet!=null) {
resultSet.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(statement!=null) {
statement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(connection!=null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
package com.jdbc;
import java.sql.*;
import java.util.ResourceBundle;
public class TestJdbc02 {
public static void main(String[] args) {
//使用资源绑定器绑定属性配置文件
ResourceBundle bundle = ResourceBundle.getBundle("com/cbx/jdbc/jdbc");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
Connection connection = null;
Statement statement = null;
try {
Class.forName(driver);
connection = DriverManager.getConnection(url,user,password);
statement = connection.createStatement();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if(statement!=null) {
statement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if(connection!=null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
5、案例:用户登录业务
1、需求:
模拟用户登录功能的实现
2、业务描述:
程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码
用户输入用户名和密码后,提交信息,java程序收集到用户信息
java程序连接数据库验证用户名和密码是否合法
合法:显示登录成功
不合法:显示登录失败
3、数据的准备:
在实际开发过程中,表的设计会使用专业的建模工具,我们这里安装一个建模工具PowerDesigner(mac用户可以安装chiner)
package com.jdbc;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class TestJdbc03 {
public static void main(String[] args) {
//初始化一个界面
Map<String,String> userLoginInfo = InitUI();
//验证用户名和密码
Boolean loginSuccess = login(userLoginInfo);
//输出结果
System.out.println(loginSuccess?"登录成功":"登录失败");
}
/**
* 验证用户名和密码
* @param userLoginInfo 用户登录信息
* @return true表示登录成功,false表示登录失败
*/
private static Boolean login(Map<String, String> userLoginInfo) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
Boolean loginSuccess = false;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/powernode";
String user = "root";
String password = "8888";
connection = DriverManager.getConnection(url,user,password);
statement = connection.createStatement();
String sql = "select * from t_user where loginName = '"+userLoginInfo.get("loginName")+"' and loginPassword = '"+userLoginInfo.get("loginPassword")+"'";
resultSet = statement.executeQuery(sql);
if(resultSet.next()) {
loginSuccess = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(resultSet!=null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(statement!=null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return loginSuccess;
}
/**
* 初始化用户界面
* @return 用户输入的用户名和密码
*/
private static Map<String, String> InitUI() {
Scanner sc = new Scanner(System.in);
System.out.println("用户名:");
String loginName = sc.nextLine();
System.out.println("密码:");
String loginPassword = sc.nextLine();
Map<String,String> userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPassword",loginPassword);
return userLoginInfo;
}
}
6、SQL注入问题
问题说明
当上面案例输入用户名和密码时,输入fdsa和fdsa’ or ‘1’ = '1时,会显示全部信息,因为字符串拼接后会成为select * from t_user where loginName = 'fdsa' and loginPassword = 'fdsa' or '1' = '1';
。这就是SQL注入问题
根本原因:
用户输入的信息中含有sql语句的关键字,并且这些关键字参与到sql语句的编译过程中,导致sql语句的原意被扭曲,进而达到sql注入。
解决sql注入问题:
只要用户提供的信息不参与sql语句的编译过程,问题就解决了
要想用户提供的信息不参与编译,就要使用java.sql.PreparedStatement
PreparedStatement继承了java.sql.Statement;
PreparedStatement属于预编译的数据库操作对象
PreparedStatement的原理是:预先对sql语句的框架进行编译,然后再给sql语句传“值”
package com.jdbc;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class TestJdbc04 {
public static void main(String[] args) {
//初始化一个界面
Map<String,String> userLoginInfo = InitUI();
//验证用户名和密码
Boolean loginSuccess = login(userLoginInfo);
//输出结果
System.out.println(loginSuccess?"登录成功":"登录失败");
}
/**
* 验证用户名和密码
* @param userLoginInfo 用户登录信息
* @return true表示登录成功,false表示登录失败
*/
private static Boolean login(Map<String, String> userLoginInfo) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
Boolean loginSuccess = false;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/powernode";
String user = "root";
String password = "8888";
connection = DriverManager.getConnection(url,user,password);
//sql语句框架
//?为占位符,不能给?加引号
String sql = "select * from t_user where loginName = ? and loginPassword = ?";
//获取预编译的数据库操作对象
//注意方法名,prepare后没有d
preparedStatement = connection.prepareStatement(sql);
//给占位符赋值,jdbc中下标从1开始
String loginName = userLoginInfo.get("loginName");
preparedStatement.setString(1,loginName);
String loginPassword = userLoginInfo.get("loginPassword");
preparedStatement.setString(2,loginPassword);
resultSet = preparedStatement.executeQuery();
if(resultSet.next()) {
loginSuccess = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(resultSet!=null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(preparedStatement!=null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return loginSuccess;
}
/**
* 初始化用户界面
* @return 用户输入的用户名和密码
*/
private static Map<String, String> InitUI() {
Scanner sc = new Scanner(System.in);
System.out.println("用户名:");
String loginName = sc.nextLine();
System.out.println("密码:");
String loginPassword = sc.nextLine();
Map<String,String> userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPassword",loginPassword);
return userLoginInfo;
}
}
Statement 和PreparedStatement对比
1、Statement未解决sql注入问题,preparedStatement解决了sql注入问题
2、Statement是编译1次执行1次,PreparedStatemnet是编译1次执行n次,所以PreparedStatement的效率会高一点(mysql中当前sql语句和上一条sql语句相同时,包括空格,不用编译,直接执行)
3、PreparedStatement会在编译阶段作类型的安全检查
4、只有当业务需求必须要支持sql注入时用Statement,如进行排序时(asc,desc),所以大部分情况下是使用PreparedStatement
7、JDBC事务机制
JDBC中的事务默认是自动提交的,但在实际的业务中,通常都是N条DML语句共同联合才能完成的,必须保证这些DML语句在同一个事务中同时成功或者同时失败
案例:银行转账
package com.jdbc;
import java.sql.*;
public class TestJdbc05 {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/powernode";
String user = "root";
String password = "8888";
connection = DriverManager.getConnection(url,user,password);
//关闭自动提交
connection.setAutoCommit(false);
String sql1 = "update t_account set balance = ? where accountNo = ?";
int count = 0;
preparedStatement = connection.prepareStatement(sql1);
preparedStatement.setDouble(1,10000);
preparedStatement.setInt(2,111);
count = preparedStatement.executeUpdate();
preparedStatement = connection.prepareStatement(sql1);
preparedStatement.setDouble(1,10000);
preparedStatement.setInt(2,222);
count += preparedStatement.executeUpdate();
if(count == 2) {
//提交事务
connection.commit();
System.out.println("转账成功");
}
} catch (Exception e) {
//手动回滚
if(connection!=null) {
try {
connection.rollback();
System.out.println("转账失败");
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
if(resultSet!=null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(preparedStatement!=null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
8、JDBC工具类的封装
package com.jdbc.utils;
import java.sql.*;
/**
* JDBC工具类,简化JDBC编程
*/
public class DBUtil {
//写工具类时,通常构造函数是私有的,其他方法是静态的,不需要new一个对象,直接采用类名调用
private DBUtil() {}
//静态代码块在类加载时执行,且只执行一次
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取数据库连接对象
* @return 数据库连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/powernode","root","8888");
}
/**
* 释放资源
* @param connection 数据库连接对象
* @param statement 数据库操作对象
* @param resultSet 结果集
*/
public static void close(Connection connection, Statement statement, ResultSet resultSet) {
if(resultSet!=null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(statement!=null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
9、JDBC实现模糊查询
package com.jdbc;
import com.jdbc.utils.DBUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TestJdbc06 {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = DBUtil.getConnection();
String sql = "select ename from emp where ename like ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"_A%");
resultSet = preparedStatement.executeQuery();
while(resultSet.next()) {
System.out.println(resultSet.getString("ename"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(connection,preparedStatement,resultSet);
}
}
}
10、悲观锁和乐观锁的概念
悲观锁:事务必须排队执行。数据锁住了,不允许并发执行。(行级锁,select后面添加for update)
乐观锁:支持并发,事务也不需要排队,只不过需要一个版本号。
悲观锁机制演示
package com.jdbc;
import com.jdbc.utils.DBUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 这个程序开启一个事务,这个事务专门进行查询,并且使用行级锁,锁住相关的记录
*/
public class TestJdbc07 {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = DBUtil.getConnection();
connection.setAutoCommit(false);
String sql = "select ename,job,sal from emp where job = ? for update";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"MANAGER");
resultSet = preparedStatement.executeQuery();
while(resultSet.next()) {
System.out.println(resultSet.getString("ename")+","+resultSet.getString("job")+","+resultSet.getString("sal"));
}
connection.commit();//在此处加断点进行debug
} catch (SQLException e) {
if(connection!=null) {
try {
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
DBUtil.close(connection,preparedStatement,resultSet);
}
}
}
package com.cbx.jdbc;
import com.cbx.jdbc.utils.DBUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* 这个程序负责修改被锁定的记录
*/
public class TestJdbc08 {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = DBUtil.getConnection();
connection.setAutoCommit(false);
String sql = "update emp set sal = sal*1.1 where job = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"MANAGER");
int count = preparedStatement.executeUpdate();
connection.commit();
System.out.println(count);
} catch (SQLException e) {
if(connection!=null) {
try {
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
DBUtil.close(connection,preparedStatement,null);
}
}
}
在TestJdbc07的commit()处加断点,运行TestJdbc08会出现下面的报错
注意:
JDBC中的sql语句不用写分号
JDBC中所有下标从1开始