1.PreparedStatement预编译对象
1.1 SQL注入
登录案例: 可以在不输入密码的情况下登录成功!
String sql = "select * from user where username = '"
+ username + "' and password = '" + password + "' ";
自控制台输入:
欢迎来到……
请输出用户名:
admin '#
请输入用户密码:
[空着]
select * from user where username = 'admin '#' and password = ''
登陆成功!
原因: 将用户输入的实际参数与sql字符串进行拼接,发送给数据库(编译,执行),改变了数据库原有的含义, 这个现象叫做SQL注入。
1.2解决方案
不用实际数据直接与数据库进行拼接,用占位符(?)解决
String sql="select * from user where username = ? and password = ?;";
//创建预编译语句
public PreparedStatement prepareStatement(sql);
//设置实际参数
public void setXXX(int 问号位置,Object 实际参数);
//执行方法
public boolean exctue(); //dml返回false,dql返回ture
//执行dml
public int executeUpdate(); //影响行数
//执行dql
public ResultSet executeQuery();//结果集
1.3修复用户登录
代码:
public static void main(String[] args) throws SQLException {
//1. 在控制台输入用户名和密码
Scanner scanner = new Scanner(System.in);
//提示
System.out.println("欢迎来到……");
System.out.println("请输出用户名:");
String username = scanner.nextLine();
System.out.println("请输入用户密码:");
String password = scanner.nextLine();
//2.根据输入的用户名和密码查询数据库 jdbc
//2.1 获取连接
Connection connection = JDBCTools01.getConnection();
//2.2 编写sql代码
//String sql = "select * from user where username = '" + username + "' and password = '" + password + "' ";
String sql = "select * from `user` where username = ? and password=?";
System.out.println(sql);
//创建预编译语句执行者
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//设置值
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
// System.out.println(sql);
//2.3 创建执行者
// statement = connection.createStatement();
//2.4 发送sql语句并接收返回结果
ResultSet resultSet = preparedStatement.executeQuery();
//2.5处理事件
if (resultSet.next()) {
System.out.println("登陆成功!");
} else {
System.out.println("登录失败!");
}
//2.6关闭资源
JDBCTools01.closeResorce(resultSet, preparedStatement, connection);
}
1.4 优点
- 防止sql注入,提高了安全性
- 减少编译次数,提高效率
- 参数与sql分离,提高了程序的可读性。
1.5 实现CURD
步骤分析:
- 连接数据库
- 编写sql语句(使用占位符)
- 创建预编译语句(PreparedStatement),发送sql语句
- 设置实际参数
- 执行sql语句并返回结果
- 处理结果
- 关闭资源
代码(一部分)
//新增(主要修改部分)
//2.编写sql语句
String sql = "insert into user(username,password) values(?,?)";
// 3.创建预编译语句执行执行者,发送给数据库编译sql语句
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//4.设置实际参数
preparedStatement.setString(1, "keke");
preparedStatement.setString(2, "123");
查询
@Test
public void test04() throws Exception{
Connection connection = JDBCTools01.getConnection();
String sql = "select * from user where id=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"1");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
System.out.println(id+" "+username+" "+ password);
}
JDBCTools01.closeResorce(resultSet,preparedStatement,connection);
}
PS: 以上代码都需要一个 “JDBCTools01”的工具类D18有
2. 连接池
2.1 概述
2.1.1 每次创建数据库连接问题
- 每次获取数据库连接需要消耗资源和时间(创建连接,运行sql语句,关闭连接),为了减轻服务器的压力,提高效率,我们使用连接池 保证连接的复用性。
2.1.2连接池能够解决的问题的原理
- 程序从一开始就创建一定数量的连接,放在一个容器中,叫连接池
- 使用的时候从连接池 取出一个对象。
- 关闭资源的时候,并不是真正的关闭,而是将对象放回连接池中,以便下次使用
2.2 API
公共的接口: javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这
样应用程序可以方便的切换不同厂商的连接池。
接口中有一个方法: Connection getConnection(): 从连接池获取一个连接
归还连接的时候直接调用连接的close() 方法即可.
2.3 c3p0
- 是java的开源连接池,目前【hibernate(JDBC封装的框架)、spring】默认推荐使用的连接池
2.3.1 常用API
public ComboPooledDataSource( ) :无参构造使用默认配置(使用xml中default‐config标签中对应的参数) 创建连接池
public ComboPooledDataSource():有参构造使用命名配置
public Connection getConnection() throws SQLException
从连接池中取出一个连接
2.3.2常用配置参数解释
- 初始连接数initialPoolSize()
- 最大连接数maxPoolSize()
- 最大等待时间checkoutTimeout()
- 最大空闲回收时间maxIdleTime()
a) 硬编码(了解)
@Test
public void test01() throws Exception {
//创建连接池
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
//设置参数
//2.1数据库的基本四项
comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");
comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/student2");
comboPooledDataSource.setUser("root");
comboPooledDataSource.setPassword("root");
//2.2设置参数
comboPooledDataSource.setInitialPoolSize(5);//初始连接数5
comboPooledDataSource.setMaxPoolSize(20);//最大连接数20
comboPooledDataSource.setAcquireIncrement(5);//一次创建五个连接
comboPooledDataSource.setMaxIdleTime(30000);//最大空闲销毁时间,单位毫秒
comboPooledDataSource.setCheckoutTimeout(3000);//连接等待时间
//获取连接
Connection connection = comboPooledDataSource.getConnection();
System.out.println(connection);
//归还资源
connection.close();
//之前咱们使用close方法销毁连接,而现在变成了归还连接池,其实是连接池厂商对此方法进行了增强(动态代理)
}
配置文件
步骤:
- 导入jar包:c3p0-0.9.5.2.jar 和 mchange-commons-java-0.2.12.jar
- 定义配置文件
要求:
文件名必须是 c3p0-config.xml
位置必须是在src根目录下
- 编写代码
创建连接池
获取连接
归还连接池
@Test
public void test01() throws Exception{
//创建连接池对象
//默认去src根目录下去加载c3p0-config.xml中的<named-config name="test">标签信息
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
//获取连接
Connection connection = comboPooledDataSource.getConnection();
//归还连接池
connection.close();
}
2.4 druid
(德鲁伊)是阿里巴巴开源的连接池,号称目前最好的连接池没有之一,并支持日志监控功能。
2.4.1 配置文件
步骤:
1.导入 jar包
druid-1.0.9.jar
- 定义配置文件
建议:
文件名为: druid.properties
位置在src根目录下
- 编写代码
- 创建连接池对象,提供一个工具类
- 获取连接
- 归还连接池
代码片段:
@Test
public void test01() throws Exception{
//获取druid.properties
InputStream is = Demo02.class.getClassLoader().getResourceAsStream("druid.properties");
//创建配置文件对象
Properties properties = new Properties();
properties.load(is);
//创建连接池对象,提供一个工具类
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//2.获取连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
//支持日志的监控功能,初体验dataSource toString
System.out.println(dataSource);
//归还连接池
connection.close();
System.out.println(dataSource);
}
2.5 连接池工具类(web阶段都在使用)
步骤分析:
- 提供获取连接池的方法
- 提供连接的方法
- 提供关闭资源的方法(归还)
代码片段:
package com.itheima05_pooltools;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class Demo01DateSourceUtils {
private static DataSource dataSource;
//保证连接池初始化一次
static {
try {
//获取配置文件流
InputStream is = Demo01DateSourceUtils.class.getClassLoader().getResourceAsStream("druid.properties");
//创建配置文件对象
Properties properties = new Properties();
properties.load(is);
//使用工厂创建连接池对象
DataSource dataSource1 = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//提供获取连接池的方法
public static DataSource getDataSource(){
return dataSource;
}
//提供获取连接的方法
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
//归还连接池
public static void closeResource(ResultSet resultSet, Statement statement,Connection connection){
closeResultSet(resultSet);
closeStatement(statement);
closeConnection(connection);
}
public static void closeResource( Statement statement,Connection connection){
closeStatement(statement);
closeConnection(connection);
}
private static void closeConnection(Connection connection) {
if (connection!=null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
private static void closeStatement(Statement statement) {
if (statement!=null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
private static void closeResultSet(ResultSet resultSet) {
if (resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
3 动态代理
jdbc中调用connection.close()是关闭连接,但上面c3p0、druid最后调用close()方法时是归还连接池(动态代理)。
3.1 动态代理
- 在程序运行期间(反射),有jvm虚拟机来创建代理对象,针对目标方法进行增强
JDK动态代理:
- 目标类是接口的实现,JDK根据接口规范来创建代理对象。
步骤分析:
- 创建接口,
- 并创建实现接口的对象
- 使用JDK技术实现代理:Proxy工具类
代码:
package com.itheima06_proxy;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Demo01 {
@Test
public void test01() throws Exception{
//1.创建目标对象
JiJi jiJi = new JiJi();
//使用proxy工具类创建代理对象
/*
参数一:目标对象的类加载器
参数二:目标对象的接口数组
参数三:调用处理器接口,--实现增强逻辑
*/
Sing songze = (Sing) Proxy.newProxyInstance(JiJi.class.getClassLoader(), new Class[]{Sing.class}, new InvocationHandler() {
/*
实现增强逻辑的invoke
参数一:proxy代理对象的本身
参数二:methed当前调用的方法
参数三:args当前方法传递的参数列表
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//真正干活的是目标对象
Object obj = method.invoke(jiJi, args);
return obj;
}
});
//调用大礼对象方法实现增强
songze.sing("单身情歌");
songze.eat();
}
//版本2
@Test
public void test02() throws Exception{
//创建目标对象
JiJi jiJi=new JiJi();
//使用proxy代理对象
Sing songze=(Sing) Proxy.newProxyInstance(JiJi.class.getClassLoader(),JiJi.class.getInterfaces(),((proxy, method, args) ->{
if ("sing".equals(method.getName())){
System.out.println("收费998");
}
return method.invoke(jiJi,args);
} ));
songze.eat();
}
}
- 创建Sing接口,有eat()和sing(String name)两个抽象方法
- 创建JiJi类,实现Sing接口,并重写里面的抽象方法。