前言
现如今做软件开发离不开数据库,一般我们都会用第三方的数据库连接池技术C3P0、Druid等,而数据库连接池到底是怎么实现的呢?
本文将介绍一个简易的数据库连接池,来便于理解其原理。
一、数据库连接池是什么?
众所周知,数据库连接作为一种有限的、昂贵的资源,会大大影响整个应用程序的性能。
采用池化技术,将大大节省资源连接的浪费,具体逻辑如下:
1、每当用户做出请求时,应用程序进行业务处理,进而调用数据层资源建立数据库连接。
2、数据库连接统一交由连接池管理,若连接池内有空闲的连接,则直接使用该连接,并且标记该连接为繁忙。
3、数据库连接使用结束后,并不是传统的进行流的关闭,而是直接标记为空闲连接,等待其他请求。
二、实现连接池
1.配置jdbc.properties
Driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/atm
username=root
psd=root
2.创建MyConnection
因为数据库连接池的连接的close方法并不能直接关闭连接,而是应该将其标记为空闲状态,为了方便程序员根据初始代码习惯使用连接池,需要重写Connection的close方法。
此处采用实现Connection接口的方法。
public class MyConnection implements Connection {
//isUsed标记是否空闲,初始为false
public boolean isUsed=false;
private Connection conn;
//构造方法,创建MyConnection时传入Connection对象
public MyConnection(Connection conn){
this.conn=conn;
}
//调用MyConnection方法时,实则使用Connection方法
@Override
public Statement createStatement() throws SQLException {
return conn.createStatement();
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return conn.prepareStatement(sql);
}
//重写close方法
@Override
public void close() throws SQLException {
//将连接标记为空闲
this.isUsed=false;
System.out.println("我已经释放了");
}
//若需要真正关闭连接,则可使用realclose方法
public void realclose() throws SQLException{
this.conn.close();
MyConnectionPool.myConnections.remove(this);
System.out.println("我已经关闭了");
}
/**
**其他重写方法....
**/
}
3.创建自己的连接池
public class MyConnectionPool{
public static String Driver;
public static String url;
public static String username;
public static String psd;
//利用CopyOnWriteArrayList存放MyConnection,初始化池
public static CopyOnWriteArrayList<MyConnection> myConnections=new CopyOnWriteArrayList<MyConnection>();
//初始化池内连接数量
public static int count=0;
//读取jdbc配置文件
private static void readPro(){
Properties pro=new Properties();
InputStream in=MyConnectionPool.class.getClassLoader().getResourceAsStream("jdbc.properties");
try{
pro.load(in);
Driver=pro.getProperty("Driver");
url=pro.getProperty("url");
username=pro.getProperty("username");
psd=pro.getProperty("psd");
}catch (IOException e){
e.printStackTrace();
}
}
//在池中获取连接
public static synchronized Connection getConnection(){
for(int i=0;i<count;i++){ //若池内有空闲的连接则连上
if(!myConnections.get(i).isUsed){
myConnections.get(i).isUsed=true;
System.out.println("我空闲我连上");
return myConnections.get(i);
};
}
//若池内连接数超过10个连接(可自定义池内最大连接数),则提示系统繁忙
if(count>=10){
System.out.println("系统繁忙!");
return null;
}
//若池内没有连接空闲,且连接数不超过池可以承载的最大数量,则创建一个新连接
readPro();
Connection conn=null;
try{
//--1,加载Driver驱动--
Class.forName(Driver);
//--2,创建数据库连接对象Connection--
conn = DriverManager.getConnection(url, username, psd);
}catch (ClassNotFoundException |SQLException e){
e.printStackTrace();
}
MyConnection myconnection=new MyConnection(conn);
//标记连接为繁忙
myconnection.isUsed=true;
//将其加入池中
myConnections.add(myconnection);
System.out.println("我新建一个");
count++; //池内连接数量+1
return myconnection;
}
}
4.连接池应用
以登录功能为例
public int doLogin(String code_input,int password_input){
Connection conn = null;
PreparedStatement pstmt=null;
ResultSet rs = null;
try{
//数据库连接从连接池内获得
conn=MyConnectionPool.getConnection();
//创建一个可向数据库发送SQL命令并返回结果的传送对象Statement--
String sql="SELECT * FROM account WHERE code = ? and password = ?";
pstmt=conn.prepareStatement(sql);
pstmt.setString(1,code_input); //防止SQL注入
pstmt.setInt(2,password_input);
rs=pstmt.executeQuery();
if(rs.next()){
return rs.getInt("id");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
if(rs!=null){
try{
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(pstmt!=null){
try{
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
//此处的close方法实则是MyConnection的close方法,标记连接为空闲
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return -1;
}
拓展
连接池的连接还需要更为复杂的管理,以下展示了两种情况。
1、池内空闲数太多,一段时间内请求数太少了,需要关闭流以免占用空间
public static void release(){
int count=0;
for(MyConnection myConnection:MyConnectionPool.myConnections){
if (!myConnection.isUsed){ //若连接池有6个空闲,则关闭其他空闲
if(count>=6){
try {
myConnection.realclose();
} catch (SQLException e) {
e.printStackTrace();
}
continue;
}
count++;
}
}
}
2、连接空闲太久需要不定时向数据库发送假的sql语句,方式数据源检测到连接空闲时长太长而自动断开连接,导致空闲连接失效。
public static void send(){
Statement stmt = null;
String sql="SELECT * FROM account";
/**
* 定时发送sql语句确保连接
*/
for(MyConnection myConnection:MyConnectionPool.myConnections){
if(myConnection.isUsed)
continue;
try {
stmt = myConnection.createStatement();
stmt.executeQuery(sql);
} catch (SQLException e) {
e.printStackTrace();
}finally {
if (stmt!=null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(myConnection!=null){
try {
myConnection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
可以在连接池类中采用静态代码块,类加载时即开启线程,实时监测数据库连接的情况,定时调用release方法以及send方法。
static{
new Timer().schedule(new TimerTask() {
@Override
public void run() {
release();
}
},0,1000);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
send();
}
},0,1000*10L);
}
数据库连接池的内容,需要管理的东西不止如此,这里只简单介绍了实现自己的数据库连接池,便于原理的理解。有兴趣也可以自己手动实现拓展数据库连接池的功能。