JDBC加载数据库驱动

My day is done, and I am like a boat drawn on the beach, listening to the dance-music of the tide in the evening.

我的白昼已经完了,我象一只泊在海滩上的小船,谛听着晚潮跳舞的乐声。

JDBC基本使用

Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标。我们通常说的JDBC是面向关系型数据库的。

导入mysql-connector-java-版本.jar,然后将jar包添加入项目库中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//固定写法,加载驱动 Mysql 8.0以上
Class.forName("com.mysql.cj.jdbc.Driver");
//2.用户信息和url
String url = "jdbc:mysql://localhost:端口/数据库名?useSSL=false&serverTimezone=UTC&characterEncoding=utf8";
String username = "用户名"; //数据库用户名
String password = "密码";//数据库密码

Connection connection = DriverManager.getConnection(url,username,password);

//执行SQL的对象 statement
Statement statement = connection.createStatement();
//sql语句
String sql = "SELECT * FROM 数据库名.表名";
//执行sql
ResultSet resultSet = statement.executeQuery(sql);

//从获取的结果中输出
while (resultSet.next()){
System.out.println("variable="+ resultSet.getObject("列名"));
}

//依次关闭连接
resultSet.close();
statement.close();
connection.close();
}

驱动

com.mysql.cj.jdbc.Driver部分源码

1
2
3
4
5
6
7
8
9
10
11
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
1
2
3
4
//DriverManager.registerDriver(new Driver());   不推荐使用
//因为Drive类中静态代码块中已经注册了
//固定写法,加载驱动 Mysql 8.0以上
Class.forName("com.mysql.cj.jdbc.Driver"); //推荐

URL

1
2
3
4
5
String url = "jdbc:mysql://主机地址:3306/数据库名?useSSL=false&serverTimezone=UTC&characterEncoding=utf8";

mysql -- 3306
oralce -- 1521
jdbc:oralce:thin:@localhost:1521:sid

Connection

1
Connection connection = DriverManager.getConnection(url,username,password);

connection代表数据库,是数据库的对象

1
2
3
connection.rollback();
connection.commit();
connection.setAutoCommit();

Statement

不能防止sql注入点击跳转PreparedStatement

statement 具体的执行类,是执行sql的对象

1
2
3
statement.executeQuery();  //查询操作返回ResultSet
statement.execute(); //查询任何sql
statement.executeUpdate(); //更新 插入 删除 返回一个受影响的行数

ResultSet

resultSet 查询的结果集,封装了所有的结果集

1
2
3
4
5
6
7
8
9
resultSet.getObject();   //在不知道列类型的情况下使用
resultSet.getString();
resultSet.getInt();

resultSet.beforeFirst(); //移动到最前面
resultSet.afterLast(); //移动到最后面
resultSet.next(); //移动到下一个数据
resultSet.previous(); //移动到前一行
resultSet.absolute(row); //移动到制定行

释放资源

1
2
3
resultSet.close();
statement.close();
connection.close();

提取工具类

创建db.properties配置文件

1
2
3
4
driver = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:端口/数据库名?useSSL=false&serverTimezone=UTC&characterEncoding=utf8&useUnicode=true
username = 用户名
password = 密码

通过反射获取类加载器来拿到资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class JdbcUtils {

private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;

static {
try{
//通过反射获取类加载器来拿到资源(mysql配置文件),返回一个输入流
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
//将配置文件读出
properties.load(in);

driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");

//驱动只用加载一次
Class.forName(driver);

}catch (Exception e){
e.printStackTrace();
}
}

//获取连接
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,username,password);
}

//释放连接
public static void release(Connection conn, Statement st, ResultSet rs){
if(rs!=null){
try{
rs.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if(st!=null){
try{
st.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if(conn!=null){
try{
conn.close();
}catch (SQLException e){
e.printStackTrace();
}
}
}
}

Class是当前类的Class对象,Class.getClassLoader()是获取当前类的类加载器。类加载器的大概作用是当需要使用一个类时,加载该类的”.class”文件,并创建对应的class对象,将class文件加载到虚拟机的内存。getResourceAsStream()是获取资源的输入流。类加载器默认是从classPath路径加载资源。

因此,当使用Class.getClassLoader.getResourceAsStream()加载资源时,是从classPath路径下进行加载,放在resources下的文件加载时不能加(“/”)。

1
InputStream in = PropertiesUtil.class.getClassLoader().getResourceAsStream("xx.properties");

Class.getResourceAsStream()

1
2
3
4
//当前类的URI目录,不包括自己
Class.getResourceAsStream("");
//当前的classpath的绝对URI路径
Class.getResourceAsStream("/");

在使用 Class.getResourceAsStream()时,一定注意要加载的资源路径与当前类所在包的路径是否一致【使用时注意子目录】。

1)要加载的资源路径与当前类所在包的路径一致

1
InputStream in = PropertiesUtil.class.getResourceAsStream("xx.properties");

2)要加载的资源路径在resources下

1
InputStream in = PropertiesUtil.class.getResourceAsStream("/xx.properties");

提取插入类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class TestInsert {
public static void main(String[] args) {
//提高作用域
Connection conn = null;
Statement st = null;
ResultSet rs = null;

try {
conn = JdbcUtils.getConnection(); //获取数据库连接
st = conn.createStatement(); //获得sql的执行对象
String sql = "插入的sql语句";

int i = st.executeUpdate(sql);
if (i>0){
System.out.println("插入成功");
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}

SQL注入问题

SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。

注入实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class SQL注入 {
public static void main(String[] args) {
// login("bobobobo","123123");
login("'or'1=1","'or'1=1");
}

//登陆业务
public static void login(String username,String password){
Connection conn = null;
Statement st = null;
ResultSet rs = null;

try {
conn = JdbcUtils.getConnection();
st = conn.createStatement();

//select * from users where `NAME` = '' or '1=1' and `password` = '' or '1=1'
String sql = "select * from users where `NAME`='" + username + "' AND `password` = '" + password + "'";
rs = st.executeQuery(sql);

while(rs.next()){
System.out.println("查询成功");
System.out.println(rs.getString("NAME"));
System.out.println(rs.getString("password"));
}

} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}

login()方法传递进去的参数会被拼接入sql语句中,称为执行sql语句的一部分,使sql语句保持正确的语法,进而被执行;

PreparedStatement

点击跳转Statement

PreparedStatement可以防止sql注入,效率更好

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import com.bobo.lesson02.utils.JdbcUtils;
import java.sql.*;

public class SQL注入 {
public static void main(String[] args) {
login("'' or 1=1","123123"); //防止sql注入
}

//登陆业务
public static void login(String username,String password){
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;

try {
conn = JdbcUtils.getConnection();

//区别
//使用 ?占位符 代替参数
//把传递进来的参数当作字符 假设其中存在转义字符 直接忽略 ' 会被直接转义
String sql = "select * from users where `NAME`= ? and `password` = ?";

//预编译sql语句
st = conn.prepareStatement(sql);

//从1开始,1代表第一个占位符 ? 以此类推,填入参数
st.setString(1,username);
st.setString(2,password);

//与之前不同,不需要sql参数,因为之前已经进行过预编译,直接执行对象
rs = st.executeQuery();

while(rs.next()){
System.out.println("查询成功");
System.out.println(rs.getString("NAME"));
System.out.println(rs.getString("password"));
}

} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}

login()传递进来的参数会被当作字符串,只相当于参数,而不是sql执行语句中的一部分,字符串的转义字符会被忽略,无法创造满足的条件。

数据库连接池

池化技术

概念

池化技术:把一些能够复用的东西(比如说数据库连接、线程)放到池中,避免重复创建、销毁的开销,从而极大提高性能。

在开发过程中我们会用到很多的连接池,像是数据库连接池、HTTP 连接池、Redis 连接池等等。而连接池的管理是连接池设计的核心,我就以数据库连接池为例,来说明一下连接池管理的关键点。

数据库连接池

数据库连接池有两个最重要的配置:最小连接数和最大连接数,它们控制着从连接池中获取连接的流程:

  • 如果当前连接数小于最小连接数,则创建新的连接处理数据库请求
  • 如果线程池中有空闲连接,则使用空闲连接
  • 如果没有空闲连接,并且当前连接数小于最大连接数,则继续创建新的连接
  • 如果当前连接数大于等于最大连接数,并且没有空闲连接了,则请求按照超时时间等待旧连接可用。、
  • 超时之后,则获取数据库连接失败

对于数据库连接池,根据我的经验,一般在线上我建议最小连接数控制在 10 左右,最大连接数控制在 20~30 左右即可。

IDEA使用DBCP连接池

导入commons-dbcp-版本号.jar commons-pool-版本号.jar ,然后将jar包添加入项目库中

DBCP2还需要导入commons-logging-版本号.jar

提取工具类

创建dbcpconfig.properties配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#连接设置
driverClassName = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:端口/数据库名?useSSL=false&serverTimezone=UTC&characterEncoding=utf8&useUnicode=true
username = 用户名
password = 密码

#初始化连接
initialSize=10

#最大连接数量
maxActive=50

#最大空闲连接
maxIdle=20

#最小空闲连接
minIdle=5

#超时等待时间以毫秒为单位 6000毫秒/1000等于60秒
maxWait=60000

#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]
#注意:“user” 与 “password” 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=UTF8

#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true

#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则"setReadOnly"方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=

#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED

DBCP工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

public class JdbcUtils_DBCP {

private static DataSource dataSource = null;

static {
try{
InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties properties = new Properties();
properties.load(in);

//创建数据源 工厂模式 =》创建对象 返回一个数据源
dataSource = BasicDataSourceFactory.createDataSource(properties);

}catch (Exception e){
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection() throws SQLException {
//数据源中自带连接,自动连接
return dataSource.getConnection();
}

//释放连接
public static void release(Connection conn, Statement st, ResultSet rs){
if(rs!=null){
try{
rs.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if(st!=null){
try{
st.close();
}catch (SQLException e){
e.printStackTrace();
}
}
if(conn!=null){
try{
conn.close();
}catch (SQLException e){
e.printStackTrace();
}
}
}
}
查看评论