一、JDBC概述
JDBC:全称Java DataBase Connectivity,即java连接数据库。是一种用于执行SQL语句的java API。
作用:JDBC为数据库应用开发者和数据库前台工具开发者提供了一种标准的应用程序设计接口,使开发者能够用纯JAVA语言编写完整的数据库应用程序。
JDBC连接数据库的步骤:
①加载驱动
②获得连接(Connection)
③获取SQL语句发送器(Statement)
④发送并执行SQL语句,得到结果集(ResultSet)
⑤关闭资源
下面测试JDBC连接数据库,以MySQL为例:
使用mysql创建goods表:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class TestDemo_One {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
String url="jdbc:mysql://localhost:3306/test";
String user="root";
String password="123456";
//1.加载驱动
// DriverManager.registerDriver((new Driver()));第一种加载驱动方式,这种方式会导致驱动注册两次
Class.forName("com.mysql.jdbc.Driver");//第二种加载驱动的方式,也是开发中常用的方式
//2.获得连接,使用getConnection方法,传入三个参数,第一个参数url的写法:jdbc:mysql://主机名:端口号/数据库名称
Connection conn=DriverManager.getConnection(url, user, password);
//3.获得SQL语句发送器,执行SQL语句
String sql="select * from goods";
Statement state=conn.createStatement();
//4.发送并执行SQL语句,得到结果集
ResultSet set=state.executeQuery(sql);
while(set.next())
{
int id=set.getInt("id");
String name=set.getString("name");
double price=set.getDouble("price");
String desp=set.getString("desp");
System.out.println(id+"\t"+name+"\t"+price+"\t"+desp);
}
//5.关闭资源
set.close();
state.close();
conn.close();
}
}
结果如图:
进行增删改查操作:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
//执行增加操作
public class TestDemo_One {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
String url="jdbc:mysql://localhost:3306/test";
String user="root";
String password="123456";
//1.加载驱动
// DriverManager.registerDriver((new Driver()));第一种加载驱动方式,这种方式会导致驱动注册两次
Class.forName("com.mysql.jdbc.Driver");//第二种加载驱动的方式,也是开发中常用的方式
//2.获得连接,使用getConnection方法,传入三个参数,第一个参数url的写法:jdbc:mysql://主机名:端口号/数据库名称
Connection conn=DriverManager.getConnection(url, user, password);
//3.获得SQL语句发送器,执行SQL语句
String sql="insert into goods value (null,'手表',500,'蓝牙太阳能充电')";
Statement state=conn.createStatement();
//4.发送并执行SQL语句,得到结果集
int i=state.executeUpdate(sql);//
if(i>0) {
System.out.println("增加成功");
}
//5.关闭资源
state.close();
conn.close();
}
}
数据库中结果:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
//执行改操作
public class TestDemo_One {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
String url="jdbc:mysql://localhost:3306/test";
String user="root";
String password="123456";
//1.加载驱动
// DriverManager.registerDriver((new Driver()));第一种加载驱动方式,这种方式会导致驱动注册两次
Class.forName("com.mysql.jdbc.Driver");//第二种加载驱动的方式,也是开发中常用的方式
//2.获得连接,使用getConnection方法,传入三个参数,第一个参数url的写法:jdbc:mysql://主机名:端口号/数据库名称
Connection conn=DriverManager.getConnection(url, user, password);
//3.获得SQL语句发送器,执行SQL语句
String sql="update goods set price=6000 where id=4";
Statement state=conn.createStatement();
//4.发送并执行SQL语句,得到结果集
int i=state.executeUpdate(sql);//
if(i>0) {
System.out.println("修改成功");
}
//5.关闭资源
state.close();
conn.close();
}
}
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
//执行删除操作
public class TestDemo_One {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
String url="jdbc:mysql://localhost:3306/test";
String user="root";
String password="123456";
//1.加载驱动
// DriverManager.registerDriver((new Driver()));第一种加载驱动方式,这种方式会导致驱动注册两次
Class.forName("com.mysql.jdbc.Driver");//第二种加载驱动的方式,也是开发中常用的方式
//2.获得连接,使用getConnection方法,传入三个参数,第一个参数url的写法:jdbc:mysql://主机名:端口号/数据库名称
Connection conn=DriverManager.getConnection(url, user, password);
//3.获得SQL语句发送器,执行SQL语句
String sql="delete from goods where id=5";
Statement state=conn.createStatement();
//4.发送并执行SQL语句,得到结果集
int i=state.executeUpdate(sql);//
if(i>0) {
System.out.println("删除成功");
}
//5.关闭资源
state.close();
conn.close();
}
}
其中: executeQuery(String sql)是执行sql语句中的select语句 ,而executeUpdate(String sql);执行sql中的insert,update,delete语句,返回值int类型表示影响行数
问题:我们会发现很多代码都是不断重复,出现冗余现象,因此可以将大部分代码封装为一个工具类JDBCUtils类
二、抽取工具类
工具类:
package jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBCUtil {
private static final String driverclass;
private static final String url;
private static final String username;
private static final String password;
static {
driverclass="com.mysql.jdbc.Driver";
url="jdbc:mysql://localhost:3306/test";
username="root";
password="123456";
}
//1.注册驱动
public static void loadDriver() {
try {
Class.forName(driverclass);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//2.获得连接
public static Connection getConnection() throws SQLException {
loadDriver();
Connection conn=DriverManager.getConnection(url, username, password);
return conn;
}
//3.释放资源
public static void relese(Statement state,Connection conn) {
if(state!=null) {
try {
state.close();
} catch (Exception e) {
// TODO: handle exception
}
state=null;
}
if(conn!=null) {
try {
conn.close();
} catch (Exception e) {
// TODO: handle exception
}
conn=null;
}
}
public static void relese(ResultSet set,Statement state,Connection conn) {
if(state!=null) {
try {
state.close();
} catch (Exception e) {
// TODO: handle exception
}
state=null;
}
if(conn!=null) {
try {
conn.close();
} catch (Exception e) {
// TODO: handle exception
}
conn=null;
}
if(set!=null) {
try {
set.close();
} catch (Exception e) {
// TODO: handle exception
}
set=null;
}
}
}
package jdbc;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
/**
* 执行增操作
*
*
*/
public class TestDemo_One {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Connection conn=null;
Statement state=null;
ResultSet set=null;
conn=JDBCUtil.getConnection();
//3.获得SQL语句发送器,执行SQL语句
String sql="select * from goods";
state=conn.createStatement();
//4.发送并执行SQL语句,得到结果集
set=state.executeQuery(sql);
while(set.next())
{
int id=set.getInt("id");
String name=set.getString("name");
double price=set.getDouble("price");
String desp=set.getString("desp");
System.out.println(id+"\t"+name+"\t"+price+"\t"+desp);
}
//5.关闭资源
JDBCUtil.relese(set, state, conn);
}
}
然而在实际开发中,如果数据库用户名,密码等发生改变,便难以后续的维护,因此可使用Properties优化工具类
优化工具类
通过Properties对象加载并读取配置文件,动态为属性赋值
properties文件:
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=123456
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
public class JDBCUtils {
private static String driverclass;
private static String url;
private static String username;
private static String password;
static {
//加载属性文件并解析
Properties pro=new Properties();
//如何获得属性文件输入流
//通常情况下使用类的加载器方式获取
InputStream is=JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
pro.load(is);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
driverclass=pro.getProperty("driverclass");
url=pro.getProperty("url");
username=pro.getProperty("username");
password=pro.getProperty("password");
}
//注册驱动
public static void loadDriver() throws ClassNotFoundException {
Class.forName(driverclass);
}
//获得连接
public static Connection getConnection() throws Exception {
loadDriver();
Connection conn=DriverManager.getConnection(url,username,password);
return conn;
}
//资源释放
public static void relese(Statement state,Connection conn) {
if(state!=null) {
try {
state.close();
} catch (Exception e) {
// TODO: handle exception
}
state=null;
}
if(conn!=null) {
try {
conn.close();
} catch (Exception e) {
// TODO: handle exception
}
conn=null;
}
}
public static void relese(ResultSet set,Statement state,Connection conn) {
if(state!=null) {
try {
state.close();
} catch (Exception e) {
// TODO: handle exception
}
state=null;
}
if(conn!=null) {
try {
conn.close();
} catch (Exception e) {
// TODO: handle exception
}
conn=null;
}
if(set!=null) {
try {
set.close();
} catch (Exception e) {
// TODO: handle exception
}
set=null;
}
}
}
三、PreparedStatement类
1.避免SQL注入漏洞
SQL注入漏洞简单的说就是在登录时,已知用户名,不知道密码的情况下也能登录到系统中。
解决方法:使用PreparedStatement;是Statement的子接口,可以避免SQL注入的问题;
package jdbc;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;
/**
* SQL注入漏洞演示
*/
public class TestDemo {
public static void main(String[] args) {
String username;
String password;
Scanner scan=new Scanner(System.in);
System.out.println("请输入用户名:");
username=scan.nextLine();
System.out.println("请输入密码:");
password=scan.nextLine();
Connection conn=null;
Statement state=null;
ResultSet set=null;
try {
conn=JDBCUtils.getConnection();
state=conn.createStatement();
String sql="select * from users where username='"+username+"'and password='"+password+"'";
set=state.executeQuery(sql);
//判断结果集中是否有数据
if(set.next()) {
System.out.println("登录成功");
}
else {
System.out.println("登录失败");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.relese(set, state, conn);
}
}
}
当用户名输入时,输入aaa ‘or 1=1’或者aaa ‘-- ’,随便输入密码也会登录成功,原因是or是关键字,前面用户名为true,那么返回结果为true;而-- 在SQL语句中是注释,那么后面的密码被注释掉,只执行了用户名部分,因此会产生漏洞
解决办法:使用PreparedStatement
新的工具类:
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtils {
private static final String driverclass;
private static final String url;
private static final String username;
private static final String password;
static {
//加载属性文件并解析
Properties pro=new Properties();
//如何获得属性文件输入流
//通常情况下使用类的加载器方式获取
InputStream is=JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
pro.load(is);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
driverclass=pro.getProperty("driverClass");
url=pro.getProperty("url");
username=pro.getProperty("username");
password=pro.getProperty("password");
}
//注册驱动
public static void loadDriver() throws ClassNotFoundException {
Class.forName(driverclass);
}
//获得连接
public static Connection getConnection() throws Exception {
loadDriver();
Connection conn=DriverManager.getConnection(url,username,password);
return conn;
}
//绑定参数
public static void bind(PreparedStatement state,Object...params) throws SQLException {
//一定是小于等于参数数组的长度!!!不然报错!之前一直出错了!!
for(int i=1;i<=params.length;i++) {
state.setObject(i, params[i-1]);
}
}
//资源释放
public static void relese(PreparedStatement state,Connection conn) {
if(state!=null) {
try {
state.close();
} catch (Exception e) {
// TODO: handle exception
}
state=null;
}
if(conn!=null) {
try {
conn.close();
} catch (Exception e) {
// TODO: handle exception
}
conn=null;
}
}
public static void relese(ResultSet set,PreparedStatement state,Connection conn) {
if(state!=null) {
try {
state.close();
} catch (Exception e) {
// TODO: handle exception
}
state=null;
}
if(conn!=null) {
try {
conn.close();
} catch (Exception e) {
// TODO: handle exception
}
conn=null;
}
if(set!=null) {
try {
set.close();
} catch (Exception e) {
// TODO: handle exception
}
set=null;
}
}
}
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Scanner;
/**
* SQL注入漏洞解决
*/
public class TestDemo {
public static void main(String[] args) {
String username;
String password;
Scanner scan=new Scanner(System.in);
System.out.println("请输入用户名:");
username=scan.nextLine();
System.out.println("请输入密码:");
password=scan.nextLine();
Connection conn=null;
PreparedStatement state=null;
ResultSet set=null;
try {
conn=JDBCUtils.getConnection();
String sql="select * from users where username=? and password=?";
state=conn.prepareStatement(sql);
//设置具体参数
state.setString(1, username);
state.setString(2, password);
set=state.executeQuery();//前面已经进行预处理,那么此时不用再传入SQL语句,提高了执行效率
//判断结果集中是否有数据
if(set.next()) {
System.out.println("登录成功");
}
else {
System.out.println("登录失败");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtils.relese(set, state, conn);
}
}
}
2.提高数据库执行效率
Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出,PreparedStatement可对SQL进行预编译,同时还可以批处理数据,提高数据库的执行效率;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
/**
*使用PreparedStatement批处理数据
*将2~1000之间的数字插入数据库,并记录数字的类型(质数还是合数)
*
*/
public class TestNum {
public static void main(String[] args) throws Exception {
int num=1000;
long startTime=System.currentTimeMillis();
insertNum1(num);// 使用Statement插入数字
long endTime=System.currentTimeMillis();
System.out.println("使用statement耗时:"+(endTime-startTime));
// insertNum2(num);// 使用PreparedStatement插入数字
// System.out.println("使用PreparedStatement耗时:"+(endTime-startTime));
}
/**
* 使用PreparedStatement插入数字,批处理
* @throws Exception
*/
private static void insertNum2(int num) throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql="insert into numbers values(null,?,?)";
PreparedStatement state=conn.prepareStatement(sql);
for(int i=2;i<=num;i++) {
//绑定参数
JDBCUtils.bind(state, i,getType(i));
//加入批处理
state.addBatch();
}
//统一执行批处理
state.executeBatch();
JDBCUtils.relese(state, conn);
}
/**
* 使用Statement插入数字,并且只开关一次连接
* @throws Exception
*
*/
private static void insertNum1(int num) throws Exception {
Connection conn=JDBCUtils.getConnection();
Statement state=conn.createStatement();
for(int i=2;i<=num;i++) {
String sql="insert into numbers values(null,"+i+",'"+getType(i)+"')";
state.executeUpdate(sql);
}
JDBCUtils.relese(state, conn);
}
/**
* 判断数字类型
* @return
*/
private static String getType(int num) {
if(num<4) {
return "ZS";
}
for(int i=2;i<Math.sqrt(num);i++) {
if(num % i==0) {
return "HS";
}
}
return "ZS";
}
}
传入一千条数据差别不是很大,当数据量很大时,就会有很大差别!