63、JDBC的理解
Jdbc:Java database connection,java数据库连接。数据库管理系统(mysql、oracle等)是很多的,每个数据库管理系统支持的命令是不一样的。
Java只定义接口,让数据库厂商自己实现接口,对于我们开发者而言。只需要导入对应厂商开发的实现即可。然后以接口方式进行调用。(mysql + mysql驱动(实现)+jdbc)。
64、数据库连接池
64.1、早期进行数据库操作
1、原理:一般来说,java 应用程序访问数据库的过程是:
①、加载数据库驱动程序;
②、通过 jdbc 建立数据库连接;
③、访问数据库,执行 sql 语句;
④、断开数据库连接。
2、代码
// 查询所有用户
Public void FindAllUsers() {
// 1、装载 sqlserver 驱动对象
DriverManager.registerDriver(new SQLServerDriver());
// 2、通过 JDBC 建立数据库连接
Connection con = DriverManager.getConnection("jdbc:sqlserver://192.168.2.6:1433;DatabaseName=customer", "sa", "123");
// 3、创建状态
Statement state = con.createStatement();
// 4、查询数据库并返回结果
ResultSet result = state.executeQuery("select * from users");
// 5、输出查询结果
while(result.next()) {
System.out.println(result.getString("email"));
}
// 6、断开数据库连接
result.close();
state.close();
con.close();
}
3、分析
程序开发过程中,存在很多问题:
首先,每一次 web 请求都要建立一次数据库连接。建立连接是一个费时的活动,每次都得花费 0.05s~1s 的时间,而且系统还要分配内存资源。这个时间对于一次或几次数据库操作,或许感觉不出系统有多大的开销。可是对于现在的 web 应用,尤其是大型电子商务网站,同时有几百人甚至几千人在线是很正常的事。在这种情况下,频繁的进行数据库连接操作势必占用很多的系统资源,网站的响应速度必定下降,严重的甚至会造成服务器的崩溃。不是危言耸听,这就是制约某些电子商务网站发展的技术瓶颈问题。
其次,对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将不得不重启数据库。还有,这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
通过上面的分析,我们可以看出来,“数据库连接”是一种稀缺的资源,为了保障网站的正常使用,应该对其进行妥善管理。其实我们查询完数据库后,如果不关闭连接,而是暂时存放起来,当别人使用时,把这个连接给他们使用。就避免了一次建立数据库连接和断开的操作时间消耗。
64.2、数据库连接池
由上面的分析可以看出,问题的根源就在于对数据库连接资源的低效管理。我们知道,对于共享资源,有一个著名的设计模式:资源池设计模式。该模式正是为了解决资源的频繁分配﹑释放所造成的问题。为解决上述问题,可以采用数据库连接池技术。
数据库连接池的基本思想就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。我们可以通过设定连接池最大连接数来防止系统无尽地与数据库连接。 更为重要的是我们可以通过连接池的管理机制监视数据库的连接的数量、使用情况,为系统开发﹑测试及性能调整提供依据。
我们自己尝试开发一个连接池,来为上面的查询业务提供数据库连接服务:
①、编写 class 实现 DataSource 接口
②、在 class的构造器一次性创建 10 个连接,将连接保存 LinkedList 中
③、实现 getConnection, 从 LinkedList 中返回一个连接
④、提供将连接放回连接池中的方法
1、连接池代码
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 releaseConnection(Connection conn) {
dataSources.add(conn);
}
}
2、使用连接池重构我们的用户查询函数
// 查询所有用户
Public void FindAllUsers() {
// 1、使用连接池建立数据库连接
MyDataSource dataSource = new MyDataSource();
Connection conn = dataSource.getConnection();
// 2、创建状态
Statement state = conn.createStatement();
// 3、查询数据库并返回结果
ResultSet result = state.executeQuery("select * from users");
// 4、输出查询结果
while(result.next()){
System.out.println(result.getString("email"));
}
// 5、断开数据库连接
result.close();
state.close();
// 6、归还数据库连接给连接池
dataSource.releaseConnection(conn);
}
64.3、连接池的工作原理
连接池的核心思想是连接的复用,通过建立一个数据库连接池以及一套连接使用、分配和管理策略,使得该连接池中的连接可以得到高效,安全的复用,避免了数据库连接频繁建立和关闭的开销。
连接池的工作原理主要由三部分组成,分别为连接池的建立,连接池中连接的使用管理,连接池的关闭。
第一、连接池的建立。 一般在系统初始化时,连接池会根据系统配置建立,并在池中建立几个连接对象,以便使用时能从连接池中获取。java 中提供了很多容器类,可以方便的构建连接池,例如 Vector(线程安全类),linkedlist 等。
第二、连接池的管理。 连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其策略是:
当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用并作相应处理(即标记该连接为正在使用,引用计数加 1);如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没有达到最大连接数,就重新创建一个连接给请求的客户;如果达到,就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。
当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过了就从连接池中删除该连接,并判断当前连接池内总的连接数是否小于最小连接数,若小于就将连接池充满;如果没超过就将该连接标记为开放状态,可供再次复用。
第三、连接池的关闭。 当应用程序退出时,关闭连接池中所有的链接,释放连接池相关资源,该过程正好与创建相反。
64.4、连接池的主要优点
1)、减少连接的创建时间。连接池中的连接是已准备好的,可以重复使用的,获取后可以直接访问数据库,因此减少了连接创建的次数和时间。
2)、更快的系统响应速度。数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。
3)、统一的连接管理。如果不使用连接池,每次访问数据库都需要创建一个连接,这样系统的稳定性受系统的连接需求影响很大,很容易产生资源浪费和高负载异常。连接池能够使性能最大化,将资源利用控制在一定的水平之下。连接池能控制池中的链接数量,增强了系统在大量用户应用时的稳定性。
65、PreparedStatement相比Statement的好处
大多数时候我们都使用PreparedStatement代替Statement
1、PreparedStatement是预编译的,比Statement速度快;
2、代码的可读性和可维护性更好
虽然用PreparedStatement来代替Statement会使代码多出几行,但这样的代码无论从可读性还是可维护性上来说,都比直接用Statement的代码高很多档次:
stmt.executeUpdate("insert into tb_name (col1,col2,col2,col4) values ('"+var1+"','"+var2+"',"+var3+",'"+var4+"')");
perstmt = con.prepareStatement("insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)");
perstmt.setString(1,var1);
perstmt.setString(2,var2);
perstmt.setString(3,var3);
perstmt.setString(4,var4);
perstmt.executeUpdate();
不用多说,对于第一种方法,别说其他人去读你的代码,就是你自己过一段时间再去读,都会觉得伤心。
3、安全性
PreparedStatement可以防止SQL注入攻击,而Statement却不能。比如说:
String sql = "select * from tb_name where name= '"+varname+"' and passwd='"+varpasswd+"'";
如果我们把[' or '1' = '1]作为varpasswd传入进来,用户名随意,看看会成为什么?
select * from tb_name where name = '随意' and passwd = '' or '1' = '1';
因为'1'='1'肯定成立,所以可以任何通过验证,更有甚者:
把[';drop table tb_name;]作为varpasswd传入进来,则:
select * from tb_name where name = '随意' and passwd = '';drop table tb_name;有些数据库是不会让你成功的,但也有很多数据库就可以使这些语句得到执行。
而如果你使用预编译语句你传入的任何内容就不会和原来的语句发生任何匹配的关系,只要全使用预编译语句你就用不着对传入的数据做任何过虑。而如果使用普通的statement,有可能要对drop等做费尽心机的判断和过虑。
66、Truncate与delete的区别
TRUNCATE table:删除内容、不删除定义、释放空间
DELETE table:删除内容、不删除定义、不释放空间
DROP table:删除内容和定义,释放空间
1、truncate table:只能删除表中全部数据;delete from table where……,可以删除表中全部数据,也可以删除部分数据。
2、delete from记录是一条条删的,所删除的每行记录都会进日志,而truncate一次性删掉整个页,因此日志里面只记录页的释放。
3、truncate的执行速度比delete快。
4、delete执行后,删除的数据占用的存储空间还在,还可以恢复数据;truncate删除的数据占用的存储空间不在,不可以恢复数据。也因此truncate 删除后,不能回滚,delete可以回滚。