一. JDBC体系结构
JDBC API支持用于数据库访问的两层和三层处理模型,但通常,JDBC体系结构由两层组成:
JDBC API:这提供了应用程序到JDBC管理器连接。
JDBC驱动程序API:这支持JDBC管理器到驱动程序连接。
JDBC API使用驱动程序管理器和特定于数据库的驱动程序来提供与异构数据库的透明连接。
二. JDBC核心组件
DriverManager: 此类管理数据库驱动程序列表。使用通信子协议将来自java应用程序的连接请求与适当的数据库驱动程序匹配。
Driver:此接口处理与数据库服务器的通信,我们很少会直接与Driver对象进行交互。而是使用DriverManager对象来管理这种类型的对象。
Connection:该界面具有用于联系数据库的所有方法。连接对象表示通信上下文,即与数据库的所有通信仅通过连接对象。
Statement:使用从此接口创建的对象将SQL语句提交到数据库。除了执行存储过程之外,一些派生接口还接受参数。
ResultSet:在使用Statement对象执行SQL查询后,这些对象保存从数据库检索的数据。它作为一个迭代器,允许我们移动其数据。
SQLException:此类处理数据库应用程序中发生的任何错误。
三. 使用步骤
导入包:需要包含包含数据库编程所需的JDBC类的包。大多数情况下,使用import java.sql.*就足够了。
注册JDBC驱动程序:要求您初始化驱动程序,以便您可以打开与数据库的通信通道。
打开连接:需要使用DriverManager.getConnection()方法创建一个Connection对象,该对象表示与数据库的物理连接。
执行查询:需要使用类型为Statement的对象来构建和提交SQL语句到数据库。
从结果集中提取数据:需要使用相应的ResultSet.getXXX()方法从结果集中检索数据。
释放资源:需要明确地关闭所有数据库资源,而不依赖于JVM的垃圾收集。
四. JDBC连接步骤
导入JDBC包:将Java语言的*import*语句添加到Java代码中导入所需的类。
注册JDBC驱动程序:此步骤将使JVM将所需的驱动程序实现加载到内存中,以便它可以满足您的JDBC请求。
数据库URL配置:这是为了创建一个格式正确的地址,指向要连接到的数据库。
创建连接对象:最后,调用DriverManager对象的getConnection()方法来建立实际的数据库连接。
(1)Class.forName();
注册驱动程序最常见的方法是使用Java的Class.forName()方法,将驱动程序的类文件动态加载到内存中,并将其自动注册。
try {
Class.forName("com.mysql.cj.jdbc.Driver");
}catch(ClassNotFoundException ex) {
System.out.println("Error: unable to load driver class!");
System.exit(1);
}
(2)DriverManager.registerDriver();
第二种方法是使用静态DriverManager.registerDriver()方法。
try {
Driver myDriver = new com.mysql.cj.jdbc.Driver();
DriverManager.registerDriver( myDriver );
}catch(ClassNotFoundException ex) {
System.out.println("Error: unable to load driver class!");
System.exit(1);
}
(3)数据库URL配置
加载驱动程序后,可以使用DriverManager.getConnection()方法建立连接。为了方便参考,让我列出三个重载的DriverManager.getConnection()方法
getConnection(String url)
getConnection(String url,Properties prop)
getConnection(String url,String user,String password)
(4)
RDBMS JDBC驱动程序名称 网址格式
MYSQL8 com.mysql.cj.jdbc.Driver jdbc:mysql://hostname:3306/databaseName?serverTimezone=UTC
MySQL com.mysql.jdbc.Driver jdbc:mysql://hostname:3306/databaseName
ORACLE oracle.jdbc.driver.OracleDriver jdbc:oracle:thin:@hostname:port Number:databaseName
DB2 com.ibm.db2.jdbc.net.DB2Driver jdbc:db2:hostname:port Number / databaseName
SYBASE com.sybase.jdbc.SybDriver jdbc:sybase:Tds:hostname:port Number / databaseName
(5)创建数据库连接对象
String URL = "jdbc:mysql://localhost:3306/yhp2?serverTimezone=UTC";
String USER = "username";
String PASS = "password";
Connection conn = DriverManager.getConnection(URL, USER, PASS);
(6)关闭数据库连接
为确保连接关闭,您可以在代码中提供一个“finally”块。一个finally块总是执行,不管是否发生异常。要关闭上面打开的连接,你应该调用close()方法如下conn.close();
(7)JDBC执行SQL语句
一旦获得了连接,我们可以与数据库进行交互。JDBC Statement和PreparedStatement接口定义了能够发送SQL命令并从数据库接收数据的方法和属性。
Statement
创建语句对象
在使用Statement对象执行SQL语句之前,需要使用Connection对象的createStatement()方法创建一个,例子:
Statement stmt = null;
stmt = conn.createStatement( );
创建Statement对象后,您可以使用它来执行一个SQL语句,其中有三个执行方法之一。
boolean execute(String SQL):如果可以检索到ResultSet对象,则返回一个布尔值true; 否则返回false。
int executeUpdate(String SQL):返回受SQL语句执行影响的行数。用于数据的增删改。
ResultSet executeQuery(String SQL):返回一个ResultSet对象。用于查询数据。
关闭Statement对象
一个简单的调用close()方法将执行该作业。如果先关闭Connection对象,它也会关闭Statement对象。但是,应始终显式关闭Statement对象,以确保正确清理。
五. SQL注入
就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。
比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击。
例子:
String username ="admin";
String password=" ‘abc‘ or 1=1 ";
String sql="select * from users where username= ‘"+username+"‘ and password="+password;
六. PreparedStatement(预状态通道)
该PreparedStatement的接口扩展了Statement接口,它为您提供了一个通用的Statement对象有两个优点附加功能。
PreparedStatement pstmt = null;
String SQL = "Update Employees SET age = ? WHERE id = ?";
pstmt = conn.prepareStatement(SQL);
JDBC中的所有参数都由?符号,这被称为参数标记。在执行SQL语句之前,必须为每个参数提供值。
所述的setXXX()方法将值绑定到所述参数,其中XXX代表要绑定到输入参数的值的Java数据类型。如果忘记提供值,将收到一个SQLException。
每个参数标记由其顺序位置引用。第一个标记表示位置1,下一个位置2等等。该方法与Java数组索引不同,从0开始。
对比statement和PreparedStatement:
(1)statement属于状态通道,PreparedStatement属于预状态通道
(2)预状态通道会先编译sql语句,再去执行,比statement执行效率高
(3)预状态通道支持占位符?,给占位符赋值的时候,位置从1开始
(4)预状态通道可以防止sql注入,原因:预状态通道在处理值的时候以字符串的方式处理
七. 数据库事务
(1)事务概述
事务开始于
连接到数据库上,并执行一条DML语句insert、update或delete
前一个事务结束后,又输入了另一条DML语句
事务结束于
执行commit或rollback语句。
执行一条DDL语句,例如create table语句,在这种情况下,会自动执行commit语句。
执行一条DDL语句,例如grant语句,在这种情况下,会自动执行commit。
断开与数据库的连接
执行了一条DML语句,该语句却失败了,在这种情况中,会为这个无效的DML语句执行rollback语句。
(2)事务应用
如果JDBC连接处于自动提交模式,默认情况下,则每个SQL语句在完成后都会提交到数据库。
事务使您能够控制是否和何时更改应用于数据库。它将单个SQL语句或一组SQL语句视为一个逻辑单元,如果任何语句失败,则整个事务将失败。
要启用手动事务支持,而不是JDBC驱动程序默认使用的自动提交模式,请使用Connection对象的setAutoCommit()方法。如果将boolean false传递给setAutoCommit(),则关闭自动提交。我们可以传递一个布尔值true来重新打开它。
(3)事务的提交和回滚
完成更改后,我们要提交更改,然后在连接对象上调用commit()方法,如下所示:
conn.commit( );
否则,要使用连接名为conn的数据库回滚更新,请使用以下代码。
conn.rollback( );
(4)事务案例——转账
try{
//将事务设置为手动提交
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
//用户1将钱转给用户2,用户1减钱,用户2加钱。
String SQL = "update money set send=send-100 where userid=1";
stmt.executeUpdate(SQL);
String SQL = "update money set send=send+100 where userid=2";
stmt.executeUpdate(SQL);
conn.commit();
}catch(SQLException se){
conn.rollback();
}
此时正常执行,用户1减钱,用户2加钱。
try{
//将事务设置为手动提交
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
//用户1将钱转给用户2,用户1减钱,用户2加钱。
String SQL = "update money set send=send-100 where userid=1";
stmt.executeUpdate(SQL);
String SQL = "update money set send=send+100 where userid=2";
stmt.executeUpdate(SQL);
System.out.println(5/0);
conn.commit();
}catch(SQLException se){
conn.rollback();
}
此时出现异常,回滚
try{
Statement stmt = conn.createStatement();
//用户1将钱转给用户2,用户1减钱,用户2加钱。
String SQL = "update money set send=send-100 where userid=1";
stmt.executeUpdate(SQL);
String SQL = "update money set send=send+100 where userid=2";
stmt.executeUpdate(SQL);
System.out.println(5/0);
conn.commit();
}catch(SQLException se){
conn.rollback();
}
取消手动提交,虽然出现异常,但是SQL语句还是执行了,即用户1减钱,用户2加钱,不符合运行的期望。
八. Savepoints
新的JDBC 3.0 Savepoint接口为您提供了额外的事务控制。
设置保存点时,可以在事务中定义逻辑回滚点。如果通过保存点发生错误,则可以使用回滚方法来撤消所有更改或仅保存在保存点之后所做的更改。
Connection对象有两种新的方法来帮助您管理保存点 :
(1)setSavepoint(String savepointName):定义新的保存点。它还返回一个Savepoint对象。
(2)releaseSavepoint(Savepoint savepointName):删除保存点。请注意,它需要一个Savepoint对象作为参数。此对象通常是由setSavepoint()方法生成的保存点。
try{
//将事务设置为手动提交
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
String SQL = "INSERT INTO Employees VALUES (106, 20, ‘Rita‘, ‘Tez‘)";
stmt.executeUpdate(SQL);
//设置保存点
Savepoint savepoint1 = conn.setSavepoint("Savepoint1");
String SQL = "INSERTED IN Employees VALUES (107, 22, ‘Sita‘, ‘Tez‘)";
stmt.executeUpdate(SQL);
conn.commit();
}catch(SQLException se){
conn.rollback(savepoint1);
conn.commit();
}
如果没有设置保存点,则有异常时回滚,上面两条SQL语句都无法执行。
在有保存点时,遇到异常会回滚到保存点的位置,如果catch里面有commit的话则会执行保存点前面的SQL语句。可以在满足某些需求时使用。
九. JDBC批处理
(1)Statement批处理
使用createStatement()方法创建Statement对象。
使用setAutoCommit()将auto-commit设置为false 。
使用addBatch()方法在创建的语句对象上添加您喜欢的SQL语句到批处理中。
在创建的语句对象上使用executeBatch()方法执行所有SQL语句。
最后,使用commit()方法提交所有更改。
Statement stmt = conn.createStatement();
conn.setAutoCommit(false);
//sql1
String SQL = "INSERT INTO Employees (id, first, last, age) VALUES(200,‘Zia‘,‘Ali‘, 30)";
stmt.addBatch(SQL);
//sql2
String SQL = "INSERT INTO Employees (id, first, last, age) VALUES(201,‘Raj‘,‘Kumar‘, 35)";
stmt.addBatch(SQL);
//sql3
String SQL = "UPDATE Employees SET age = 35 WHERE id = 100";
stmt.addBatch(SQL);
int[] count = stmt.executeBatch();
conn.commit();
(2)PreparedStatement批处理
使用占位符创建SQL语句。
使用prepareStatement() 方法创建PrepareStatement对象。
使用setAutoCommit()将auto-commit设置为false 。
使用addBatch()方法在创建的语句对象上添加您喜欢的SQL语句到批处理中。
在创建的语句对象上使用executeBatch()方法执行所有SQL语句。
最后,使用commit()方法提交所有更改。
String SQL = "INSERT INTO Employees (id, first, last, age) VALUES(?, ?, ?, ?)";
PreparedStatement pstmt = conn.prepareStatement(SQL);
conn.setAutoCommit(false);
pstmt.setInt( 1, 400 );
pstmt.setString( 2, "Pappu" );
pstmt.setString( 3, "Singh" );
pstmt.setInt( 4, 33 );
pstmt.addBatch();
pstmt.setInt( 1, 401 );
pstmt.setString( 2, "Pawan" );
pstmt.setString( 3, "Singh" );
pstmt.setInt( 4, 31 );
pstmt.addBatch();
int[] count = stmt.executeBatch();
conn.commit();
十. properties文件保存数据库信息-特点:key-value存储方式
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/yhp
user=root
password=123456
工具类中读取属性文件:
方式1:
InputStream inputStream = 当前类名.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(inputStream);
dirverName = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("user");
password = properties.getProperty("password");
方式2:
static{
ResourceBundle bundle = ResourceBundle.getBundle("db");
driver = bundle.getString("driver");
url = bundle.getString("url");
username = bundle.getString("user");
password = bundle.getString("password");
}
使用ResourceBundle访问本地资源
在设计时,我们往往需要访问一些适合本地修改的配置信息,如果作为静态变量,那么每次修改都需要重新编译一个class,.config保存此类信息并不适合,这时我们需要ResourceBundle。
通过ResourceBundle,我们需要访问位于/WEB-INF/classes目录下的一个后缀名为properties的文本类型文件,从里面读取我们需要的值。
十一. 连接池
(1)数据连接池原理
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等,也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
(2)各参数含义
最小连接数:
是数据库一直保持的数据库连接数,所以如果应用程序对数据库连接的使用量不大,将有大量的数据库资源被浪费。
初始化连接数:
连接池启动时创建的初始化数据库连接数量。
最大连接数:
是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求被加入到等待队列中。
最大等待时间:
当没有可用连接时,连接池等待连接被归还的最大时间,超过时间则抛出异常,可设置参数为0或者负数使得无限等待(根据不同连接池配置)。
注1:在DBCP连接池的配置中,还有一个maxIdle的属性,表示最大空闲连接数,超过的空闲连接将被释放,默认值为8。对应的该属性在Druid连接池已不再使用,配置了也没有效果,c3p0连接池则没有对应的属性。
注2:数据库连接池在初始化的时候会创建initialSize个连接,当有数据库操作时,会从池中取出一个连接。如果当前池中正在使用的连接数等于maxActive,则会等待一段时间,等待其他操作释放掉某一个连接,如果这个等待时间超过了maxWait,则会报错;如果当前正在使用的连接数没有达到maxActive,则判断当前是否空闲连接,如果有则直接使用空闲连接,如果没有则新建立一个连接。在连接使用完毕后,不是将其物理连接关闭,而是将其放入池中等待其他操作复用。
(3)常用连接池
DBCP连接池:
DBCP是一个依赖Jakarta commons-pool对象池机制的数据库连接池.DBCP可以直接的在应用程序中使用,Tomcat的数据源使用的就是DBCP。
使用:
1. 导入相应jar包
mysql-jdbc.jar
commons-dbcp.jar
commons-pool.jar
2. 项目中添加配置
文件名称: info.properties
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/day2
username=root
password=111
#<!-- 初始化连接 -->
initialSize=10
#最大连接数量
maxActive=50
#<!-- 最大空闲连接 -->
maxIdle=20
#<!-- 最小空闲连接 -->
minIdle=5
#<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->
maxWait=6000
3. 代码实现
//1.创建dbcp的工具类对象
static BasicDataSource datasource=new BasicDataSource();
//2.加载驱动
static {
try {
//加载属性文件
//1.使用工具类 ,参数是属性文件的文件名(不要加后缀)
ResourceBundle bundle = ResourceBundle.getBundle("db");
driverClass = bundle.getString("driverclass");
url = bundle.getString("url");
username = bundle.getString("uname");
password = bundle.getString("upass");
init=bundle.getString("initsize");
//2.将驱动地址等信息传递给dbcp
datasource.setDriverClassName(driverClass);
datasource.setUrl(url);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setInitialSize(Integer.parseInt(init));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//3.获得连接
public static Connection getConn() {
try {
con= datasource.getConnection();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return con;
}
C3P0连接池:
c3p0是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。
c3p0与dbcp区别:
dbcp没有自动回收空闲连接的功能;c3p0有自动回收空闲连接功能
dbcp需要手动设置配置文件;c3p0不需要手动设置
实现方式:
1. 手动设置 ComboPooledDataSource
2. 加载配置文件方式
导入jar包
c3p0-0.9.1.2.jar
mysql-connector-java-5.0.8.jar
添加配置文件
c3p0是在外部添加配置文件,工具直接进行应用,因为直接引用,所以要求固定的命名和文件位置
文件位置: src
文件命名:c3p0-config.xml/c3p0-config.properties
<?xml version="1.0" encoding="utf-8"?>
<c3p0-config>
<!-- 默认配置,如果没有指定则使用这个配置 -->
<default-config>
<!-- 基本配置 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day2</property>
<property name="user">root</property>
<property name="password">111</property>
<!--扩展配置-->
<!-- 连接超过30秒报错-->
<property name="checkoutTimeout">30000</property>
<!--30秒检查空闲连接 -->
<property name="idleConnectionTestPeriod">30</property>
<property name="initialPoolSize">10</property>
<!-- 30秒不适用丢弃-->
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
<property name="maxStatements">200</property>
</default-config>
<!-- 命名的配置 -->
<named-config name="abc">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day2</property>
<property name="user">root</property>
<property name="password">111</property>
<!-- 如果池中数据连接不够时一次增长多少个 -->
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">20</property>
<property name="minPoolSize">10</property>
<property name="maxPoolSize">40</property>
<property name="maxStatements">20</property>
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
注意: c3p0的配置文件内部可以包含命名配置文件和默认配置文件!默认是选择默认配置!如果需要切换命名配置可以在创建c3p0连接池的时候填入命名即可!
定义代码:
Connection con=null;
ComboPooledDataSource db=new ComboPooledDataSource("abc");
public Connection getCon(){
try {
con=db.getConnection();
System.out.println("初始化的链接数量:"+db.getInitialPoolSize());
} catch (SQLException e) {
e.printStackTrace();
}
return con;
}
Druid(德鲁伊)连接池:
阿里出品,淘宝和支付宝专用数据库连接池,但它不仅仅是一个数据库连接池,它还包含一个ProxyDriver(代理驱动),一系列内置的JDBC组件库,一个SQL Parser(sql解析器)。支持所有JDBC兼容的数据库,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等等。
Druid针对Oracle和MySql做了特别优化,比如Oracle的PS Cache内存占用优化,MySql的ping检测优化。
Druid提供了MySql、Oracle、Postgresql、SQL-92的SQL的完整支持,这是一个手写的高性能SQLParser,支持Visitor模式,使得分析SQL的抽象语法树很方便。
简单SQL语句用时10微秒以内,复杂SQL用时30微秒。
通过Druid提供的SQL Parser可以在JDBC层拦截SQL做相应处理,比如说分库分表、审计等。Druid防御SQL注入攻击的WallFilter就是通过Druid的SQL Parser分析语义实现的。
Druid 是目前比较流行的高性能的,分布式列存储的OLAP框架(具体来说是MOLAP)。它有如下几个特点:
一. 亚秒级查询
druid提供了快速的聚合能力以及亚秒级的OLAP查询能力,多租户的设计,是面向用户分析应用的理想方式。
二. 实时数据注入
druid支持流数据的注入,并提供了数据的事件驱动,保证在实时和离线环境下事件的实效性和统一性。
三. 可扩展的PB级存储
druid集群可以很方便的扩容到PB的数据量,每秒百万级别的数据注入。即便在加大数据规模的情况下,也能保证时其效性。
四. 多环境部署
druid既可以运行在商业的硬件上,也可以运行在云上。它可以从多种数据系统中注入数据,包括hadoop,spark,kafka,storm和samza等
五. 丰富的社区
druid拥有丰富的社区,供大家学习
使用步骤:
导入jar包
编写工具类
/**
* 阿里的数据库连接池
* 性能最好的
* Druid
* */
public class DruidUtils {
//声明连接池对象
private static DruidDataSource ds;
static{
//实例化数据库连接池对象
ds=new DruidDataSource();
//实例化配置对象
Properties properties=new Properties();
try {
//加载配置文件内容
properties.load(DruidUtils.class.getResourceAsStream("dbcpconfig.properties"));
//设置驱动类全称
ds.setDriverClassName(properties.getProperty("driverClassName"));
//设置连接的数据库
ds.setUrl(properties.getProperty("url"));
//设置用户名
ds.setUsername(properties.getProperty("username"));
//设置密码
ds.setPassword(properties.getProperty("password"));
//设置最大连接数量
ds.setMaxActive(Integer.parseInt(properties.getProperty("maxActive")));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//获取连接对象
public static Connection getConnection() {
try {
return ds.getConnection();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}
注:在Druid连接池的配置中,driverClassName可配可不配,如果不配置会根据url自动识别dbType(数据库类型),然后选择相应的driverClassName。