(ps:如果只有这几个数据源,请选择一个默认的数据源和对应的事务管理器均加上@Primary注解)示例:
1.在yml文件中配置多数据源/池的信息
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: select 'x'
test-whileidle: true
test-on-borrow: false
test-on-return: false
pool-prepared-statements: false
max-pool-prepared-statement-per-connection-size: -1
filters: stat,wall,slf4j
use-global-data-source-stat: true
connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: /druid/*,*.js,*.gif,*.jpg,*.png,*.css,*.ico
stat-view-servlet:
enabled: true
url-pattern: /druid/*
reset-enable: false
login-username: xxxx
login-password: xxxx
allow: "*" # 允许所有IP访问
# deny:
auth_record:
dev:
datasource:
url: xxxx
username: xxxx
password: xxxx
driverClassName: com.mysql.cj.jdbc.Driver
test:
datasource:
url: xxxx
username: xxxx
password: xxxx
driverClassName: com.mysql.cj.jdbc.Driver
pre:
datasource:
url: jdbc:xxxx
username: xxxx
password: xxxx
driverClassName: com.mysql.cj.jdbc.Driver
data:
dev:
datasource:
url: xxxx
username: xxxx
password: xxxx
driverClassName: com.mysql.cj.jdbc.Driver
test:
datasource:
url: jdbc:xxxx
username: xxxx
password: xxxx
driverClassName: com.mysql.cj.jdbc.Driver
pre:
datasource:
url: xxxx
username: xxxx
password: xxxx
driverClassName: com.mysql.cj.jdbc.Driver
2.在实体类中使用@Value分别将各个源的信息注入(这里只展示一个实体);
@Data
@Configuration
public class AuthDevMySQLParams {
@Value("${auth_record.dev.datasource.driverClassName}")
private String driverClassName;
@Value("${auth_record.dev.datasource.username}")
private String userName;
@Value("${auth_record.dev.datasource.password}")
private String password;
@Value("${auth_record.dev.datasource.url}")
private String mysqlUrl;
}
3.将连接池的配置同样注入给实体
@Data
@Configuration
public class DriudParams {
@Value("${spring.datasource.druid.initial-size}")
private int initialSize;
@Value("${spring.datasource.druid.min-idle}")
private int minIdle;
@Value("${spring.datasource.druid.max-active}")
private int maxActive;
@Value("${spring.datasource.druid.max-wait}")
private long maxWait;
@Value("${spring.datasource.druid.time-between-eviction-runs-millis}")
private long timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.druid.min-evictable-idle-time-millis}")
private long minEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.validation-query}")
private String validationQuery;
@Value("${spring.datasource.druid.test-whileidle}")
private boolean testWhileIdle;
@Value("${spring.datasource.druid.test-on-borrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.druid.test-on-return}")
private boolean testOnReturn;
@Value("${spring.datasource.druid.pool-prepared-statements}")
private boolean poolPreparedStatements;
@Value("${spring.datasource.druid.max-pool-prepared-statement-per-connection-size}")
private int maxPoolPreparedStatementPerConnectionSize;
@Value("${spring.datasource.druid.filters}")
private String filters;
@Value("${spring.datasource.druid.use-global-data-source-stat}")
private boolean useGlobalDataSourceStat;
@Value("${spring.datasource.druid.connection-properties}")
private String connectionProperties;
}
4.创建对应连接池的数据源
@Component
public class CreateDataSourceBean {
@Autowired
private AuthPreMySQLParams authPreMySQLParams;
@Autowired
private AuthTestMySQLParams authTestMySQLParams;
@Autowired
private DataTestMySQLParams dataTestMySQLParams;
@Autowired
private DataPreMySQLParams dataPreMySQLParams;
@Autowired
private AuthDevMySQLParams authDevMySQLParams;
@Autowired
private DataDevMySQLParams dataDevMySQLParams;
@Autowired
private DriudParams driudParams;
@Bean(name = "dataDevDataSource")
public DataSource CreateDataDevDataSource() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(dataDevMySQLParams.getMysqlUrl());
dataSource.setUsername(dataDevMySQLParams.getUserName());
dataSource.setPassword(dataDevMySQLParams.getPassword());
dataSource.setDriverClassName(dataDevMySQLParams.getDriverClassName());
return SetDataSourceProperty(dataSource);
}
@Bean(name = "authDevDataSource")
public DataSource CreateAuthDevDataSource() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(authDevMySQLParams.getMysqlUrl());
dataSource.setUsername(authDevMySQLParams.getUserName());
dataSource.setPassword(authDevMySQLParams.getPassword());
dataSource.setDriverClassName(authDevMySQLParams.getDriverClassName());
return SetDataSourceProperty(dataSource);
}
@Bean(name = "authPreDataSource")
public DataSource CreateAuthPreDataSource() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(authPreMySQLParams.getMysqlUrl());
dataSource.setUsername(authPreMySQLParams.getUserName());
dataSource.setPassword(authPreMySQLParams.getPassword());
dataSource.setDriverClassName(authPreMySQLParams.getDriverClassName());
return SetDataSourceProperty(dataSource);
}
@Bean(name = "authTestDataSource")
public DataSource CreateAuthTestDataSource() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(authTestMySQLParams.getMysqlUrl());
dataSource.setUsername(authTestMySQLParams.getUserName());
dataSource.setPassword(authTestMySQLParams.getPassword());
dataSource.setDriverClassName(authTestMySQLParams.getDriverClassName());
return SetDataSourceProperty(dataSource);
}
@Bean(name = "dataTestDataSource")
public DataSource CreateDataTestDataSource() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(dataTestMySQLParams.getMysqlUrl());
dataSource.setUsername(dataTestMySQLParams.getUserName());
dataSource.setPassword(dataTestMySQLParams.getPassword());
dataSource.setDriverClassName(dataTestMySQLParams.getDriverClassName());
return SetDataSourceProperty(dataSource);
}
@Bean(name = "dataPreDataSource")
public DataSource CreateDataPreDataSource() throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(dataPreMySQLParams.getMysqlUrl());
dataSource.setUsername(dataPreMySQLParams.getUserName());
dataSource.setPassword(dataPreMySQLParams.getPassword());
dataSource.setDriverClassName(dataPreMySQLParams.getDriverClassName());
return SetDataSourceProperty(dataSource);
}
private DruidDataSource SetDataSourceProperty(DruidDataSource dataSource) throws SQLException {
// 具体配置
dataSource.setInitialSize(driudParams.getInitialSize());
dataSource.setMinIdle(driudParams.getMinIdle());
dataSource.setMaxActive(driudParams.getMaxActive());
dataSource.setMaxWait(driudParams.getMaxWait());
dataSource.setTimeBetweenEvictionRunsMillis(driudParams.getTimeBetweenEvictionRunsMillis());
dataSource.setMinEvictableIdleTimeMillis(driudParams.getMinEvictableIdleTimeMillis());
dataSource.setValidationQuery(driudParams.getValidationQuery());
dataSource.setTestWhileIdle(driudParams.isTestWhileIdle());
dataSource.setTestOnBorrow(driudParams.isTestOnBorrow());
dataSource.setTestOnReturn(driudParams.isTestOnReturn());
dataSource.setPoolPreparedStatements(driudParams.isPoolPreparedStatements());
dataSource.setMaxPoolPreparedStatementPerConnectionSize(driudParams.getMaxPoolPreparedStatementPerConnectionSize());
dataSource.setFilters(driudParams.getFilters());
dataSource.setUseGlobalDataSourceStat(driudParams.isUseGlobalDataSourceStat());
dataSource.setConnectionProperties(driudParams.getConnectionProperties());
return dataSource;
}
}
5.至此已经完成了多数据源的配置,后续想要使用只需通过bean注入即可拿到
使用示例:(若不用try cathch 记得返回连接池的连接.close() )
@Service
public class YourService {
private final DataSource dataSource;
@Autowired
public YourService(DataSource dataSource) {
this.dataSource = dataSource;
}
public void performJdbcQuery() throws SQLException {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = dataSource.getConnection();
String sql = "SELECT * FROM your_table";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
Long id = resultSet.getLong("id");
String name = resultSet.getString("name");
// Process the retrieved data as needed
}
} finally {
if (resultSet != null) {
resultSet.close();
}
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
}
}
}
6.上面的方法每次都需要手动的去拼接sql,在实现的过程中很容易出错
所以还可以使用SqlSessionFactory扫描对应路径下的xml文件(其中正常编写xml格式的sql语句)
示例(创建SqlSessionFactory的方法在该代码最后):
@Configuration
@MapperScan(basePackages = AuthPreDataSourceConfig.PACKAGE, sqlSessionFactoryRef = "authPreSqlSessionFactory")
public class AuthPreDataSourceConfig {
/**
* 配置多数据源 关键就在这里 这里配置了不同的mapper对应不同的数据源
*/
static final String PACKAGE = "io.metersphere.python_exec.mapper.authorizedRecords.pre";
static final String MAPPER_LOCATION = "classpath:mapper/authorizedRecords/pre/*.xml";
/**
* 连接数据库信息 这个其实更好的是用配置中心完成
*/
@Value("${auth_record.pre.datasource.url}")
private String url;
@Value("${auth_record.pre.datasource.username}")
private String username;
@Value("${auth_record.pre.datasource.password}")
private String password;
@Value("${auth_record.pre.datasource.driverClassName}")
private String driverClassName;
/**
* 下面的配置信息可以读取配置文件,其实可以直接写死 如果是多数据源的话 还是考虑读取配置文件
*/
@Value("${spring.datasource.druid.initial-size}")
private int initialSize;
@Value("${spring.datasource.druid.min-idle}")
private int minIdle;
@Value("${spring.datasource.druid.max-active}")
private int maxActive;
@Value("${spring.datasource.druid.max-wait}")
private int maxWait;
@Value("${spring.datasource.druid.time-between-eviction-runs-millis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.druid.min-evictable-idle-time-millis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.validation-query}")
private String validationQuery;
@Value("${spring.datasource.druid.test-whileidle}")
private boolean testWhileIdle;
@Value("${spring.datasource.druid.test-on-borrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.druid.test-on-return}")
private boolean testOnReturn;
@Value("${spring.datasource.druid.pool-prepared-statements}")
private boolean poolPreparedStatements;
@Value("${spring.datasource.druid.max-pool-prepared-statement-per-connection-size}")
private int maxPoolPreparedStatementPerConnectionSize;
@Value("${spring.datasource.druid.filters}")
private String filters;
@Value("${spring.datasource.druid.connection-properties}")
private String connectionProperties;
@Bean(name = "authPreDataSource")
public DataSource authPreDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setDriverClassName(driverClassName);
//具体配置
dataSource.setInitialSize(initialSize);
dataSource.setMinIdle(minIdle);
dataSource.setMaxActive(maxActive);
dataSource.setMaxWait(maxWait);
dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
dataSource.setValidationQuery(validationQuery);
dataSource.setTestWhileIdle(testWhileIdle);
dataSource.setTestOnBorrow(testOnBorrow);
dataSource.setTestOnReturn(testOnReturn);
dataSource.setPoolPreparedStatements(poolPreparedStatements);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
/**
* 这个是用来配置 druid 监控sql语句的 非常有用 如果你有两个数据源 这个配置哪个数据源就坚实哪个数据源的sql 同时配置那就都监控
*/
try {
dataSource.setFilters(filters);
} catch (SQLException e) {
e.printStackTrace();
}
dataSource.setConnectionProperties(connectionProperties);
return dataSource;
}
@Bean(name = "authPreTransactionManager")
public DataSourceTransactionManager authPreTransactionManager() {
return new DataSourceTransactionManager(authPreDataSource());
}
@Bean(name = "userPreSqlSessionFactory")
public SqlSessionFactory authPreSqlSessionFactory(@Qualifier("authPreDataSource") DataSource preDataSource)
throws Exception {
final SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(preDataSource);
sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(AuthPreDataSourceConfig.MAPPER_LOCATION));
// //分页插件
// Interceptor interceptor = new PageInterceptor();
// Properties properties = new Properties();
// //数据库
// properties.setProperty("helperDialect", "mysql");
// //是否将参数offset作为PageNum使用
// properties.setProperty("offsetAsPageNum", "true");
// //是否进行count查询
// properties.setProperty("rowBoundsWithCount", "true");
// //是否分页合理化
// properties.setProperty("reasonable", "false");
// interceptor.setProperties(properties);
// sessionFactory.setPlugins(new Interceptor[] {interceptor});
return sessionFactory.getObject();
}
}
7.事务管理器的使用
我们可以在第四步中加入(选择对应的数据源注册对应的事务管理器bean)
@Bean(name = "dataDevTransactionManager")
public DataSourceTransactionManager transactionManager(@Qualifier("dataDevDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
然后在service类上加入@Transactional注解选择对应的事务管理器
@Slf4j
@Transactional(transactionManager = "dataDevTransactionManager")
public class AuthPreCopyServiceImpl implements AuthService {
@Transactional注解源代码
@Target({ElementType.TYPE, ElementType.METHOD}) // 表明该注解可以应用在类和方法上
@Retention(RetentionPolicy.RUNTIME) // 保留期间为运行时,这样可以通过反射获取注解信息
@Inherited // 表示该注解可以被子类继承
@Documented // 表示该注解将被包含在 JavaDoc 中
@Reflective // 这可能是一个自定义的元注解,用于指示该注解支持反射操作
public @interface Transactional {
@AliasFor("transactionManager")
String value() default ""; // 别名为 transactionManager,可以用于指定事务管理器名称
@AliasFor("value")
String transactionManager() default ""; // 别名为 value,可以用于指定事务管理器名称
String[] label() default {}; // 用于指定事务的标签,可能用于更细粒度的事务控制
Propagation propagation() default Propagation.REQUIRED; // 事务传播行为,默认为 REQUIRED
Isolation isolation() default Isolation.DEFAULT; // 事务隔离级别,默认为 DEFAULT
int timeout() default -1; // 事务超时时间,默认为 -1 表示不超时
String timeoutString() default ""; // 事务超时时间的字符串表示,用于兼容性
boolean readOnly() default false; // 是否为只读事务,默认为 false
Class<? extends Throwable>[] rollbackFor() default {}; // 触发回滚的异常类型,默认为空数组,表示仅回滚 RuntimeException 和 Error
String[] rollbackForClassName() default {}; // 触发回滚的异常类型名称,默认为空数组
Class<? extends Throwable>[] noRollbackFor() default {}; // 不触发回滚的异常类型,默认为空数组
String[] noRollbackForClassName() default {}; // 不触发回滚的异常类型名称,默认为空数组
}
8.事务的传播行为和隔离级别
事务的传播行为 (Propagation Behavior)
事务的传播行为定义了在一个方法调用另一个方法时,事务应该如何被传播或创建。Spring框架提供了以下几种传播行为:
PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常用的传播行为。
PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续执行。
PROPAGATION_MANDATORY: 强制要求当前存在一个事务,如果没有,则抛出异常。
PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则挂起当前事务。
PROPAGATION_NOT_SUPPORTED: 以非事务方式执行操作,并挂起当前事务(如果有)。
PROPAGATION_NEVER: 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED: 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则其行为类似于PROPAGATION_REQUIRED。
事务的隔离级别 (Isolation Levels)
事务的隔离级别定义了事务如何处理并发操作,以及它对其他事务的可见性。常见的隔离级别包括:
ISOLATION_DEFAULT: 使用底层数据库系统的默认隔离级别。
ISOLATION_READ_UNCOMMITTED: 允许脏读,即一个事务可以读取到另一个事务尚未提交的数据。
ISOLATION_READ_COMMITTED: 防止脏读,但允许不可重复读和幻读。
ISOLATION_REPEATABLE_READ: 防止脏读和不可重复读,但可能允许幻读。
ISOLATION_SERIALIZABLE: 提供最强的隔离级别,防止所有并发问题,但可能会导致性能下降。