今日内容
1. PreparedStatement
PreparedStatement --预编译 步骤 1)注册驱动 2)获取数据库连接对象 3)准备sql语句 --不需要拼接 --需要的参数全部使用 ? 占位符 4)通过数据库连接对象,获取预编译对象,同时将sql语句房费数据库,将参数和参数类型都存储在预编译中 Connection中的方法 PreparedStatement prepareStatement(String sql) 5)给参数赋值 void setXX(int Index,XX实际值) index -- 代表第几个参数 实际值 -- 就是参数的实际值 6)执行预编译对象 --在这里不用将sql语句给进去,因为第4步已经将语句传过去了,只需要执行即可 int executeUpdate() ResultSet executeQuery() 7)释放资源
import utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
//预编译
public class preparedStatementTest {
public static void main(String[] args) throws SQLException {
//注册驱动,获取数据库连接对象
Connection connection = JdbcUtils.getConnection();//调用工具类方法即可!
//准备sql语句--使用?占位符,不需要拼接,大大提升安全性
String sql = "insert into student values (?,?,?,?,?) ;" ;
//获取执行对象--将sql语句传到数据库进行预编译--已经给数据库传过去了
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//给参数赋值
preparedStatement.setInt(1,5); //一号参数id,赋值为5
preparedStatement.setString(2,"胡桃"); //二号参数name,赋值胡桃
preparedStatement.setInt(3,18); //三号参数age,赋值18
preparedStatement.setString(4,"女"); //四号参数gender,赋值女
preparedStatement.setString(5,"往生堂"); //五号参数address,赋值往生堂
//执行语句
preparedStatement.executeUpdate();//这里不给sql,sql已经传过去了
//释放资源
JdbcUtils.close(preparedStatement,connection);
}
}
1.1 面试题
Statement和PreparedStatement的区别 1)Statement --每次书写一条sql就需要通过Statment将sql语句发送给数据库 -效率低并且数据库的压力大! --发送的sql语句存在字符串拼接 -非常不安全--sql注入!--获取全部数据! 2)PreparedStatement --将参数化的sql语句发送给数据库,进行预编译,以后执行语句只需要赋值即可,不需要重新传sql -效率高,数据库压力小 --参数化的sql语句中,参数全部使用?占位符来代替,不存在拼接 -安全
1.2 SQL注入
SQL注入安全问题 在JDBC使用Statement获取执行对象,并将sql语句发送给数据库的过程中 -用户利用sql拼接的漏洞,将用户名和密码全部绕过! 举例 select * from user where username='helloworld' and password = 'hello 'or '1'='1' ; --利用or的"或"特性--1是常量,恒成立--直接绕过用户名密码--直接访问其他人全部数据! 解决方案 全部使用PreparedStatement来进行后续过程!
2. JDBC方式控制事务
JDBC方式控制事务 JDBC中如果不主动设置,默认自动提交! public void setAutoCommit(boolean autoCommit) --手动设置是否自动提交 参数boolean autoCommit -true,自动提交 -false,手动提交 public void rollBack() --事务回滚,撤销之前的所有更新操作--前提是必须手动提交! public void commit() --手动提交
JDBC自动提交可能会出现的问题 不控制事务 --执行多个sql语句期间出问题了,会造成数据紊乱!
import utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCTransaction {
public static void main(String[] args) throws SQLException {
//利用表中数字增加减少举例
//获取连接数据库
Connection connection = JdbcUtils.getConnection();
//准备sql语句
//数字减少语句
String sql1 = "update student set age = age - 100 where id = ? ; " ;
//数字增加语句
String sql2 = "update student set age = age + 100 where id = ? ; " ;
//获取执行对象
PreparedStatement ps1 = connection.prepareStatement(sql1);
PreparedStatement ps2 = connection.prepareStatement(sql2);
//参数赋值
ps1.setInt(1,1);//给表中id为1的人减去100
ps2.setInt(1,2);//给表中id为2的人加上100
//执行
ps1.executeUpdate();//执行减
//制造错误
int a = 1/0 ;//测试用错误
ps2.executeUpdate();//执行加
/*
执行结果报错,但是数据库内容,该减的减了,该加的没加
*/
}
}
解决方案 手动开启提交,利用回滚解决安全问题
import utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCTransaction {
public static void main(String[] args) {
//利用表中数字增加减少举例
//获取连接数据库
Connection connection = null ;
PreparedStatement ps1 = null ;
PreparedStatement ps2 = null ;
try {
connection = JdbcUtils.getConnection();
//准备sql语句
//数字减少语句
String sql1 = "update student set age = age - 100 where id = ? ; " ;
//数字增加语句
String sql2 = "update student set age = age + 100 where id = ? ; " ;
//获取执行对象
ps1 = connection.prepareStatement(sql1);
ps2 = connection.prepareStatement(sql2);
//参数赋值
ps1.setInt(1,1);//给表中id为1的人减去100
ps2.setInt(1,2);//给表中id为2的人加上100
//手动开启提交
connection.setAutoCommit(false);//false为手动提交
//执行
ps1.executeUpdate();//执行减
//制造错误
int a = 1/0 ;//测试用错误
ps2.executeUpdate();//执行加
connection.commit();
} catch (SQLException throwables) {
try {
//加入回滚,保证数据出错时一切回到未改变前!保证数据安全
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
throwables.printStackTrace();
}finally {
JdbcUtils.close(ps1,connection);
JdbcUtils.close(ps2,connection);
}
/*
执行结果报错 ,但是数据库没有任何改变,保护数据安全
*/
}
}
3. 数据库连接池DateSource
数据库连接池DataSource DataSource可以看作数据源,它封装了数据库参数,连接数据库 -程序中操作DataSource对象即可对数据库进行增删改查操作 DataSource连接池--类比线程池 连接池在创建的时候会带默认参数--从配置文件中获取 默认创建一定数量的数据库连接对象 每当使用完毕后会回到连接池中,等到下次继续使用! Druid 德鲁伊! 连接池工具--jar包 来实现Java提供的DataSource接口 参数 driverClassName 创建驱动链接--com.mysql.jdbc.Driver url 连接数据库链接--jdbc:mysql://localhost:3306/库名 username 数据库用户名 password 数据库密码 initialSize 定义初始化连接数 maxAction 最大连接数量 maxWait 连接等待时间(毫秒值)--超过等待时间直接结束 Druid获取连接数据库对象过程 1)导包 2)配置文件--严格按照Druid要求书写 3)创建属性列表集合 Properties 4)读取配置文件 5)将读取的字节输入流文件加载到属性列表中 6)从连接池获取对象 com.alibaba.druid.pool.DruidDataSourceFactory工厂类提供了方法 --public static DataSource createDataSource(配置文件名) --创建数据源DataSource数据源接口对象 本质 DruidDataSource实现了DataSource接口
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
//利用Druid连接池获取数据库连接对象
public class druidTest {
public static void main(String[] args) throws Exception {
//导包
//书写配置文件
//创建属性列表集合
Properties prop = new Properties();
//读取配置文件中的内容
InputStream input = druidTest.class.getClassLoader()
.getResourceAsStream("druid.properties");
//将读取的内容加载到属性列表集合中
prop.load(input);
//利用DruidDataSourceFactory类方法实现DataSource接口
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//利用DataSource中方法getConnection()获取数据库连接对象
Connection connection = dataSource.getConnection();
//输出查看结果
System.out.println(connection);
/*
十二月 06, 2022 8:44:24 下午 com.alibaba.druid.pool.DruidDataSource info
信息: {dataSource-1} inited
com.mysql.jdbc.JDBC4Connection@75412c2f
*/
//释放资源,归还连接池
connection.close();
}
}
4. ThreadLocal
ThreadLocal--线程变量--与连接池连用,保护数据安全! 隔离线程 synchronized --使多个线程安全的抢占同一资源--数据共享 ThreadLocal --使每个线程都使用自己的资源--数据隔离 格式 ThreadLocal<存储类型> 名 = new ThreadLocal<>() 方法 public T get() --获取当前线程中执行的值 public void set(T value) --将任意内容绑定当前线程,不为空则更新,为空则直接赋值 public void remove() --线程使用完毕之后,需要将内容从中解绑--否则可能会造成数据泄露
5. 优化Utils工具类-Druid-ThreadLocal
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
//优化工具类,加入Druid和ThreadLocal
public class DruidUtils {
//声明DataSource变量
private static DataSource dataSource = null ;
//声明ThreadLocal对象--存储连接对象,所以泛型存储Connection
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
//无参构造私有--外界不能创建实例
private DruidUtils(){}
//书写静态代码块,类加载就读取配置文件,完成数据库连接以及获取对象
static {
try {
//创建属性集合列表
Properties properties = new Properties();
//读取配置文件
InputStream inputStream = DruidUtils.class.getClassLoader()
.getResourceAsStream("druid.properties");
//将读取结果加载进列表
properties.load(inputStream);
//通过DruidDataSourceFactory类的方法获取DataSource对象
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
//创建公共的获取数据源方法--后面有用
public static DataSource getDataSource(){
return dataSource ;
}
//创建公共的获取数据库链接对象的安全方法!--ThreadLocal
public static Connection getConnection() {
Connection connection = null ;
//通过ThreadLocal获取一个线程
connection = threadLocal.get() ;//因为存储的是Connection,所以取出也是
//判断取出内容是否为空,是否占用已有内容线程
if (connection==null){
//当前线程没有连接对象,从连接池中取出一个绑定在一起
try {
connection = dataSource.getConnection();
return connection ;
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
return null ;
}
//JDBC控制事务
//手动开启事务
public static void controlTransaction(){
try {
Connection connection = getConnection();
connection.setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//回滚
public static void rollBack(){
Connection connection = getConnection() ;
try {
connection.rollback();
connection.close(); //释放资源
threadLocal.remove(); //解绑
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//提交
public static void commit(){
Connection connection = getConnection();
try {
connection.commit();
connection.close();//释放资源
threadLocal.remove();//解绑
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
//释放资源
//释放资源--增删改
public static void close(PreparedStatement preparedStatement, Connection connection){
if (preparedStatement!=null){
try {
preparedStatement.close();//释放资源
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();//释放资源
threadLocal.remove(); //解绑
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
//释放资源--查询
public static void close(ResultSet resultSet,PreparedStatement preparedStatement,Connection connection){
if (resultSet!=null){
try {
resultSet.close();//释放资源
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (preparedStatement!=null){
try {
preparedStatement.close();//释放资源
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection!=null){
try {
connection.close();//释放资源
threadLocal.remove(); //解绑
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}