AbstractRoutingDataSource 在运行时更改映射

AbstractRoutingDataSource change map in runtime

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

  1. 用户
  2. user_database

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

我希望用户登录到我的页面,下一次连接他所做的将被发送到用户数据库。为什么我需要什么?我规划流行的电子商务并创建 android 应用程序,用户可以在其中登录并查看商店数据,可以添加和查看产品订单。
现在该去练习了,我对 spring 技术的了解很少,当我做错的时候请解释一下。 Web 上 AbstractRoutingDataSource 的所有示例都在持久性文件中声明数据源或创建数据源 bean 并开始使用 AbstractRoutingDataSource。 在我的项目中,我现在没有用户连接,我需要从数据库中获取它。我尝试使用存储库和这个例子 但是我在控制器中的 @Autowired 上得到空值,我认为存储库的连接是空的。如何为此存储库设置连接并设置路由?此方法的缺陷是当我添加用户时我需要重新启动服务器以刷新连接。
接下来尝试我现在​​使用的是 class 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

}

我运行构造函数和determineCurrentLookupKey上的这个方法

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 我从你的示例中更改了一些代码 这是我的 class 地图

@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());
    }

}

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

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

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

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

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

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