1. 什么是JDBC?标签:JDBC、行级锁、乐观锁与悲观锁
- 名称:JDBC,java database connectivity
- 中文:即java数据库连接
- 含义:在java程序中连接各大数据库的一套接口
- 结构:JDBC设计三方面,分别是程序员,Oracle公司和各大数据库厂商,程序员们用Oracle编写的JDBC接口规范进而能够实现各种数据库的操作,如Mysql数据库的增删改查。
- 作用:面向接口编程,解耦合且拓展性强
- 成员:接口的调用者→程序员;接口的实现者→各大数据库公司的java程序员;接口标准的制定者→Oracle公司(JDBC标准)
- 位置:JDBC是接口,位于java.sql.*包下
- 存在意义:统一各大数据库厂商,屏蔽其差异性
- 驱动:驱动即各大数据库厂商定制的java包,一般以.jar结尾,这就是驱动,例如可在如下连接找到(mysql-connector-java-8.0.27.zip),解压即可发现.jar文件,这就是Mysql提供给java程序员的jdbc实现类。
MySQL :: Download Connector/Jhttps://dev.mysql.com/downloads/connector/j/
2.环境配置
2.1 如果是记事本开发,则需要把上面下载的驱动包引入环境变量,在classpath引入下载驱动包的路径,这样编译执行时才能让java找到。
2.2 如果是IDEA开发,只需要在项目文件夹下新建名为lib的包,然后右键add as library即可。
3.六步法编程
3.1 注册驱动→告诉java程序我要开始连接Mysql数据库了
- 法1:
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());//高版本用这个 DriverManager.registerDriver(new com.mysql.jdbc.Driver());//低版本用这个
- 法2:更为常用→利用反射机制,加载静态代码块代码;
Class.forName("com.mysql.cj.jdbc.Driver");//Mysql高版本用这个 Class.forName("com.mysql.jdbc.Driver");//Mysql低版本用这个
【注意】由于该方法参数是字符串,故可结合.properties配置文件实现实时读取功能,实现让客户傻瓜式操作。
package src.JDBC;
import java.sql.*;
import java.util.ResourceBundle;
public class gaijin {
public static void main(String[] args) {
// DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//资源绑定器绑定配置文件;
ResourceBundle bunch = ResourceBundle.getBundle("src/JDBC/jdbc");
String driver = bunch.getString("driver");
String url = bunch.getString("url");
String username = bunch.getString("username");
String password = bunch.getString("password");
Statement sta = null;
Connection col = null;
//注册驱动、获取连接、获取数据库对象、查询、获得结果、终结资源
try {
// String driver = "com.mysql.cj.jdbc.Driver";
Class.forName(driver);
// String url = "jdbc:mysql://localhost:3306/mydb";
// String username = "root";
// String password = "123456";
col = DriverManager.getConnection(url,username,password);
//数据库的对象为com.mysql.cj.jdbc.ConnectionImpl@f0f277
System.out.println("数据库的对象为"+col);
//获取对象
sta = col.createStatement();
//执行sql语句
// String sql = "insert into hh values('张三',300,3000.22);";
String sql = "update hh set id = 800 where name = '张三'";
//count为历史记录条数;专门执行DML语句的insert delete update
int count = sta.executeUpdate(sql);
//处理结果集合
} catch (SQLException e) {
e.printStackTrace();
}
catch(ClassNotFoundException ex){
ex.printStackTrace();
}
finally{
//释放资源
//遵循从小到大关闭所有对象,分别对齐try-catch→保证都能关闭;
try{
if(sta!=null){
sta.close();
}
}
catch(SQLException ex){
ex.printStackTrace();
}
try{
if(col!=null){
col.close();
}
}
catch(SQLException ex){
ex.printStackTrace();
}
}
}
}
3.2 建立连接→java程序正式与数据库进程通信,建立管道
-
Connection col = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb","root","123456");
3.3 返回对象→java一切都是对象,我们需要先获取连接对象,传入sql语句
- 法1:
Statement sta = col.createStatement();
- 法2:
PreparedStatement ps = col.PrepareStatement();
3.4 执行sql语句→执行sql语句即可→一般是DQL和DML语言居多
- 法1:
sta.executeUpdate(sql);
- 法2:
ps.executeUpdate();
3.5 遍历结果集→如果采用select语句,则对于获取的结果可以遍历的方式打印出来
- 模板:
while(rs.next()){//rs.next 为该行的下一个值; // String name = rs.getString(1); //把所有值返回成String类型的值; //上述可以将参数改为字段名名,即RS对象的列名,如果改变了则必须跟着改变; String name = rs.getString("name");//把所有值返回成String类型的值; //JDBC中列从1开始,而不是0; // String id = rs.getString(2); //还可以以int和Double类型读取; int id = rs.getInt("id"); String salary = rs.getString(3); System.out.println("name is "+name + " id is "+ id + " salary is " + salary);}
3.6 关闭资源→关闭占用的连接、对象、返回的结果
-
//遵循从小到大关闭所有对象,分别对齐try-catch→保证都能关闭; finally{ if(rs !=null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if(sta !=null) { try { sta.close(); } catch (SQLException e) { e.printStackTrace(); } } if(col !=null) { try { col.close(); } catch (SQLException e) { e.printStackTrace(); } }
4.六步法模板1:
package src.JDBC;
import java.sql.*;
public class outcomedealWith {
public static void main(String[] args) {
Connection col = null;
Statement sta = null;
ResultSet rs = null;
try{
Class.forName("com.mysql.cj.jdbc.Driver");
col = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb","root","123456");
sta = col.createStatement();
String sql = "select * from hh;";
rs = sta.executeQuery(sql);
//处理结果集;
while(rs.next()){//rs.next 为该行的下一个值;
// String name = rs.getString(1);//把所有值返回成String类型的值;
//上述可以将参数改为字段名名,这个名字为RS对象的列名,
//如果改变了则必须跟着改变;
String name = rs.getString("name");
//把所有值返回成String类型的值;
//JDBC中列从1开始,而不是0;
// String id = rs.getString(2);
//还可以以int和Double类型读取;
int id = rs.getInt("id");
String salary = rs.getString(3);
System.out.println("name is "+name + " id is "+ id +
" salary is "+ salary);
}
}catch(Exception 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(col !=null) {
try {
col.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
5. SQL注入问题演示及其解决办法(六步法模板2)
- SQL注入
package src.JDBC; import java.sql.*; import java.util.HashMap; import java.util.Map; import java.util.Scanner; public class loginTest_laodu { public static void main(String[] args) { long startTime = System.currentTimeMillis(); //初始化用户输入界面 MapuserInfo = initUi();//返回用户输入的密码和用户信息→hashmap; //收集用户提交的的信息 boolean isSuccess = login(userInfo); System.out.println(isSuccess?"登录成功":"登陆失败"); long endTime = System.currentTimeMillis(); System.out.println("耗时"+(endTime - startTime)+"ms"); //连接数据库 //验证并返回 } private static boolean login(Map userInfo) { //与JDBC尝试连接 boolean loginSuccess = false; String username = userInfo.get("loginName"); String password = userInfo.get("loginPwd"); Connection col = null; Statement sta = null; ResultSet rs = null; try { Class.forName("com.mysql.cj.jdbc.Driver"); col = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb","root","123456"); sta = col.createStatement(); String sql = "select * from user where username ="+("'"+username+"'")+"and userpassword ="+("'"+password+"';"); System.out.println(sql); rs = sta.executeQuery(sql); while(rs.next()){ loginSuccess = true; } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch(SQLException e ){ return false; }finally { if (rs!=null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if(sta!=null){ try { sta.close(); } catch (SQLException e) { e.printStackTrace(); } } if (col !=null) { try { col.close(); } catch (SQLException e) { e.printStackTrace(); } } } return loginSuccess; } // private static Map initUi() { Map userinfoBag = new HashMap<>(); Scanner in = new Scanner(System.in); System.out.println("请输入你的用户名:"); String loginName = in.nextLine(); System.out.println("请输入你的密码:"); String loginPwd = in.nextLine(); userinfoBag.put("loginName",loginName); userinfoBag.put("loginPwd",loginPwd); return userinfoBag; } } 此时,有如下意料之外的结果: 其实,SQL注入本质是把用户输入的信息与SQL语句一起编译,将用户的输入信息直接作为SQL语句进行查询,此时可以调用PreparedStatement方法,进行预编译,然后传“值”,这样就可以避免SQL注入问题,即如下的模板2.
- 六步法模板2→解决了SQL注入问题
package src.JDBC; import java.sql.*; import java.util.HashMap; import java.util.Map; import java.util.Scanner; public class loginTest_laodu_changeVersion { public static void main(String[] args) { //初始化用户输入界面 MapuserInfo = initUi();//返回用户输入的密码和用户信息→hashmap; //收集用户提交的的信息 boolean isSuccess = login(userInfo); System.out.println(isSuccess?"登录成功":"登陆失败"); //连接数据库 //验证并返回 } private static boolean login(Map userInfo) { //与JDBC尝试连接 boolean loginSuccess = false; String username = userInfo.get("loginName"); String password = userInfo.get("loginPwd"); Connection col = null; PreparedStatement ps = null; //变为预编译的Statemnet对象 ResultSet rs = null; try { Class.forName("com.mysql.cj.jdbc.Driver"); col = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb","root","123456"); //第三步:获取预编译数据库对象; // String sql = "select * from user where username ="+("'"+username+"'")+"and userpassword ="+("'"+password+"';"); String sql = "select * from user where username =? and userpassword =?"; //?用作占位符,传入“值”,不能加引号;上面是sql语句的框架; //程序执行到此处,把sql语句框子发送给DBMS,然后后者进行sql语句的预先编译; ps = col.prepareStatement(sql);//对应的也要变化调用对象的方法;这里就是动词了,而上面类名是名词 //给占位符传值:第一个问哈下标为1,第二个问哈下标为2; ps.setString(1,username);//setString自动填入?并是字符串,即自动加上''; ps.setString(2,password); rs = ps.executeQuery();//这里括号内不要写sql了!! while(rs.next()){ loginSuccess = true; } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch(SQLException e ){ return false; }finally { if (rs!=null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if(ps!=null){ try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } if (col !=null) { try { col.close(); } catch (SQLException e) { e.printStackTrace(); } } } return loginSuccess; } // private static Map initUi() { Map userinfoBag = new HashMap<>(); Scanner in = new Scanner(System.in); System.out.println("请输入你的用户名:"); String loginName = in.nextLine(); System.out.println("请输入你的密码:"); String loginPwd = in.nextLine(); userinfoBag.put("loginName",loginName); userinfoBag.put("loginPwd",loginPwd); return userinfoBag; }} - Statement和PreparedStatement对比:
-
前者有sql注入问题,后者没有
-
后者效率更高;
-
原理简介:mysql中,如果出现同样的语句(包括空格等),则不会编译,直接返回;
-
Mysql也是高级语言,会编译然后执行
-
编译一次,执行N次,每次都是传“值”;
-
-
后者会在编译阶段进行类型安全检查,更安全;
-
故99%用PreparedStatement,以下情况必须使用前者。
-
业务要求必须使用SQL语句拼接的时候;
-
例如京东商城购物界面需要按照价格降序,此时相当于加上order by xx desc,因此就不能用占位符(因为占位符是值,而不是自有的字段),故此时必须拼接
-
-
- 模板选择:
- 如果传入SQL语句中的是普通字段,则选择Statement
- 如果传入SQL语句的是特殊字段或者为了解决SQL注入问题,使用PreparedStatement。
7.JDBC事务机制
- 事务:在Mysql中讲过事务是一个业务逻辑,可近似看成一个线程。
- JDBC事务:在JDBC中事务是自动提交的,即对表数据的操作是立马生效的。
- JDBC事务机制:行级锁(又名悲观锁)与乐观锁
- JDBC支持悲观锁,即在select查询返回的结果所在的原表中的对应记录在该事务执行完后不能被其他事物修改,简言之,我在查询,你却不能修改,只能等我完成,不难发现,悲观锁即让多个事务只能排队进行,牺牲了效率,换取了数据安全。
- 乐观锁:由此可知,乐观锁即事务间可以并发,不过其中实现细节则是每个记录有一个版本号,任何事务在操作时都会检查该记录的版本号,如果发现修改了则自己不能操作,必须等待完毕。
- JDBC加悲观锁方法:
select * from tableName where name ='444' for update; //即在语句后加上for update,加上锁。
- JDBC事务模板
package src.JDBC;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Transaction_Bank {
public static void main(String[] args) {
Connection col = null;
PreparedStatement ps = null;
// ResultSet rs = null;
try {
//1.驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.连接
col = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb","root","123456");
//将自动提交机制修改为false;
col.setAutoCommit(false);//设置事务为手动提交!!!
//3.获取对象→预编译
String sql = "update shiwu set balance =? where actno = ?";
ps = col.prepareStatement(sql);
ps.setDouble(1,3000);
ps.setInt(2,1);
int count = ps.executeUpdate();
String s =null;
s.toString();
String sq2 = "update shiwu set balance =? where actno = ?";
ps = col.prepareStatement(sq2);
ps.setDouble(1,3000);
ps.setInt(2,2);
count += ps.executeUpdate();
System.out.println(count==2?"转账成功":"转账失败");
//手动提交,能到这里说明没有异常;
col.commit();
//5.遍历结果集→省略
} catch(Exception e){
if(col!=null){
try {
col.rollback();//出现异常则事务回滚,全部清空;
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}finally {
//6.关闭:
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(col!=null){
try {
col.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
- 行级锁演示
8.JDBC工具类封装模板
package src.JDBC;
import java.sql.*;
import java.util.ResourceBundle;
public class Utils {
static{//静态代码块只执行一次,类加载的时候执行,这里可以把注册驱动执行一次即可;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private Utils() {
//防止new对象;
}
public static Connection getConnection(String username,String password) throws SQLException{
Connection col =null;
col = DriverManager.getConnection("jdbc:mysql://localhost:3306",username,password);
return col;
}
public static Connection getConnection() throws SQLException{
Connection col = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb","root","123456");
return col;
}
public static void close(Connection col, Statement ps, ResultSet rs){
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(ps!=null){
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(col!=null){//在原来的版本中,不需要catch异常,故不用抛出,自己处理即可;
try {
col.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}



