JDBC存在的问题
- 代码的冗余:在对数据库进行增删改查时,每个操作的JDBC流程和SQL执行代码的流程都一样,造成代码的冗余,所以我们可以把冗余的部分封装起来,封装之后,我们就不用再去写JDBC流程,只需要些SQL语句---这一部分的封装下一篇文章描述
- 性能问题:每一次对数据库的操作都要建立连接,操作完之后又要释放连接,如果数据库访问次数不多时性能还可以,但当我们的程序有很多人使用时,对数据库的操作次数也会越来越多,每一次操作都要建立和关闭连接,势必会降低性能,本篇文章主要解决这部分的问题
如何解决性能问题
我们先用一个例子来描述一下这个JDBC性能问题。
- 比如说,当两个地方本来互相不通,分别是A和B两个地方。这时我们就需要在A和B之间修一条路能让他们连通,有人在这条路上通行的时候就处于连接状态,但是当这条路可能有段时间没人走了我们就把它炸掉了。但是每当有人需要从A去到B时,又需要再修一条路,而每一次修一条路的时间都需要非常久。所以我们不能一不用这条路就把它炸掉,这样非常浪费时间和资金。那我们该怎么解决呢?我们在A和B两地之间多修几条路,在每条路的路口设置关卡,当有人在其中一条路上通行时,那么这个关卡就不允许其他人在这条路上通行,当路上没人通行时那么关卡允许其他人通行。(可能这举的例子和实际生活不是特别恰当,但也希望大家能够理解,实在想不到一个更为贴切的例子)
- 解决JDBC性能问题也是这个道理,一开始我们就创建几个连接通道,用完之后不关闭,而是用一个标志位来表示当前通道是否可用,建立的这几条通道放在统一的一个地方进行管理,看起来就像个池子一样,所以就叫JDBC连接池
封装JDBC的步骤
- 1、为了更好的管理项目,我们把连接数据库用到的驱动类、url地址、用户名和密码都放到一个properties文件里,之后改起来也方便,一处改动全部都跟着改动。需要一个类来帮我们读取配置文件的信息
- 配置信息如下(Configuration.properties)
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/bank?serverTimezone=CST
user=root
password=123456
minConnectCount=5
waitTime=5
- ConfigReader类
package util;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class ConfigReader {
private static Properties properties;
//用Map集合存放properties文件下的配置信息
private static Map configMap;
//配置文件只加载一次
static {
properties = new Properties();
configMap = new HashMap<>();
InputStream inputStream = null;
try {
inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("configuration.properties");
properties.load(inputStream);
Enumeration en = properties.propertyNames();
while (en.hasMoreElements()){
String key = (String) en.nextElement();
String value = properties.getProperty(key);
configMap.put(key, value);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//获取配置文件里面的value值
public static String getPropertyValue(String key){
return configMap.get(key);
}
}
- 2、在连接JDBC的时候我们用DriverManager.getConnection()方法获取连接,返回了Connection对象,成功获取后也表明和数据库连接成功,当我们不需要用的时候调用close()方法关闭连接。为了实现用完之后不关闭连接,我们需要自己封装一个类,这个类下有一个Connection属性,还有一个标志位,用来判断该连接可用还是不可用。为了能够让自己封装的类和Connection类的使用起来是一样的,我们自己创建一个AdapterConnection抽象类,这个类实现了Connection接口,我们自己封装的Connection类继承了这个抽象类,并重写了其中的三个方法,分别用来创建状态参数以及关闭连接。(这部分的设计思想体现出缺省适配器模式)
- AdapterConnection类
package pool;
import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
public abstract class AdapterConnection implements Connection {
//这里将这三个写为抽象方法,是为了当子类继承这个抽象类的时候提示子类必须重写这个三个方法
@Override
public abstract Statement createStatement() throws SQLException;
@Override
public abstract PreparedStatement prepareStatement(String sql) throws SQLException ;
@Override
public abstract void close() throws SQLException;
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
return null;
}
@Override
public String nativeSQL(String sql) throws SQLException {
return null;
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
}
@Override
public boolean getAutoCommit() throws SQLException {
return false;
}
@Override
public void commit() throws SQLException {
}
@Override
public void rollback() throws SQLException {
}
@Override
public boolean isClosed() throws SQLException {
return false;
}
@Override
public DatabaseMetaData getMetaData() throws SQLException {
return null;
}
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
}
@Override
public boolean isReadOnly() throws SQLException {
return false;
}
@Override
public void setCatalog(String catalog) throws SQLException {
}
@Override
public String getCatalog() throws SQLException {
return null;
}
@Override
public void setTransactionIsolation(int level) throws SQLException {
}
@Override
public int getTransactionIsolation() throws SQLException {
return 0;
}
@Override
public SQLWarning getWarnings() throws SQLException {
return null;
}
@Override
public void clearWarnings() throws SQLException {
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
return null;
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
return null;
}
@Override
public Map> getTypeMap() throws SQLException {
return null;
}
@Override
public void setTypeMap(Map> map) throws SQLException {
}
@Override
public void setHoldability(int holdability) throws SQLException {
}
@Override
public int getHoldability() throws SQLException {
return 0;
}
@Override
public Savepoint setSavepoint() throws SQLException {
return null;
}
@Override
public Savepoint setSavepoint(String name) throws SQLException {
return null;
}
@Override
public void rollback(Savepoint savepoint) throws SQLException {
}
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
return null;
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
return null;
}
@Override
public Clob createClob() throws SQLException {
return null;
}
@Override
public Blob createBlob() throws SQLException {
return null;
}
@Override
public NClob createNClob() throws SQLException {
return null;
}
@Override
public SQLXML createSQLXML() throws SQLException {
return null;
}
@Override
public boolean isValid(int timeout) throws SQLException {
return false;
}
@Override
public void setClientInfo(String name, String value) throws SQLClientInfoException {
}
@Override
public void setClientInfo(Properties properties) throws SQLClientInfoException {
}
@Override
public String getClientInfo(String name) throws SQLException {
return null;
}
@Override
public Properties getClientInfo() throws SQLException {
return null;
}
@Override
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
return null;
}
@Override
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
return null;
}
@Override
public void setSchema(String schema) throws SQLException {
}
@Override
public String getSchema() throws SQLException {
return null;
}
@Override
public void abort(Executor executor) throws SQLException {
}
@Override
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
}
@Override
public int getNetworkTimeout() throws SQLException {
return 0;
}
@Override
public T unwrap(Class iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class> iface) throws SQLException {
return false;
}
}
- MyConnection类
package pool;
import util.ConfigReader;
import java.sql.*;
public class MyConnection extends AdapterConnection{
private Connection conn;
//标志位,true表示被占用,false表示该通道可用
private boolean used = false;
private static String driver;
private static String url;
private static String user;
private static String password ;
//静态块,让加载类的步骤只执行一次,并初始化四个属性
static {
try {
driver = ConfigReader.getPropertyValue("driver");
url = ConfigReader.getPropertyValue("url");
user = ConfigReader.getPropertyValue("user");
password = ConfigReader.getPropertyValue("password");
Class.forName(driver);
}catch (ClassNotFoundException e){
e.printStackTrace();
}
}
//用来初始化连接通道,每一次被创建时都进行初始化
{
try {
conn = DriverManager.getConnection(url, user, password);
}catch (SQLException e){
e.printStackTrace();
}
}
//获取连接通道
public Connection getConn() {
return conn;
}
//判断该连接通道是否可用
public boolean isUsed() {
return used;
}
//设置标志位
public void setUsed(boolean used) {
this.used = used;
}
@Override
public Statement createStatement() throws SQLException {
return this.conn.createStatement();
}
//重写了继承自AdapterConnection类的方法
//该方法是为了获取状态参数,本质上还是调用了PreparedStatement
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
PreparedStatement pstate = this.conn.prepareStatement(sql);
return pstate;
}
//重写了继承自AdapterConnection类的方法
//该方法的目的是为了将连接通道设置为可用,看起来就像关闭流一样
@Override
public void close() throws SQLException {
this.used = false;
}
}
- 3、我们需要一个连接池对象来帮我们管理连接对象,我们需要把连接对象放到一个List集合中,当我们需要的时候就从集合里取。连接对象的个数我们也放置在properties文件里,便于管理。由于连接对象有限,当有多个用户同时访问时,可能有的用户拿不到连接对象,这里用了一个等待机制,当用户拿不到连接对象时,就让它等一会,如果超出等待时间还拿不到,就抛出一个异常通知用户。
- ConnectionPool类
package pool;
import util.ConfigReader;
import javax.swing.*;
import java.io.InputStream;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
public class ConnectionPool {
//将ConnectionPool设计成单例模式
private ConnectionPool(){}
private static volatile ConnectionPool connectionPool;
public static ConnectionPool getInstance(){
if (connectionPool == null){
synchronized (ConnectionPool.class){
if (connectionPool == null){
connectionPool = new ConnectionPool();
}
}
}
return connectionPool;
}
//获取最小连接个数以及等待时间
private int minConnectCount = Integer.parseInt(ConfigReader.getPropertyValue("minConnectCount"));
private int waitTime = Integer.parseInt(ConfigReader.getPropertyValue("waitTime"));
//属性---List集合,用来存储连接对象
private List pool = new ArrayList<>();
//往pool集合里面存放连接对象
{
for (int i = 1; i <= minConnectCount; i ++){
pool.add(new MyConnection());
}
}
//方法,获取连接对象
private Connection getMC(){
Connection result = null;
//遍历连接池中的对象
for (Connection conn : pool){
MyConnection mc = (MyConnection) conn;
if (!mc.isUsed()){//表示连接是可使用的
synchronized (ConnectionPool.class){
if (!mc.isUsed()){
mc.setUsed(true);
result = mc;
}
}
break;
}
}
return result;
}
//该方法是为了获取连接对象,并增加了等待机制
public Connection getConnection(){
Connection result = this.getMC();
int count = 0;//记录循环的次数
while (result == null && count < waitTime*10){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
result = this.getMC();
count++;
}
if (result == null){
throw new SystemBusyException("当前系统繁忙,请稍后再试");
}
return result;
}
}
自定义异常类SystemBusyException
package pool;
public class SystemBusyException extends RuntimeException {
public SystemBusyException(){}
public SystemBusyException(String msg){
super(msg);
}
}
这样整个JDBC连接池就封装好了,整体的功能也比较简单,代码是经过测试的了,所以运行起来应该是没有什么问题,如果有什么问题还望指正。