目录
一、JDBC概述
1.1JDBC介绍
JDBC指的是Java数据库连接,全称为Java Database Connectivity,
是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法,
数据库驱动就是对JDBC的实现,不同的数据库有不同的数据库驱动,也对应着不同的实现,
JDBC与数据库驱动为接口和实现的关系,
JDBC规范(四个核心对象):
- DriverManager:用于注册驱动
- Connection:表示与数据库创建的连接
- Statement:操作数据库sql语句的对象
- ResultSet:结果集或一张虚拟表
其关系如下图所示,
1.2JDBC简单的使用
JDBC规范一般在JDK的java.sql.*和javax.sql.*中,里面定义了访问数据库功能的接口,
二者的区别是前者为java的核心类库,后者是扩展类库,
而真正实现这些接口的是不同数据库厂商提供的驱动jar文件,
我们以IDEA为例,将驱动jar文件导入到java项目中,并通过JDBC访问数据库中的数据,
首先我们创建一个新的项目,然后新建一个lib文件夹,将jar文件拖入到文件夹中,
然后依次点击菜单File->Project Structure,然后选中左边的Modules,点击右边的小加号,选择第一个JARS,添加我们lib路径下的jar文件,
然后jar文件就可以使用了,接下来我们测试一个简单的代码,看看访问数据库的效果,
首先我们在数据库中添加一些数据,这里我们就是用之前创建的mydb1数据库user表的数据,
然后使用代码访问这个表中的数据,一共分为六步走:
- 注册驱动
- 创建连接
- 得到执行sql语句的statement对象
- 执行sql语句,并返回结果集resultset
- 处理结果
- 关闭资源
以下是代码实现,
import java.sql.*;
public class jdbcTest {
public static void main(String[] args) throws SQLException {
//注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());
//获取连接connection
//第一个参数为连接数据库的地址,第二个为用户名,第三个为密码
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb1","root","3837");
//得到执行sql语句的对象statement
Statement statement = connection.createStatement();
//执行sql语句,并返回结果resultset
ResultSet resultSet = statement.executeQuery("select * from user");
//处理结果,next()方法得到结果集下一行,有数据则返回true,否则返回false
while(resultSet.next()){
System.out.print(resultSet.getObject(1)+"\t");//数据库的下标从1开始,输出第一列数据
System.out.print(resultSet.getObject(2)+"\t");//输出第二列数据
System.out.print(resultSet.getObject(3)+"\t");//输出第三列数据
System.out.println();
}
//关闭资源
resultSet.close();
statement.close();
connection.close();
}
}
控制台输出如下:
二、JUnit
JUnit是一个Java语言的单元测试框架,可以测试不同的单元,
测试方法要求不能有返回值,也不能有参数,
我们首先需要导入JUnit库,我是直接在代码中输入@测试名字,这里我用的是@Test,然后alt+enter进行自动补全JUnit4库,
package test;
import org.junit.Assert;
import org.junit.Test;
public class TestCalc {
@Test
public void test1(){
Calc c=new Calc();
//利用断言测试结果是否正确
Assert.assertEquals(8,c.add(5,3));
}
@Test
public void test2(){
Calc c=new Calc();
//利用断言测试结果是否正确
Assert.assertEquals(3,c.div(10,3));
}
}
运行结果:
三、JDBC常用的类和接口
3.1 java.sql.Drivermanager类
java.sql.Drivermanager类是用于创建数据库的连接,常用的接口包括注册驱动和建立连接,
- 注册驱动
- DriverManager.registerDriver(new com.mysql.jdbc.Driver());//注册给定驱动程序DriverManager,这种方法不建议使用
- 这种方法驱动会被注册2次
- 强烈依赖数据库的驱动jar
- 解决办法:使用反射机制加载驱动类,Class.forName("com.mysql.jdbc.Driver");
- DriverManager.registerDriver(new com.mysql.jdbc.Driver());//注册给定驱动程序DriverManager,这种方法不建议使用
- 与数据库建立连接
- static Connection getConnection(String url, String user, String password)//试图建立到给定数据库URL的连接
- 例:getConnection("jdbc:mysql://localhost:3306/mydb1", "root", "3837");
- URL:SUN公司与数据库厂商之间的一种协议
- 协议:子协议://localhost:端口号/数据库名称
- mysql写法:jdbc:mysql://localhost:3306/mydb1(这里的localhost:3306可以省略,不写默认为本机连接)
- oracle写法:jdbc:oracle:thin:@localhost:1521:mydb1
- user:数据库用户名
- password:数据库密码
- URL:SUN公司与数据库厂商之间的一种协议
- static Connection getConnection(String url, Properties info)
- URL:连接数据库的地址
- info:Properties类型的配置文件,使用键值对的方法存储用户名和密码信息
- static Connection getConnection(String url)
- URL:连接数据库的地址,地址中包括了用户名和密码信息
- 例如:jdbc:mysql://localhost:3306/mydb1?user=root&password=3837
我们用单元测试的方法测试一下这三种建立连接访问数据库的数据,
import org.junit.Test;
import java.sql.*;
import java.util.Properties;
public class jdbcTest2 {
@Test
public void test1() throws Exception {
System.out.println("----------Test1----------");
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接connection
//第一个参数为连接数据库的地址,第二个为用户名,第三个为密码
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb1","root","3837");
//得到执行sql语句的对象statement
Statement statement = connection.createStatement();
//执行sql语句,并返回结果resultset
ResultSet resultSet = statement.executeQuery("select * from user");
//处理结果,next()方法得到结果集下一行,有数据则返回true,否则返回false
while(resultSet.next()){
System.out.print(resultSet.getObject(1)+"\t");//数据库的下标从1开始,输出第一列数据
System.out.print(resultSet.getObject(2)+"\t");//输出第二列数据
System.out.print(resultSet.getObject(3)+"\t");//输出第三列数据
System.out.println();
}
//关闭资源
resultSet.close();
statement.close();
connection.close();
}
@Test
public void test2() throws Exception{
System.out.println("----------Test2----------");
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接connection
//第一个参数为连接数据库的地址,第二个为配置信息,包括账户名和密码
Properties info =new Properties();//创建配置文件
info.setProperty("user","root");//添加用户名键值对
info.setProperty("password","3837");//添加密码键值对
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb1",info);
//得到执行sql语句的对象statement
Statement statement = connection.createStatement();
//执行sql语句,并返回结果resultset
ResultSet resultSet = statement.executeQuery("select * from user");
//处理结果,next()方法得到结果集下一行,有数据则返回true,否则返回false
while(resultSet.next()){
System.out.print(resultSet.getObject(1)+"\t");//数据库的下标从1开始,输出第一列数据
System.out.print(resultSet.getObject(2)+"\t");//输出第二列数据
System.out.print(resultSet.getObject(3)+"\t");//输出第三列数据
System.out.println();
}
//关闭资源
resultSet.close();
statement.close();
connection.close();
}
@Test
public void test3() throws Exception{
System.out.println("----------Test3----------");
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接connection
//参数为连接数据库的地址,地址中包括了用户名和密码信息
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb1?user=root&password=3837");
//得到执行sql语句的对象statement
Statement statement = connection.createStatement();
//执行sql语句,并返回结果resultset
ResultSet resultSet = statement.executeQuery("select * from user");
//处理结果,next()方法得到结果集下一行,有数据则返回true,否则返回false
while(resultSet.next()){
System.out.print(resultSet.getObject(1)+"\t");//数据库的下标从1开始,输出第一列数据
System.out.print(resultSet.getObject(2)+"\t");//输出第二列数据
System.out.print(resultSet.getObject(3)+"\t");//输出第三列数据
System.out.println();
}
//关闭资源
resultSet.close();
statement.close();
connection.close();
}
}
输出结果:
3.2 java.sql.Connection接口
其作用是与数据库取得连接,接口的实现在数据库驱动中,
所有与数据库交互都是基于连接对象的,我们可以通过createStatement()方法创建sql语句对象,
Statement statement=connection.createStatement();//创建操作sql语句的对象
3.3 java.sql.Statement接口
其作用为执行静态SQL语句,并返回它所生成的结果集对象,
同样该接口的实现也在数据库驱动中,常用的对数据库操作的方法如下:
ResultSet executeQuery(String sql);//根据查询语句返回结果集,注意只能执行select语句
int executeUpdate(String sql);//根据执行的DM(insert、update、delete)语句,返回受影响的行数
boolean execute(String sql);//此方法可以执行任意sql语句。返回boolean值,表示是否返回ResultSet结果集。仅当执行select语句,且有返回结果时返回true, 其它语句都返回false
我们同样用单元测试一下数据库的增删改查操作,
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class jdbcTestCRUD {
//Create、Read、Update、Delete
@Test
public void testInsert() throws Exception{
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接connection
//第一个参数为连接数据库的地址,第二个为用户名,第三个为密码
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb1","root","3837");
//得到执行sql语句的对象statement
Statement statement = connection.createStatement();
//执行sql语句,并返回结果resultset
int line = statement.executeUpdate("insert into user values('tz','male','wuhan')");
if(line>0){
System.out.println("数据插入成功");
}
//关闭资源
statement.close();
connection.close();
}
@Test
public void testUpdate() throws Exception{
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接connection
//第一个参数为连接数据库的地址,第二个为用户名,第三个为密码
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb1","root","3837");
//得到执行sql语句的对象statement
Statement statement = connection.createStatement();
//执行sql语句,并返回结果resultset
int line = statement.executeUpdate("update user set address='beijing' where name='tz'");
if(line>0){
System.out.println("数据修改成功");
}
//关闭资源
statement.close();
connection.close();
}
@Test
public void testDelete() throws Exception{
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接connection
//第一个参数为连接数据库的地址,第二个为用户名,第三个为密码
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb1","root","3837");
//得到执行sql语句的对象statement
Statement statement = connection.createStatement();
//执行sql语句,并返回结果resultset
int line = statement.executeUpdate("delete from user where name='username4'");
if(line>0){
System.out.println("数据删除成功");
}
//关闭资源
statement.close();
connection.close();
}
@Test
public void testSelect() throws Exception{
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接connection
//第一个参数为连接数据库的地址,第二个为用户名,第三个为密码
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb1","root","3837");
//得到执行sql语句的对象statement
Statement statement = connection.createStatement();
//执行sql语句,并返回结果resultset
ResultSet resultSet = statement.executeQuery("select * from user");
//处理结果,next()方法得到结果集下一行,有数据则返回true,否则返回false
while(resultSet.next()){
System.out.print(resultSet.getObject(1)+"\t");//数据库的下标从1开始,输出第一列数据
System.out.print(resultSet.getObject(2)+"\t");//输出第二列数据
System.out.print(resultSet.getObject(3)+"\t");//输出第三列数据
System.out.println();
}
//关闭资源
resultSet.close();
statement.close();
connection.close();
}
}
运行结果:
3.4 java.sql.ResultSet接口
ResultSet接口用于存放sql语句执行后返回的结果集,主要有封装结果集和提供可移动游标的方法
3.4.1封装结果集
结果集提供一个游标,默认游标是指向结果集第一行之前,
调用一次next(),游标就向下移动一行,并且通过提供的getObject()方法来获取结果集中的数据,
Object getObject(int columnIndex);//根据列号下标取值,索引从1开始
Object getObject(String ColumnName);//根据列名取值
有时候我们还需要将结果集中的数据显示在前端网页中,这时就需要将数据封装到JavaBean中,
JavaBean是一种特殊的Java类,主要有以下几个特点:
- 提供一个默认的无参构造函数
- 需要被序列化并且实现了Serializable接口
- 可能有一系列可读写属性
- 可能有一系列的getter或setter方法
获得结果集的数据时,需要注意数据库的类型和java数据类型的对应关系,
java数据类型 | 数据库数据类型 |
byte | tinyint |
short | smallint |
int | int |
long | bigint |
float | float |
double | double |
String | char、varchar |
Date | date |
获取各个数据类型的方法如下:
boolean next();//将光标从当前位置向下移动一行
int getInt(int colIndex);//以int形式获取ResultSet结果集当前行指定列号值
int getInt(String colLabel);//以int形式获取ResultSet结果集当前行指定列名值
float getFloat(int colIndex);//以float形式获取ResultSet结果集当前行指定列号值
float getFloat(String colLabel);//以float形式获取ResultSet结果集当前行指定列名值
String getString(int colIndex);//以String形式获取ResultSet结果集当前行指定列号值
String getString(String colLabel);//以String形式获取ResultSet结果集当前行指定列名值
Date getDate(int columnIndex);//以Date形式获取ResultSet结果集当前行指定列号值
Date getDate(String columnName);//以Date形式获取ResultSet结果集当前行指定列名值
void close();//关闭ResultSet对象
我们用查询语句测试一下,首先我们创建一个User的对象存储数据库中每条记录的信息,
package entity;
public class User {
//保持java类的变量名和数据库的列名相同
private String name;
private String gender;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", address='" + address + '\'' +
'}';
}
}
然后连接数据库进行查询,将得到的记录存储在User对象中,最后输出,
import entity.User;
import org.junit.Test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class jdbcTestCRUD {
//Create、Read、Update、Delete
@Test
public void testSelect() throws Exception{
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接connection
//第一个参数为连接数据库的地址,第二个为用户名,第三个为密码
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb1","root","3837");
//得到执行sql语句的对象statement
Statement statement = connection.createStatement();
//执行sql语句,并返回结果resultset
ResultSet resultSet = statement.executeQuery("select * from user");
//处理结果,next()方法得到结果集下一行,有数据则返回true,否则返回false
List<User> list=new ArrayList<User>();
while(resultSet.next()){
User u=new User();
u.setName(resultSet.getString("name"));
u.setGender(resultSet.getString("gender"));
u.setAddress(resultSet.getString("address"));
list.add(u);//将一条用户记录添加到集合中
}
//关闭资源
resultSet.close();
statement.close();
connection.close();
//打印集合中用户信息
for (User u:list) {
System.out.println(u.toString());
}
}
}
输出结果如下:
3.4.2可移动游标的方法
结果集有一系列移动游标的方法,
boolean next();//将光标从当前位置向下移一行
boolean previous();//将光标从当前位置向上移一行
boolean absolute(int row);//参数是需要移动到指定行的索引(从1开始)
void afterLast();//将光标移动到末尾,正好位于最后一行之后
void beforeFirst();//将光标移动到开头,正好位于第一行之前
3.5资源的释放
在连接数据库的时候,我们申请了很多的资源,
这些资源需要得到正确的释放,如果仅仅将释放代码放在最后,那么程序出现异常时提前终止,这个时候资源就没有得到正确的释放,
如果其他用户这个时候需要访问数据库,资源没有释放被占用则会影响其他用户的操作,我们应该使用finally语句对资源进行释放,
正确的资源释放代码如下:
import org.junit.Test;
import java.sql.*;
public class jdbcTestRelease {
@Test
public void TestRelease(){
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接connection
//第一个参数为连接数据库的地址,第二个为用户名,第三个为密码
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb1","root","3837");
//得到执行sql语句的对象statement
statement = connection.createStatement();
//执行sql语句,并返回结果resultset
resultSet = statement.executeQuery("select * from user");
//处理结果,next()方法得到结果集下一行,有数据则返回true,否则返回false
while(resultSet.next()){
System.out.print(resultSet.getObject(1)+"\t");//数据库的下标从1开始,输出第一列数据
System.out.print(resultSet.getObject(2)+"\t");//输出第二列数据
System.out.print(resultSet.getObject(3)+"\t");//输出第三列数据
System.out.println();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet=null;
}
if(statement!=null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement=null;
}
if(connection!=null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection=null;
}
}
}
}
四、JDBC实现增删改查操作
前面我们基本都实现了一遍,现在我们将代码进行优化,重复的代码优化提取为方法,
import entity.User;
import org.junit.Test;
import java.sql.*;
import java.util.*;
public class jdbcTestCRUDpro {
@Test
public void testSelect(){
Connection connection=null;
Statement statement=null;
ResultSet resultSet=null;
try {
connection=DBUtils.getConnection();//获取连接
statement=connection.createStatement();
resultSet=statement.executeQuery("select * from user");
List<User> list=new ArrayList<>();
while(resultSet.next()){
User u=new User();
u.setName(resultSet.getString("name"));
u.setGender(resultSet.getString("gender"));
u.setAddress(resultSet.getString("address"));
list.add(u);
}
for (User u:list) {
System.out.println(u);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtils.closeAll(resultSet,statement,connection);//释放资源
}
}
@Test
public void testInsert(){
Connection connection=null;
Statement statement=null;
try {
connection=DBUtils.getConnection();//获取连接
statement=connection.createStatement();
int line=statement.executeUpdate("insert into user values('tz','male','wuhan')");
if(line>0){
System.out.println("数据插入成功");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtils.closeAll(null,statement,connection);//释放资源
}
}
@Test
public void testDelete(){
Connection connection=null;
Statement statement=null;
try {
connection=DBUtils.getConnection();//获取连接
statement=connection.createStatement();
int line=statement.executeUpdate("delete from user where name='tz'");
if(line>0){
System.out.println("数据删除成功");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtils.closeAll(null,statement,connection);//释放资源
}
}
@Test
public void testUpdate(){
Connection connection=null;
Statement statement=null;
try {
connection=DBUtils.getConnection();//获取连接
statement=connection.createStatement();
int line=statement.executeUpdate("update user set address='beijing' where name='username1'");
if(line>0){
System.out.println("数据删除成功");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtils.closeAll(null,statement,connection);//释放资源
}
}
}
class DBUtils{
private static String driverClass;
private static String url;
private static String username;
private static String password;
static {
//静态代码块,加载类的时候就加载配置文件
ResourceBundle rb=ResourceBundle.getBundle("DBinfo");//加载属性资源文件properties
driverClass=rb.getString("driverClass");
url=rb.getString("url");
username=rb.getString("username");
password=rb.getString("password");
//加载驱动
try {
Class.forName(driverClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//得到数据库的连接
public static Connection getConnection() throws Exception{
return DriverManager.getConnection(url,username,password);
}
//关闭资源
public static void closeAll(ResultSet resultSet, Statement statement, Connection connection){
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet=null;
}
if(statement!=null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement=null;
}
if(connection!=null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection=null;
}
}
}
五、用户登录DEMO
我们简单分析一下用户登录需要的几个类,
- User类:用于定义用户对象,包括用户名和密码
- Client类:用于实现客户端,并调用判断用户是否合法的方法,告诉用户登录成功与否
- DoLogin类:用于判断用户是否合法,在数据库中查找用户输入的用户名和密码是否正确
- DBUtils类:用于实现一些使用频率较高的数据库公用方法(比如连接数据库,资源的释放等)
下面是代码实现,
User用户类:
package entity;
public class User {
//保持java类的变量名和数据库的列名相同
private String name;
private String gender;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", address='" + address + '\'' +
'}';
}
}
DBUtils数据库常用方法类:
package LoginDemo;
import java.sql.*;
import java.util.ResourceBundle;
public class DBUtils {
private static String driverClass;
private static String url;
private static String username ;
private static String password;
static {
//静态代码块,加载类的时候就加载配置文件
ResourceBundle rb=ResourceBundle.getBundle("DBinfo");//加载属性资源文件properties
driverClass=rb.getString("driverClass");
url=rb.getString("url");
username=rb.getString("username");
password=rb.getString("password");
//加载驱动
try {
Class.forName(driverClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//得到数据库的连接
public static Connection getConnection() throws Exception{
return DriverManager.getConnection(url,username,password);
}
//关闭资源
public static void closeAll(ResultSet resultSet, Statement statement, Connection connection){
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet=null;
}
if(statement!=null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement=null;
}
if(connection!=null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection=null;
}
}
}
Client客户端类:
package LoginDemo;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
System.out.println("请输入用户名:");
String username=scanner.nextLine();
System.out.println("请输入密码:");
String password=scanner.nextLine();
User u=DoLogin.findUser(username,password);//在数据库中查询用户数据
if(u!=null){
System.out.println("欢迎"+u.getUsername());
}else{
System.out.println("用户名或密码错误");
}
}
}
DoLogin登录判断类:
package LoginDemo;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
public class DoLogin {
public static User findUser(String name,String pwd){
Connection connection=null;
Statement statement=null;
ResultSet resultSet=null;
User u=null;
try {
connection=DBUtils.getConnection();
statement=connection.createStatement();
resultSet=statement.executeQuery("select * from user where username='"+name+"' and password='"+pwd+"'");
if(resultSet.next()){
u=new User();
u.setUsername(resultSet.getString("username"));
u.setPassword(resultSet.getString("password"));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
DBUtils.closeAll(resultSet,statement,connection);
}
return u;
}
}
然后我们运行Client.java,结果如图:
六、SQL注入问题
6.1 SQL注入问题简介
在完成上面的登录Demo后,我们用这样一组数据库中没有的username和password进行登录,
结果发现同样可以登录,这就是SQL注入问题,这是一个安全问题,
简单来说,SQL注入问题就是利用SQL语句在拼接过程中,加入自己的代码使得SQL语句的执行结果和原本开发者的意图不一样,
下面我们举个简单的例子,原来要执行的SQL语句如下:
String sql="select * from user where username='"+name+"' and password='"+pwd+"'";
如果使用上面的用户名:asd和密码:asd' or '1'='1,那么得到的SQL语句为:
select * from user where username='asd' and password='asd' or '1'='1'
这句话加上OR语句后条件一直成立,所以可以访问到数据库的所有用户名和密码,自然就成功登录了。
6.2 PreparedStatement对象
解决这个问题的办法就是使用PreparedStatement对象,该对象是Statement对象的子类,该对象有以下几个特点:
- 性能更高
- 会将SQL语句预先编译
- SQL语句中的参数会发生变化,过滤掉用户输入的关键字
我们首先修改一下代码,用PreparedStatement对象替换Statement对象,
package LoginDemo;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class DoLoginpro {
public static User findUser(String name,String pwd){
Connection connection=null;
PreparedStatement statement=null;
ResultSet resultSet=null;
User u=null;
try {
connection=DBUtils.getConnection();
String sql="select * from user where username=? and password=?";//用问号表示变量
statement=connection.prepareStatement(sql);//对sql语句进行预编译
statement.setString(1,name);//给第一个问号赋值
statement.setString(2,pwd);//给第二个问号赋值
System.out.println(statement);
resultSet=statement.executeQuery();//执行sql语句返回结果集
if(resultSet.next()){
u=new User();
u.setUsername(resultSet.getString("username"));
u.setPassword(resultSet.getString("password"));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
DBUtils.closeAll(resultSet,statement,connection);
}
return u;
}
}
然后用同样的数据测试一下可不可以通过数据库的检测,同时我们把sql语句输出:
我们可以看到PreparedStatement对象解决SQL注入问题的方法是将用户输入的单引号添加了反斜杠做转义字符,
比如用户输入的:asd' or '1'='1经过PreparedStatement对象处理变成了字符串:asd\' or \'1\'=\'1。这样就可以解决SQL安全注入问题了。