文章目录
一、JDBC编写步骤(牢记)
1、 注册驱动
通知java我们连接的是哪个品牌的数据库,本文是在idea中使用mysql数据库
第一种:DriverManager.registerDriver(new com.mysql.jdbc.Driver());
第二种:Class.forName('com.mysql.jdbc.Driver');
推荐使用第二种方法,Class.forName(“类名”)是向虚拟机实例化一个Class实例,而com.mysql.jdbc.Driver()源码如下:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
// ~ Static fields/initializers
// ---------------------------------------------
//
// Register ourselves with the DriverManager
//
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
其中的静态代码块会在实例化时优先执行,功能是注册一个Driver实例。
若选用第一种方法,那么算上这个静态代码块的注册实际上实例化了两个Driver对象,而且对mysql形成了一种依赖;
第二种方法只实例化了一个Driver,可以在运行处动态改变。
2、获取数据库连接
java进程和mysql进程,两个进程之间的通道开启了。
Connection con=DriverManager.getConnection(url,username,password);
url是数据库地址
username是数据库用户名
password是数据库密码
本文所用数据库连接如下:
conn =DriverManager.getConnection("jdbc:mysql://localhost:3306/myemployees","root","root")
3、获取数据库操作对象
代码如下:
Statement sta = con.createStatement();
这个对象是用来执行sql语句的。
4、执行操作语句
执行grub操作,需要sql语句,创建一条sql语句
String sql ="SELECT last_name,email FROM employees ";
如果是查询语句:
sta.executeQuery(sql);
如果是增、删、改语句:
sta.executeUpdate(sql);
5、处理查询结果集
只有第四步是查询语句,才有第五步,结果用ResultSet对象接收
ResultSet rs = sta.executeQuery(sql);
6、释放资源
关闭所有资源,因为jdbc是进程间的通信,占用很多资源。
conn.close();
sta.close();
rs.close();
二、连接数据库的信息统一写到配置文件
1、创建配置文件
新建file,名字为db.properties,内容如下:
driver = com.mysql.jdbc.Driver
url = jdbc:mysql://localhost:3306/myemployees
user = root
password = root
2、获取配置文件信息
代码如下:
ResourceBundle bundle = ResourceBundle.getBundle("resources/db") ;
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
三、实现一个登录功能
1、SQL注入问题
代码实现如下:
package connection;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Scanner;
/**
* @author LiYin
* @create 2021-11-12-17:26
*/
public class LoginTest {
public static void main(String[] args) {
Map LoginInfo = initUI();
boolean ok=checkLogin(LoginInfo.get("login-name"),LoginInfo.get("login-pwd"));
System.out.println(ok?"登录成功":"登录失败");
}
private static boolean checkLogin(Object o, Object o1) {
boolean ok = false;
ResourceBundle bundle = ResourceBundle.getBundle("resources/db") ;
String driver =bundle.getString("driver");
String url =bundle.getString("url");
String user =bundle.getString("user");
String password =bundle.getString("password");
Connection con=null;
Statement sta=null;
ResultSet rs = null;
try {
//注册驱动
Class.forName(driver);
//创建连接
con = DriverManager.getConnection(url,user,password);
//创建Statement
sta = con.createStatement();
//编写sql语句
String sql = "select * from stu_info where name='"+o+"' and password='"+o1+"'";
rs =sta.executeQuery(sql);
if(rs!=null){
ok=true;
}
} catch (SQLException | ClassNotFoundException e) {
e.printStackTrace();
}finally {
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(sta!=null){
try {
sta.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(con!=null){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return ok;
}
public static Map<String,String> initUI(){
//初始化用户登录界面,引导用户输入用户名和密码
System.out.println("欢迎来到中国,请输入用户名:");
Scanner s = new Scanner(System.in);
String uname = s.next();
System.out.println("请输入密码:");
String pwd = s.next();
Map<String,String> map = new HashMap<>();
map.put("login-name",uname);
map.put("login-pwd",pwd);
return map;
}
}
以上代码有sql注入的风险需要解决,问题出在代码:
String sql = "select * from stu_info where name='"+o+"' and password='"+o1+"'";
当输入密码为111’ or ‘1’=‘1这类形式,由于密码中含有SQL的关键字,拼接到SQL语言中师代码不需要用户名密码正确就可以登录成功。
2、解决SQL注入
解决SQL注入,要使用 java.sql.PrepareStatement
先编译后传值可以避免sql注入,缺点是无法进行sql语句的拼接,代码修改部分如下:
//注册驱动
Class.forName(driver);
//创建连接
con = DriverManager.getConnection(url,user,password);
//编写sql语句,一个问号代表一个占位符
String sql = "select * from stu_info where name=? and password=?";
//创建Statement
sta = con.prepareStatement(sql);//此时会发送sql语句给DBMS进行编译
//给sql语句进行传值,一个占位符只可以传一个值,JDBC中下标从1开始
sta.setString(1,loginname);
sta.setString(2,loginpwd);
rs =sta.executeQuery();//不再需要传sql语句
if(rs!=null){
ok=true;
}
四、使用PrepareStatement实现增删改的功能
public static void main(String[] args) {
Connection con =null;
PreparedStatement sta =null;
String table ="stu_info";
try {
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、连接数据库
con= DriverManager.getConnection("jdbc:mysql://localhost:3306/myemployees","root","root");
//3、使用SQL语句进行事物预编译
String sql="insert into stu_info set name=?,password=?";
sta=con.prepareStatement(sql);
//给sql语句传值
sta.setString(1,"红日");
sta.setString(2,"333");
//4、执行sql语句
sta.executeUpdate();
sta.setString(1,"筛子");
sta.setString(2,"333");
sta.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally {
//6、释放资源
if(sta!=null) {
try {
sta.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(con!=null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
五、JDBC开启事物
三步:
1、con.setAutoCommit(false);//关闭自动提交机制
2、con.commit();//手动提交
3、con.rollback();//回滚
代码如下:
public static void main(String[] args) {
Connection con =null;
PreparedStatement sta =null;
String table ="stu_info";
try {
//1、注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2、连接数据库
con= DriverManager.getConnection("jdbc:mysql://localhost:3306/myemployees","root","root");
//开启事物,将自动提交机制关闭
con.setAutoCommit(false);//改为手动提交
//3、使用SQL语句进行事物预编译
String sql="update salary_info set salary=? where name= ?";
sta=con.prepareStatement(sql);
//给sql语句传值
sta.setDouble(1,30000);
sta.setString(2,"李寅");
//4、执行sql语句
sta.executeUpdate();
sta.setDouble(1,0);
sta.setString(2,"史悦");
sta.executeUpdate();
con.commit();//手动提交
} catch (Exception e) {
//判断一下是否要回滚
if(con!=null){
try {
con.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
}finally {
//6、释放资源
if(sta!=null) {
try {
sta.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(con!=null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
六、JDBC工具类封装
为了便于开发,可以封装一个JDBC的工具类,将注册驱动,创建连接,释放资源的方法封装起来:
public class DBUtils {
//工具类的构造方法是私有的,是为了防止new对象,因为工具类的方法都是静态的,不需要new对象
private DBUtils(){}
//类加载时绑定资源文件
private static ResourceBundle bundle=ResourceBundle.getBundle("resources/db");
//注册驱动,使用静态代码块,类加载时静态代码块自动执行
static {
try {
Class.forName(bundle.getString("driver"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取数据库连接对象
public static Connection getConnection() throws SQLException {
String url=bundle.getString("url");
String usr=bundle.getString("user");
String password=bundle.getString("password");
Connection conn=DriverManager.getConnection(url,usr,password);
return conn;
}
//释放资源
public static void release(ResultSet rs, Statement stm,Connection conn){
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stm!=null){
try {
stm.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
测试工具类:
public class JDBCTest {
public static void main(String[] args) {
Connection con=null;
PreparedStatement sta=null;
ResultSet rs=null;
try {
con= DBUtils.getConnection();
String sql="insert into stu_info set name=?,password=?";
sta=con.prepareStatement(sql);
//给sql语句传值
sta.setString(1,"红");
sta.setString(2,"333");
//4、执行sql语句
sta.executeUpdate();
sta.setString(1,"筛");
sta.setString(2,"333");
sta.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}finally {
DBUtils.release(rs,sta,con);
}
}
}
可以看到,代码明显简洁的多。
七、关于DQL语句的悲观锁
for update是在数据库中上锁用的,可以为数据库中的行上一个排它锁。当一个事务的操作未完成时候,其他事务可以读取但是不能写入或更新。
select * from table_name where xxx for update
其中,锁行还是锁表有以下几种情况:
1、明确指定主键/索引,并且有此记录,行级锁
2、未指定主键/索引,并且有此记录,表级锁
3、当我们使用范围条件而不是相等条件检索数据并请求共享锁或排他锁时,InnoDB会给条件已有数据记录的索引项加锁,对于键值在条件范围但不存在的记录加间隙锁
4、明确指定主键/索引,若查无此记录,加间隙锁
5、无主键/索引,表级锁
6、主键/索引不明确,表级锁