缓存池和数据库连接池的使用(Java)

一、基本概念

缓存池

缓存池机制主要用于存储和管理经常访问的数据,以提高应用的性能和响应速度。它通过将计算的数据或着将数据库查询的结果缓存起来,减少重复计算和数据重新获取,从而提高效率。

1. 基本概念

缓存池是一种临时存储空间,用于保存数据的副本,这些副本可以是计算结果、数据库查询结果或其他需要频繁访问的数据。当需要数据时,首先检查缓存,如果缓存中存在,则直接使用;否则,从原始数据源获取并将其存入缓存。

2. 缓存的类型

  • **内存缓存:**数据存储在应用程序的内存中,速度快,但受限于可用内存。
  • **分布式缓存:**数据存储在多个节点上,适用于大规模系统,如Redis。

3. 使用考虑:

  • 缓存失效策略:①设置超时失效时间;② 限制缓存最大容量,到达限制时,可采用LRU、LFU等策略淘汰旧数据;③ 根据特定条件(如数据更新)主动清除缓存。
  • 缓存数据与源数据的一致性
  • 多线程数据的一致性

4. 缓存池与进程、线程的关系

4.1 进程中的缓存池:

  • 隔离性:不同进程相互隔离,如需跨进程共享缓存池,需使用共享内存或外部服务实现数据共享。
  • 资源竞争:多个进程同时访问共享资源时,可能会导致资源竞争,需要使用同步机制(互斥锁)管理。

4.2 线程中的缓存池:

  • 共享性:同一进程的线程可以共享资源池,一个线程修改缓存内容会对其他线程造成影响。
  • 线程安全:多个线程可以同时访问/修改缓存池,因此需要保证线程安全,可以使用synchronized关键字或其他并发工具进行同步。
  • 性能问题:高并发情况下,过多同步可能会导致性能瓶颈。为了提高性能,可以考虑使用并发集合(ConcurrentHashMap)或设计无锁算法。

数据库连接池

数据库连接池是用来管理数据库连接的工具,尤其对于高并发的环境,使用连接池可以避免频繁创建和关闭数据库连接,减少资源消耗。

1. 基本概念

  • **数据库连接:**每次应用程序与数据库交互时,都需要建立一个连接。这一过程往往开销较大,涉及网络通信、身份验证等。
  • **连接池:**连接池是一组预先建立好的数据库连接,这些连接可以被多个客户端共享,避免频繁的连接和断开。

2. 工作原理

  • **初始化:**当应用程序启动时,连接池会创建一定数量的数据库连接并保持在池中,等待请求。
  • **获取连接:**当应用程序需要访问数据库时,它会从连接池中获取一个可用的连接,而不是新建一个连接。
  • **释放连接:**使用完毕后,连接不会被关闭,而是返回连接池,以便其他请求重用。
  • **管理连接:**连接池会定期检查连接的有效性,自动关闭失效的连接,并根据需要创建新的连接以维持池的大小。

3. 常见参数配置:

  • **最大/最小连接数:**连接池中的最大/最小连接数量
  • **连接超时时间:**连接申请的最大等待时间
  • **空闲连接超时时间:**连接在池中空闲的最大时间
  • **最大等待实现:**当连接池已满时,申请连接的最大等待时间

二、代码实现

1. 场景描述:

1. 场景描述:

现有一个主系统,该主系统下有多个子系统,主系统可以连接所有子系统的数据库(主系统的数据表base_datasource中存着所有子系统的数据库信息),查询这些子系统的用户信息(用户信息表:base_person)。

2. 实现以下功能:

主系统程序要连接某个子系统的数据库查询用户信息,程序先从缓存池中获取数据库信息,如果缓存池中没有,则从数据库中查询该子系统的数据库信息,创建一个连接池,将其添加到缓存池中,最后连接该数据库查询数据。

2. 该功能的实现用到的工具类

2.1 ConcurrentHashMap(缓存池)

ConcurrentHashMap是Java中用于高效并发访问的哈希表实现,它允许多个线程同时读取和写入数据,而不会导致数据不一致或性能瓶颈。

1. 基本概念:

  • **线程安全:**ConcurrentHashMap是线程安全的,可以在多个线程中安全使用。
  • **分段锁:**它采用分段锁的机制,将数据分为多个段,每个段都有独立的锁,这样可以减少锁竞争,提高并发性能。

2. 主要特性:

  • **高效性:**相比于其他同步集合(HashTable),ConcurrentHashMap提供更高的并发性能,尤其在读操作频繁的场景下。
  • **非阻塞读:**读取操作不会被写入操作阻塞,只有当修改某个特定段时,才会对该段加锁。
  • **弱一致性:**在遍历时可能会看到一些"瞬态"值,即在遍历期间更新的数据。

3. 使用场景:

  • **高并发环境:**适合多线程环境中需要频繁读取和写入的场景,如缓存、计数器等。
  • **共享数据:**在多个线程共享数据时,ConcurrentHashMap可以避免复杂的同步机制。

2.2 DruidDataSource(数据库连接池)

DruidDataSource是阿里巴巴开源的一个高性能、可扩展的数据库连接池,他是Druid项目的一部分。Druid提供了一个丰富的功能,特别适合用于生产环境中的Java应用程序。

1. 基本特性:

  • **高性能:**Druid提供了高效的数据库连接管理,能够处理大量的连接请求。
  • **监控功能:**内置了监控和统计功能,可以对数据库连接的使用情况进行实时监控。
  • **SQL解析:**支持SQL的解析和审计功能,可以记录SQL执行的详细情况。

2. 配置:

Druid的配置非常灵活,可以通过XML、properties文件或Java代码进行配置,常见配置包括:

  • **连接池大小:**设置初始连接数、最大连接数等。
  • **连接超时时间:**设置获取连接的最大等待时间。
  • **验证查询:**可以配置在获取连接时执行的SQL语句,以检查连接是否有效。

3. 监控与管理:

  • Druid提供了Web界面,可以通过访问特定的URL进行连接池的监控和管理,例如查看当前连接数量、活动连接、SQL执行统计等。
  • 也可以通过JMX(Java Management Extensions)来监控连接池的状态。

4. 安全性

Druid提供了一些安全特性,比如SQL防注入、黑白名单等。

2.3 SqlSessionFactory (管理数据库连接和会话)

SqlSessionFactory是MyBatis框架中用于创建SqlSessiobn的工厂接口。它是MyBatis的核心组件之一,负责管理数据库连接和会话的配置。

1. 基本概念

  • SqlSession:代表与数据库的一个会话,用于执行Sql语句、获取映射器等。
  • SqlSessionFactory:用来创建SqlSession,通常在应用启动时创建一次并在整个应用生命周期中重用。

2. 创建SqlSessionFactory

SqlSessionFactory的创建通常通过读取MyBatis的配置文件来实现。可以通过SqlSessionFactoryBuilder来构建它。

3. mybatis-config.xml的配置:

  • 数据源:设置数据库连接池的信息(如JDBC URL、用户名、密码等)。
  • 映射器:指定映射器XML文件或注解的路径。
  • 插件:可以配置MyBatis插件,用于扩展功能。

4. 线程安全:

SqlSessionFactory是线程安全的,可以在应用中共享,每个线程应该自己创建SqlSession实例,而不是共享SqlSession对象。

5. 常见问题:

  • **事务管理:**确保在操作完成后正确提交和回滚事务,以保证数据的一致性。
  • **性能优化:**合理配置缓存和映射器,来优化性能。

2.4 Mapper (定义数据库操作)

Mapper是MyBatis的重要组成部分,它用于定义数据库操作的方法并将这些方法与SQL语句映射。

3. 代码实现

3.1 添加依赖:

XML 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.9</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.13</version>
</dependency>

3.2 配置文件配置主数据库信息

java 复制代码
#oracle
spring.datasource.dynamic.primary=anita
spring.datasource.dynamic.datasource.anita.url=jdbc:oracle:thin:@oracle_url
spring.datasource.dynamic.datasource.anita.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.dynamic.datasource.anita.username=anita
spring.datasource.dynamic.datasource.anita.password=anitazxq

3.3 定义实体类

3.3.1 base_datasource实体类:DataSourceModel
java 复制代码
package com.example.dataSource.dto;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("base_datasource")
public class DataSourceModel {
    private String id;
    private String dbname;
    private String dbuser;
    private String dbpassword;
    private String dburl;
    private String dbdriver;
}
3.3.2 base_person的实体类:BasePersonModel
java 复制代码
package com.example.dataSource.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class BasePersonModel {
    private String id;
    private String name;
}

3.4 定义Mapper

3.4.1 DataSourceModel对应的Mapper:DataSourceMapper
java 复制代码
package com.example.dataSource.mapper;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.dataSource.dto.DataSourceModel;
import org.apache.ibatis.annotations.Mapper;

@Mapper
@DS("anita")  //指定数据源
public interface DataSourceMapper extends BaseMapper<DataSourceModel> {
}
3.4.2 BasePersonModel 对应的Mapper:BasePersonMapper
java 复制代码
package com.example.dataSource.mapper;

import com.example.dataSource.dto.BasePersonModel;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface BasePersonMapper {
    @Select("SELECT * FROM base_person")
    List<BasePersonModel> getAllData();
}

3.5 定义Service层

根据dbName查询数据库连接信息

java 复制代码
package com.example.dataSource.service;

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.dataSource.dto.BasePersonModel;
import com.example.dataSource.dto.DataSourceModel;
import com.example.dataSource.mapper.BasePersonMapper;
import com.example.dataSource.mapper.DataSourceMapper;
import com.example.dataSource.utils.DataSourceCacheUtil;
import com.example.dataSource.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class DataOperateService {
    @Autowired
    private DataSourceMapper dataSourceMapper;
    //根据数据源名称查询数据库信息
    public DataSourceModel getDataSourceByDbName(String dbName)
    {
        QueryWrapper<DataSourceModel> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("dbname",dbName);
        DataSourceModel dataSource=dataSourceMapper.selectOne(queryWrapper);
        return dataSource;
    }
}

3.6 数据库连接池

java 复制代码
package com.example.dataSource.utils;

import com.alibaba.druid.pool.DruidDataSource;
import com.example.dataSource.dto.DataSourceModel;
import org.springframework.stereotype.Component;

@Component
public class DataSourceConnectionUtil {
    //创建数据库连接池
    public static DruidDataSource createDataSource(DataSourceModel dataSourceModel)
    {
        DruidDataSource dataSource=new DruidDataSource();

        dataSource.setDriverClassName(dataSourceModel.getDbdriver());
        dataSource.setUrl(dataSourceModel.getDburl());
        dataSource.setUsername(dataSourceModel.getDbuser());
        dataSource.setPassword(dataSourceModel.getDbpassword());

        dataSource.setValidationQuery("SELECT 1 FROM DUAL");//ORACLE验证查询
        dataSource.setTestWhileIdle(true);
        dataSource.setTestWhileIdle(false);
        dataSource.setTestOnReturn(false);

        //避免连接数一直增加
        dataSource.setKeepAlive(false);
        //设置keepAlive的时间间隔,单位毫秒
        dataSource.setKeepAliveBetweenTimeMillis(60000);
        dataSource.setTimeBetweenEvictionRunsMillis(50000);
        dataSource.setMinEvictableIdleTimeMillis(300000);
        dataSource.setMaxEvictableIdleTimeMillis(480000);
        dataSource.setBreakAfterAcquireFailure(true);
        dataSource.setConnectionErrorRetryAttempts(1);
        dataSource.setMinIdle(5);
        dataSource.setMaxActive(200);
        dataSource.setMaxWait(30000);
        return dataSource;
    }
}

3.7 缓存池

java 复制代码
package com.example.dataSource.utils;

import com.alibaba.druid.pool.DruidDataSource;
import com.example.dataSource.dto.DataSourceModel;
import com.example.dataSource.service.DataOperateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.ConcurrentHashMap;

@Component
public class DataSourceCacheUtil {
    @Autowired
    private DataOperateService dataOperateService;
    @Autowired
    private DataSourceConnectionUtil dataSourceConnectionUtil;
    private static final ConcurrentHashMap<String, DruidDataSource> dataSourceCache=new ConcurrentHashMap<>();

    //获取数据库连接
    public DruidDataSource getDataSource(String dbName)
    {
        //从缓存池中获取数据库信息
        DruidDataSource dataSource=dataSourceCache.get(dbName);
        if(dataSource==null)
        {
            DataSourceModel ds=dataOperateService.getDataSourceByDbName(dbName);
            //创建数据库连接池
            dataSource=dataSourceConnectionUtil.createDataSource(ds);
        }
        dataSourceCache.put(dbName,dataSource);
        return dataSource;
    }
    //在应用结束时关闭数据库连接池
    public void close()
    {
        for(DruidDataSource dataSource : dataSourceCache.values())
        {
            dataSource.close();
        }
    }
}

3.8 创建SqlSessionFactory

初始化 MyBatis 所需的配置,并准备好一个可以用于数据库操作的 SqlSessionFactory 实例。通过这个工厂,应用程序可以方便地获取 SqlSession,从而进行数据库操作。

java 复制代码
package com.example.dataSource.utils;

import com.alibaba.druid.pool.DruidDataSource;
import com.example.dataSource.mapper.BasePersonMapper;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;

import javax.sql.DataSource;

public class MyBatisUtil {
    public static SqlSessionFactory getSqlSessionFactory(DruidDataSource dataSource)
    {
        // 创建 MyBatis 配置
        Configuration configuration = new Configuration();
        configuration.setEnvironment(new Environment("development", new JdbcTransactionFactory(), dataSource));

        // 注册 Mapper
        configuration.addMapper(BasePersonMapper.class);

        // 返回 SqlSessionFactory
        return new SqlSessionFactoryBuilder().build(configuration);
    }
}

3.9 从缓存池中获取目标数据库,查询数据。

在2.5的DataOperateService 类中添加方法,实现以下功能:从缓存池中找到目标数据源,切换数据源,查询该数据源中的base_person表。

java 复制代码
    @Autowired
    private DataSourceCacheUtil dataSourceCacheUtil;
    public List<BasePersonModel> getData(String dbName)
    {
        DruidDataSource dataSource=dataSourceCacheUtil.getDataSource(dbName);
        SqlSessionFactory sqlSessionFactory= MyBatisUtil.getSqlSessionFactory(dataSource);
        // 获取 SqlSession
        try(SqlSession sqlSession = sqlSessionFactory.openSession()){
            // 获取 Mapper
            BasePersonMapper basePersonMapper=sqlSession.getMapper(BasePersonMapper.class);
            //查询
            List<BasePersonModel> data=basePersonMapper.getAllData();
            //sqlSession.commit();//如果执行的是insert、update操作,需要提交事务
            return data;
        }
        catch (Exception e) {
            // 记录异常或抛出自定义异常
            e.printStackTrace();
            // 这里可以根据需要返回空列表或重新抛出异常
            return Collections.emptyList();
        }

    }

2.10 通过接口将上述功能进行串联

java 复制代码
package com.example.dataSource.controller;

import com.example.dataSource.dto.BasePersonModel;
import com.example.dataSource.service.DataOperateService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/api/differentDSUse")
public class DataOperation {

    @Autowired
    DataOperateService dataOperateService;

    @GetMapping("/search")
    public List<BasePersonModel> get()
    {
        return dataOperateService.getData("test");
    }
}
相关推荐
魔道不误砍柴功1 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2341 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨1 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
测开小菜鸟3 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
Ai 编码助手4 小时前
MySQL中distinct与group by之间的性能进行比较
数据库·mysql
P.H. Infinity4 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天4 小时前
java的threadlocal为何内存泄漏
java
陈燚_重生之又为程序员4 小时前
基于梧桐数据库的实时数据分析解决方案
数据库·数据挖掘·数据分析
caridle4 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
白云如幻4 小时前
MySQL排序查询
数据库·mysql