上一篇博文中的最后的用户登录案例,使用Statement对象执行SQL语句会出现一个问题:
用户不输入密码,只输入用户名,一样可以登录成功
问题
SELECT * FROM USER WHERE username = 'admin ' #' and password = '';
-- 使用#号将后半段的密码验证消除,#号在mysql中是备注
我们将用户输入的实际参数与sql字符串进行了拼接,发送给数据库先编译后执行,改变了sql原有的意义,这个现象我们称为sql注入
要想解决sql注入的问题就不能用户输入的实际参数与sql字符串进行了拼接,使用PreparedStatement【预编译】
SELECT * FROM USER WHERE username ="admin'#" AND PASSWORD ="";
-- 同 SELECT * FROM USER WHERE username =? AND PASSWORD =?;
通过Connection对象创建预编译语句执行者
// 使用?占位符代替 sql实际参数
String sql = "select * from user where username = ? and password = ?";
connection.prepareStatement(sql);
给问号设置实际参数
preparedStatement.setXxx(int 问号的位置,Object 实际参数);
例如:
Int
Double
String
Object
执行sql语句
// 执行sql 了解
public boolean execute();
// 执行dml
public int executeUpdate(); 影响行数
// 执行dql
public ResultSet executeQuery();
// idea 中使用@Test 结合scanner 会有bug
public static void main(String[] args) throws Exception {
// 在控制台输入用户名和密码 scanner技术
Scanner scanner = new Scanner(System.in);
System.out.println("欢迎来到德莱联盟");
System.out.println("请输入用户名:");
String username = scanner.nextLine();
System.out.println("请输入密码:");
String password = scanner.nextLine();
// 使用增强版的jdbc 防止sql注入
Connection connection = JDBCUtils.getConnection();
// 编写sql 使用问号占位符代替实际的参数
String sql = "select * from user where username = ? and password = ?;";
System.out.println(sql);
// 创建预编译语句执行者【高铁】
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 给sql语句设置实际参数
preparedStatement.setString(1, username);
preparedStatement.setObject(2, password);
// 执行sql语句
ResultSet resultSet = preparedStatement.executeQuery();
// 处理结果 判断用户是否登录成功
if (resultSet.next()) {
System.out.println("用户登录成功");
} else {
System.out.println("用户名或密码错误");
}
// 关闭资源
JDBCUtils.closeResource(resultSet, preparedStatement, connection);
}
步骤分析
// 获取连接
// 编写sql【使用问号占位符】
// 创建预编译对象【高铁】
// 设置sql的实际参数
// 执行sql并返回结果
// 处理结果
// 关闭资源
新增
// 新增
@Test
public void test01() throws Exception {
// 获取连接
Connection connection = JDBCUtils.getConnection();
// 编写sql【使用问号占位符】
String sql = "insert into user values(null,?,?);";
// 创建预编译对象【高铁】
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 添加一个用户
preparedStatement.setObject(1, "宝强");
preparedStatement.setObject(2, "123");
System.out.println(preparedStatement.executeUpdate()); // 返回1 成功
// 再添加一个用户
preparedStatement.setObject(1, "乃亮");
preparedStatement.setString(2, "456");
System.out.println(preparedStatement.executeUpdate());
// 。。。。。
// 关闭资源
JDBCUtils.closeResource(preparedStatement, connection);
}
修改
// 修改
@Test
public void test02() throws Exception {
// 获取连接
Connection connection = JDBCUtils.getConnection();
// 编写sql【使用问号占位符】
String sql = "update user set password = ? where id = ? ;";
// 创建预编译对象【高铁】
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 设置sql的实际参数
preparedStatement.setString(1, "789");
preparedStatement.setInt(2, 5);
// 执行sql并返回结果
int i = preparedStatement.executeUpdate();
// 处理结果
if (i > 0) {
System.out.println("修改成功");
} else {
System.out.println("修改失败");
}
// 关闭资源
JDBCUtils.closeResource(preparedStatement, connection);
}
删除
// 删除
@Test
public void test03() throws Exception {
// 获取连接
Connection connection = JDBCUtils.getConnection();
// 编写sql【占位符】
String sql = "delete from user where id= ?;";
// 创建预编译语句执行者
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 设置实际参数
preparedStatement.setInt(1, 5);
// 执行sql并返回结果
int i = preparedStatement.executeUpdate();
// 处理结果
System.out.println(i);
// 关闭资源
JDBCUtils.closeResource(preparedStatement, connection);
}
查询
// 查询
@Test
public void test03() throws Exception {
// 获取连接
Connection connection = JDBCUtils.getConnection();
// 编写sql【占位符】
String sql = "select * from user where id = ?";
// 创建预编译语句执行者
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 设置实际参数
preparedStatement.setInt(1, 5);
// 执行sql并返回结果
ResultSet resultSet = preparedStatement.executeQuery();
// 处理结果
while(resultSet.next()){
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
System.out.println(id+" "+name);
}
// 关闭资源
JDBCUtils.closeResource(preparedStatement, connection);
}
我们在使用JDBC操作数据库时,每次都需要创建连接、销毁连接,这些操作是消耗时间和浪费资源的,性能不好,我们使用连接池来优化;在使用连接时从连接池中获得,使用完毕归还到连接池,提高连接的复用性,减轻服务器压力
DataSource
是java对数据库连接池提供的一套规范(接口),咱们只需要学习接口的方法即可
实现类由连接池厂商提供(jar)
他是一个开源连接池,目前hibernate【JDBC】等框架推荐使用
步骤分析
1 导入jar包
2 编写代码
// 创建连接池对象
// 设置参数
// 获取连接
// 归还到连接池
代码片段
@Test
public void test01() throws Exception {
// 创建连接池对象 组合池
ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 设置参数
// 数据库基本四项
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/db_day04");
dataSource.setUser("root");
dataSource.setPassword("root");
// 基本参数 这些都有默认值 开发一般不用,结合自己网站实际情况
dataSource.setInitialPoolSize(5); // 初始化连接池5个
dataSource.setMaxPoolSize(20); // 最大连接个数
dataSource.setAcquireIncrement(5); // 一次性创建5个连接
dataSource.setMaxIdleTime(1000000);// 单位是毫秒 最大空闲销毁时间
dataSource.setCheckoutTimeout(3000); // 最大等待时间3秒
// 获取连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
// 归还到连接池
connection.close(); // 之前咱们学习JDBC close是销毁连接,现在使用了连接时是归还到连接池,连接池厂商对此方法进行了增强 【动态代理】
}
步骤分析
1 导入jar包
2 定义一个配置文件【由连接池提供】
要求:
文件名必须为:c3p0-config.xml 【不需要修改】
文件位置必须在:src根目录下
3 编写代码
// 创建连接池对象
// 获取连接
// 归还到连接池
代码片段
@Test
public void test01() throws Exception {
// 创建连接池对象
// new ComboPooledDataSource(); 它会去 src目录下加载 c3p0-config.xml 配置文件 标签的信息
// 底层技术我昨天讲解的 类加载器
// ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 它会去 src目录下加载 c3p0-config.xml 配置文件
ComboPooledDataSource dataSource = new ComboPooledDataSource("test");
// 获取连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
// 归还到连接池
connection.close(); // 是归还到连接池
}
druid(德鲁伊),它是阿里巴巴旗下开源产品,它号称目前最好的连接池没有之一,支持日志监控
目前主要的互联网网站,都在使用druid作为第三方连接池
步骤分析
1 导入jar包
2 定义一个配置文件【由连接池提供】
建议:
名称为:druid.properties 【见名知意】
位置在:src根目录下
3 编写代码
// 创建连接池对象
// 获取连接
// 归还到连接池
代码片段
@Test
public void test01() throws Exception {
// 获取druid.properteis文件 io流 通过类加载器
InputStream is = DemoUse.class.getClassLoader().getResourceAsStream("druid.properties");
// 创建配置文件对象 key=value
Properties properties = new Properties();
properties.load(is);
// 创建连接池对象
// druid 提供了一个工具类
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 获取连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
// 打印连接池
System.out.println(dataSource);
// 归还到连接池
connection.close(); // 此方法在使用连接池后,是被增强的(动态代理)
}
连接池是一个重量级的对象,连接创建是非常消耗时间和浪费资源的,一般进入企业开发一个项目只有一个连接池,并且初始化一次,提取到工具类放入静态代码块,简化代码,提供效率
步骤分析
// 提供获取连接池的方法
// 提供获取连接的方法
// 提供关闭资源的方法【connection是归还到连接池,其他为关闭】
代码片段
public class DataSourceUtils {
// 声明变量 static
public static DataSource dataSource;
// 初始化连接池(仅一次)
static {
// 加载 druid.properteis 使用类加载
InputStream is = DataSourceUtils.class.getClassLoader().getResourceAsStream("druid.properties");
// 创建连接池对象 使用工具类
try {
// 创建配置文件对象
Properties properties = new Properties();
// 加载io流 key = value
properties.load(is);
// 使用第三方连接池 druid
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
// 提供获取连接池的方法
public static DataSource getDataSource() {
return dataSource;
}
// 提供获取连接的方法
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
// 提供关闭资源的方法【connection是归还到连接池】
// 提供关闭资源的方法 【方法重载】3 dql
public static void closeResource(ResultSet resultSet, Statement statement, Connection connection) {
// 关闭结果集
// ctrl+alt+m 将java语句抽取成方法
closeResultSet(resultSet);
// 关闭语句执行者
closeStatement(statement);
// 关闭连接
closeConnection(connection);
}
// 提供关闭资源的方法 【方法重载】 2 dml
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();
}
}
}
}
补充:
采用第三方 druid
1 导入druid的jar包
2 在src目录下放一个 druid.properties 配置文件
刚才我们学习完了常用的连接池【c3p0,druid,DBCP】,我们知晓了如果连接需要归还给连接池,那么直接调用close方法即可。 但是我们在学习jdbc时调用connection.close() 是关闭连接,而现在是将连接还回连接池。这个是怎么做到的呢 ? 其实是对Connection的close方法进行了增强。
针对于目标对象,创建一个子类,重写父类的方法
缺点:
java是单继承,多实现,如果说我们只为了增强其中一个一个方法,就要浪费一个继承位,这种设计不好,子类还需要我们自己手写,更加繁琐,这种方案我们不采纳
代理模式【掌握】
班长【听歌】 – 豆豆老师【唱歌】
班长【听歌】-- 宋哲【收费】-- 豆豆老师【唱歌】
调用者—代理对象【增强】–目标对象【唱歌】
动态代理
在程序运行期间【反射】,由JVM虚拟机来创建代理对象,对其目标对象方法增强
动态代理技术
JDK、【GCLIB、JAVA ASSIST】spring框架
JDK动态代理
jdk根据接口规范来创建代理对象,目标对象也需要实现此接口
需求
班长【听歌】 – 豆豆老师【唱歌】
班长【听歌】-- 宋哲【收费】-- 豆豆老师【唱歌】
需求分析
针对唱歌的方法进行增强,使用jdk的动态代理,目标对象是由接口,根据接口规范创建代理对象
步骤分析
1 创建接口 singer
唱歌、吃饭
public interface Singer {
public void sing();
public void eat();
}
2 创建目标类 豆豆老师 实现singer接口
唱歌:单身情歌
吃饭:珍珠翡翠白玉汤
public class DouDou implements Singer {
@Override
public void sing() {
System.out.println("单身情歌");
}
@Override
public void eat() {
System.out.println("珍珠翡翠白玉汤");
}
}
3 针对目标对象进行增强 【宋哲】
public class TestProxy {
// 版本一
@Test
public void test01() throws Exception {
// 现有目标对象
DouDou douDou = new DouDou();
/* douDou.sing();
douDou.eat();*/
// 创建代理对象 proxy 唱歌方法 :先收费后唱歌
/*
* 参数一:目标对象类加载器
* 参数二:目标类接口数组
* 参数三:调用处理器接口 -- 增强逻辑
*
* */
// 多态效果实现
Singer songzhe = (Singer) Proxy.newProxyInstance(douDou.getClass().getClassLoader(), new Class[]{Singer.class}, new InvocationHandler() {
// 重写 invoke 具体实现增强的功能
/*
* 参数一: proxy --- this
* 参数二: method当前执行的方法 sing == method,eat == method
* 参数三:当前调用方法传递的参数列表 【数组】
*
* */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// System.out.println("我是invoke");
// System.out.println(method.getName());
// 进行增强
if ("sing".equals(method.getName())) {
System.out.println("收费。。9999");
}
// 调用目标对象去执行方法
Object obj = method.invoke(douDou, args);
return obj;
}
});
// 调用代理对象方法
songzhe.sing();
songzhe.eat();
}
// 版本二
@Test
public void test02() throws Exception {
// 创建目标对象
DouDou douDou = new DouDou();
// 创建代理对象 Proxy jdk要求根据接口规范
/*
* 参数一 目标类加载器
* 参数二 目标类接口数组
* 参数三 执行处理器接口 -- 增强
*
* */
Singer songzhe = (Singer) Proxy.newProxyInstance(douDou.getClass().getClassLoader(), douDou.getClass().getInterfaces(), (proxy, method, args) -> {
// 对唱歌增强
if ("sing".equals(method.getName())) {
System.out.println("收费。。8888");
}
// 调用目标对象 反射
return method.invoke(douDou, args);
});
// 调用代理对象的方法,实现增强
songzhe.sing();
songzhe.eat();
}
}
jdk提供了一个工具类 Proxy 专门创建代理对象
能够通过PreparedStatement完成增、删、改、查
String str = "sql语句,使用占位符?给字段赋值"
PreparedStatement preparedStatement = connection.preparedStatement(str);
preparedStatement.setXXX(占位符位置,实际参数);
能够完成PreparedStatement改造登录案例
能够理解连接池解决现状问题的原理
能够使用C3P0连接池
能够使用DRUID连接池
能够编写连接池工具类
能够说出动态代理的好处
可以对其目标对象的方法进行增强
能够使用动态代理