了解JDBC
JDBC全称是Java DataBase Connectivity,寓意是Java语言连接数据库
JDBC的本质
JDBC是SUN公司制定的一套接口
接口有调用者和实现者
面向接口调用,面向接口写实现类,都属于面向接口编程,目的是松耦合,降低程序的耦合度,提高程序的扩展力,典型的是多态机制,面向了抽象编程
JDBC过程
首先SUN公司定义了一套JDBC接口,然后Java程序员面向JDBC接口写代码,不同的数据库管理系统有不同的JDBC接口的实现类,比如MySQL,Oracle等编写的JDBC接口实现类都不同,这些厂家写好的实现类被称为MySQL驱动,Oracle驱动等.所谓的驱动都是以jar包形式存在,jar包中有很多.class文件,这些.class文件都是对JDBC接口的实现.驱动不是SUN公司的,而是各大数据库厂家提供的,下载驱动jar包需要去对应数据库厂家的官网下载
idea导入mysql驱动
MySQL官网下载mysql驱动jar包
官网下载地址==>由此去!
选择Connector/J,选择platform…,选择ZIP格式下载即可!
JDBC开发前的准备工作,先从官网下载jar包,然后配置到classpath当中,(.;jar包路径)注意分号必须是英文的,.;代表当前路径下找字节码文件也行,这是针对用编辑器开发才这么配置,如果是idea,直接导入jar包即可.
idea导入MySQL驱动
首先项目的模块上右键选择Open Module Settings,再点Libraries,点加号导入mysql驱动的jar包即可!
JDBC编程6步(必背)
第一步,注册驱动,作用是告诉Java程序我们连接的是哪个品牌的数据库
第二步,获取连接,表示JVM的进程和数据库进程之间的通道打开了,重量级的,使用后一定要关闭
第三步,获取数据库操作对象,专门执行sql语句的对象
一般使用预编译的数据库操作对象,所以执行SQL一般也会在第三步执行
第四步,执行SQL
第五步,处理查询结果集,只有第四步是查询语句,才会执行第五步
第六步,释放资源,使用完java和数据库属于进程的通信,开启后一定要关闭
解决SQL注入问题
SQL注入的根本原因:用户输入的信息含有SQL语句关键字,并且这些关键字参与sql语句的编译过程,导致SQL的原来的意思被扭曲,从而造成了SQL注入
SQL注入严重会改变数据库的信息,有安全隐患.
解决办法
只要用户提供的信息不参与SQL语句的编译过程,问题就解决了.
想要用户信息不参与SQL语句的编译,必须使用java.sql.PreparedStatement
PreparedStatement接口继承了java.sql.statement,属于预编译的数据库操作对象
PreparedStatement的原理是,预先对SQL语句的框架进行编译,然后给SQL语句传值
解决SQL注入的关键:就算用户提供的信息中含有关键字,但是这些关键字并没有参与编译,不起作用
Statement和PreparedStatement
Statement存在SQL注入问题,每次SQL语句的值都可能不一样,是编译一次执行一次
PreparedStatement解决了SQL注入问题,只需要编译一次SQL语句的框子,就能执行N次,效率更高,并且会在编译阶段做类型的安全检查,99%的几率都是使用的PreparedStatement
在某些特殊的场合下,需要使用SQL注入
业务方面要求必须支持SQL注入的时候
要求SQL语句拼接的时候
注意PreparedStatement的SQL框子的问号只能是值,不能是SQL关键字,否则发生错误
测试statement代码
package io;
import java.sql.*;
import java.util.Scanner;
/**
* 用户输入desc或者asc,测试statement的使用场景
*/
public class CodeTest {
public static void main(String[] args) {
statementTest();
}
private static void statementTest() {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入desc或者asc,desc表示降序排列,asc表示升序:");
String input = scanner.nextLine();
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
//注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
connection = DriverManager.getConnection
("jdbc:mysql://localhost:3306/mydatabase", "root", "981122");
/*
//获取预编译的数据库操作对象
//String sql = "select ename from emp order by ename ?";
//preparedStatement = connection.prepareStatement(sql);
//拼接值
//preparedStatement.setString(1, input);//发生错误,?只能传入值,不能是SQL关键字,而用户输入的input就是SQL关键字
*/
//获取数据库操作对象
statement = connection.createStatement();
//执行SQL
String sql = "select ename from emp order by ename " + input;
resultSet = statement.executeQuery(sql);
//获取查询结果集
while (resultSet.next()) {
System.out.println(resultSet.getString("ename"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源,如果不为null
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();
}
}
}
}
}
JDBC事务机制
JDBC的事务是自动提交的,只要执行任意一条DML语句就自动提交一次
在实际开发中,通常是多条MDL语句共同联合才能完成的,必须保证多条DML语句同时成功或失败
演示JDBC的事务自动提交
package io;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/*
* 测试JDBC事务的自动提交
* 加断点测试结果:一条DML语句也会执行成功,不符合实际开发需求.
*/
public class CodeTest {
public static void main(String[] args) {
statementTest();
}
private static void statementTest() {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2,获取连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "root", "981122");
//3.获取预编译的数据库操作对象
String sql = "update t_user set loginName = ? where realName = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"wangwu");
preparedStatement.setString(2,"王五");
//4.执行SQL
int i = preparedStatement.executeUpdate();
System.out.println(i);
//重新给占位符传值
preparedStatement.setString(1,"lisi");
preparedStatement.setString(2,"李四");
i = preparedStatement.executeUpdate();
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放资源,如果不是null
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
模拟用户登陆作业
业务需求:模拟用户登陆功能的实现
描述:程序运行的时候提供一个输入的入口,可以让用户输入用户名和密码,用户输入后提交信息,java程序收集到用户信息后,java程序连接数据库验证用户信息是否合法,合法登陆成功,不合法登陆失败.
准备:在实际开发中,表的设计会使用专业的建模工具PowerDesigner,需要安装这个建模工具软件
package io;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/**
* 完成用户登陆信息效验
*/
public class CodeTest {
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) {
//设置标记
boolean loginSuccess = false;
//获取用户输入的信息
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
//扩大变量的作用于范围
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
//JDBC代码
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mydatabase", "root", "981122");
//3.获取预编译的数据库操作对象
//这是SQL语句的框子,?代表占位符,用来接收值,?不能用单引号括起来
String sql = "select * from t_user where loginName = ? and loginPwd = ?";
//程序执行到此处会发送SQL框子给DBMS,DBMS对SQL进行预编译
preparedStatement = connection.prepareStatement(sql);
//之后给占位符?传值(第一个?下表是1,第二个?是2,JDBC的下表从1开始)
preparedStatement.setString(1, loginName);
preparedStatement.setString(2, loginPwd);
//4.执行SQL
resultSet = preparedStatement.executeQuery();
//5.获取查询结果集
while (resultSet.next()) {
loginSuccess = true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//如果不为null,关流
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//如果不为null,关流
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//如果不为null,关流
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return loginSuccess;
}
/**
* 初始化用户界面
*
* @return 用户输入的用户名和密码等登陆信息
*/
private static Map<String, String> initUI() {
//创建键盘扫描对象
Scanner scanner = new Scanner(System.in);
System.out.print("用户名:");
//接收用户输入的字符串类型
String loginName = scanner.nextLine();
System.out.print("密码:");
//接收用户输入的字符串类型
String loginPwd = scanner.nextLine();
//把用户输入的信息存入Map集合
//创建map集合对象
Map<String, String> userLoginInfo = new HashMap<>();
//存入信息
userLoginInfo.put("loginName", loginName);
userLoginInfo.put("loginPwd", loginPwd);
return userLoginInfo;
}
}
账户转账事务作业
准备SQL脚本
//balance double(7,2)//7表示有效数为7,2表示2位小数
drop table if exists t_act;
create table t_act(
actno int,
balance double(7,2)
);
insert into t_act(actno,balance) values(111,20000);
insert into t_act(actno,balance) values(222,0);
commit;
select * from t_act;
重点:三行代码
//关闭事务自动提交
connection.setAutoCommit(false);
最后手动提交数据,事务结束
connection.commit();
catch (Exception e) {
//如果发生错误,回滚
catch{
if (connection != null){
try {
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
}
JDBC的事务转账作业
package io;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/*
* JDBC事务
*/
public class CodeTest {
public static void main(String[] args) {
statementTest();
}
private static void statementTest() {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "root", "981122");
//关闭事务自动提交,开启事务
connection.setAutoCommit(false);
//获取预编译的数据库操作对象
String sql = "update t_act set balance = ? where actno = ?";
preparedStatement = connection.prepareStatement(sql);
//拼接值
preparedStatement.setDouble(1, 10000);
preparedStatement.setInt(2, 111);
//执行SQL
int count = preparedStatement.executeUpdate();
/*//如果在这发生错误,数据会丢失
String s = null;
s.toString();*/
//解决数据丢失的办法是在获取连接之后,关闭事务自动提交功能,调用setAutoCommit
//拼接值
preparedStatement.setDouble(1, 10000);
preparedStatement.setInt(2, 222);
//执行SQL
count += preparedStatement.executeUpdate();
System.out.println(count == 2 ? "转账成功" : "转账失败");
//程序能执行到这,说明程序没有错误,手动提交数据,事务结束
connection.commit();
} catch (Exception e) {
//如果发生错误,回滚
if (connection != null){
try {
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
//释放资源,如果不为null
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//释放资源,如果不为null
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
封装JDBC工具类并测试
封装代码
package io;
import java.sql.*;
/*
* JDBC工具类,简化JDBC编程
* 工具类的构造方法应该是私有化的,因为工具类的方法都是静态方法,不需要对象,直接采用类名.调用
*/
public class JdbcUtil {
//私有化构造方法
private JdbcUtil() {
}
/**
* JDBC:注册驱动
* 静态代码块在类加载时执行,并且只执行一次
*/
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* JDBC:获取连接
* @return 连接对象
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection
("jdbc:mysql://localhost:3306/mydatabase", "root", "981122");
}
/**
* JDBC:释放资源
* @param connection 连接对象
* @param statement 数据库操作对象
* @param resultSet 查询结果集
*/
public static void close(Connection connection, Statement statement, ResultSet resultSet){
//如果不为null
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();
}
}
}
}
做模糊查询测试JDBC工具类
package io;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/*
* 测试JDBC工具,写模糊查询
*/
public class CodeTest {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//注册驱动和获取连接
connection = JdbcUtil.getConnection();
//获取预编译的数据库操作对象
String sql = "select ename from emp where ename like ?";
preparedStatement = connection.prepareStatement(sql);
//拼接值
preparedStatement.setString(1, "_A%");
//执行SQL
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
System.out.println(resultSet.getString("ename"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
悲观锁和乐观锁
悲观锁(又叫行级锁:select后面添加for update)
事务必须排队执行,数据锁住了,不允许并发.
乐观锁
支持并发,事务也不需要排队,只不过需要一个版本号
事务1读取到的版本号是1.1
事务读取到的版本号也是1.1
事务1先修改了,查看版本号是1.1,于是提交修改的数据,将版本号改为1.2
事务2后修改了,看版本号是1.2,跟原来的1.1不一样,于是回滚