目录
声明部分图片来自百战尚学堂
什么是Jdbc:
JDBC定义了在Java语言中连接数据库的标准,当我们查询数据时就需要使用到数据库驱动,而不同的数据库有不同的驱动,java通过jdbc标准屏蔽了这种不同,将不同化为相同,应用程序只要实现了JDBC且驱动实现了JDBC那么就可以用不同的驱动
Jdbc的执行顺序: ![](https://i-blog.csdnimg.cn/blog_migrate/d6166cc129785d6f711ad6a88081e2f3.png)
加载并注册数据库驱动:
首先我们需要导入外部数据库驱动,我们这里使用的是mysql的数据库驱动,大家要的话可以拿去:
百度网盘链接:https://pan.baidu.com/s/1wmdtKDnxIOxc1jpIRE6Ndw?pwd=2bkx
提取码:2bkx
我们可以通过反射机制Class.forName(数据库驱动名称)加载数据库驱动器,在mysql数据库jar包中有一个jdbc包,mysql实现了java的jdbc标准,在该包下有一个Driver类,这个类就是用来加载并注册驱动器的
我们可以通过他的源码可知:
该类的代码中有一段静态代码块,静态代码块在类加载时加载,只会加载一次,所以当我们通过反射机制加载数据库驱动器时,相当于调用了该类的静态代码块
那么为什么要放到静态代码块中呢,如果你放在非静态的方法里,那么就表示他是和对象有关的
可是我们一般都只是想加载驱动后,得到一个Connection而已,跟对象一点关系都没有,那么又为什么要去实例一个对象先,然后再得到一个Connection这么劳累的事
mysql数据库的url:jdbc:mysql://localhost:数据库端口号/数据库名?useSSL=false
代码示例:
Connection对象
java会将加载好的数据库驱动放在DriverManager类中,我们可以通过DriverManager调用getConnection(String url,String username,String password)静态方法获取Connection对象,而这个方法需要传递三个参数,
第一个就是数据库驱动的url,因为java会将加载好的数据库驱动放在DriverManager类中,那都是数据库驱动java咋知道我们用的是哪个数据库驱动呢,所以我们需要使用数据库驱动的url进行获取相对应的数据库连接
mysql数据库的url:jdbc:mysql://localhost:数据库端口号/数据库名?useSSL=false
第二个是登录数据库的用户名
第三个是登录数据库的密码
代码示例:
通过这种方式获取Connection对象会有什么问题?
通过这种方式获取Connection对象会有硬编译的问题,硬编码是指将可变变量用一个固定值来代替的方法。用这种方法编译后,如果以后需要更改此变量就非常困难了。
使用properties文件解决硬编码问题
通过properties属性文件可以解决这种问题,这种文件以key=value格式存储内容。Java中可以使用Properties工具类来读取这个文件。项目中会将一些配置信息放到properties文件中,所以properties文件经常作为配置文件来使用。
Properties工具类中常用方法
load(InputStream is) 通过给定的输入流对象读取properties文件并解析
getProperty(String key) 根据key获取对应的value
我们可以将url、username、password这些可变的变量提取放到properties文件中
然后通过Properties对象的getProperty(String key)方法,传递key参数返回对应的value
try(//通过字节输入流读取properties文件
FileInputStream fis = new FileInputStream("src/jdbc.properties")){
//实例化Properties对象
Properties prop = new Properties();
//解析读取到的properties文件
prop.load(fis);
//读取连接数据库的url
url = prop.getProperty("url");
//获取用户名
name = prop.getProperty("username");
//获取密码
pwd = prop.getProperty("pwd");
//获取数据库驱动全名
String driver = prop.getProperty("driver");
//通过反射机制加载数据库驱动
Class.forName(driver);
}catch (Exception e){
e.printStackTrace();
}
然后我们就可以通过DriverManager创建Connection对象了,而此时每当我们需要加载mysql连接时就得这么写岂不是太麻烦了,所以我们可以将这部分代码抽离出来写一个Utils工具类
将部分代码抽离写出JdbcUtils工具类
此时我们可以看到我将这段代码放到了静态代码块中,为什么呢,我们可以参考一下mysql的jdbc包中的Driver类,它通过了静态代码块进行数据库驱动器的加载,而静态代码块在类加载时执行一次,之后就会再执行了
而我们操作properties文件是需要用到IO流的,大家都知道IO流是比较耗时间的,而且操作properties文件只需要执行一次就好了,所以我们直接将操作properties文件的代码放到静态代码块中
然后定义了getConnection方法,该方法的返回值是Connection对象,通过该方法我们可以获取到mysql的连接器
/**
* Jdbc工具类
*/
public class JdbcUtils {
private static String url;
private static String name;
private static String pwd;
static{
try(//通过字节输入流读取properties文件
FileInputStream fis = new FileInputStream("src/jdbc.properties")){
//实例化Properties对象
Properties prop = new Properties();
//解析读取到的properties文件
prop.load(fis);
//读取连接数据库的url
url = prop.getProperty("url");
//获取用户名
name = prop.getProperty("username");
//获取密码
pwd = prop.getProperty("pwd");
//获取数据库驱动全名
String driver = prop.getProperty("driver");
//通过反射机制加载数据库驱动
Class.forName(driver);
}catch (Exception e){
e.printStackTrace();
}
}
//获取数据库连接对象
public static Connection getConnection(){
Connection connection = null;
try {
connection = DriverManager.getConnection(url,name,pwd);
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
}
而Connection对象时需要关闭的,包括后期的Statement对象,ResultSet对象都是需要关闭的,而且他们经常联合使用所以我们再添加一些关闭这些对象的方法
//关闭连接对象
public static void closeConnection(Connection connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//提交事务
public static void commit(Connection connection){
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
//事务回滚
public static void rollback(Connection connection){
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
//关闭Statement对象
public static void closeStatement(Statement statement){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//关闭ResultSet
public static void closeResultSet(ResultSet resultSet){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//DML操作时关闭资源
public static void closeResource(Statement statement,Connection connection){
//先关闭Statement对象
closeStatement(statement);
//再关闭Connection对象
closeConnection(connection);
}
//DQL操作时关闭资源
public static void closeResource(ResultSet resultSet,Statement statement,Connection connection){
//先关闭ResultSet
closeResultSet(resultSet);
//再关闭Statement
closeStatement(statement);
//最后关闭Connection
closeConnection(connection);
}
Statement对象
Statement对象有张图就说过了,它是用来执行sql语句的,我们通过Connection对象下的createStatement()方法创建Statement对象
Statement执行sql语句的代码编写顺序:
1、首先先搭建好try…catch…finally框架,并且在try语句外声明Connection和Statement对象
2、获取Connection连接
3、通过Connection对象的createStatement()方法创建Statement对象
4、编写sql语句
5、Statement对象调用execute(String sql)传递sql语句来执行sql
6、关闭Statement对象、Connection对象
注意:先关闭Statement对象再关闭Connection对象
那执行sql语句首先我们得要有表把,所以我们这里创建一个表叫users,userid为主键列
代码示例:实现向users表添加数据,这里主要是看代码的编写顺序,sql语句怎么写完全是看你自己,execute()方法返回的是一个布尔值,如果sql语句执行过后有结果集返回true,没有结果集返回false
添加用户:
使用Statement对象会遇到什么问题:sql注入
sql注入是什么:
可以用它来从数据库获取敏感信息、利用数据库的特性执行添加用户、导出文件等一系列恶意操作,甚至有可能获取数据库乃至系统用户最高权限。
sql注入如何解决:
使用PreparedStatement对象执行sql语句
造成SQL注入的原因
程序没有有效过滤用户的输入,使攻击者成功的向服务器提交恶意的 SQL 脚本,程序在接收后错误的将攻击者的输入作为 SQL 语句的一部分执行,导致原始的查询逻辑被改变,执行了攻击者精心构造的恶意 SQL 语句。
PrepareStatement对象
PreparedStatement对象拥有预编译sql语句的能力,我们可以使用通配符(?)代替参数部位,PreparedStatement对象可将sql语句和参数分离,预编译sql语句过后再绑定参数
PreparedStatement对象执行sql语句代码编写顺序:
1、首先先搭建好try…catch…finally框架,并且在try语句外声明Connection和PreparedStatement对象
2、获取Connection对象
3、编写sql语句参数部位通过通配符(?)占位
4、通过Connection对象的prepareStatement()方法创建PreparedStatement对象
5、sql语句中的参数绑定,需要传递?的位置(?的位置从1开始)
6、preparedStatement对象通过execute()方法执行sql语句
7、关闭PreparedStatement对象、Connection对象
代码示例:
根据userid修改用户信息username、userage
这段代码就遵循了我们刚刚写PreparedStatement对象执行sql语句的代码编写顺序,这里主要要讲的是绑定参数是通过preparedStatement对象调用set参数类型(通配符位置,参数);通配符位置从1开始
那么学到这里大家可能云里雾里,哎呀这跟sql注入有毛关系啊,这样子还不如使用Statement对象呢,不是这样的下面我们就来演示一下sql注入
Sql注入演示:
我们刚刚说过Statement对象会出现sql注入的问题,那么我们就使用Statement对象来演示一下sql注入,这里先声明一下这个ResultSet是接收查询到的结果集的,executeQuery()方法就是执行DQL语句的,这些在ResultSet对象讲解中会详细的讲
现在我们来看sql语句的编写,此时sql语句是通过字符串拼接的方式将参数写入的,查询users表中用户名username为xxx并且用户年龄userage为xxx的用户信息,顺便将拼接过后的sql语句输出出来
这是users表中所有用户信息
现在我们来些一个测试类,测试类中用户名我们这么写:"oldlu 'or 1=1 -- " ,按理来说此时执行sql语句进行DQL操作是没有结果的因为我们根本就没有这个名字的人
执行过后我们会发现,我们查询除了所有用户的信息,这是一个非常可怕的问题,攻击者通过改变DQL操作的逻辑,查询到所有用户的信息,这相当于就是数据泄露了呀
此时我们可以看看sql语句在拼接过后是什么样的,查询users表中用户名username = 'oldlu'或者 1=1的用户信息,后面的--在mysql中是注释的意思,所以后面相当于注释掉了。or就是或者的意思,一方为true就为true,而1=1永远都为true,所以这个条件将永远成立,那么就可以查询出所有用户的信息
sql注入就是通过更改我们DQL查询的逻辑进行从数据库获取敏感信息
那么PreparedStatement对象为什么就不会出现sql注入的问题呢?刚刚不是说了嘛,PreparedStatement对象先预编译sql语句,然后再将参数绑定,所以不管怎样都不会对我们的查询逻辑进行改变
我们使用PreparedStatement对象模拟一下sql注入看是否会出现问题,在执行sql语句过后输出sql语句
通过测试类再次进行sql注入
通过结果我们可以发现参数绑定过后sql语句照样还是和我们编写的DQL查询逻辑一样,这就说明了预编译功能的作用,就是说不管我们输入的参数是什么他就只是一个参数,不是通过Statement字符串拼接那样可以改变DQL查询的逻辑。
ResultSet对象
ResultSet用来存放数据库查询操作获得结果集,通过对ResultSet的操作可以获取查询到的结果集数据。
ResultSet特点
- ResultSet 对象具有指向其当前数据行的指针。最初,指针被置于第一行之前。next 方法将指针移动到下一行;因为该方法在 ResultSet 对象中没有下一行时返回 false,所以可以在 while 循环中使用它来迭代结果集。
- 默认的 ResultSet 对象仅有一个向前移动的指针。因此,只能迭代它一次,并且只能按从第一行到最后一行的顺序进行。
- ResultSet 接口提供用于获取当前行检索列值的获取方法(getBoolean、getLong 等)。可以使用列的索引位置或列的名称检索值。
ResultSet使用原理
代码示例:
连接池对象
什么是数据库连接池?
为数据库建立一个缓冲区,预先在缓冲区中创建多个连接对象,当我们需要使用时就从缓冲池中拿连接对象,使用完之后返还给缓冲池
为什么要有数据库连接池?
如果我们使用直连的方式每次向数据库建立连接然后获取Connection对象,用完之后关闭,这样子就会浪费资源和时间,为什么我们不在代码运行之前就建立好一个缓冲区,当我们需要使用连接时就通过缓冲区获得连接,用完之后返还不就好了吗
获取数据库缓冲池的代码示例:
Java使用DataSource存放数据库连接池对象,而数据库缓冲池有很多种第三方的,我们这里使用的是阿里的Druid,阿里的数据库缓冲池性能较好,大家如果想要可以通过一下连接获取,获取过后在项目路径下创建一个lib目录用于存放jar包,这个目录名字是你自己定的,然后将jar包拉进去即可,最后右键添加为库才能使用
百度网盘链接:https://pan.baidu.com/s/1ssYWSdVEo10OZkn_CyO_cQ?pwd=j913
提取码:j913
通过DruidDataSourceFactory的静态方法createDataSource(Properties pos);传递存放了数据库url,用户名,密码的Properties对象,创建连接池对象
properties文件:
initialSize代表的是初始化容量,就是说这个缓冲池中初始化时里边放多少个数据库连接对象
maxActive代表最大容量,代表该缓冲池中的数据库连接对象不允许超过这么多个
driverClassName指的是数据库驱动的全名,Druid通过加载这个类获取数据库驱动器
/**
* 基于Druid连接池获取数据库连接的工具类
*/
public class JdbcDruidUtils {
//连接池对象
private static DataSource ds;
static{
//加载Druid配置文件
FileInputStream fis;
try {
//获取读取Druid配置文件的输入流
fis = new FileInputStream("src/druid.properties");
//创建Properties对象
Properties pos = new Properties();
//加载Druid配置文件
pos.load(fis);
//获取连接池对象
ds = DruidDataSourceFactory.createDataSource(pos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
要点:记住PreparedStatement对象执行sql语句的代码编写顺序,Statement对象以后不常用
1、搭建try...catch...finally矿建,try上边声明Connection对象和PreparedStatement对象
2、获取连接Connection
3、编写sql语句
4、通过Connection对象的prepareStatement(String sql)方法创建PreparedStatement对象并且预编译sql语句
5、参数绑定
6、PreparedStatement对象调用execute()方法执行sql语句
7、关闭PreparedStatement对象、Connection对象
注意:一定要先关闭PreparedStatement对象再关闭Connection对象