工具类:
解决代码重复:
1.编写配置文件:
在src目录下创建config.properties配置文件
2.编写jdbc工具类:
在config.properties里面编写:
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/dbtest
username=root
password=123456
3.使用jdbc工具类优化student表CRUD操作
在项目包下面建一个utils包,然后新建一个类:把需要封装的方法写在里面
package com.www.utils;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JDBCUtils {
//1.私有构造方法
public JDBCUtils() {
}
//2.声明所需的配置文件
private static String driverClass;
private static String url;
private static String username;
private static String password;
private static Connection con;
//3.提供静态代码块,读取配置文件的信息为变量赋值,注册驱动
static {
try {
//读取配置文件的信息为变量赋值
//io流,自动从流信息中加载集合
InputStream is =JDBCUtils.class.getClassLoader().getResourceAsStream("config.properties");
Properties prop = new Properties();//流对象
prop.load(is);
driverClass=prop.getProperty("driverClass");
url=prop.getProperty("url");
username=prop.getProperty("username");
password=prop.getProperty("password");
//注册驱动
Class.forName(driverClass);
}catch (Exception e){
e.printStackTrace();
}
}
//4.提供获取数据库连接方法
public static Connection getconnection(){
try {
con= DriverManager.getConnection(url,username,password);
}catch (SQLException e){
e.printStackTrace();
}
return con;
}
//提供释放资源方法
public static void close(Connection con, Statement stat, ResultSet rs){
if (con != null) {//在进行判断一下是否为null
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (stat != null) {
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
(一):提供静态代码块,读取配置文件的信息为变量赋值,注册驱动,静态代码块在该类初始化是只运行一遍;上述静态代码块类似与系统自带的注册驱动模式一样,从摸个文件中读取地址,密码信息。这样做到目的安全。其他人只需要把文件给使用者填写地址,把密码隐藏起来。
InputStream is =JDBCUtils.class.getClassLoader().getResourceAsStream("config.properties");
Properties prop = new Properties();//流对象
prop.load(is);
driverClass=prop.getProperty("driverClass");
url=prop.getProperty("url");
username=prop.getProperty("username");
password=prop.getProperty("password");
如果不写这一步:
就直接把地址,密码写在再上面:代码如下:
public static Connection getconnection(){
try {
con= DriverManager.getConnection(url,username,password)con = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/dbtest", "root", "123456
}catch (SQLException e){
e.printStackTrace();
}
return con;
}
(二):使用静态写的方法可以直接用类名调用即可;
简化后代码:
package com.www;
import com.www.utils.JDBCUtils;
import java.beans.Statement;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.ResultSet;
public class JDBCDemo1 {
public static void main(String[] args) throws Exception{
Connection con=null;
// 第一步:注册驱动://调用JDBC工具自动加载静态代码块
// 第二步:获取连接
con=JDBCUtils.getconnection();
// 第三步:获取数据库操作对象
java.sql.Statement stat = con.createStatement();
// 第四步:执行sql语句
String sql ="SELECT * FROM employees";
ResultSet rs= stat.executeQuery(sql);
// 第五步:处理查询结果集
while(rs.next()){
System.out.println(rs.getInt("employee_id")+"\t"+rs.getString("last_name"));
}
第六步:释放资源(使用完一定要关闭)
JDBCUtils.close(con,stat,rs);
}
}
sql注入优化
1.什么是sql注入:
例如:用户名乱填,密码为’or 1=1-,这种情况下点击登录按钮后竟然成功登录了。我们看一下最终的SQL就会找到原因:
SQL语句参数化的登录语句是这样的
Select * From Where mytest UserName=xxx and Password=xxx
然后判断返回的行数,如果有返回行,证明账号和密码是正确的,即登录成功,而这样的语句的话,就很容易被注入代码,直接在密码框输入’or 1=1-,那么它的登录语句就会变成
"Select * From mytest Where UserName='xxx' and Password='aaa'or 1=1-'"
从代码可以看出,前一半单引号被闭合(‘aaa’),后一半单引号被 “–”给注释掉,中间多了一个永远成立的条件1=1,这就造成任何字符都能成功登录的结果。而Sql注入的危害却不仅仅是匿名登录。
**2.优化;
案例:**
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import static sun.security.jgss.GSSUtil.login;
public class JDBCtest {
public static void main(String[] args) {
//初始化一个页面
Map<String,String> userLoginInfo = initUI();
//验证用户和密码
boolean loginSuccess=login(userLoginInfo);
//输出结构
System.out.println(loginSuccess ? "登陆为真":"登陆失败");
}
/**
* 用户登陆
* @ruturn false 表示失败,ture 表示成功
*/
@Test
private static boolean login(Map<String, String> userLoginInfo) {
//打标记
boolean loginSuccess =false;
Connection con =null;
Statement stat = null;
ResultSet rs = null ;
//注册驱动,也可以省略
try {
//获取连接
con= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mytest","root","123456");
//获取数据库作对象
stat=con.createStatement();
//执行sql
String sql ="SELECT * FROM t_user WHERE loginName='"+userLoginInfo.get("username")+"'and loginPwd='"+userLoginInfo.get("password")+"'";
rs =stat.executeQuery(sql);
//处理结果
if(rs.next()){
//登陆成功
loginSuccess =true;
}
//
//
}catch (Exception e){
e.printStackTrace();
}finally {
if(con!=null){
try {
con.close();
}catch(Exception e){
e.printStackTrace();
}
}
if(stat!=null){
try {
con.close();
}catch(Exception e){
e.printStackTrace();
}
}
if(rs!=null){
try {
con.close();
}catch(Exception 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 loginPwd= sc.nextLine();
Map<String ,String> userLoginInfo =new HashMap<>();
userLoginInfo.put("loginName",loginName);
userLoginInfo.put("loginPwd",loginPwd);
return userLoginInfo;
}
}
优化:
* 用户登陆
* @ruturn false 表示失败,ture 表示成功
*/
@Test
private static boolean login(Map<String, String> userLoginInfo) {
//打标记
boolean loginSuccess =false;
Connection con =null;
PreparedStatement ps =null;//PreparedStatement是预编译数据库操作对象
ResultSet rs = null ;
String loginName= userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get(" loginPwd ");
//注册驱动,也可以省略
try {
//获取连接
con= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mytest","root","123456");
// //获取预编译的数据库作对象
//一个问号表示一个占位符,一个问号只接收一个值,占位符不能使用单引号扩起来
String sql ="SELECT * FROM t_user WHERE loginName=? and loginPwd=?";//PreparedStatement中sql框架
//该出会发送sql框子给DBMS,然后DBMS进行sql语句的预先编译
ps= con.prepareStatement(sql);
//给占位符?传值,第一个问号是1,第二个问号是2
ps.setString(1,loginName);
ps.setString(2,loginPwd);
//执行sql
rs=ps.executeQuery();
//处理结果
if(rs.next()){
//登陆成功
loginSuccess =true;
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(con!=null){
try {
con.close();
}catch(Exception e){
e.printStackTrace();
}
}
if(ps!=null){
try {
ps.close();
}catch(Exception e){
e.printStackTrace();
}
}
if(rs!=null){
try {
con.close();
}catch(Exception e){
e.printStackTrace();
}
}
return loginSuccess;
}
}
解决:只要用户提空信息不参与sql语句的编译过程就行,即使有sql语句关键字,不参与过程就不起作用。
PreparedStatement是预编译数据库操作对象,然后在传值;
3.Statement和PreparedStatement区别及应用:
关系:PreparedStatement继承自Statement,都是接口
**区别:**1.PreparedStatement可以使用占位符,是预编译的,批处理比Statement效率高
详解:
2.statement每次执行sql语句,相关数据库都要执行sql语句的编译,preparedstatement是预编译得,preparedstatement支持批处理
应用:
preparedstatement用于密码登陆,
statement应用于需要注入的,例如选择按照降序还是升序进行查询信息;
select id from emplees where order by id desc
select id from emplees where order by id ?
select id from emplees where order by id key
如果是占位符编译时会将?的内容变成select id from emplees where order by id ’ desc’,所以之前写?时不需要单引号;程序无法识别 ’ desc’是啥
故这里面只能应用statement,key就可以直接转换成desc
事务自动机制
在执行数据库更行的时候,都是默认的自动提交数据的,但是有很多时候,我们不能设置默认提交,生活中最常见就是,你给某人转钱,你的钱已经转出去了,但是对方在收款的时候却出现了某些问题,导致对方账户上的钱并没有增加,这个时候,为了防止问题的发生,我们就不能设置数据库默认提交。要手动提交并加锁,要不一次性成功,要不都不成功
* 用户登陆
* @ruturn false 表示失败,ture 表示成功
*/
@Test
private static boolean login(Map<String, String> userLoginInfo) {
//打标记
boolean loginSuccess =false;
Connection con =null;
PreparedStatement ps =null;//PreparedStatement是预编译数据库操作对象
ResultSet rs = null ;
String loginName= userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get(" loginPwd ");
//注册驱动,也可以省略
try {
//获取连接
con= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/mytest","root","123456");
// //获取预编译的数据库作对象
//一个问号表示一个占位符,一个问号只接收一个值,占位符不能使用单引号扩起来
String sql ="SELECT * FROM t_user WHERE loginName=? and loginPwd=?";//PreparedStatement中sql框架
//该出会发送sql框子给DBMS,然后DBMS进行sql语句的预先编译
ps= con.prepareStatement(sql);
//给占位符?传值,第一个问号是1,第二个问号是2
//一次传值;
ps.setString(1,loginName);
ps.setString(2,loginPwd);
//第二次传值
ps.setString(1,loginName);
ps.setString(2,loginPwd);
//执行sql
rs=ps.executeQuery();
//处理结果
if(rs.next()){
//登陆成功
loginSuccess =true;
}
}
上述代码中第一次传值默认的自动提交数据的,在等到第二次传值是,中间有个异常,第二个数据就没修改,就类似转账一样:
解决方案:
不让他自动提交,改为手动提交
在前面关闭获取预编译的数据库作对象添加con=setAtutoCommit(false),参数false为开启事务,所有传值结束后添加con.commit(),自己手动提交事务;con.close()改为回滚事务con.rollback()
悲观锁:
SELECT job,salary FROM t_user WHERE job='aaa' for update;
这段语句后面加for update,表示某个事务结束前job为‘aaa’的数据不能修改。
**乐观锁:**可以并发运行,但是在数据后面有个标识版本1.0,原第一人改了数据就添加版本为号2.0,又来一个人修改数据,看见版本号为2.0,认为有人修改了数据,所有他就回滚数据。