一、代码展示(简洁版+规范版)
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
/**
* JDBC简洁写法
*/
public class Jdbc {
// 四大参数
public static final String driverClassName = "com.mysql.jdbc.Driver";
public static final String URL = "jdbc:mysql://localhost:3306/testdb";
public static final String USER = "root";
public static final String PASSWORD = "root";
public static void main(String[] args) throws Exception{
/**
* 一.获取连接
*/
//1.加载驱动类(注册驱动)
Class.forName(driverClassName);
//等价于 com.mysql.jdbc.Driver driver = new com.mysql.jdbc.Driver(); + DriverManage.registerDriver(driver);
//原因是因为 加载驱动类的时候 会默认执行 静态代码块,其中包含 注册驱动。
//2. 获得数据库连接
Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
/**
* 二.操作数据库(int executeUpdate、ResultSet executeQuery)
*/
//0.利用conn获取声明对象
Statement stmt = conn.createStatement(); //没带参数之后生成的结果集:不滚动、不敏感、不可更新
//Statement stmt = conn.createStatement(int,int);
//1.实现 insert into
String sqlInsert = "insert into employer values(54,'嘿嘿嘿','哒哒哒','呲呲呲')";
int a = stmt.executeUpdate(sqlInsert);
//2.实现 delete from
String sqlDelete = "delete from employer where ID='50'";
stmt.executeUpdate(sqlDelete);
//3.实现 update set
String sqlUpdate = "update employer set LOGO='哒哒哒' where ID=52";
stmt.executeUpdate(sqlUpdate);
//4.实现 select
ResultSet rs = stmt.executeQuery("SELECT * FROM employer");
//如果有数据,rs.next()返回true
while(rs.next()){
System.out.println(rs.getInt("ID")+" LOGO:"+rs.getString("LOGO"));
}
/**
* 三、关闭资源
*/
rs.close();
stmt.close();
conn.close();//连接必须关闭
}
}
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
/**
* 规范化的Jdbc抒写形式
*/
public class JdbcNorm {
// 四大参数
public static final String driverClassName = "com.mysql.jdbc.Driver";
public static final String URL = "jdbc:mysql://localhost:3306/testdb";
public static final String USER = "root";
public static final String PASSWORD = "root";
public static void main(String[] args) throws Exception{
//在上方声明conn和stmt,方便finally最后处理这些资源,如果不这样写,finally就获取不到这些引用(不在一个代码块)
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
/**
* 一.获取连接
*/
//1.加载驱动类(注册驱动)
Class.forName(driverClassName);
//等价于 com.mysql.jdbc.Driver driver = new com.mysql.jdbc.Driver(); + DriverManage.registerDriver(driver);
//原因是因为 加载驱动类的时候 会默认执行 静态代码块,其中包含 注册驱动。
//2. 获得数据库连接
conn = DriverManager.getConnection(URL, USER, PASSWORD);
/**
* 二.操作数据库(int executeUpdate(String sql)、ResultSet executeQuery(String sql))
*/
//0.利用conn获取声明对象
stmt = conn.createStatement();
//1.实现 insert into
String sqlInsert = "insert into employer values(55,'嘿嘿嘿','哒哒哒','呲呲呲')";
int a = stmt.executeUpdate(sqlInsert);
//2.实现 delete from
String sqlDelete = "delete from employer where ID='50'";
stmt.executeUpdate(sqlDelete);
//3.实现 update set
String sqlUpdate = "update employer set LOGO='哒哒哒' where ID=52";
stmt.executeUpdate(sqlUpdate);
//4.实现 select
rs = stmt.executeQuery("SELECT * FROM employer");
//如果有数据,rs.next()返回true
while(rs.next()){
System.out.println(rs.getInt("ID")+" LOGO:"+rs.getString("LOGO"));
}
}catch (Exception exception){ //处理异常
throw new RuntimeException(exception);
}finally { //最终必须要执行的代码
/**
* 三、关闭资源
*/
if(rs!=null){
rs.close();
}
if(stmt!=null){
stmt.close();
}
if(conn!=null){
conn.close();//连接必须关闭
}
}
}
}
二、JDBC主要对象介绍
1、DriverManager
该类有重要方法:
1.public static synchronized void registerDriver(java.sql.Driver driver)
2.private static Connection getConnection
在我们平时写时只需要下面两行语句即可获取连接:
语句1:Class.forName(driverClassName);
语句2:Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
【注】为什么所以语句1和语句2有联系,即语句一是如何到DriverManager的
这是因为语句一在加载类时,驱动类com.mysql.jdbc.Driver默认在静态代码块里面写了registerDriver方法,
因此语句一已经隐式的将驱动类注册到驱动管理类DriverManager中了,接了来直接获取连接即可。
2、Connection
Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
//Connection类中最重要的方法就是为了获取statement
Statement stmt = conn.createStatement(); //没带参数之后生成的结果集:不滚动、不敏感、不可更新
Statement stmt = conn.createStatement(int 参数一,int 参数二);
1.参数一的选项(1.3很少用):
(1.1)ResultSet.TYPE_FORWARD_ONLY(不滚动): 不滚动结果集
(1.2)ResultSet.TYPE_SCROLL_INSENSITIVE(滚动敏感): 滚动结果集,但结果集的数据不会跟随数据库变化
(1.3)ResultSet.TYPE_SCROLL_SENSITIVE(滚动不敏感): 滚动结果集,结果集的数据会跟随数据库变化
2.参数二的选项:
(2.1)CONCUR_READ_ONLY: 结果集是只读的,不能通过修改结果集反而影响数据库
(2.2)CONCUR_Update: 结果集是可更新的,可以通过修改结果集来影响数据库
----------------------------------------------------------------------------------
【注】
当使用 "Statement stmt = conn.createStatement(~);" 语句创建Statement对象时,就已经确定了stmt生成的结果集的特性。
结果集特性:
(1)是否可滚动
(2)是否敏感
(3)是否可更新
3、Statement
Statement最为重要的方法是:
1、ResultSet executeQuery(String sql)
语句1用来执行查询操作,返回的是结果集ResultSet
2、int executeUpdate(String sql)
语句2用来执行更新操作(增删改),其实也可以执行DDL语句,但我们通常只用来执行DML语句,返回更新语句影响的行数
3、boolean execute(String sql)
语句3可以执行DDL+DML+DQL,返回的是boolean类型表示SQL语句是否有结果集!
如果execute()执行的是更新语句,那么还需要通过 int getUpdateCount()方法来获取更新语句影响的行数
如果execute()执行的是查询语句,那么还需要通过 ResultSet getResult()来获取结果集合。
4、ResultSet的特性
(1)是否可滚动
(2)是否敏感
(3)是否可更新
5、ResultSet之 滚动 结果集
ResultSet表示结果集,它是一个二维表格。ResultSet内部维护一个行光标(游标,从1开始),
ResultSet提供了一系列的方法来移动游标,可用(rs.方法)来使用:
1、void beforeFirst(): 把光标放到第一行的前面,这也是光标默认的位置
2、void afterLast(): 把光标放到最后一行的后面
3、boolean first(): 把光标放到第一行的位置上,返回值表示调控光标是否成功
4、boolean last(): 把光标放到最后一行的位置上
5、boolean isBeforeFirst(): 当前光标是否在第一行前面
6、boolean isAfterLast(): 当前光标是否在最后一行后面
7、boolean ifFirst(): 当前光标是否在第一行
8、boolean ifLast(): 当前光标是否在最后一行
9、boolean previous(): 把光标向上挪一行
10、boolean next(): 把光标向下挪一行
11、boolean relative(int row): 相对位移,当row为正数时表示向下移动row行,负数时表示向上移动row行
12、boolean absolute(int row): 绝对位移,把光标移动到指定的行上
13、int getRow(): 返回当前光标所有行
【实例:获取结果集的行数】
rs.last(); //先把光标放到最后一行的位置上
int row = rs.getRow(); //当前光标所有行即为行数
【实例:获取结果集的列数】
//获取元数据 (ResultSetMetaData 类型的方法)
ResultSetMetaData resultSetMetaData = rs.getMetaData();
//获取结果集的列数 (int 类型的方法)
int count = rs.getMetaData().getColumnCount();
//获取指定列的列名 (String 类型的方法)
String columnName = rs.getMetaData().getColumnName(int columnIndex);
上面总结的方法分为两类:(1)用来判断游标位置 (2)用来移动游标。
但如过结果集是"不可滚动"的,那只能使用 rs.next() 来移动游标,而例如 first()、last()等方法都不能使用。
要判断结果集是否可滚动,需要从 Connection类的createStatement()语句来判断,即connection创建的statement决定了使用statement创建的ResultSet是否支持滚动。
6、ResultSet之获取 列数据
可以通过rs.next()使ResultSet的游标向下移动,当游标移动到所需要的行时就可以来获取该行的数据了,ResultSet提供了一系列获取列数据的方法:
方式一:通过列的索引,例如 1
1、String getString(int columnIndex)
2、int getInt(int columnIndex)
3、float getFloat(int columnIndex)
4、double getDouble(int columnIndex)
5、Object getObject(int columnIndex) //获取Object类型的数据
6、boolean getBoolean(int columnIndex) //获取boolean类型的数据
【注】参数columnIndex为列的索引,索引从1开始,与数组不同。在我们解析ResultSet每一列的数据时,
若我们知道每一列的数据类型,我们可以通过"类型 变量 =rs.get类型(columnIndex/columnName)"来获取数据
但如果我们不知道列的数据类型时,我们应该使用类型 变量 =rs.getObject(columnIndex)方法来获取
方式二:通过列的名称,例如 "id"
1、String getString(String columnName)
2、int getInt(String columnName)
3、float getFloat(String columnName)
4、double getDouble(String columnName)
5、Object getObject(String columnName)
6、boolean getBoolean(String columnName)
三、PreparedStatement
PreparedStatement是statement的子接口
强大之处:
防SQL攻击;
提高代码可读性、可维护性
提高效率
1、PreparedStatement的用法
//学习PreparedStatement的用法
//1、给出SQL模板语句,参数用?替代,可以防止SQL攻击
String sql = "select * from user where username=? and password=?";
//2、调用conn.prepareStatement方法得到PreparedStatement
PreparedStatement pastmt = conn.prepareStatement(sql);
//3、为参数赋值pastmt.setXxx
int username = 123;
String password = "123";
pastmt.setInt(1,username);
pastmt.setString(2,password);
//4、调用pstmt的executeUpdate()或executeQuery(),但它的方法都没有参数,因为sql生来就绑定过了
ResultSet rs = pastmt.executeQuery();
while (rs.next())
{
System.out.println(rs.getInt("username"));
}
2、什么是SQL攻击
在需要用户输入的地方,用户如果输入的是SQL语句的片段,最终用户输入的SQL片段与我们DAO中写的SQL语句组合成一个完整的SQL语句!例如用户在登录时输入的用户名和密码都是SQL语句的片段,那么即使数据库没有账号也能登录。
例如:
//登陆时的SQL查询语句为:
sql:"select * from user where username=" + username + "and password=" + password;
//而用户输入的账号密码分别为:
username(用户输入): 'a' or 'a'='a'
password(用户输入): 'a' or 'a'='a'
//因此由于参数的读取而 重组了 SQL语句,引起了SQL攻击,组合后的SQL语句变成
sql:"select * from user where username='a' or 'a'='a' and password='a' or 'a'='a'"
【注】
组合后的sql中两个or注定查询成功
3、预处理的原理
1、服务器的工作
a.校验sql语句的语法
b.编译:一个与函数相似的东西
c.执行:调用函数
2、PreparedStatement
前提:连接的数据库必须支持预处理,几乎没有不支持的
每个pstmt(PreparedStatement对象)都和一个SQL模板绑定在一起,先把SQL模板给数据库,数据库先校验,再进行编译,执行时只会把参数传递过去而已
若二次执行时,就不用再次校验语法,也不用再次编译!
四、JdbcUtils工具类
1、 jdbcconfig.properies:用来定义参数
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=root
2、JdbcUtils类:用来获取连接
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class JdbcUtils {
public static Properties props = null;
//该静态方法体里面的东西只在该类被加载时被执行一次
static {
//1、加载配置文件
try {
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbcconfig.properties");
props = new Properties();
props.load(in);
}catch (IOException e){
throw new RuntimeException();
}
//2
try{
Class.forName(props.getProperty("driverClassName"));
}catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws IOException,ClassNotFoundException, SQLException {
/**
* 1.加载配置文件
* 2.加载驱动类
* 3.获取连接
*/
// 步骤一二放在静态方法体中执行,因为不需要改变
//3、得到connection
return DriverManager.getConnection(props.getProperty("url"),props.getProperty("username"),props.getProperty("password"));
}
}
3、JdbcUtilsTest类:测试JdbcUtils类是否返回连接
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
public class JdbcUtilsTest {
public static void main(String[] args) throws SQLException, IOException, ClassNotFoundException {
Connection conn = JdbcUtils.getConnection();
System.out.println(conn);
}
}
五、批处理
1、Statement批处理
批处理就是一批一批的处理,而不是一个一个的处理!当你有10条SQL语句要执行的时候,一次向服务器发送一条SQL语句效率会很差!那么这时就需要使用批处理,即一次向服务器发送多条SQL语句,然后由服务器一次性处理。
批处理只针对更新(增删改)语句,和查询没有关系。
可以多次调用Statement类的addBatch(String sql)方法,把需要执行的所有SQL语句添加到一个"批"中,然后调用Statement类的executeBatch()方法来执行当前"批"中的语句。
void addBatch(String sql): 添加一条语句到"批"中
int[] executeBatch(): 执行"批"中所有语句,返回值表示每条语句所影响的行数据
void clearBatch(): 清空"批"中的所有SQL语句
【实例】
for(int i=0;i<0;i++){
int id = 1 + i;
String name = "name" + i ;
String sql = "insert into user values('"+number+"','"+name+"')";
stmt.addBatch(sql); 添加SQL语句到批中
}
stmt.executeBatch(); 执行批中的所有语句
stmt.clearBatch(); 清空批中的所有语句
当执行了批中的语句后,批中的SQL语句就会被清空!
2、PreparedStatement批处理
PreparedStatement的批处理有所不同,因为每个PreparedStatement对象都绑定一条sql模板,所以向PreparedStatement中添加的不是sql语句,而是通过?给参数赋值
pstmt对象内部有集合,步骤:
0、定义sql模板,得到pstmt
1、用循环疯狂向pstmt中添加sql参数,他自己有模板
2、调用执行方法
【例如】
0、定义sql模板,得到pstmt
Connection conn = JdbcUtils.getConnection();
String sql = "insert into user values(?,?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
1、疯狂添加参数
for(int i=0;i<1000;i++){
pstmt.setInt(1,i+1);
pstmt.setString(2,"name"+i);
pstmt.addBatch(); 添加批,这一组参数就保存到集合中了。
}
2、调用执行方法
pstmt.executeBatch();
pstmt.clearBathc();
【注】
MySQL默认关闭批处理,若要开启需要手动开启(更改获取连接时的url):
url=jdbc:mysql://localhost:3306/user?rewriteBatchStatements=true
?后的内容即为打开批处理操作
六、事务处理
事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元)。
1、事务的四大特性(ACID)
1.原子性(atomicity):事务中所有操作是不可再分割的原子单位。事务中所有操作要么全么执行成功,要么全部执行失败。
2.一致性(consistency):事务执行后,数据库状态与其他业务规则保持一致。例如转账业务,无论事务执行成功与否,参与转账的两个账号余额之和不变。
3.隔离性(isolation):隔离性是指在并发操作中,不同的事务应该隔离开来,是每个并发的事务之间不会相互干扰。
4.持久性(durability):一旦事务提交成功,事务的所有数据库操作都必须被持久化到数据库中,即使提交事务后数据库马上崩溃,也必须能保证通过某种机制恢复数据。
2、MySQL开启和关闭事务
在默认情况下,MySQL每执行一条SQL语句,都是一个单独的事务。如果需要在一个事务中包含多条SQL语句,那么需要开启事务和结束事务。
开启事务:start transaction
结束事务:commit 或 rollback
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> update account set balance=900 where name='zs';
Query OK, 1 row affected (0.02 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update account set balance=1100 where name='ls';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | zs | 900 |
| 2 | ls | 1100 |
| 3 | ww | 1000 |
+----+------+---------+
3 rows in set (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | zs | 1000 |
| 2 | ls | 1000 |
| 3 | ww | 1000 |
+----+------+---------+
3 rows in set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> update account set balance=900 where name='zs';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update account set balance=1100 where name='ls';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | zs | 900 |
| 2 | ls | 1100 |
| 3 | ww | 1000 |
+----+------+---------+
3 rows in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;
+----+------+---------+
| id | name | balance |
+----+------+---------+
| 1 | zs | 900 |
| 2 | ls | 1100 |
| 3 | ww | 1000 |
+----+------+---------+
3 rows in set (0.00 sec)
【注】commit后再rollback将不会回滚事务。
3、JDBC完成事务处理(通过Connection完成事务处理)
同一事务中的所有操作,都必须使用同一个Connection对象!
Connection的三个方法与事务相关:
1.setAutoCommit(boolean):设置是否自动提交事务,true表示自动提交即每条SQL语句都是一个单独的事务,false表示开启了事务。
【注】 conn.setAutoCommit(false) :表示开启事务
2.commit():提交结束事务
【注】 conn.commit() :表示提交事务
3.rollback():回滚结束事务
【注】 conn.rollback():表示回滚事务
JDBC处理事务的代码格式:
try{
conn.setAtuoCommit(false);开启事务
... ...
conn.commit; 提交事务
}catch(){
conn.rollback(); 若"..."代码出现异常则不会提交事务,而是回滚事务
}
简要代码实例:
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
public class JdbcTransaction {
/**
* 模拟dao层操作数据库
*/
public static void updateBalance(Connection conn,String name,int balance) throws SQLException, IOException, ClassNotFoundException {
try{
/**
* 1.抒写SQL模板,并完成创建pstmt
*/
String sql = "update account set balance=balance+? where name=?";
PreparedStatement pstmt = conn.prepareStatement(sql);
/**
* 2.对参数赋值
*/
pstmt.setDouble(1,balance);
pstmt.setString(2,name);
/**
* 3.执行
*/
pstmt.executeUpdate();
}catch(Exception e) {
throw new RuntimeException(e);
}
}
/**
* 转账操作
* @param args
*/
public static void main(String[] args) throws SQLException, IOException, ClassNotFoundException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入转账人的姓名:");
String name1 = scanner.next();
System.out.println("请输入转账金额:");
int balance = scanner.nextInt();
System.out.println("请输入收人的姓名:");
String name2 = scanner.next();
Connection conn = JdbcUtils.getConnection();
try{
//开启事务
conn.setAutoCommit(false);
//调用updateBalance方法
updateBalance(conn,name1,-balance); //转账人
updateBalance(conn,name2,+balance);//收涨人
//提交事务
conn.commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
【注】因为同一个事务的操作要用同一个Connection,因此需要把conn当作参数前去调用方法。
4、事务的隔离级别
4.1、事务的并发读问题
脏读: 一个事物读取到另一个事务还未提交的数据(脏数据),形成脏读。
不可重复读: 一个事务两次读取同一行数据,结果得到不同状态结果,如中间正好另一个事务更新了该数据,两次结果相异,不可信任。
幻读(虚读): 一个事务执行两次查询,第二次查询比第一次多出或少一些数据,造成两次结果不一致。只是另一个事务在这两次查询中间插入或者删除了数据造成的。
【案例】
【脏读】
事务1:张三给李四转账100元
事务2:李四查看自己的账户
t1:事务1:开始事务
t2:事务1:张三给李四转账100元
t3:事务2:开始事务
t4:事务2:李四查看自己的账户,看到账户多出100元(脏读)
t5:事务2:提交事务
t6:事务1:回滚事务,回到转账之前的状态
【不可重复读】
事务1:酒店查看两次1048号房间状态
事务2:预订1048号房间
t1:事务1:开始事务
t2:事务1:查看1048号房间状态为空闲
t3:事务2:开始事务
t4:事务2:预定1048号房间
t5:事务2:提交事务
t6:事务1:再次查看1048号房间状态为使用
t7:事务1:提交事务
对同一记录的两次查询结果不一致!
【幻读】
事务1:对酒店房间预订记录两次统计
事务2:添加一条预订房间记录
t1:事务1:开始事务
t2:事务1:统计预订记录100条
t3:事务2:开始事务
t4:事务2:添加一条预订房间记录
t5:事务2:提交事务
t6:事务1:再次统计预订记录为101记录
t7:事务1:提交
对同一表的两次查询不一致!
4.2、四大隔离级别
4个等级的事务隔离级别,再相同数据的环境下,使用相同的输入执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同事物隔离级别能够解决的数据并发问题是不同的:
为了解决数据库事务并发运行时的各种问题数据库系统提供四种事务隔离级别:
1. Serializable 串行化 (没人用)
不会出现任何并发问题,因为对同一数据的访问是串行的,并非并发访问的
性能最差(容易死锁)
2. Repeatable Read 可重复读 (MySQL 默认隔离级别)
防止 "脏读"、"不可重复读",不能防止 "幻读"
性能比 "串行化" 好
3. Read Commited 读已提交 (Oracle 默认隔离级别)
防止 "脏读",不能解决 "不可重复读",不能防止 "幻读"
性能比 "串行化"、"可重复读"好
4. Read Uncommited 读未提交 (没人用)
可能出现任何事务并发问题
性能 最好
4.3、MySQL 隔离级别
可通过命令进行查看隔离级别:
select @@tx_isolation;
也可以通过命令设置当前连接的隔离级别:
set transaction isolationlevel [4选1]
4.4、JDBC 设置隔离级别
conn.setTransactionIsolation(int level)
参数可选:
Connection.TRANSACTION_READ_UNCOMMITTED
Connection.TRANSACTION_READ_COMMITTED
Connection.TRANSACTION_REPEATABLE_READ
Connection.TRANSACTION_SERIALIZABLE
七、数据库连接池
程序开发过程中,存在很多问题:
首先,每一次 web 请求都要建立一次数据库连接。建立连接是一个费时的活动,每次都得花费 0.05s~1s 的时间,而且系统还要分配内存资源。这个时间对于一次或几次数据库操作,或许感觉不出系统有多大的开销。可是对于现在的 web 应用,尤其是大型电子商务网站,同时有几百人甚至几千人在线是很正常的事。在这种情况下,频繁的进行数据库连接操作势必占用很多的系统资源,网站的响应速度必定下降,严重的甚至会造成服务器的崩溃。不是危言耸听,这就是制约某些电子商务网站发展的技术瓶颈问题。
其次,对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将不得不重启数据库。还有,这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
通过上面的分析,我们可以看出来,“数据库连接”是一种稀缺的资源,为了保障网站的正常使用,应该对其进行妥善管理。其实我们查询完数据库后,如果不关闭连接,而是暂时存放起来,当别人使用时,把这个连接给他们使用。就避免了一次建立数据库连接和断开的操作时间消耗。
数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽地与数据库连接。 更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量、使用情况,为系统开发﹑测试及性能调整提供依据。
1、自定义连接池
我们自己尝试开发一个连接池,来为上面的查询业务提供数据库连接服务:
①、编写 class 实现 DataSource 接口
②、在 class的构造器一次性创建 10 个连接,将连接保存 LinkedList 中
③、实现 getConnection, 从 LinkedList 中返回一个连接
④、提供将连接放回连接池中的方法(即close方法)
public class MyDataSource implements DataSource {
// 因为 LinkedList 是用链表实现的,对于增删实现起来比较容易
LinkedList<Connection> dataSources = new LinkedList<Connection>();
// 初始化连接数量
public MyDataSource() {
// 问题:每次new MyDataSource 都会建立 10 个链接,可使用单例设计模式解决此类问题
for(int i = 0; i < 10; i++) {
try {
// 1、装载 sqlserver 驱动对象
DriverManager.registerDriver(new SQLServerDriver());
// 2、通过 JDBC 建立数据库连接
Connection con =DriverManager.getConnection("jdbc:sqlserver://192.168.2.6:1433;DatabaseName=customer", "sa", "123");
// 3、将连接加入连接池中
dataSources.add(con);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public Connection getConnection() throws SQLException {
// 取出连接池中一个连接
final Connection conn = dataSources.removeFirst(); // 删除第一个连接返回
return conn;
}
// 将连接放回连接池
public void close(Connection conn) {
dataSources.add(conn);
}
}
2、dbcp连接池
1.1、池参数(所有池参数都有默认值)
初始大小、最小空闲连接数、最大空闲连接数、最大连接数、最大等待时间
1.2、四大连接参数
DriverClassName、url、username、password
1.3、实现的接口
连接池必须实现:javax.sql.DataSource接口!
【注】连接池返回的Connection对象,它的close()方法不是关闭连接,而是把连接归还给池!
3、c3p0连接池