文章目录
下载jar包配置环境变量
- 在官网中下载java链接数据库的驱动,用什么数据库就去哪里下载
- 将下载的驱动解压配置到环境变量的classpath中,使用IDEA不用配置,IDEA有专门的导入方式
JDBC链接数据库的六个步骤
- 注册数据库驱动
//方式一:手动注册(不常用)
Driver driver = new com.mysql.cj.jdbc.Driver();
DriverManager.registerDriver(driver);
//方式二:使用反射机制自动注册
//原因是在获取类时,会自动运行类中的静态代码块,而com.mysql.cj.jdbc.Driver的静态代码块中会自动注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
- 获取数据库连接对象
//2 获取数据库连接
String url = "jdbc:mysql://localhost:3306/***"; //***数据库名
String user = "***"; //用户名
String password = "***"; //密码
Connection conn = DriverManager.getConnection(url, user, password);
- 获取数据库操作对象
//3 获取数据库操作对象
Statement sm = conn.createStatement();
- 执行SQL语句
//4 执行SQL语句
String sql = "select * from dept";
//ResultSet executeQuery(String sql)
//执行给定的SQL语句,该语句返回单个 ResultSet对象。查询语句
//int executeUpdate(String sql)
//执行给定的SQL语句,这可能是 INSERT , UPDATE ,或 DELETE语句,或者不返回任何内容,如SQL DDL语句的SQL语句。 返回值是影响记录的行数
rs = sm.executeQuery(sql);
- 处理查询结果集
//5 处理查询结果集
//只有执行查询语句时,才有结果集
while(rs.next()){
System.out.println(rs.getString("deptno")+","+rs.getString("dname")+","+rs.getString("loc"));
}
- 关闭连接
//6 关闭连接 从内到外
if(rs != null){
try{
rs.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(sm != null){
try{
sm.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(conn != null){
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
完整流程
import java.sql.*;
public class MySqlTest{
public static void main(String[] args){
Connection conn = null;
Statement sm = null;
ResultSet rs = null;
try{
//1 注册数据库驱动
//方式一:手动注册(不常用)
//Driver driver = new com.mysql.cj.jdbc.Driver();
//DriverManager.registerDriver(driver);
//方式二:使用反射机制自动注册,原因是在获取类时,会自动运行类中的静态代码块,而com.mysql.cj.jdbc.Driver的静态代码块中会自动注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2 获取数据库连接
String url = "jdbc:mysql://localhost:3306/wcy";
String user = "root";
String password = "root";
conn = DriverManager.getConnection(url, user, password);
System.out.println("数据库操作对象:"+conn);
//3 获取数据库操作对象
sm = conn.createStatement();
//4 执行SQL语句
String sql = "select * from dept";
rs = sm.executeQuery(sql);
//5 处理查询结果集
while(rs.next()){
System.out.println(rs.getString("deptno")+","+rs.getString("dname")+","+rs.getString("loc"));
}
}catch(Exception e){
}finally{
//6 关闭连接 从内到外
if(rs != null){
try{
rs.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(sm != null){
try{
sm.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(conn != null){
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
模拟用户登陆
package com.wcy.code01;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/**
* JDBC的使用
* 模拟一个登陆功能
*/
public class JdbcTest01 {
public static void main(String[] args) {
Map<String, String> userInfo = getUserInput();
boolean isLogin = login(userInfo);
System.out.println(isLogin ? "登陆成功" : "登陆失败");
}
/**
* 用户登陆验证
* @param userInfo:用户输入的信息
* @return boolean 登陆是否成功
*/
private static boolean login(Map<String, String> userInfo) {
boolean isLogin = false;
String username = userInfo.get("username");
String password = userInfo.get("password");
Connection conn = null;
Statement sm = null;
ResultSet rs = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/***", "***", "***");
sm = conn.createStatement();
//TODO sql注入问题
//当密码输入为 fdsfas' or '1'='1时 也能登陆成功
String sql = "select * from userinfo where username='" + username + "' and password='" + password + "'";
rs = sm.executeQuery(sql);
if (rs.next()) {
isLogin = true;
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (sm != null) {
try {
sm.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return isLogin;
}
/**
* 接收用户输入
* @return userInfo:返回用户输入信息的Map
*/
private static HashMap<String, String> getUserInput() {
HashMap<String, String> userInfo = new HashMap<>();
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String username = scanner.nextLine();
System.out.print("请输入密码:");
String password = scanner.nextLine();
userInfo.put("username", username);
userInfo.put("password", password);
return userInfo;
}
}
当前该程序存在一个问题
当密码输入为 fdsfas’ or ‘1’='1时 也能登陆成功
这种现象被称为sql注入 通过用户的输入来破坏原来的SQL语句
当用户的输入为上述时 SQL语句变成如下
select * from userinfo where username='111' and password='fsdfas or '1'='1';
后面的条件恒成立,所以可以查询到结果。
如何避免SQL注入问题
- 导致SQL注入的根本原因
用户的输入中含有SQL语句的关键字,并且这些关键字参与了SQL语句的编译过程,导致SQL语句的原意被扭曲,从而到达了SQL注入的问题。
- 解决SQL注入问题
只要不让用户输入的信息不参与SQL编译的过程,就可以解决
即使用户输入含有SQL关键字,没有参与编译也不起作用
使用java.sql.PreparedStatement
PreparedStatement是属于预编译数据库操作对象
PreparedStatement的原理:预先编译SQL语句的框架,然后在给SQL语句传值,即使用户输入有关键字也不编译
package com.wcy.code01;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
/**
* 解决SQL注入问题
* 只要不让用户输入的信息不参与SQL编译的过程,就可以解决
* 即使用户输入含有SQL关键字,没有参与编译也不起作用
* 使用java.sql.PreparedStatement
* PreparedStatement是属于预编译数据库操作对象
* PreparedStatement的原理:预先编译SQL语句的框架,然后在给SQL语句传值,即使用户输入有关键字也不编译
*
* Statement和PreparedStatement对比
* Statement存在SQL注入问题,PreparedStatement不存在
* Statement编译一次执行一次,PreparedStatement编译一次执行n次,效率较高
* PreparedStatement在编译阶段会做类型的安全检查
*
* 大多数情况下我们都使用PreparedStatement,少数情况下使用Statement
* 在业务方面要求支持SQL注入时,就必须使用Statement
* 例如desc和asc
*/
public class JdbcTest02 {
public static void main(String[] args) {
Map<String, String> userInfo = getUserInput();
boolean isLogin = login(userInfo);
System.out.println(isLogin ? "登陆成功" : "登陆失败");
}
/**
* 用户登陆验证
* @param userInfo:用户输入的信息
* @return boolean 登陆是否成功
*/
private static boolean login(Map<String, String> userInfo) {
boolean isLogin = false;
String username = userInfo.get("username");
String password = userInfo.get("password");
Connection conn = null;
//使用数据库预编译对象
PreparedStatement psm = null;
ResultSet rs = null;
try {
//1 注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2 获取数据库连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/wcy", "root", "root");
//3 获取数据库预编译对象
//这里要提前准备sql语句模板,需要用户输入的地方使用占位符?进行占位,不能用单引号括起来'?',这样会认为是普通的字符
String sql = "select * from userinfo where username=? and password=?";
//这里会把sql语句模板发送给数据库完成预先编译
psm = conn.prepareStatement(sql);
//给占位符传值,使用下标来传值,jdbc中所有的下标从1开始!!
//使用一系列set方法,setInt(),setString....
//使用setString()方法会自动将输入的信息用单引号括起来传入
psm.setString(1, username);
psm.setString(2, password);
//4 执行Sql, 这里不能再传入sql语句,否则会再编译一次sql语句,前面的操作全部作废
rs = psm.executeQuery();
//5处理查询结果
if (rs.next()) {
isLogin = true;
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
//6 关闭连接 释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (psm != null) {
try {
psm.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return isLogin;
}
/**
* 接收用户输入
* @return userInfo:返回用户输入信息的Map
*/
private static HashMap<String, String> getUserInput() {
HashMap<String, String> userInfo = new HashMap<>();
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String username = scanner.nextLine();
System.out.print("请输入密码:");
String password = scanner.nextLine();
userInfo.put("username", username);
userInfo.put("password", password);
return userInfo;
}
}
使用PrepareStatement执行增删改操作
package com.wcy.code01;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class JdbcTest03 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement psm = null;
Scanner scanner = new Scanner(System.in);
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/**", "**", "**");
String sql = "insert into userinfo(id, username, password) values(?, ?, ?)";
psm = conn.prepareStatement(sql);
psm.setInt(1, 3);
psm.setString(2, "hhh");
psm.setString(3, "123424");
//返回结果是影响记录的条数
int count = psm.executeUpdate();
System.out.println(count);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
if (psm != null) {
try {
psm.close();
} catch (SQLException throwable) {
throwable.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException throwable) {
throwable.printStackTrace();
}
}
}
}
}
String sql = "update userinfo set username=?, password=? where id=3";
psm = conn.prepareStatement(sql);
psm.setString(1, "sdfsdf");
psm.setString(2, "dfsadfs");
String sql = "delete from userinfo where id=?";
psm = conn.prepareStatement(sql);
psm.setInt(1, 3);
//返回结果是影响记录的条数
int count = psm.executeUpdate();
jdbc事务提交机制
-
jdbc中的事务是自动提交的
-
只要执行任意一条DML语句,则自动提交一次,这是jdbc默认的事务行为
-
在实际的业务中,通常都是n条DML语句共同联合完成,必须保证这些语句在同一个事务中同时成功或者是同时失败
-
jdbc事务控制
1. conn.setAutoCommit(flase); 关闭自动提交
2. conn.commit(); 提交事务
3. conn.rollback(); 回滚事务 -
模拟账户转账
package com.wcy.code02;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* 模拟账户转账
* 使用事手动提交事务
*/
public class JdbcCommitTest01 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement psm = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/wcy", "root", "root");
//关闭自动提交
conn.setAutoCommit(false);
String sql = "update userinfo set balance=? where id=?";
psm = conn.prepareStatement(sql);
//用户1转出10000到用户2
psm.setDouble(1,10000);
psm.setInt(2,1);
int count = psm.executeUpdate();
//用户2收到10000
psm.setDouble(1,10000);
psm.setInt(2,2);
count += psm.executeUpdate();
//同时提交成功就提交事务
if(count==2){
conn.commit();
}else {
conn.rollback();
}
} catch (ClassNotFoundException | SQLException e) {
try {
if (conn != null) {
//发生异常也回滚
conn.rollback();
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}
e.printStackTrace();
}
}
}
封装jdbc代码
package com.wcy.code02;
import java.sql.*;
/**
* 封装数据库的连接的工具包
*/
public class DBUtil {
//注册驱动
//驱动只需要注册一次 所以写在静态代码块中
//只要加载该类时就执行
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//连接数据库 并返回数据库连接对象
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/*", "*", "*");
}
//关闭连接
public static void close(Connection conn, Statement sm, ResultSet rs){
if (rs != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (sm != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
模糊查询
package com.wcy.code02;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* jdbc的模糊查询
*/
public class JdbcTest04 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement psm = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
String sql = "select * from dept where loc like ?";
psm = conn.prepareStatement(sql);
// 和其他的传参一样
psm.setString(1, "_E%");
rs = psm.executeQuery();
while (rs.next()){
System.out.println(rs.getString("loc"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
DBUtil.close(conn, psm, rs);
}
}
}
悲观锁和乐观锁
- 悲观锁(行级锁)
事务必须要排队执行,数据被锁住,不能并发,数据只能被一个事务执行, 在select语句后添加for update即可。 - 乐观锁
- 数据支撑并发,事务也不需要排队,只不过每个事务需要获得一个版本号,如果数据提交前后版本号不一致,就回滚