JDBC概述:
Java DataBase Connectivity Java数据库的连接。
- 目的使用Java的代码来操作数据库
- 需要使用JDBC(Java数据库的连接)规范来操作数据
JDBC规范:
- JDBC是一套接口规范,为访问不同的数据库提供了一种统一的途径。
- JDBC的实现类都是由各个数据库的生产商来提供的。
驱动:
- 它是数据传输的桥梁
- 驱动指的是各个数据库生产商提供的实现类 (比如我们操作mysql的数据库就需要导入mysql的驱动包)
JDBC相关的接口和API介绍:
- java.sql.DriverManager用来管理JDBC的驱动的实现类。
- java.sql.Connection完成对某一指定数据库的联接。
- java.sql.Statement在一个给定的连接中作为SQL执行声明的容器。
- java.sql.ResultSet对于给定声明取得结果的途径。
下面用来写一个简单的入门程序,以查询操作为例,大致分为如下几个步骤:
准备工作:建库建表。
- 加载驱动类(必须预先导入jar包)
- 取得连接
- 执行一些sql语句(此处执行查询语句)
- 遍历结果集(查询到的数据会封装到结果集中)
- 释放资源
代码示例:
public class Test1 {
public static void main(String[] args) {
Connection conn = null;
ResultSet rs = null;
Statement state = null;
try {
//1 加载mysql驱动类----方式一:这种方式一般不用
// DriverManager.registerDriver(new Driver());
//方式二
Class.forName("com.mysql.jdbc.Driver");
//2 获取连接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo",
"root", "root");
//3 执行sql语句
String sql = "select * from t_user";
//不要用这种方式
state = conn.createStatement();
rs = state.executeQuery(sql);
//4 遍历结果集
while(rs.next()) {
int id = rs.getInt("id");
String userName = rs.getString("username");
String password = rs.getString("password");
String email = rs.getString("email");
System.out.println(id + " " + userName + " " + password + " " + email);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//释放资源
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(state != null) {
try {
state.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
输出结果:
来简单分析一下上述代码:
一、加载驱动类
有两种方式:第一种需要实例化一个Driver对象。 第二种方式是利用全限定名来直接加在该类的.class文件。
第一种方式一般是不用的,我们来看一下Driver这个类的源码:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
public Driver() throws SQLException {}
}
首先确实可以看到该类是java.sql.Driver的实现类。再者该类中的静态代码块中已经帮我们做了方式一的操作了。那我们就不用再重复进行了。静态块中的内容是伴随着类的加载过程(初始化阶段)执行的,所以我们只需要将该类加载进来就可以了。所以可以用方式二加载驱动。而且方式二是传一个字符串参数值。那就可以通过读取外部配置来加载。这样我们只需要修改配置文件就可以修改相关内容。
二、获取连接:可以用上述方法获取连接,需要传入数据库服务器的URL,账号,密码。
下面列举几个常见数据库的URL:
- Oracle 数据库:jdbc:oracle:thin:@localhost:1521:xxx
- SQLServer 数据库:jdbc:microsoft:sqlserver//localhost:1433; DatabaseName=xxx
- MYSQL 数据库:jdbc:mysql://localhost:3306/xxx
以mysql的url为例:
其中jdbc代表主协议,mysql为子协议,localhost:3306子名称(数据库服务器地址)如果访问的是本机的数据库,此处可以省略不写,xxx你建立的数据库名称。
对于上述代码,直接将这三个参数值写死,很明显不好。这块也可以改为可配置的方式。当然还可以采用数据库连接池的方式来获取连接。
三、执行sql语句:
- 创造sql语句例如此处的select * from t_user;
- 接着执行查询命令。可用executeQuery方法。
不过上述代码中的方式存在sql注入的问题,比如将上述sql语句稍微变一下:
select * from t_user where username='zhang' or 1=1 and password='你看我出不出来'
执行结果:
他竟然获取到了。。。如果将动态的username和password参数值按照上述方式拼接就可以获取到那还是比较害怕的,显然是不好,不是我们想要的结果。
其实上述sql语句主要能执行成功是因为有or这个关键字 因为前面username=‘zhang’ 这个是正确的,and优先级高于or,先执行and,故后面一串 1=1 and password=‘你看我出不出来’ 这个整体上为false。只不过username为真,那后面的就被短路了。
我们可以再变一下sql语句:select * from t_user where username='zhang' or '无所谓了' 结果依然能够获取到数据。而且不仅仅是这个问题。如果要批量查询多条数据,这种方式就是存在大量的字符串拼凑!效率也不好。
解决方法:
对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement 取代 Statement 就可以了。PreparedStatement接口,是Statement的子接口。其有预编译的功能,可以把SQL语句中的参数的部分使用?(占位符)来代替,他是先将编写的SQL语句发送到MySQL服务器端,然后对这条SQL语句进行编译,编译后的SQL语句的格式就是固定的了,再传入任何的值,都会做?的参数来出现。我们可以利用 conn.prepareStatement(String sql)方法对sql语句进行预编译。然后利用 PreparedStatement接口提供的各种setXXX(...)方法填充占位符即可。
代码展示如下:
public class Test1 {
public static void main(String[] args) {
Test1.getUser("zhang or 1=1", "234242");
}
public static void getUser(String userName, String password) {
Connection conn = null;
ResultSet rs = null;
PreparedStatement state = null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbcdemo",
"root", "root");
String sql = "select * from t_user where username=? and password=?";
//执行sql预编译
state = conn.prepareStatement(sql);
//索引是从1开始的,填充占位符
state.setString(1, userName);
state.setString(2, password);
rs = state.executeQuery();
if(!rs.next()) {
System.out.println("没获取到哦");
}
while(rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("username");
String psw = rs.getString("password");
String email = rs.getString("email");
System.out.println(id + " " + name + " " + psw + " " + email);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//关闭资源
}
}
}
执行结果:
四、获取结果:
当我们获取到数据时,比如上述所有字段包括id,username,password,email。很显然可以将上述内容封装到一个User对象中。
上述就是我对这些知识的一些粗浅理解。接下来的文章会继续分享。