JDBC编程
JDBC简介:
JDBC(Java Data Base Connection):Java数据库连接。是一种标准Java应用编程接口(Java API),将数据库和Java程序连接起来,是开发人员可以通过Java程序访问数据库。
常见的JDBC组件(JDBC开发中主要用到的类):
- DriverManager :这个类管理一系列数据库驱动程序。
匹配连接使用通信子协议从 JAVA 应用程序中请求合适的数据库驱动程序。
识别 JDBC 下某个子协议的第一驱动程序将被用于建立数据库连接。 - Driver : 这个接口处理与数据库服务器的通信。你将很少直接与驱动程序互动。
相反,你使用 DriverManager 中的对象,它管理此类型的对象。
它也抽象与驱动程序对象工作相关的详细信息。 - Connection : 此接口具有接触数据库的所有方法。该连接对象表示通信上下文,
即:所有与数据库的通信仅通过这个连接对象进行。 - Statement : 使用创建于这个接口的对象将 SQL 语句提交到数据库。
除了执行存储过程以外,一些派生的接口也接受参数。 - ResultSet : 在你使用语句对象执行 SQL 查询后,这些对象保存从数据库获得的数据。
是一个迭代器,方便遍历得到的结果集。 - SQLException : 这个类处理发生在数据库应用程序的任何错误。
JDBC编程步骤:
- 加载数据库驱动,通过反射Class.forName(String driverName);加载。
- 通过Connection connection=DriverManager.getConnection(url,user,password);来获得数据库连接。
- url:数据库地址,MySQL中为jdbc:mysql://localhost:3306/数据库名 localhost是本地主机,也就是说这个位置放的是要连接的数据库所在的主机信息;3306是数据库的端口号;后再跟上要访问的数据库名。
- user:安装数据库时设置的用户名。
- password:用户对应的密码。
- 可能需要设置隔离级别
connection.setTransactionIsolation(Connection.TRANSACTION_NONE); - 创建Statemebt对象(常用的有两种)
- Statement statement = connection.createStatement();
- PreparedStatement prepareStatement = connection.prepareStatement();
- 通过statement/prepareStatement对象来执行SQL语句,并拿到结果集ResultSet(如果是查询多条语句).
5.如果有结果集则迭代遍历结果集。 - 关闭连接资源。结果集、statement对象、connection都要关闭。
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
public class JdbcDemo {
private String jdbcDriver;//数据库驱动名
private String jdbcUrl;//访问的数据库所在主机url
private String user;//数据据的用户名
private String password;//数据库的密码
//以上信息我可以放在.properties文件中,该文件是Java中的配置文件,支持key-value的存储方式,这里定义一个流来访问该文件,在java.util包路径下
//这样做以后要改上面的信息就不用改代码,而是改文件
private Properties properties;
private String dbpro;//db.properties的路径
private Connection connection;//先定义一个connection对象,注意在java.sql包路径下
private Statement statement;//定义一个Statement接口的对象statement
private PreparedStatement ps;//定义一个PreparedStatement接口的对象ps
//构造方法初始化成员变量
public JdbcDemo(){
properties=new Properties();//实例化流
try {
properties.load(new FileInputStream("D:\\Java\\servlet_demo\\src\\main\\resources\\db.properties"));//加载db.properties文件
jdbcDriver=properties.getProperty("jdbcDriver");//从文件中读取key=jdbcDriver对应的value值
jdbcUrl=properties.getProperty("jdbcUrl");
user=properties.getProperty("user");
password=properties.getProperty("password");
} catch (IOException e) {
e.printStackTrace();
}
}
//获取数据库连接,封装在这个方法中
public void getConnection(){
try {
Class.forName(jdbcDriver);//1.加载数据库驱动
connection=DriverManager.getConnection(jdbcUrl,user,password);//2.获取数据库连接
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
//下面我把对数据库的操作都封装在每个方法中
//查询Student表中的所有数据(通过statement执行SQL获取结果集)
public void selectAll1(){
try {
statement=connection.createStatement();//3.创建statement对象
String sql="select * from Student";
ResultSet resultSet = statement.executeQuery(sql);//4.执行sql,获取结果集
while(resultSet.next()){//5.处理结果集:迭代遍历结果集
System.out.println("SID"+resultSet.getString("SID")//可以根据在表中字段名获取对应字段
+" Sname"+resultSet.getString("Sname")
+" Sage"+resultSet.getString(3)//也可以根据在表中第几列(从0开始)获取对应字段
+" Ssex"+resultSet.getString(4)
);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
//查询Student表中的所有数据(通过PreparedStatement执行SQL获取结果集)注意和Statement对比
public void selectAll2(){
try {
String sql="select * from Student";
ps=connection.prepareStatement(sql);//3.创建PreparedStatement,预编译sql并保存在ps
ResultSet resultSet = ps.executeQuery();//4.执行sql并获取结果集
while(resultSet.next()){//5.处理结果集:迭代遍历结果集
System.out.println("SID"+resultSet.getString("SID")//可以根据在表中字段名获取对应字段
+" Sname"+resultSet.getString("Sname")
+" Sage"+resultSet.getString(3)//也可以根据在表中第几列(从0开始)获取对应字段
+" Ssex"+resultSet.getString(4)
);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
//根据指定SID来查询Student表中的对应信息(通过Statement执行SQL获取结果集)
public void selectById1(String id){
try {
String sql="select * from Student where SID="+id;//只能通过字符串拼接传递参数
statement=connection.createStatement();//3.创建Statemnet对象
ResultSet resultSet = statement.executeQuery(sql);//4.执行sql并获取结果集
while(resultSet.next()){//5.通过迭代遍历结果集
System.out.println("SID"+resultSet.getString("SID")//可以根据在表中字段名获取对应字段
+" Sname"+resultSet.getString("Sname")
+" Sage"+resultSet.getString(3)//也可以根据在表中第几列(从0开始)获取对应字段
+" Ssex"+resultSet.getString(4)
);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
//根据指定SID来查询Student表中的对应信息(通过PreparedStatement执行SQL获取结果集)
public void selectById2(String id){
try {
String sql="select * from Student where SID=?";
ps=connection.prepareStatement(sql);//3.创建PreparedStatemnet对象,预编译sql并保存在ps
ps.setString(1,id);//4.将形参传给底层,数字1指sql中的第几个?
ResultSet resultSet=ps.executeQuery();//5.执行sql并获取结果集
while(resultSet.next()){//6.通过迭代遍历结果集
System.out.println("SID"+resultSet.getString("SID")//可以根据在表中字段名获取对应字段
+" Sname"+resultSet.getString("Sname")
+" Sage"+resultSet.getString(3)//也可以根据在表中第几列(从0开始)获取对应字段
+" Ssex"+resultSet.getString(4)
);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
//给Student表插入一条数据(通过Statement执行SQL获取结果集)
public void insert(String id,String name){
try {
String sql="insert into Student (SID,Sname) values('"+id+"','"+name+"')";//看到这,总该知道statement非常烦人的一点了吧
System.out.println(sql);
statement=connection.createStatement();//3.创建statement对象
//4.执行sql语句并返回结果(插入操作返回int值代表更改的行数/记录数),对数据的更新(insert、delete、update)调用executeUpdate()
int i = statement.executeUpdate(sql);
//上面的操作也可以这样实现
// String sql="insert into Student (SID,Sname) values(?,?)";//不用字符串拼接啊,使用?占位符 好爽!
// ps=connection.prepareStatement(sql);//3.创建PreparedStatement对象,预编译sql并保存在ps
// ps.setString(1,id);
// ps.setString(2,name);
// int i1 = ps.executeUpdate();
System.out.println("插入了"+i+"条数据");
} catch (SQLException e) {
e.printStackTrace();
}
}
//更改Student表中SID对应的姓名
public void update(String id,String name){
try {
String sql="update Student set Sname=? where SID=?";
System.out.println(sql);
ps=connection.prepareStatement(sql);
ps.setString(1,name);
ps.setString(2,id);
//对数据的更新(insert、delete、update)也可以调用execute(),
//返回true表示MySQL给我们返回了一个ResultSet对象(查询操作),返回false表示MySQL没有返回信息或返回了int(影响行数)
boolean execute = ps.execute();
System.out.println(execute);//这里打印为false
} catch (SQLException e) {
e.printStackTrace();
}
}
//删除Student表中SID对应的数据
public void delete(String id){
try {
String sql="delete from Student where SID=?";
System.out.println(sql);//打印sql是为了对比字符串拼接和占位符的效果
ps=connection.prepareStatement(sql);
ps.setString(1,id);
int i = ps.executeUpdate();
System.out.println("删除了"+i+"条数据");
} catch (SQLException e) {
e.printStackTrace();
}
}
//关闭所有资源
public void close(){
try {
if(connection!=null){ //不等于null即没有关闭
connection.close();//关闭connection
connection=null;//将其致为null,是为了防止内存泄漏,方便GC(jvm的垃圾回收机制)回收
}
if(statement!=null){//同上
statement.close();
statement=null;
}
if(ps!=null){//同上
ps.close();
ps=null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
JdbcDemo jdbcDemo=null;
try {
jdbcDemo = new JdbcDemo();
jdbcDemo.getConnection();//调用该方法来获取数据库连接
jdbcDemo.selectAll1();//进行查询操作
System.out.println("==========================");
jdbcDemo.selectById1("02");//和selectById2("02")执行结果相同,但底层的执行过程差异很大
System.out.println("==========================");
jdbcDemo.selectById2("02");
System.out.println("==========================");
jdbcDemo.insert("09", "大神");//执行一次表中就有这条数据了,可不敢再插入一次同样的数据
jdbcDemo.selectAll1();//全表查询看是否打印成功,注意:因为我没有设置是否自动提交,所以默认自动提交(commit),即不支持事务
jdbcDemo.update("09", "大仙");
jdbcDemo.selectAll1();//看一下更改效果
System.out.println("==========================");
jdbcDemo.delete("09");
jdbcDemo.selectAll1();//看一下删除效果
}finally {//程序执行到最后会执行finally中关闭资源的代码
if(jdbcDemo!=null){//不为null则没有关闭
jdbcDemo.close();//关闭jdbcDemo
jdbcDemo=null;//方便GC(垃圾回收机制)清理内存,防止内存泄漏
}
}
}
}
执行结果:
常用的方法都在里面使用且对比了效果,认真跟一下代码就能掌握。
Statement和PreparedStatement对比
- 都是接口,且PreparedStatement继承了Statement
- PreparedStatement的sql可以使用占位符,是预编译的,预编译后将带有占位符的SQL语句传给数据库服务器,再根据 ps.setString();把传占位符对应的值传给数据库。
int parameterIndex是第几个占位符(从0开始数),String x是要传的值:
要传其他类型的数据也可以使用对应的方法
- Statement必须使用字符串拼接来实现SQL语句,过程繁琐容易出错,且会出现SQL注入攻击。
- PreparedStatement可以使用SQL缓存区,效率更高。
总结:
- 安全性:PreparedStatement可以防止依赖SQL注入,安全性高;
- 运行效率:PreparedStatement可以使用SQL缓存区,运行效率高;
- 语法不同:PreparedStatement可以使用预编译的SQL,而Statement只能使用静态SQL;
- 代码美感:PreparedStatement使用占位符,不需要字符串拼接,编写方便且代码优雅。
SQL注入攻击:
先来看一段代码:
public class SQLTest {
public static void main(String[] args) {
Connection connection=null;
Statement statement=null;
try {
String name="张三' or 1=1";
String password="djndaed' or 1=1";
String sql = "select * from User where name='"+name+" and password='"+password;
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test_sql", "root", "123456");
statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
while (resultSet.next()){
System.out.println("登陆成功");
System.out.println(sql);
return;//如果登陆成功,下面代码不执行
}
System.out.println("登陆失败");
System.out.println(sql);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
if(connection!=null){
connection.close();
connection=null;
}
if(statement!=null){
statement.close();
statement=null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
执行结果:
注意!name=‘张三’ or 1=1这个表达式判断下来永真(永远不会假),因为or就是有真则真;password=‘’djndaed‘ or 1=1一样是永真。这样无论我id和password传什么,只要保证可以字符串拼接出永真式。当进行登陆操作时,这样判断name和password是否在User表中存在的结果一定是存在,所以会登录成功,还可以返回表中的用户信息,造成安全性问题。这就是SQL注入攻击。
来看看PreparedStatement:
public class SQLTest {
public static void main(String[] args) {
Connection connection=null;
PreparedStatement ps=null;
try {
String name="张三 or 1=1";
String password="123 or 1=1";
// String sql = "select * from User where id="+id+" and password="+password;
String sql = "select * from User where name=? and password=?";
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test_sql", "root", "123456");
ps = connection.prepareStatement(sql);
ps.setString(1,name);
ps.setString(2,password);
ResultSet resultSet = ps.executeQuery();
while (resultSet.next()){
System.out.println("登陆成功");
System.out.println(resultSet.getString("name")+" "+resultSet.getString(password));
return;//如果登陆成功,下面代码不执行
}
System.out.println("登陆失败");
System.out.println(sql);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
if(connection!=null){
connection.close();
connection=null;
}
if(ps!=null){
ps.close();
ps=null;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
执行结果:
可见PreparedStatement中将id和password传过去,形成的SQL应该是:select * from User where name='张三 or 1=1' and password='123 or 1=1'
,这样就算or前面的信息正确,MySQL也会把id和password的内容当成整体,不会像Statement那样拼接成字符串而变成永真式。