什么是SQL注入
由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加一些SQL 关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。
简单来说就是:用户在页面提交数据的时候人为的添加一些特殊字符,使得sql语句的结构发生了变化,最终可以在没有用户名或者密码的情况下进行登录。
ext:
https://www.cnblogs.com/geaozhang/p/9891338.html
sql注入原因演示_模拟登陆
需求: 登录功能演示SQL注入;
public class LoginDemo {
public static void main(String[] args) throws SQLException {
//1)获取连接对象
Connection conn = JDBCUtil2.getConnection(true);
//2)获取发送sql语句的对象
Statement stm = conn.createStatement();
//3)发送sql查询用户是否存在
//select * from user where username='xx or 1=1 -- ' and password='66666';
//String userName="tianqi' -- '";
String userName="xx' or 1=1 -- '";
String password="66666";
String loginSql="select * from user where username='"+userName+"' and password='"+password+"'";
ResultSet rs = stm.executeQuery(loginSql);
if(rs.next()){
System.out.println(rs.getString("username")+rs.getString("password"));
}else{
System.out.println("您输入的用户名或者密码错误!");
}
JDBCUtil2.close(rs,stm,conn);
}
}
PreparedStatement解决SQL注入方案
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IZFhxToN-1648792875190)(imgs/image-20201021144925862.png)]
1.获取PreparedStatement对象
PreparedStatement是Statement的子接口,可以防止sql注入问题。可以通过Connection接口中的prepareStatement(sql)方法获得PreparedStatement的对象。
方法如下所示:
//创建一个 PreparedStatement 对象来将参数化的 SQL 语句发送到数据库
PreparedStatement prepareStatement(String sql);
注意:sql提前创建好的。sql语句中需要参数。使用**?**进行占位。
举例:
select *from user where username=‘zhangsan’ and password = ‘123456’;
使用?进行占位
select *from user where username=? and password = ?;
String sql=”select *from user where username=? and password = ?”;
步骤一:PreparedStatement pstmt = conn.prepareStatement(sql); -----需要你事先传递sql模板。如果sql需要参数,使用?进行占位。
步骤二:设置参数(执行sql之前):pstmt.setXXX(int index, 要放入的值) -----根据不同类型的数据进行方法的选择。第一个参数index表示的是?出现的位置。**从1开始计数,有几个问号,就需要传递几个参数**。
方法的参数说明:
第一个参数:int index ;表示的是问号出现的位置。 问号是从1开始计数
第二个参数:给问号的位置传入的值。
步骤三、执行,**不需要在传递sql了**。
pstmt.executeQuery();---执行select
pstmt.executeUpdate();---执行insert,delete,update
实现:预编译实现用户登录;
//使用PrepareStatement防止sql注入
@Test
public void test2() throws SQLException {
//1)获取连接对象
Connection conn = JDBCUtil2.getConnection(true);
//定义登录的sql模板 参数索引位从1开始
String loginSql="select * from user where username=? and password=?";
//2)获取发送sql语句的预编译对象
PreparedStatement pstm = conn.prepareStatement(loginSql);
//设置参数
//pstm.setString(1,"tianqi");
//防止了sql注入
pstm.setString(1,"tianqi' or 1=1 -- '");
pstm.setString(2,"66666");
//发送参数,执行sql
ResultSet rs = pstm.executeQuery();
if(rs.next()){
System.out.println(rs.getString("username")+rs.getString("password"));
}else{
System.out.println("您输入的用户名或者密码错误!");
}
JDBCUtil2.close(rs,pstm,conn);
}
PreparedStatement的应用(掌握)
练习: 预编译实现插入用户名 liuyan ,密码123.
public class InsertUser {
public static void main(String[] args) throws SQLException {
//获取连接对象
Connection conn = JDBCUtil2.getConnection(true);
//定义sql模板
String sql="insert into user values(null,?,?)";
//获取预编译对象同时与sql模板绑定
PreparedStatement pstm = conn.prepareStatement(sql);
pstm.setString(1,"赵国庆");
pstm.setString(2,"7788");
//发送参数,执行sql
int count = pstm.executeUpdate();
System.out.println(count);
//关闭资源
JDBCUtil2.close(null,pstm,conn);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c5Dt54sC-1648792903223)(imgs/1604475735855.png)]
PreparedStatement小结:
1.Statement与PrepareStatement区别?
Statement有sql注入的风险;
PrepareStatemen对sql进行预编译,防止sql注入;
连接池
连接池介绍
1、操作数据库都需要创建连接,操作完成还需要关闭连接
2、创建连接和关闭连接需要可能比执行sql需要的时间都长
3、一个网站需要高频繁的访问数据库,如果短时间频繁的访问数据库服务器,就容易造成服务器的宕机,即死机。
1)连接池解决现状问题的原理
注意:
分析:当前的jdbc程序每次访问数据库都需要创建一个新的连接,访问完毕之后,还需要释放资源。那么在这样的一个过程中,连接的创建和销毁所消耗的资源是远远大于我们发送sql并执行的时间的。基于这样的情况,我们发现我们的jdbc程序将大量的资源浪费在了连接的创建和销毁上。
举例:就像在上海坐地铁,就一站2分钟的路程,往往在买地铁票的过程需要等待至少10分钟以上的时间。这样是不合理的。所以我们 需要对这样的结构进行优化。
思考上面的结构,大部分的时间浪费在了创建和销毁上。那么我们能不能实现将这些连接回收和利用呢?这样我们就不需要不停的创建和销毁了。只需要创建一次,放在指定的地方。当我们使用的时候,直接从里面拿就行了。用完放回原来的地方。不去销毁,当我再次使用的时候,去拿就行了。而这样的解决方案就是我们需要的。
优化后的结构如下:
说明:首先创建一定数量的连接,然后放到指定的地方。当我们需要获取连接的时候,直接从指定的地方获取。用完了,我们再将连接放回去。这样就能将我们连接的回收利用。并且不用花费大量时间在创建和销毁连接上。
2)连接池好处
连接池中保存了一些数据库连接,这些连接是可以重复使用的。节省数据库的资源消耗。
3)常用连接池的介绍
javax.sql.DataSource
表示数据库连接池,DataSource本身只是Sun公司提供的一个接口,没有具体的实现,它的实现由连接池的数据库厂商去实现。我们只需要学习这个工具如何使用即可。
该接口如下:
public interface DataSource {
Connection getConnection();
}
常用的连接池实现组件有以下这些:
- 阿里巴巴-德鲁伊Druid连接池:Druid是阿里巴巴开源平台上的一个项目,整个项目由数据库连接池、插件框架和SQL解析器组成。该项目主要是为了扩展JDBC的一些限制,可以让程序员实现一些特殊的需求。
- C3P0是一个开源的JDBC连接池,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。C3P0有自动回收空闲连接功能。
- DBCP(DataBase Connection Pool)数据库连接池,是Apache上的一个Java连接池项目。dbcp没有自动回收空闲连接的功能。
小结:
1.连接池的好处?
1)提高了连接对象的复用性;
2)避免了数据库连接对象反复的创建与销毁带来的性能开销;
3)提高了数据库的性能;
4)从客户端角度看避免了oom(out of memory error);
2.连接池的原理?
1)初始化一定数量的数据库连接对象;
2)客户端不需要自己创建连接对象,直接从连接池中获取连接对象,直接使用即可;
3)客户端使用完毕后,归还连接对象到连接池;
C3P0快速入门
准备数据:
create table emp(
id int primary key auto_increment,
name varchar(50),
city varchar(50)
);
insert into emp values(null, '刘备', '北京');
insert into emp values(null, '关羽', '上海');
insert into emp values(null, '张飞', '广州');
select * from emp;
1)C3P0连接池简介
C3P0 是一个开源的JDBC连接池,目前spring 和 hibernate框架对C3P0是支持的。
使用c3p0数据库连接池之前,首先需要在资料中找到如下的jar包,加载到项目中。
2)C3P0常用的配置参数解释
参数 | 说明 |
---|---|
driverClass | 数据库驱动类。例如:com.mysql.jdbc.Driver |
jdbcUrl | 连接数据库的url地址。例如:jdbc:mysql://localhost:3306/day05_db |
user | 连接数据库的用户名。例如:root |
password | 连接数据库的密码。例如:1234 |
initialPoolSize | 刚创建好连接池的时候连接数量 |
maxPoolSize | 连接池中最多可以放多少个连接 |
3)API介绍
com.mchange.v2.c3p0.ComboPooledDataSource
类表示C3P0的连接池对象,常用2种创建连接池的方式:
1.无参构造,使用默认配置
,
2.有参构造,使用命名配置
-
public ComboPooledDataSource() 无参构造使用默认配置(使用xml中default-config标签中对应的参数)
-
public ComboPooledDataSource(String configName) 有参构造使用命名配置(configName:xml中配置的名称,使用xml中named-config标签中对应的参数)
-
public Connection getConnection() throws SQLException 从连接池中取出一个连接
4)使用步骤
1.导入jar包c3p0-0.9.1.2.jar
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZzUDvYwK-1648792966277)(imgs/1604479969612.png)]
2.编写c3p0-config.xml
配置文件,配置对应参数
<?xml version="1.0" encoding="utf-8" ?>
<!--配置的跟标签-->
<c3p0-config>
<!--默认数据源配置-->
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql:///day05_1</property>
<property name="user">root</property>
<property name="password">1234</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
</default-config>
<!-- This app is massive! -->
<named-config name="xx">
</named-config>
</c3p0-config>
3.将配置文件放在src目录下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BVjkQ9pE-1648792966278)(imgs/1604480612382.png)]
4.创建**连接池对象ComboPooledDataSource
,**使用默认配置或命名配置
//创建数据源(连接池对象)对象,自动加载src根路径下c3p0-config.xml
ComboPooledDataSource dataSource = new ComboPooledDataSource();
5.从连接池中获取连接对象
//获取连接对象
Connection conn = dataSource.getConnection();
6.使用连接对象操作数据库,查询表中所有数据并输出到控制台。
//3)创建发送sql的对象
Statement stm = conn.createStatement();
//4)发送sql语句,获取ResultSet结果集
String selectSql="select * from user";
ResultSet rs = stm.executeQuery(selectSql);
//5)解析结果集
ArrayList<User> users = new ArrayList<>();
while (rs.next()){
int id=rs.getInt(1);
String name=rs.getString("username");
String password=rs.getString(3);
User user = new User();
user.setId(id);
user.setPassword(password);
user.setUserName(name);
users.add(user);
}
System.out.println(users);
6.关闭资源,归还连接
//6)close 归还连接对象到连接池
rs.close();
stm.close();
conn.close();
注意事项
C3P0配置文件名称必须为c3p0-config.xml
C3P0命名配置可以有多个
C3P0命名指定数据库
需求:读取数据库下面的user表中的信息。
1)修改c3p0-config.xml配置文件中的配置信息;
2)连接池对象指定数据库环境
ComboPooledDataSource dataSource =new ComboPooledDataSource("dev")
1)条件其他数据源配置
<?xml version="1.0" encoding="utf-8" ?>
<!--配置的跟标签-->
<c3p0-config>
<!--默认数据源配置-->
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql:///day05_1</property>
<property name="user">root</property>
<property name="password">1234</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
</default-config>
<!-- This app is massive! -->
<named-config name="dev">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql:///day05_2</property>
<property name="user">root</property>
<property name="password">1234</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
</named-config>
<!-- This app is massive! -->
<named-config name="test">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql:///day05_2</property>
<property name="user">root</property>
<property name="password">1234</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
</named-config>
</c3p0-config>
2)在数据源构造器中指定数据库开发环境
@Test
public void test2() throws SQLException {
//创建数据源(连接池对象)对象,自动加载src根路径下c3p0-config.xml
//ComboPooledDataSource dataSource = new ComboPooledDataSource();
//指定dev作为数据库开发环境
ComboPooledDataSource dataSource = new ComboPooledDataSource("dev");
//获取连接对象
Connection conn = dataSource.getConnection();
//3)创建发送sql的对象
Statement stm = conn.createStatement();
//4)发送sql语句,获取ResultSet结果集
String selectSql="select * from user";
ResultSet rs = stm.executeQuery(selectSql);
//5)解析结果集
ArrayList<User> users = new ArrayList<>();
while (rs.next()){
int id=rs.getInt(1);
String name=rs.getString("username");
String password=rs.getString(3);
User user = new User();
user.setId(id);
user.setPassword(password);
user.setUserName(name);
users.add(user);
}
System.out.println(users);
//6)close 归还连接对象到连接池
rs.close();
stm.close();
conn.close();
}
Druid连接池
1)Druid简介
Druid是阿里巴巴开发的号称为监控而生的数据库连接池(可以监控访问数据库的性能),Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。如:一年一度的双十一活动,每年春运的抢火车票。
Druid的下载地址:<https://github.com/alibaba/druid>
Druid连接池使用的jar包:druid-1.0.9.jar
2)Druid常用的配置参数
url | 数据库连接字符串jdbc:mysql://localhost:3306/数据库名 |
---|---|
username | 数据库的用户名 |
password | 数据库的密码 |
driverClassName | 驱动类名。根据url自动识别,这一项可配可不配,如果不配置druid会根据url自动识别数据库的类型,然后选择相应的数据库驱动名 |
initialSize | 初始化时建立的物理连接的个数。初始化发生在显式调用init方法,或者第一次获取连接对象时 |
maxActive | 连接池中最大连接数 |
maxWait | 获取连接时最长等待时间,单位是毫秒。 |
3)Druid连接池基本使用
API介绍
核心类:DruidDataSourceFactory
获取数据源的方法:使用com.alibaba.druid.pool.DruidDataSourceFactory类中的静态方法:
public static DataSource createDataSource(Properties properties)
创建一个连接池,连接池的参数使用properties中的数据
配置信息在properties属性对象中。
我们可以看到Druid连接池在创建的时候需要一个Properties对象来设置参数,所以我们使用properties文件来保存对应的参数。
Druid连接池的配置文件名称随便,放到src目录或者项目根目录下面加载
druid.properties
文件内容:
# 数据库连接参数
url=jdbc:mysql://localhost:3306/day05_db
username=root
password=123
driverClassName=com.mysql.jdbc.Driver
4)使用步骤
- 导入核心包druid-1.0.9.jar
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qwlxfAIm-1648793063941)(imgs/1604481946087.png)]
- 在项目下创建一个properties文件,文件名随意,设置对应参数
# 数据库连接参数
url=jdbc:mysql://localhost:3306/day05_1
username=root
password=1234
driverClassName=com.mysql.jdbc.Driver
initialSize=5
- 加载properties文件的内容到Properties对象中
//加载配置信息
InputStream in = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
//创建Properties配置对象
Properties properties = new Properties();
properties.load(in);
- 创建DRUID连接池,使用配置文件中的参数
//使用DruidDataSourceFactory工厂类构建连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
- 从DRUID连接池中取出连接
//获取连接对象
Connection conn = dataSource.getConnection();
-
执行SQL语句
-
关闭资源,归还连接
//6.归还资源
conn.close();
案例代码:
步骤1:属性文件:在项目下新建一个druid配置文件,命名为:druid.properties
url=jdbc:mysql:///day05_1
username=root
password=1234
driverClassName=com.mysql.jdbc.Driver
initialSize=5
步骤2:java代码:
public class DruidDemo {
public static void main(String[] args) throws Exception {
//加载配置信息
InputStream in = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
//创建Properties配置对象
Properties properties = new Properties();
properties.load(in);
//获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//获取连接对象
Connection conn = dataSource.getConnection();
//3)创建发送sql的对象
Statement stm = conn.createStatement();
//4)发送sql语句,获取ResultSet结果集
String selectSql="select * from user";
ResultSet rs = stm.executeQuery(selectSql);
//5)解析结果集
ArrayList<User> users = new ArrayList<>();
while (rs.next()){
int id=rs.getInt(1);
String name=rs.getString("username");
String password=rs.getString(3);
User user = new User();
user.setId(id);
user.setPassword(password);
user.setUserName(name);
users.add(user);
}
System.out.println(users);
//6)close 归还连接对象到连接池
rs.close();
stm.close();
//归还资源
conn.close();
}
}
总结:
1)jdbc开发流程:
1.注册驱动
Class.forName(驱动类的全限定名称);
2.获取连接对象
conn=DriverManager.getConnection(url,user,pwd);
3.获取发送sql的对象
pstm= conn.prepareStatement(sql模板);//防止sql注入
stm=conn.createStatement();//会有sql注入的问题
CallableStatement cstm=conn.prepareCall("{call 存储过程名称(?,?,...)}");
4.发送参数或者sql
1)发送sql: executeQuery(sql);// select
executeUpdate(sql);// insert delete update
2)发送参数
//设置参数
比如:pstm.setXX(参数索引位,值);
executeQuery();
executeUpdate();
5.结果相应
1)返回受影响的行数
2)返回结果集ResultSet
next();true/false
getXX(字段索引位,从1开始);
getXX(字段名称);
6.释放资源,关闭连接
rs.close();
stm.close();
conn.close();
2)jdbc之事务
jdbc默认事务自动提交;
conn.setAutoCommit(false);//设置事务手动提交
conn.commit();
conn.rollback();
3)连接池
常用的参数:
1)数据库的4大参数-- url driverclass user pwd
2) 初始化大小 最大连接数 最小连接数 等待时间等