AbstractRoutingDataSource 在运行时更改映射

Posted

技术标签:

【中文标题】AbstractRoutingDataSource 在运行时更改映射【英文标题】:AbstractRoutingDataSource change map in runtime 【发布时间】:2015-07-28 05:02:12 【问题描述】:

我现在在数据库中有 2 个表:

    用户 用户数据库

在用户中,我存储登录名、密码、角色 在 user_database 我存储数据库驱动程序、url、密码和用户。 图表数据库

我希望用户登录到我的页面,然后他所做的下一个连接将被发送到用户数据库。为什么我需要什么?我规划了流行的电子商务地图并创建了用户登录并查看商店数据的android应用程序,可以添加和查看产品订单。 现在该去实践了,我对弹簧技术的了解很少,当我做错的时候请解释一下。 AbstractRoutingDataSource 的所有 web 示例都在持久性文件中声明数据源或创建数据源 bean 并开始使用 AbstractRoutingDataSource。 在我的项目中,我现在没有用户连接,我需要从数据库中获取它。我尝试使用存储库和这个例子 https://***.com/a/17575648/3037869 但是我在控制器中的@Autowired 上得到了空值,我认为存储库的连接是空的。如何为此存储库设置连接并设置路由?这种方法的缺陷是当我添加用户时我需要重新启动服务器来刷新连接。 接下来尝试我现在​​使用的类User 实现UserDetails 用户登录后我可以从getPrincipal() 获取用户连接并添加到地图。

private void setDataSources() 
    HashMap<Object, Object> targetDataSources = new HashMap<>();
    DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
    dataSourceBuilder.driverClassName("org.h2.Driver");
    dataSourceBuilder.url("jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
    dataSourceBuilder.username("sa");
    dataSourceBuilder.password("");
    targetDataSources.put("auth", dataSourceBuilder.build());
    setDefaultTargetDataSource(dataSourceBuilder.build());
    if( SecurityContextHolder.getContext().getAuthentication()!=null) 
        User user=(User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        System.out.println(user.getUserDatabase().getDriver());
        dataSourceBuilder.driverClassName(user.getUserDatabase().getDriver());
        dataSourceBuilder.url(user.getUserDatabase().getUrl());
        dataSourceBuilder.username("3450_Menadzer");
        dataSourceBuilder.password(user.getUserDatabase().getPassword());
        targetDataSources.put("user", dataSourceBuilder.build());
    
    setTargetDataSources(targetDataSources);
    afterPropertiesSet(); //map is refresh when i add this


我在构造函数上运行这个方法并确定CurrentLookupKey

protected Object determineCurrentLookupKey() 
        if( SecurityContextHolder.getContext().getAuthentication()!=null) 
            setDataSources();

            return "user";
        

        return "auth";

这是有效的,但是当我刷新 3-4 次对用户数据库的请求时,我得到了

User 3450_Menadzer already has more than 'max_user_connections' active connections

手动设置连接映射而不刷新每个方法determineCurrentLookupKey运行我没有这个问题。我认为我的方法不是关闭连接。我怎样才能清理这个?这可能是更好的路由连接方法吗?

编辑 @SergeBallesta 我从您的示例中更改了一些代码 这是我的地图课

@Component
@Scope(value = "singleton")
public class DataSourceMap 
    private Map<Object,Object> dataSourceMap;
    public DataSourceMap()
    
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.driverClassName("org.h2.Driver");
        dataSourceBuilder.url("jdbc:h2:mem:AZ;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
        dataSourceBuilder.username("sa");
        dataSourceBuilder.password("");
        dataSourceMap=new HashMap<Object,Object>();
        dataSourceMap.put("auth",dataSourceBuilder.build());
    
    public void addDataSource(String session,DataSource dataSource)
    
        this.dataSourceMap.put(session,dataSource);
    
    public Map<Object,Object> getDataSourceMap()
    
        return dataSourceMap;
    
    public void removeSource(String session)
    
        dataSourceMap.remove(session);
    

对于AbstractRoutingDataSource,我做了一些更改,我添加了afterPropertiesSet(),因为数据源没有刷新。我做了一些刷新,我没有收到错误,我认为这是有效的。我需要在未来测试更多的数据库

@Component
public class CustomRoutingDataSource extends AbstractRoutingDataSource
    @Autowired
    DataSourceMap dataSources;
    @Override
    protected Object determineCurrentLookupKey() 
        setDataSources(dataSources);
        afterPropertiesSet();
        System.out.println("test");
        if( SecurityContextHolder.getContext().getAuthentication()!=null) 
            HttpServletRequest request = ((ServletRequestAttributes)
                    RequestContextHolder.getRequestAttributes()).getRequest();
            return request.getSession().getId();
        

        return "auth";
    
    @Autowired
    public void setDataSources(DataSourceMap dataSources) 
        System.out.println("data adding");
        setTargetDataSources(dataSources.getDataSourceMap());
    


【问题讨论】:

你能分享你的代码吗?用户登录后我有同样的任务,连接到一个基本数据库并获取数据源,然后 CRUD 到 taht 数据源!但是我在 Spring Framework 中很新,我尝试了一个月但没有好的结果。我希望你的代码可以帮助我。 【参考方案1】:

首先,每用户数据库是一种非常罕见的设计。如果所有这些数据库都以相同的结构结束,请不要在现实世界的应用程序中这样做,而只需在表和查询中添加 user_id。

接下来,我在我的another answer 中找到了另一个(不完整)动态 AbstractRoutingDataSource 示例。

我的代码(注意从未测试过)与您的问题之间的一大区别是我使用 SessionListener 关闭数据库以避免打开数据库的数量无限增长。

如果你要学习Spring,你可以试试下面的模式(自下而上的描述):

一个会话范围的 bean,它将为用户保存实际的数据库连接,应在第一次请求时创建连接(以确保会话中存在用户 ID)并缓存以供后续使用。销毁方法(在会话关闭时由 Spring 自动调用)应该关闭连接。 AbstractRoutingDataSource,将注入上述持有者的代理,并向持有者询问实际数据源

与另一个答案一样,如果同一用户可能有许多同时会话,您可以在会话持有者中注入一个单例,以保持实际的数据库连接以及活动会话的数量。这样一来,无论他有多少并发会话,每个用户都可以获得一个连接。

【讨论】:

我看了你的旧例子,我认为这是可能的,但你需要我解释一下。 sessionDestroyed 方法在课堂上什么实现 HttpSessionListener ?你在哪里执行AbstractRoutingDataSource 这是组件?添加连接数据库是在 servlet 上下文还是类中? @seti :我稍微修改了我的旧示例。这个想法是地图 userId -> dataSource 是注入 AbstractRoutingDataSource 实现中的单例 bean,在 HttpSessionListener 中将关闭数据源并将它们从地图中删除。您可以找到here 一个成功登录事件的监听器示例,它可以将数据库存储在地图中 我编辑了我的问题,你的逻辑帮助了我。你能解释一下我的旧代码和新代码有什么不同(这是有效的)。在旧代码中,我创建数据源映射集并刷新,在新代码中,我从 @Autowired 类集和刷新获取数据源。

以上是关于AbstractRoutingDataSource 在运行时更改映射的主要内容,如果未能解决你的问题,请参考以下文章

AbstractRoutingDataSource - 动态数据源

AbstractRoutingDataSource实现动态数据源切换 专题

切换数据库+ThreadLocal+AbstractRoutingDataSource

带有注释和(动态)AbstractRoutingDataSource 的 Spring 3.1.3 + Hibernate 配置

AbstractRoutingDataSource 实现动态数据源切换原理简单分析

AbstractRoutingDataSource 在运行时更改映射