先说结论:
在项目启动后,使用修改配置文件+restart()连接/连接池对象的方法解决。
-----------------------------------------------------
思路2:
使用读写锁,给restart()、setUrl()等代码块加写锁;
给数据库操作方法加读锁。
这样就可以在修改数据库连接信息时,先获取写锁,保证数据库操作方法不能执行(如果修改到一半,有数据库操作方法执行的话,后续修改链接方法会报错,所以不能让数据库操作方法执行);
而在不修改连接时,数据库操作方法之间获取的是读锁,不会影响线程彼此之间的操作。
具体方法:
1.读写锁用法:
//公平读写锁
//ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
//非公平读写锁,默认false
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
//读锁
lock.readLock().lock();
//...
//省略数据库增删改查方法
//执行完后,释放读锁
lock.readLock().unlock();
/*-----------------------------------*/
//Druid数据库连接池,注意对象类型是 DruidDataSource
//@Autowired
//DruidDataSource dataSource;
//写锁
lock.writeLock().lock();
//修改数据库连接信息
dataSource.restart();
dataSource.setUrl("数据库url");
dataSource.setUserName("数据库用户名");
dataSource.setPassword("数据库密码");
dataSource.setDriverClassName("数据库驱动名称");
//执行完后,释放写锁
lock.writeLock().unlock();
2.使用AOP,切入调用数据库的mapper方法,调用前加读锁,调用后释放读锁;
3.对修改数据库连接的操作加写锁,修改完成后释放写锁。
-----------------------------------------------------
之前听同事说,有一个需求,要求在spring/springboot项目启动后,留一个接口,修改数据库连接。
意思就是项目启动后,本来我查的某一个数据库;然后突然要改为查询另一个数据库,怎么办。
可能主备库会用到?当主库挂了后,要在不影响项目运行的情况下改为查备库?
然后本人稍微研究了一下,把代码与思路放在下面。
-----------------------------------------------------
1.首先,spring/springboot项目可以使用druid链接池。
springboot下需要使用的jar包:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
spring下需要使用的jar包:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.1</version>
</dependency>
2.然后,配置好druid连接池,把它交给spring容器管理。
3.在代码中,使用注解把druid连接池对象注入,例如:
@Autowired
DruidDataSource dataSource;
4.然后,就可以使用这个连接池对象修改链接信息了。需要先执行restart(),然后用set方法修改,然后修改后的链接信息就生效了,例如:
//注入Druid数据库连接池对象
@Autowired
DruidDataSource dataSource;
//注入一个测试用的mapper
@Autowired
UserMapper userMapper;
public void test(){
//先查回一个用户名来,证明现在链接没有修改。(用的是启动spring时配置的链接信息)
String username = userMapper.getUserNameById(1);
System.out.println(username);
//先执行restart(),然后才能执行set,否则会报错UnsupportedOperationException()
dataSource.restart();
//然后就能修改链接信息了
//填写一个不存在的数据库,测试用
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/nobase?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true");
//dataSource.setDriver("这里填驱动对象");
//dataSource.setUsername("这里填用户名");
//dataSource.setPassword("这里填密码");
//然后再调用下mapper,此时会报错,因为本人将链接信息修改了,对应一个不存在的数据库,当然会报错
String username = userMapper.getUserNameById(1);
System.out.println(username);
}
注意事项:
1.需要先执行restart(),然后才能执行set,否则会报错UnsupportedOperationException()
2.在高并发场景下,需要做些限制才能使用,例如以下情况,执行set时,也会报错UnsupportedOperationException()
public void test(){
String username = userMapper.getUserNameById(1);
System.out.println(username);
dataSource.restart();
//如果执行restart后,有程序执行了mapper,则下方会报错
String username = userMapper.getUserNameById(1);
System.out.println(username);
//由于上方执行了mapper,导致dataSource被init,所以这里会报错;详情见源码
//执行set时必须在dataSource刚restart后才行(此时还没有被init)
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/nobase?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true");
String username = userMapper.getUserNameById(1);
System.out.println(username);
}
原理就是,dataSource执行setUrl等方法时,源码会判断DataSource是否已经被初始化(init);如果已经被初始化,则报错UnsupportedOperationException();
dataSource执行restart()方法后,会处于未被初始化的状态,所以才能执行setUrl等操作;
而当dataSource执行restart()方法后,执行mapper等数据库操作时,会被初始化(init);
接着想执行setUrl等方法,会发现dataSource被初始化了,然后报错,导致修改链接失败。
=======================================================
controller中注入DruidDataSource报错的,可以试下下面这个:
https://blog.csdn.net/zhu0836/article/details/84330666
=======================================================
正确解决方法
1.由于数据库连接初始化时一般读取的是配置文件(假如数据库连接url与用户名密码在config.properties中保存),所以启动项目时,让项目读取外部的配置文件;
2.项目启动后,可以先把配置文件中的数据库连接信息修改掉(config.properties),手动修改或写个Controller进行修改都可;
3.然后再写个Controller,其中调用一下dataSource.restart()就可以了;之后有线程执行数据库操作时,dataSource自动重新初始化,读取修改后的配置文件,就实现项目启动后修改数据库连接的效果了。