JDBC深入浅出

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/feiyanaffection/article/details/81975056

JDBC

一、jdbc的基础知识

1、什么是jdbc

java database Connector java与数据库的连接器
一套接口,是java代码与数据库的桥梁

2、重要接口和类

1)java.sql.Driver (驱动接口,如何连接数据库)
2)java.sql.Connection (连接接口–代表了java代码与数据库服务器之间的连接)
3)java.sql.Statement (语句接口–代表了语句对象,可以用来执行各种sql)
java.sql.PreparedStatement
java.sql.CallableStatement
4)java.sql.ResultSet (结果集接口–代表的是从数据库查询结果)
5)DriverManager (驱动管理器) 辅助类 – 获取Connection连接

6)java.sql.SQLException 检查异常, 需要处理

jdbc 实现–由各个数据库厂商提供

MySQL Connector/J版本说明
Chapter 2 Connector/J Versions, and the MySQL and Java Versions They Support

There are currently two MySQL Connector/J versions available:

Connector/J 8.0 (formerly Connector/J 6.0; see Changes in MySQL Connector/J 8.0.7 for an explanation of the version number change) is a Type 4 pure Java JDBC 4.2 driver for the Java 8 platform. It provides compatibility with all the functionality of MySQL 5.5, 5.6, 5.7, and 8.0. Connector/J 8.0 provides ease of development features, including auto-registration with the Driver Manager, standardized validity checks, categorized SQLExceptions, support for large update counts, support for local and offset date-time variants from the java.time package, support for JDBC-4.x XML processing, support for per connection client information, and support for the NCHAR, NVARCHAR and NCLOB data types.

Connector/J 5.1 is also a Type 4 pure Java JDBC driver that conforms to the JDBC 3.0, 4.0, 4.1, and 4.2 specifications. It provides compatibility with all the functionality of MySQL 5.5, 5.6, 5.7, and 8.0. Connector/J 5.1 is covered by its own manual.

The following table summarizes the Connector/J versions available, along with the details of JDBC driver type, versions of the JDBC API supported, versions of MySQL Server supported, JRE supported, JDK required for building, and the support status for each of the Connector/J versions:

Table 2.1 Summary of Connector/J Versions
————————————————————————————————————————————|
|Connector/J version | Driver Type | JDBC version | MySQL Server version | Status |
——————————————————————————————————————————-|
|5.1 | 4 |3.0, 4.0, 4.1, 4.2 |5.5, 5.6*, 5.7*, 8.0* |General availability
——————————————————————————————————————————-|
|8.0 |4 |4.2 |5.5, 5.6, 5.7, 8.0| General availability. Recommended version

——————————————————————————————————————————-|

  • JRE 1.8.x is required for Connector/J 5.1 to connect to MySQL 5.6, 5.7, and 8.0 with SSL/TLS when using some cipher suites.

3、jdbc的开发步骤

1) 加载驱动
Class.forName("驱动类名");
2) 创建数据库连接
Connection conn = DriverManager.getConnection(连接字符串, 用户名, 密码);

MySQL连接字符串参考

连接字符串参数说明

3) 创建Statment(Preparedstatement)对象
Statement stmt = conn.createStatement();
4) 执行sql语句(执行增删改或查询)

执行增删改

int rows = executeUpdate(sql语句);

执行查询

ResultSet rs = executeQuery(sql语句);
5) 关闭资源

新增示例代码:

public static void insert() throws Exception{
    // 1. 加载驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    // 2. 创建数据库连接
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test3?serverTimezone=GMT%2B8&useSSL=false", "root" "root");
    // 3. 创建Statment对象,执行sql
    Statement stmt = conn.createStatement();
    // 返回值 int类型 代表影响的记录行数
    String sql = "insert into student(sid,sname,birthday,sex) values (null,'老炮儿','1999-1-1','男')";
    int x = stmt.executeUpdate(sql);
    System.out.println("影响的记录行数  x = " + x);
    // 4. 关闭资源 先打开的后关闭
    stmt.close(); // 关闭statement对象
    conn.close(); // 关闭数据库连接    
}

查询示例代码:

public static void select() throws Exception {
    // 1. 加载驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
    // 2. 创建数据库连接
    Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test3?serverTimezone=GMT%2B8&useSSL=false", "root" "root");
    // 3. 创建Statment对象,执行sql
    Statement stmt = conn.createStatement();
    // executeQuery(sql)  执行sql的方法
    // 返回值ResultSet类型 代表影响的结果集
    String sql = "select sid,sname,birthday,sex from student";
    ResultSet rs = stmt.executeQuery(sql);
    // next()方法用来移动到下一行记录 getXXX方法用来获取某列数据
    while (rs.next()) {
        int sid = rs.getInt(1);
        String sname = rs.getString(2);
        Date birthday = rs.getDate(3);
        String sex = rs.getString(4);
        System.out.println(sid + " " + sname + " " + birthday + " " + sex);
    }
    // 4. 关闭资源 先打开的后关闭
    rs.close(); // 关闭ResultSet
    stmt.close(); // 关闭Statement对象
    conn.close(); // 关闭数据库连接
}

注意
问题1:MySQL Connector/J 8.0.12 驱动连接时需要添加serverTimezone=GMT%2B8参数,否则会出现异常:java.sql.SQLException: The server time zone value ‘Öйú±ê׼ʱ¼ä’,另外GMT%2B8也必须加,否则会出现连接方和数据库时区不一致问题
问题2:在连接过程中一旦发生异常:java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed 需要添加allowPublicKeyRetrieval=true参数,此异常一种可能的发生情况是MySQL服务重启后,立刻用jdbc连接时
问题3:WARN: Establishing SSL connection without server’s identity verification is not recommended 要消除这个警告信息,需要加入参数useSSL=false

4、获取自增列的值 (只有在新增数据时execuUpdate才有返回值使用Statement.RETURN_GENERATED_KEYS)

Statement stmt = conn.createStatement();
String sql = "insert into student(sid,sname,birthday,sex) values (null,'张三','1999-1-1','男')";
stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
ResultSet rs = stmt.getGeneratedKeys();
rs.next();
System.out.println(rs.getInt(1));

5、事务控制

6、注入攻击问题与解决方法

数据库表:

create table xx_user (
   username varchar(20) primary key,
   password varchar(20) not null
);
insert into xx_user values('wang', '123');

登录方法:

// 登录方法,如果有用户名和密码其中之一不正确,返回false表示登录失败
/**
 * 
 * @param username  用户输入的用户名
 * @param password  用户输入的密码
 * @return
 */
public boolean login (String username, String password) {
    Connection conn = null;
    Statement stmt = null;
    ResultSet rs = null;
    try {
        conn = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1521:xe", "scott", "tiger");
        stmt = conn.createStatement();
        String sql = "select * from xx_user where username = '"+username+"' and password = '"+password+"'";
        System.out.println(sql);
        rs = stmt.executeQuery(sql);
        if(rs.next()) {
            return true;
        } else {
            return false;
        }

    } catch(Exception e) {
        e.printStackTrace();
        throw new RuntimeException("sql执行失败", e);
    } finally {
        if(rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

一个注入攻击问题:

用户名和密码都没有输入正确,但返回true的例子
boolean success = dao.login("laowang", "aaa' or '1'='1");
System.out.println("登录" + success);

解决注入攻击的一种手段:
PrepareStatement对应的sql语句中,可以使用?代表一个未知的值。
* ?只能代表值,不能是关键字,表名,列名
* 调用的方法根据?的实际类型而定,例如值是int 调用setInt方法
* ?的对应的set方法下标从1开始

3.2 PreparedStatement

要求SQL语句中的值使用?占位符来占位,然后通过一系列的setXXX
方法来给?赋值,XXX根据值的类型决定,例如?为字符串调用setString,?为整数调用setInt
setXXX(?下标, 值) 下标也是从1开始

// 1) 创建 PreparedStatement
PreparedStatement stmt = conn.prepareStatement("select * from xx_user where username = ? and password = ? ");
// 2) 给?占位符赋值
stmt.setString(1, "laowang");
stmt.setString(2, "aaa' or '1'='1"); // 会将整个值当做一个整体,把or当做了值而不是关键字
// 3) 运行sql
ResultSet rs = stmt.executeQuery(); // 会将sql语句以及通过set方法设置的参数值,一起发送给数据库服务器
if(rs.next()) {
    System.out.println("查询到了");
} else {
    System.out.println("没查询到");
}

二、jdbc实战

7、将数据封装至Entity

这里的Entity是指实体类,阿里的叫法为DO (Domain Object ),还有的叫做PO(Persistent Object)持久化对象

无论什么叫法,其实就是将:
* 数据库的表对应至java实体类
* 表中的列对应实体类中的属性
* 表中的一条记录对应至实体类的一个对象

8、将数据的CRUD操作封装至DAO

这里的DAO指数据访问对象(Data Access Object),通常为了解耦的目的将DAO设计为接口,把实际与底层的操作作为实现类(如MySQL实现,Oracle实现,MongoDB实现等)
DAO中可以设计一些最基本的增删改查操作,不包含复杂的业务操作

9、将多个基本的DAO组合成Service

这里的Service即服务类,阿里的叫法为BO(Business Object),有些业务不能仅仅是一条SQL就能完成,通常要包含多条SQL,具备复杂的逻辑。例如:向多张表(订单,订单详情)中插入数据,转账操作等等… 这些操作一般要具备事务完整性,作为一个整体执行。

10、将一些通用的操作封装为工具类

例如:获取连接、关闭资源、执行增删改、执行查询… 封装的功能多了,就成了框架

11、通过工厂解耦DAO层

  • 简单工厂
  • properties配置文件
  • 反射

二、jdbc的高级知识

12、提升性能

12.1 重用sql

默认情况下,通过jdbc执行一条sql语句流程如下:
1. 将sql语句从客户端程序发送给数据库服务器
2. 由命令解析器进行词法分析语法分析、生成解析树
3. 如果是查询,还要生成查询计划,对sql执行进行优化
4. 由访问控制模块检查权限、生成新的解析树
5. 进入表管理模块,打开对应的表文件
6. 调用存储引擎,执行
7. 将结果返回给客户端程序

其中2. 3. 4. 步每次都要执行,即使sql语句很类似:

select * from student where id = 1001;
select * from student where id = 1002;

如果希望2. 3. 4. 步能够被重用,可以利用预编译的sql:

prepare sql1 from 'select * from student where sid = ?';

set @param:=1001;
execute sql1 using @param;

set @param:=1002;
execute sql1 using @param;

这样只有在执行prepare的时候进行了2. 3. 4. 步,而后面的execute执行时就不必执行2. 3. 4. 步了。

jdbc中要利用预编译的功能,需要使用PreparedStatement,普通Statement不行,另外对于MySQL来说,要在jdbc 连接字符串中加入参数:useServerPrepStmts=true&cachePrepStmts=true
其中:
* useServerPrepStmts=true是开启MySQL的预编译功能,即PreparedStatement对象会利用prepare语句
* cachePrepStmts=true是同一个连接的多个PreparedStatement对象能够被缓存,否则一旦PreparedStatement对象关闭,则下一个PreparedStatement对象执行相同sql时,还是会重新执行prepare

12.2 批处理

addBatch
executeBatch

MySQL必须添加rewriteBatchedStatements=true才能真正启用批处理功能

12.3 游标支持

默认情况下,MySQL Connector/J 查询时是一次将所有结果返回,这样记录特别大时就会出现OOM异常,可以通过添加useCursorFetch=true&defaultFetchSize=50来启用游标的方式来每次返回部分结果

12.4 启用日志

my.cnf文件:

[mysqld]
log-output=FILE
log_timestamps=SYSTEM
general-log=1
general_log_file="E:\mysql-8.0.12-winx64\data\mysql.log"
slow-query-log=1
slow_query_log_file="E:\mysql-8.0.12-winx64\data\mysql_slow.log"
long_query_time=2

在安装服务时采用:

mysqld --install MySql --defaults-file=E:\mysql-8.0.12-winx64\my.cnf

12.5 连接池

1) 测试获取连接的时间
2) 测试执行sql的时间
3) 使用成熟的连接池实现

配置 缺省值 说明
name 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this). 另外配置此属性至少在1.0.5版本中是不起作用的,强行设置name会出错。详情-点此处
url 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username 连接数据库的用户名
password 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里
driverClassName 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName
initialSize 0 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive 8 最大连接池数量
maxIdle 8 已经不再使用,配置了也没效果
minIdle 最小连接池数量
maxWait 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatements false 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxPoolPreparedStatementPerConnectionSize -1 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery 用来检测连接是否有效的sql,要求是一个查询语句,常用select ‘x’。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
validationQueryTimeout 单位:秒,检测连接是否有效的超时时间。底层调用jdbc Statement对象的void setQueryTimeout(int seconds)方法
testOnBorrow true 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn false 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testWhileIdle false 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
keepAlive false(1.0.28) 连接池中的minIdle数量以内的连接,空闲时间超过minEvictableIdleTimeMillis,则会执行keepAlive操作。
timeBetweenEvictionRunsMillis 1分钟(1.0.14) 有两个含义:1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun 30分钟(1.0.14) 不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis 连接保持空闲而不被驱逐的最小时间
connectionInitSqls 物理连接初始化的时候执行的sql
exceptionSorter 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat 日志用的filter:log4j 防御sql注入的filter:wall
proxyFilters 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

4) 自己写一个简单的连接池

猜你喜欢

转载自blog.csdn.net/feiyanaffection/article/details/81975056
今日推荐