title: "Jdbc2"
date: 2019-08-15T10:20:46+08:00
本章内容为:《JDBC基础2》
作者:nuoccc
在上一篇文章了解完JDBC基础,这篇文章主要讲jdbc的使用以及如何封装成一个jdbc工具类。
不知道观看的朋友还有印象没,上一篇文章中Connection对象创建Statement对象时,用到了两种方法。
第一个是createStatement(); 另外一个是prepareStatement();
两个方法是把sql语句传参给数据库,但两个方法到底有什么区别呢?
1.SQL注入改用prepareStatement
可能看到第一个小标题就知道了,就是prepareStatement可以防止SQL注入,那什么是SQL注入?
百度百科对于SQL注入的描速:
所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令,当应用程序使用输入内容来构造动态sql语句以访问数据库时,会发生sql注入攻击。
JDBC的根本作用就是配合后面的Servlet部署网页实时的获取用户从网页上的输入,并对数据库数据进行CRUD,
所以我们的数据都是动态的,也会使用动态拼接SQL语句。
例如,我们有个登陆界面输入账号和密码,我们就会去数据库找是否存在一行数据包含这个账号和密码,
我们用Statement来使用SQL语句是这样的:
String sql = "select * from testtable where username='"+username+"' and password='"+password+"'";
-- 等号后面的username和password代表用户传递过来的数据
Statement.executeQuery(sql);
但是这里就有一个问题,就是SQL注入,例如恶意攻击的人在输入密码时,加一个"or "a"="a;
那我们的sql语句就会变成:
String sql = "select * from testtable where username = '"+username+"' and password = '+password+"or"a"="a'";
-- 这样a=a永远成立,所以就能不需要密码就能登录
那为什么使用prepareStatement可以防止SQL注入呢?
因为prepareStatement使用的是占位符,在SQL语句的体现中是这样的:
String sql = "select * from testtable where username=? and password=?";
然后这时候会执行prepareStatement的预编译这也是防止SQL注入的原因,并且性能也会高一些。
preparedStatement = conn.prepareStatement(sql);
那为什么执行预编译能防止SQL注入呢?因为我们是预编译之后再进行赋值的,
例如同样的恶意攻击的人在输入密码时,加一个"or "a"="a;
这时候SQL语句就会变成:
String sql = "select * from testtable where username = ? and password = ''"or"a"="a'";
preparedStatement = conn.prepareStatement(sql);
-- 执行预编译
preparedStatement.setObject(1,username);
preparedStatement.setObject(2,userpassword);
-- 执行预编译之后,可以直接对占位符的位置传递值,所以之后的SQL语句应该如下
String sql = "select * from testtable where username='"+username+"' and password='"+password+"'";
-- 覆盖了攻击者的SQL语句达到了防止SQL注入,实际应该是改变的是字节码的值,这里只是方便理解。
prepareStatement.execueteQuery();
-- 执行数据库的查询 不需要再传递sql了,因为之前已经 编译过一次了
所以不管是安全性还是效率上我们都更加偏向于使用prepareStatement来执行对数据库的传递。
2.完成JDBC的登录和注册操作
上一篇文章了解了jdbc最基础的操作,然后我们就在此基础上完成常用的注册和登录功能。
首先先创建一个用户表:
create table j_user(
id int(4) primary key auto_increment,
username varchar(20) not null,
password varchar(20) not null,
age int(3) not null
)engine=innodb default charset=uft8;
2.1 注册功能
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
System.out.println("请输入您的账号:");
String username = sc.next();
System.out.println("请输入您的密码:");
int age = sc.nextInt();
System.out.println("请输入您的年龄:");
String password = sc.next();
Connection con = null;
PrepareStatement ps = null;
//开始jdbc的初始化,创建两个空对象
Class.forName("com.mysql.jdbc.Driver");//加载驱动
con= conn=DriverManager.getConnection("jdbc:mysql:///testjdbc","root","123456");
//本地127.0.0.1可以使用//代替
String sql = "insert into values(null,?,?,?)";
//第一个是id是自增的所以为空,另外三个后面传参先使用占位符。
ps = con.prepareStatement(sql);
//获得prepareStatement对象,并预编译sql语句。
ps.setObject(1,username);
ps.setObject(2,password);
ps.setObject(3,age);
//设置值,并且必须跟?一一对应!
int count = ps.executeUpdate();
//执行数据库语句,并返回一个值,为1成功,为0失败
if(count>0){
System.out.println("注册成功");
}else{
System.out.println("注册失败");
}
if(ps!=null){
ps.close();
}
if(con!=null){
con.close;
}
//最后关闭流,同样的整个代码我没抛异常,麻烦自行解决。
}
完成了注册功能,我们再来完成登录功能。
2.2 登录功能
public static void main(String[] args){
Scanner sc = new Scanner(Syetem.in);
System.out.println("请输入登录账号");
String username = sc.next();
System.out.println("请输入登录密码");
String password = sc.next();
Connection con = null;
PrepareStatement ps = null;
ResultSet rs = null;
//初始化三个空对象。
Class.forName("com.mysql.jdbc.Driver");
con=DriverManager.getConnection("jdbc:mysql:///testjdbc","root","123456");
//加载驱动,连接数据库获得Connection对象
String sql = "select * from j_user where username=? and password=?";
ps=con.prepareStatement(sql);
//预编译并获得prepareStatement对象
ps.setObject(1,username);
ps.setObject(2,password);
//把用户输入的值传到占位符。
rs = ps.executeQuery();
//执行数据库查询语句,并返回一个结果集
if(rs.next()){
//如果结果集里面有值,说明查询到有这一行数据,说明账号和密码正确
System.out.println(username+"欢迎登录");
}else{
System.out.println("账号或者密码错误");
}
if(rs!=null){
rs.close();
}
if(preparedStatement!=null){
preparedStatement.close();
}
if(conn!=null){
conn.close();
}
//关闭流,没抛异常自行抛。
}
这样用JDBC就完成了最基础的注册和登录功能,但是有没有发现,每次进行类似操作我们都需要输入这么大一行代码,如果我都要进行查询功能,但我就要写两个类似的代码,冗余度太高,所以我们能不能把这些功能封装成一个工具类,需要的时候我直接调用这个函数就行,于是我们下面就来实现。
3.JDBC工具类
public class BaseDao{
private Connection con = null;
private prepareStatement ps = null;
private ResultSet rs = null;
//因为我们这个工具类包含CRUD 所以要首先创建这三个 空对象
public void openCon(){
//第一个封装方法,用于建立与数据库的连接
Class.forName("com.mysql.jdbc.Driver");
//加载驱动
con=DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/testjdbc?useUnicode=true&characterEncoding=utf-8&useSSL=true","root", "123456");
//连接数据库并获得Connection对象
}
public void closeCon(){
//第二个封装方法,用于关闭流
if(rs!=null){
rs.close();
}
if(ps!=null){
ps.close();
}
if(conn!=null){
conn.close();
}
}
public int myExecuteUpdate(String sql , Object[] parms){
/*第三个封装方法,用于执行CUD,这里除了传递sql语句,我们还把?符对象的所有值放在一个数组内,
这样减少了耦合度,例如执行一个插入语句:
insert into j_user values(null,?,?,?);
Object[] parms = {'张三','123456',23};
*/
openCon();//调用连接数据库方法
ps=con.prepareStatement(sql);//执行预编译并获取prepareStatement对象
if(parms!=null){
//把数组里面的值遍历出来进行赋值
for(int i=0;i<parms.length;i++){
ps.setObject(i+1,parms[i]);
//因为是从第一个开始赋值,不是从0开始,所以是i+1
}
}
return ps.executeUpdate();
//执行CUD语句并返回一个值,是1成功,是0失败
closeCon();
//关闭数据库
}
public ResuleSet myExecuteQuery(String sql,Object[] parms){
openCon();
ps=con.prepareStatement(sql);//预编译
if(parms!=null){
for(int i = 0;i<params.length;i++){
ps.setObject(i+1,parms[i]);
}
}
return ps.executeQuery();
}
}
这样一个JDBC工具类就完成了,基本的连接,关闭,CRUD操作全部封装在工具类里面了。
但是还有个问题,我们这个既然是工具类,那么就任何环境下都适用,但是我们这个数据库是本地的,耦合度太高,所以我们还需要把数据库配置信息,单独放在一个文件内。
Java中这种配置文件格式一般有两种:
- XML
- properties 格式 key=value
这里我们采用第二种文件,创建一个jdbc.properties文件,放在跟工具类同目录下。
# jdbc.properties存放的内容
url=jdbc:mysql://127.0.0.1:3306/testjdbc?useUnicode=true&characterEncoding=UTF-8&useSSL=true
username=root
# 数据库用户名
password=123456
# 数据库密码
driverName=com.sql.jdbc.Driver
# 驱动
这样生成了配置文件之后,我们就需要修改一下我们的工具类了
import java.util.Properties;
//java给的一个对应Properties文件的类
Public class BaseDao{
private Connection con=null;
private preparedStatement ps = null;
private ResultSet rs =null;
private static String driverClass;
private static String url;
private static String username;
private static String password;
//对应jdbc.properties四个属性,弄成static静态属性,直接加载
static{
//静态属性,使用工具类时,直接加载并且只加载一次,效率高
Properties p = new Properties();//创建一个Properties的对象
p.load(BaseDao.class.getClassLoader().getResourceAsStream("jdbc.properties"));
//BaseDao的类加载器去获得jdbc.properties文件流并加载给p对象
url=p.getProperty("url");
username=p.getProperty("username");
password =p.getProperty("password");
driverClass = p.getProperty("driverName");
//通过jbdc.properties的属性来获取值
Class.forName(driverClass);
//加载驱动
}
public void openCon(){
con=DriverManager.getConnection(url,username, password);
}
...
//剩下的跟前面一样就不写了
}
这样一个工具类就彻底完成了,然后本篇文章就到这里结束了。