本教程是手写一个对应多个mysql连接地址的连接池,即每一个连接url都有属于自己的连接池,当url初次访问时创建连接池,每个url和用户名称锁定一个连接池。
从本教程可以学到,连接池的基本思想,以及并发下如何保证连接池的创建以及存取安全
应用场景:前端传进来url,用户名,和密码,然后输入sql直接进行查询,相当于一个小工具,可以连接不同mysql数据库进行sql语句查询,并为不同连接建立相应的连接池,这种做法只适用于不同连接数不多的情况,如果不同连接太多的话,每个连接对应一个连接池,而且每个连接的使用次数并不多的话,这样会造成空间的浪费,需要对这些连接池做定期的清理
各位有好的建议都可以欢迎提出哈
mysql连接池代码
/**
* @Description: mysql连接池(单个url+user)
* @Author jarbein
* @Date 2020/1/13 14:41
*/
public class MysqlDatasource{
//用于存放连接的linkedlist
private LinkedList<Connection> dataSources = new LinkedList<Connection>();
/*以下三个属性是存放该连接池的链接地址,用户名和密码,这里之所以添加密码,
是因为后边请求进来地址获取验证用户信息时不需要再去数据库验证,直接通过连接池信息进行验证*/
private String url;
private String user;
private String password;
static{
/**
* 驱动注册
*/
try {
Class.forName("com.mysql.cj.jdbc.Driver");//注册加载驱动
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public void createDataSource(String url,String user,String password){
System.out.println("创建"+ url + "_" + user +"连接池");
//创建100个连接放到连接池中
for(int i = 0; i < 100; i++) {
try {
//通过JDBC建立数据库连接
Connection conn = DriverManager.getConnection(url, user, password);
//将连接加入连接池中
dataSources.add(conn);
} catch (Exception e) {
throw new RuntimeException("连接创建异常,请检查连接或用户是否正确!");
}
}
System.out.println("成功创建url为"+ url + "_" + user +"的连接池");
//尽量减小数据库压力,连接池建立后,再有新连接时,通过这里的密码进行校验
this.url = url;
this.user = user;
this.password = password;
}
//下边加synchronized用于并发获取连接的线程安全
public synchronized Connection getConnection() throws SQLException {
System.out.println("从"+url+"_"+user+"连接池中取连接!");
Connection connection;
try {
connection = dataSources.removeFirst();
}catch (NoSuchElementException n){
throw new RuntimeException("连接池中暂时没有空闲的连接,请稍后再试!");
} catch (Exception e){
throw new RuntimeException("从连接池中获取连接失败!");
}
return connection;
}
//同时释放线程同样加synchronized,因为LinkedList并不是线程安全的
public synchronized void releaseConnection(Connection conn) {
dataSources.add(conn);
System.out.println("成功放回" + url + "_" + user+ "连接池!");
System.out.println("剩余连接数:"+dataSources.size());
}
public String getUrl() {
return url;
}
public String getUser() {
return user;
}
public String getPassword() {
return password;
}
}
多连接池管理工具类
/**
* @Description: 管理多个连接池(不同的url+user对应不同的连接池)
* @Author jarbein
* @Date 2020/1/13 14:45
*/
public class DataSourceUtil {
//使用map进行不同连接池的存储,这里用ConcurrentHashMap以保障线程安全,这里key就是url_username
//使用static是因为这个map是公用的,使用private是因为我不想直接把这个map暴漏出去,并无冲突
private static Map<String,MysqlDatasource> datasourceMap = new ConcurrentHashMap<>();
//判断对应连接是否已经创建连接池,这个key就是url和user的拼接,作为唯一值
public static boolean hasDataSource(String key){
if (datasourceMap.containsKey(key)){
return true;
}else {
return false;
}
}
//通过key获取连接池
public static MysqlDatasource getDataSource(String key){
return datasourceMap.get(key);
}
//创建连接池
public static MysqlDatasource createDataSource(String url, String user, String password){
String key = url + "_" + user;
MysqlDatasource mysqlDatasource = new MysqlDatasource();
mysqlDatasource.createDataSource(url,user,password);
datasourceMap.put(key,mysqlDatasource);
return mysqlDatasource;
}
}
连接工具类(最终操作类)
这个类才是我们应该直接调用的类,我们对数据层操作时只需要对这个类进行调用获取连接,关闭连接就行了,拿到连接就可以进行操作了
/**
* @Description: 我们获取连接只需要调用这个类就行了
* @Author jarbein
* @Date 2020/1/13 16:07
*/
public class ConnectionUtils {
/**
*
* @param url
* @param user
* @param password
* @return
* @throws SQLException
*/
public static Connection getConnection(String url, String user, String password) throws SQLException{
/*关键:intern()保证这里的key如果存在,就直接从常量池中获取,如果没有就放入常量池,因为url和ip
值在编译期间无法确定,所以会放入堆中,通过intern方法将最终结果放入常量池,相同就取同一
个对象,用于下边加锁,如果没有intern方法,因为key不一样,加锁时就会出现线程安全问题*/
String key = (url + "_" + user).intern();
Connection conn;
//这里先做判断如果存在连接池就不需要去在synchronized代码块前进行阻塞了
if (DataSourceUtil.hasDataSource(key)){
MysqlDatasource dataSource = DataSourceUtil.getDataSource(key);
//匹配密码是否相同,不相同说明密码错误
if (!dataSource.getPassword().equals(password)){
throw new RuntimeException("用户信息错误,请检查!");
}
conn = dataSource.getConnection();
}else {
//如果不存在连接池,这里创建连接池需要加锁,防止多线程同一个key同时创建连接池
synchronized (key){
//获取锁后还要进行一次判断,因为在抢锁的过程可能已经有其他线程创建过了连接池
if (DataSourceUtil.hasDataSource(key)){
MysqlDatasource dataSource = DataSourceUtil.getDataSource(key);
if (!dataSource.getPassword().equals(password)){
throw new RuntimeException("用户信息错误,请检查!");
}
conn = dataSource.getConnection();
}else {
MysqlDatasource dataSource = DataSourceUtil.createDataSource(url, user, password);
conn = dataSource.getConnection();
}
}
}
return conn;
}
/**
* 关闭资源
*/
public static void close(String url,String user,ResultSet resultSet, Statement statement, Connection connection){
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//将连接放回到对应的连接池中
if (connection != null) {
String key = url + "_" + user;
MysqlDatasource dataSource = DataSourceUtil.getDataSource(key);
dataSource.releaseConnection(connection);
}
}
}
对这个工具类进行压测
这个工具类主要是对应前端传来url,user,password和相应的sql查询语句,我已经把核心代码连接池给实现了,其它代码就是对连接进行操作了,然后通过压测可以看出1s内1000请求是没有问题的,而且后端控制台输出正常
jmeter压力测试
结果:
后端控制台输出
可以看出最终执行完连接都成功回到了连接池中