1. 绪言
想要学习mybatis的相关知识,学习之前复习了下JDBC的相关知识发现自己竟然连如何实现JDBC连接都不知道了,真的是少壮用CV(粘贴复制),老大徒伤悲😂总结一下,JDBC连接分为三步:
加载JDBC驱动创建数据库连接操作数据库实现增删改查:获取statement,执行SQL语句,处理执行结果
简单的连接和查询示例如下:
import java
.sql
.*
;
public class Test {
private static final String DRIVER
= "com.mysql.jdbc.Driver";
private static final String URL
= "jdbc:mysql://localhost:3306/lucy?useUnicode=TRUE&characterEncoding=UTF-8";
private static final String USER
= "root";
private static final String PASSWORD
= "123456";
private static Connection conn
= null
;
private static Statement stmt
= null
;
private static ResultSet rs
= null
;
private static PreparedStatement ptmt
= null
;
public static void main(String
[] args
) {
try {
Class
.forName(DRIVER
);
conn
= DriverManager
.getConnection(URL
, USER
, PASSWORD
);
stmt
= conn
.createStatement();
rs
= stmt
.executeQuery("select * from stu");
while (rs
.next()) {
StringBuilder sb
= new StringBuilder("[id=");
sb
.append(rs
.getLong("id"));
sb
.append(", name=" + rs
.getString("name"));
sb
.append(", age=" + rs
.getInt("age"));
sb
.append(", class_id=" + rs
.getLong("class_id"));
sb
.append("]");
System
.out
.println(sb
.toString());
}
} catch (ClassNotFoundException e
) {
e
.printStackTrace();
} catch (SQLException throwables
) {
throwables
.printStackTrace();
} finally {
try {
if (rs
!= null
) {
rs
.close();
}
if (stmt
!= null
) {
stmt
.close();
}
if (ptmt
!= null
) {
ptmt
.close();
}
if (conn
!= null
) {
conn
.close();
}
} catch (SQLException throwables
) {
throwables
.printStackTrace();
}
}
}
}
stu表的建表语句见博客:数据库中的四大join & 笛卡尔乘积(以MySQL为例)
2. SQL注入的问题
① 字符串拼接引发SQL注入
新需求:这是一个简单的学生信息管理系统,需要通过学生姓名查询学生信息。详细设计:编写一个方法,入参为学生姓名,将入参拼接成完整的SQL语句代码实现:
public static void queryWithParam(String name
) throws SQLException
{
String sql
= "select * from stu where name='" + name
+ "'";
rs
= stmt
.executeQuery(sql
);
while (rs
.next()) {
StringBuilder sb
= new StringBuilder("[id=");
sb
.append(rs
.getLong("id"));
sb
.append(", name=" + rs
.getString("name"));
sb
.append(", age=" + rs
.getInt("age"));
sb
.append(", class_id=" + rs
.getLong("class_id"));
sb
.append("]");
System
.out
.println(sb
.toString());
}
}
如果用户传入的参数为lucy,此时SQL字符串为select * from stu where name='lucy',能查询到想要的数据如果这是一个急性子的用户,他不仅想要知道lucy的信息,还想要知道郭麒麟的信息,这时传入的参数为:lucy' or name='郭麒麟拼接出来的SQL语句为select * from stu where name='lucy' or name='郭麒麟',查询出来的学生信息就不止lucy一条信息了由此可见,这样的字符串拼接方式存在SQL注入的风险
② 字符串转义避免SQL注入
上面的例子会可能发生SQL注入,那是因为传入的字符串在特定的环境下它不再是一个整体,而是可以拆分出多个查询条件为了避免SQL注入,最直接的想法就是让传入的参数始终是一个整体。我们可以对入参中的'字符进行转义\',这样就保证其整体性实践发现,lucy\' or name=\'郭麒麟简单的转义在字符串拼接时,会自动取消转义:select * from stu where name='lucy' or name='郭麒麟'因此,拼接而成的SQL语句任然有SQL注入的风险廖雪峰官网给出的转义方法:
要避免SQL注入攻击,一个办法是针对所有字符串参数进行转义,但是转义很麻烦,而且需要在任何使用SQL的地方增加转义代码。
看起来,通过转义避免SQL注入是很不现实的
③ 使用PreparedStatement避免SQL注入
可以使用PreparedStatement来实现数据库操作,它的特点是:使用占位符?表示SQL语句中的参数,通过set方法为SQL语句传入参数简单示例如下:
public static void queryPrepare(String name
) throws SQLException
{
ptmt
= conn
.prepareStatement("select * from stu where name=?");
ptmt
.setString(1, name
);
System
.out
.println(ptmt
.toString());
rs
= ptmt
.executeQuery();
while (rs
.next()) {
StringBuilder sb
= new StringBuilder("[id=");
sb
.append(rs
.getLong("id"));
sb
.append(", name=" + rs
.getString("name"));
sb
.append(", age=" + rs
.getInt("age"));
sb
.append(", class_id=" + rs
.getLong("class_id"));
sb
.append("]");
System
.out
.println(sb
.toString());
}
}
这里,我们仍然传入之前成功发生SQL注入的参数lucy' or name='郭麒麟,发现打印出来的SQL语句为:
select * from stu
where name
='lucy\' or name=\'郭麒麟'
神奇之处:预编译自动将SQL语句进行了转义,使得传入的参数成为了一个整体也就是说,这里的查询条件变成了lucy\' or name=\'郭麒麟,SQL语句无法被截断了总结: 使用PreparedStatement的优点:
安全性不同: PreparedStatement可以有效防止sql注入,而Statment不能防止sql注入。语法不同:PreparedStatement可以使用预编译的sql,而Statment只能使用静态的sql效率不同:对于更改参数的同一SQL语句,PreparedStatement可以使用sql缓存区,效率比Statment高