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,赋值为5preparedStatement.setString(2,"胡桃"); //二号参数name,赋值胡桃preparedStatement.setInt(3,18); //三号参数age,赋值18preparedStatement.setString(4,"女"); //四号参数gender,赋值女preparedStatement.setString(5,"往生堂"); //五号参数address,赋值往生堂//执行语句preparedStatement.executeUpdate();//这里不给sql,sql已经传过去了//释放资源JdbcUtils.close(preparedStatement,connection);}
}
Statement和PreparedStatement的区别1)Statement--每次书写一条sql就需要通过Statment将sql语句发送给数据库-效率低并且数据库的压力大!--发送的sql语句存在字符串拼接-非常不安全--sql注入!--获取全部数据!2)PreparedStatement--将参数化的sql语句发送给数据库,进行预编译,以后执行语句只需要赋值即可,不需要重新传sql-效率高,数据库压力小--参数化的sql语句中,参数全部使用?占位符来代替,不存在拼接-安全
SQL注入安全问题在JDBC使用Statement获取执行对象,并将sql语句发送给数据库的过程中-用户利用sql拼接的漏洞,将用户名和密码全部绕过!举例select * from user where username='helloworld' and password = 'hello 'or '1'='1' ;--利用or的"或"特性--1是常量,恒成立--直接绕过用户名密码--直接访问其他人全部数据!解决方案全部使用PreparedStatement来进行后续过程!
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的人减去100ps2.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的人减去100ps2.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);}/*执行结果报错 ,但是数据库没有任何改变,保护数据安全*/}
}
数据库连接池DataSourceDataSource可以看作数据源,它封装了数据库参数,连接数据库-程序中操作DataSource对象即可对数据库进行增删改查操作DataSource连接池--类比线程池连接池在创建的时候会带默认参数--从配置文件中获取默认创建一定数量的数据库连接对象每当使用完毕后会回到连接池中,等到下次继续使用! Druid 德鲁伊!连接池工具--jar包来实现Java提供的DataSource接口参数driverClassName 创建驱动链接--com.mysql.jdbc.Driverurl 连接数据库链接--jdbc:mysql://localhost:3306/库名username 数据库用户名password 数据库密码initialSize 定义初始化连接数maxAction 最大连接数量maxWait 连接等待时间(毫秒值)--超过等待时间直接结束Druid获取连接数据库对象过程1)导包2)配置文件--严格按照Druid要求书写3)创建属性列表集合Properties4)读取配置文件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} initedcom.mysql.jdbc.JDBC4Connection@75412c2f*///释放资源,归还连接池connection.close();}
}
ThreadLocal--线程变量--与连接池连用,保护数据安全!隔离线程synchronized--使多个线程安全的抢占同一资源--数据共享ThreadLocal--使每个线程都使用自己的资源--数据隔离格式ThreadLocal<存储类型> 名 = new ThreadLocal<>()方法public T get()--获取当前线程中执行的值public void set(T value)--将任意内容绑定当前线程,不为空则更新,为空则直接赋值public void remove()--线程使用完毕之后,需要将内容从中解绑--否则可能会造成数据泄露
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对象--存储连接对象,所以泛型存储Connectionprivate static ThreadLocal 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 ;}//创建公共的获取数据库链接对象的安全方法!--ThreadLocalpublic 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();}}}
}