一、分库分表思路

 1、垂直分表

  拆分列。基于列进行。

  根据业务耦合性,将关联度低的不同表存储在不同的数据库,与微服务类似,按照业务独立划分,每个微服务使用单独的一个数据库。也可将字段较多

的表拆分新建一张扩展表,将不常用或字段较大的字段拆分出去到扩展表中。

  在字段很多的情况下(例如一个大表有100多个字段),通过"大表拆小表",更便于开发与维护,也能避免跨页问题,MySQL底层是通过数据页存储的,

一条记录占用空间过大会导致跨页,造成额外的性能开销。另外数据库以行为单位将数据加载到内存中,这样表中字段长度较短且访问频率较高,内存能加

载更多的数据,命中率更高,减少了磁盘IO,从而提升了数据库性能。

  优点:业务解耦清晰;高并发下,提升一定程度的IO,数据库连接数、单机硬件资源瓶颈。

  缺点:部分表无法join,只能通过接口聚合,提升了开发复杂度;分布式事务处理复杂。

 2、水平分表

  拆分行。基于行进行。

  分为库内分表,分库分表,根据表内数据内在的逻辑关系,将同一个表按条件分散到多个数据库或多个表中,每张表中包含一部分数据,从而使单张表的数

据量变小。

  库内分表只解决了单一表数据量过大的问题,但没有将表分布到不同机器的库上,因此对于减轻MySQL数据库的压力来说,帮助不是很大,大家还是竞争同

一个物理机的CPU、内存、网络IO,最好通过分库分表来解决。

  优点:不存在单库数据量过大、高并发的性能瓶颈,提升系统稳定性和负载能力;应用端改造较小,不需要拆分业务模块。

  缺点:跨分片的事务一致性难以保证;跨库的join关联性能较差;数据不易维护

二、Spring动态数据源实现分库

在分库分表的情况下,在执行SQL时选择连接不同的数据源(库)的思路:配置多个数据源加到动态数据源对象中,根据实际的情况动态切换到相应的数据源中。

如存放订单信息的有10个库,每个库中有100张表,根据用户的ID做分库分表。可以选择ID的某一位的值作为选择的第几个库,某两位的值作为对应的表名。

实现:在service中使用AOP拦截具有某个注解的方法,根据方法中的参数的用户信息,按照上面的规则指定要连接的数据源。

  环境2.1.0.RELEASE,MySQL

1、所有的数据源信息配置:application.properties

#dataSource sharding
tvbss.jdbc.username=${config.tvbss.jdbc.username}
tvbss.jdbc.password=${config.tvbss.jdbc.password}
tvbss.jdbc.driver=${config.tvbss.jdbc.driver}
tvbss.jdbc.server00=${config.tvbss.jdbc.server00}
tvbss.jdbc.server01=${config.tvbss.jdbc.server01}
tvbss.jdbc.server02=${config.tvbss.jdbc.server02}
tvbss.jdbc.server03=${config.tvbss.jdbc.server03}
tvbss.jdbc.server04=${config.tvbss.jdbc.server04}
tvbss.jdbc.server05=${config.tvbss.jdbc.server05}
tvbss.jdbc.server06=${config.tvbss.jdbc.server06}
tvbss.jdbc.server07=${config.tvbss.jdbc.server07}
tvbss.jdbc.server08=${config.tvbss.jdbc.server08}
tvbss.jdbc.server09=${config.tvbss.jdbc.server09}

2、读取配置信息到属性类中

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component; import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map; /**
* 数据源属性配置
*
* @author yangyongjie
* @date 2019/9/27
* @desc
*/
@Component
@ConfigurationProperties(prefix = "tvbss.jdbc")
public class ShardingProperties {
private String username;
private String password;
private String driver;
private String server00;
private String server01;
private String server02;
private String server03;
private String server04;
private String server05;
private String server06;
private String server07;
private String server08;
private String server09; private Map<String, String> urlsMap = new HashMap<>(); @PostConstruct
public void initUrlList() {
urlsMap.put("server00", server00);
urlsMap.put("server01", server01);
urlsMap.put("server02", server02);
urlsMap.put("server03", server03);
urlsMap.put("server04", server04);
urlsMap.put("server05", server05);
urlsMap.put("server06", server06);
urlsMap.put("server07", server07);
urlsMap.put("server08", server08);
urlsMap.put("server09", server09);
} }

3、指定动态数据源的关键类

import com.xxx.common.bean.BaseShardedBean;
import com.xxx.config.datasource.prop.ShardingProperties;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.StringUtils; import java.util.HashMap;
import java.util.Map; /**
* 动态数据源切换
* 方式:配置多个数据源加到动态数据源对象中,根据实际的情况动态的切换到相应的数据源
*/
public class DynamicDataSource extends AbstractRoutingDataSource { private static Logger LOGGER = LoggerFactory.getLogger(DynamicDataSource.class); @Autowired
private ShardingProperties shardingProperties; /**
* 连接池分配的最大连接数
*/
private int maxActive = GenericObjectPool.DEFAULT_MAX_ACTIVE; /**
* 空闲时保留的最大连接数
*/
private int maxIdle = GenericObjectPool.DEFAULT_MAX_IDLE; /**
* 空闲时保留的最少连接数
*/
private int minIdle = GenericObjectPool.DEFAULT_MIN_IDLE; /**
* 当连接池没有空闲时等待的最大毫秒数,小于0表示无限期等待
*/
private long maxWait = GenericObjectPool.DEFAULT_MAX_WAIT;
/**
* 连接池的初始连接数
*/
private int initialSize = 0; /**
* 返回数据源dataSource的key值,实现数据源切换的关键方法
* 在determineTargetDataSource方法中根据key获取目标连接数据源
*
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
Object value = CurentDataSourceHolder.getCurentDataSource();
if (StringUtils.isEmpty(value)) {
value = BaseShardedBean.defaultKey();
}
return value;
} /**
* 启动时初始化所有的目标数据源
*/
@Override
public void afterPropertiesSet() {
super.setTargetDataSources(buildDataSource());
super.afterPropertiesSet();
} /**
* 根据配置文件的配置构建数据源
*
* @return
*/
private Map<Object, Object> buildDataSource() {
Map<Object, Object> dataSources = new HashMap<>(16);
Map<String, String> urls = shardingProperties.getUrlsMap();
if (MapUtils.isEmpty(urls)) {
LOGGER.error("获取配置的数据库urls为空");
return dataSources;
}
String driver = shardingProperties.getDriver();
String username = shardingProperties.getUsername();
String password = shardingProperties.getPassword();
urls.forEach((key, value) -> {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setUrl(value);
dataSource.setMaxWait(maxWait);
dataSource.setMaxIdle(maxIdle);
dataSource.setMinIdle(minIdle);
dataSource.setMaxActive(maxActive);
dataSources.put(key, dataSource);
});
return dataSources;
} /**
* 数据源存放对象,用于动态切换用
*/
public static class CurentDataSourceHolder { private static ThreadLocal<Object> holder = new ThreadLocal<>(); /**
* 设置当前线程的数据源
*
* @param source
*/
public static void setCurentDataSource(Object source) {
holder.set(source);
} /**
* 获取当前线程的数据源
*
* @return
*/
public static Object getCurentDataSource() {
return holder.get();
} /**
* 清楚数据源
*/
public static void remove() {
holder.remove();
}
} public int getMaxActive() {
return maxActive;
} public void setMaxActive(int maxActive) {
this.maxActive = maxActive;
} public int getMaxIdle() {
return maxIdle;
} public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
} public int getMinIdle() {
return minIdle;
} public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
} public long getMaxWait() {
return maxWait;
} public void setMaxWait(long maxWait) {
this.maxWait = maxWait;
} public int getInitialSize() {
return initialSize;
} public void setInitialSize(int initialSize) {
this.initialSize = initialSize;
} }

  afterPropertiesSet()方法初始化了所有的目标数据源

  determineCurrentLookupKey()方法用来获取当前线程变量中指定的当前数据源的key,若没有,则使用默认数据源

AbstractRoutingDataSource :

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

    @Nullable
private Map<Object, Object> targetDataSources; // 多个目标数据源 @Nullable
private Object defaultTargetDataSource; // 默认的目标数据源 private boolean lenientFallback = true; private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup(); @Nullable
private Map<Object, DataSource> resolvedDataSources; @Nullable
private DataSource resolvedDefaultDataSource; /**
* Specify the map of target DataSources, with the lookup key as key.
* The mapped value can either be a corresponding {@link javax.sql.DataSource}
* instance or a data source name String (to be resolved via a
* {@link #setDataSourceLookup DataSourceLookup}).
* <p>The key can be of arbitrary type; this class implements the
* generic lookup process only. The concrete key representation will
* be handled by {@link #resolveSpecifiedLookupKey(Object)} and
* {@link #determineCurrentLookupKey()}.
*/
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
} /**
* Specify the default target DataSource, if any.
* <p>The mapped value can either be a corresponding {@link javax.sql.DataSource}
* instance or a data source name String (to be resolved via a
* {@link #setDataSourceLookup DataSourceLookup}).
* <p>This DataSource will be used as target if none of the keyed
* {@link #setTargetDataSources targetDataSources} match the
* {@link #determineCurrentLookupKey()} current lookup key.
*/
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
this.defaultTargetDataSource = defaultTargetDataSource;
} /**
* Specify whether to apply a lenient fallback to the default DataSource
* if no specific DataSource could be found for the current lookup key.
* <p>Default is "true", accepting lookup keys without a corresponding entry
* in the target DataSource map - simply falling back to the default DataSource
* in that case.
* <p>Switch this flag to "false" if you would prefer the fallback to only apply
* if the lookup key was {@code null}. Lookup keys without a DataSource
* entry will then lead to an IllegalStateException.
* @see #setTargetDataSources
* @see #setDefaultTargetDataSource
* @see #determineCurrentLookupKey()
*/
public void setLenientFallback(boolean lenientFallback) {
this.lenientFallback = lenientFallback;
} /**
* Set the DataSourceLookup implementation to use for resolving data source
* name Strings in the {@link #setTargetDataSources targetDataSources} map.
* <p>Default is a {@link JndiDataSourceLookup}, allowing the JNDI names
* of application server DataSources to be specified directly.
*/
public void setDataSourceLookup(@Nullable DataSourceLookup dataSourceLookup) {
this.dataSourceLookup = (dataSourceLookup != null ? dataSourceLookup : new JndiDataSourceLookup());
} @Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());
this.targetDataSources.forEach((key, value) -> {
Object lookupKey = resolveSpecifiedLookupKey(key);
DataSource dataSource = resolveSpecifiedDataSource(value);
this.resolvedDataSources.put(lookupKey, dataSource);
});
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
} /**
* Resolve the given lookup key object, as specified in the
* {@link #setTargetDataSources targetDataSources} map, into
* the actual lookup key to be used for matching with the
* {@link #determineCurrentLookupKey() current lookup key}.
* <p>The default implementation simply returns the given key as-is.
* @param lookupKey the lookup key object as specified by the user
* @return the lookup key as needed for matching
*/
protected Object resolveSpecifiedLookupKey(Object lookupKey) {
return lookupKey;
} /**
* Resolve the specified data source object into a DataSource instance.
* <p>The default implementation handles DataSource instances and data source
* names (to be resolved via a {@link #setDataSourceLookup DataSourceLookup}).
* @param dataSource the data source value object as specified in the
* {@link #setTargetDataSources targetDataSources} map
* @return the resolved DataSource (never {@code null})
* @throws IllegalArgumentException in case of an unsupported value type
*/
protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
if (dataSource instanceof DataSource) {
return (DataSource) dataSource;
}
else if (dataSource instanceof String) {
return this.dataSourceLookup.getDataSource((String) dataSource);
}
else {
throw new IllegalArgumentException(
"Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
}
} @Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
} @Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
} @Override
@SuppressWarnings("unchecked")
public <T> T unwrap(Class<T> iface) throws SQLException {
if (iface.isInstance(this)) {
return (T) this;
}
return determineTargetDataSource().unwrap(iface);
} @Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
} /**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
} /**
* Determine the current lookup key. This will typically be
* implemented to check a thread-bound transaction context.
* <p>Allows for arbitrary keys. The returned key needs
* to match the stored lookup key type, as resolved by the
* {@link #resolveSpecifiedLookupKey} method.
*/
@Nullable
protected abstract Object determineCurrentLookupKey(); }

4、Spring容器中数据源配置类

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager; /**
* 数据源配置文件
*
* @author yangyongjie
* @date 2019/9/25
* @desc
*/
@Configuration
public class ShardingDataSourceConfig { private static final Logger LOGGER = LoggerFactory.getLogger(ShardingDataSourceConfig.class); /**
* 数据源
*
* @return
*/
@Bean
public DynamicDataSource shardingDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
dynamicDataSource.setMaxActive(8);
dynamicDataSource.setMaxIdle(2);
dynamicDataSource.setMinIdle(1);
dynamicDataSource.setMaxWait(60000);
return dynamicDataSource;
} @Bean
public SqlSessionFactory shardingSqlSessionFactory() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// 扫描相关mapper文件
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
SqlSessionFactory sqlSessionFactory = null;
try {
sqlSessionFactoryBean.setMapperLocations(resolver.getResources("classpath:/mapper/**/*Mapper.xml"));
sqlSessionFactoryBean.setDataSource(shardingDataSource());
// sqlSessionFactoryBean.setConfigLocation();
sqlSessionFactory = sqlSessionFactoryBean.getObject();
} catch (Exception e) {
LOGGER.error("创建SqlSessionFactory:"+e.getMessage(),e);
}
return sqlSessionFactory;
} @Bean
public SqlSessionTemplate shardingSqlSessionTemplate() {
return new SqlSessionTemplate(shardingSqlSessionFactory());
} /**
* sharding数据源事务管理器
*
* @return
*/
@Bean
public DataSourceTransactionManager shardingTransactionManager() {
return new DataSourceTransactionManager(shardingDataSource());
} }

  

5、指定了默认数据源,提供 generateShardingKey 方法,用来根据当前bean分配库名,并保存在线程本地变量中,以及生成表名,为bean的tableIndex属性赋值

public abstract class BaseShardedBean {

    static String DEFAULT_SHARDING_SCHAME = "server";
static String DEFAULT_SHARDING_INDEX = "00"; public abstract Object dbKey(); public abstract String tableKey(); /**
* 子类必须指定分片的key
*/
public abstract Object genShardedKey(); /**
* 子类必须根据bean的映射选择表名
*/
public abstract String genTableName(); /**
* 子类必须制定业务数据库类型
*/
public abstract String genDbSchema(); /**
* 默认的库名,server00
*
* @return
*/
public static Object defaultKey() {
return DEFAULT_SHARDING_SCHAME + DEFAULT_SHARDING_INDEX;
} /**
* 生成库名和表名
*/
public void generateShardingKey() {
Object key = dbKey();
key = StringUtils.isEmpty(key) ? defaultKey() : key;
DynamicDataSource.CurentDataSourceHolder.setCurentDataSource(key);
tableKey();
} }

6、具体的库表映射实体,继承了BaseShardedBean ,用户实体需继承它,以及顶一个公共的分库分表规则,即所有的实体类对应的分库分表规则都相同,不同的是库名前缀和表名前缀

**
* 支持分片的Bean,只支持单表操作 required ${genShardedKey} 指定类型的分片key ${genTableName} 指定类型映射的表名
**/
public abstract class ShardedMapperBean extends BaseShardedBean { @JsonIgnore
protected String orderByClause;
@JsonIgnore
protected boolean distinct;
@JsonIgnore
protected String tableName;
@JsonIgnore
private String tableIndex;
@JsonIgnore
protected Object shardKey; public String getOrderByClause() {
return orderByClause;
} public void setOrderByClause(String orderByClause) {
this.orderByClause = orderByClause;
} public boolean isDistinct() {
return distinct;
} public void setDistinct(boolean distinct) {
this.distinct = distinct;
} public String getTableName() {
return tableName;
} public void setTableName(String tableName) {
this.tableName = tableName;
} public String getTableIndex() {
return tableIndex;
} public void setTableIndex(String tableIndex) {
this.tableIndex = tableIndex;
} @Override
public Object dbKey() {
String key = genShardedKey() == null ? null : String.valueOf(genShardedKey());
if (StringUtils.isEmpty(key)) {
return null;
}
return genDbSchema() + "0" + Integer.parseInt(getLastSub4to2(key)) / 10; // 库名为key倒数三四位除10取整,即倒数第四位的值,一共分10个库
} @Override
public String tableKey() {
this.setTableName(genTableName());
String key = genShardedKey() == null ? null : String.valueOf(genShardedKey());
if (StringUtils.isEmpty(key)) {
setTableIndex(genTableName());
return null;
} else {
String tableIndex = genTableName() + "_" + getLastSub2(key); // 表名为倒数一二位的值,一共分100张表
setTableIndex(tableIndex);
return tableIndex;
}
} private static String getLastSub4to2(String key) {
if (StringUtils.isEmpty(key) || key.length() < 4) {
return "";
}
int keyLen = key.length();
return key.substring(keyLen - 4, keyLen - 2);
} private static String getLastSub2(String key) {
if (StringUtils.isEmpty(key) || key.length() < 2) {
return "";
}
int keyLen = key.length();
return key.substring(keyLen - 2);
}
}

如具体的实体继承ShardedMapperBean 之后重写的方法,自定义库名前缀和表名前缀,以及用什么作为分库分表的key

    @Override
public Object genShardedKey() {
return id;
} @Override
public String genTableName() {
return "order";
} @Override
public String genDbSchema() {
return "databaseNamePrefix";
}

7、最后使用AOP拦截方法的执行,给DynamicDataSource中的静态内部类CurentDataSourceHolder中的线程本地变量指定实际连接的数据源即可。

import com.xxx.common.bean.BaseShardedBean;
import com.xxx.config.datasource.DynamicDataSource;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import java.lang.reflect.Method; /**
* 事务注解的拦截器,为事务管理器为shardingTransactionManager的方法指定数据源
*
* @author yangyongjie
* @date 2019/9/30
* @desc
*/
@Aspect
@Component
public class TransactionAspect { private static final String SHARD_TRANSACTION_MANAGER = "shardingTransactionManager"; @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
private void transactionbPointCut() {
// donothing
} /**
* 方法执行前的拦截,指定数据源
* 因此如果需要动态指定数据源,则方法参数bean需要继承ShardedMapperBean并重写genShardedKey()、genTableName()和genDbSchema()
*
* @param joinPoint
*/
@Before(value = "transactionbPointCut()")
public void beforeTransaction(JoinPoint joinPoint) {
// 获取方法上的注解
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method pointCutMethod = methodSignature.getMethod();
Transactional ann = pointCutMethod.getAnnotation(Transactional.class);
if (!StringUtils.equals(ann.value(), SHARD_TRANSACTION_MANAGER)) {
return;
}
// 获取方法参数
Object[] methodArgs = joinPoint.getArgs();
for (Object args : methodArgs) {
if (args instanceof BaseShardedBean) {
((BaseShardedBean) args).generateShardingKey();
break;
}
} } /**
* 方法执行后删除存放数据源key的线程本地变量
*/
@After(value = "transactionbPointCut()")
public void afterTransaction() {
DynamicDataSource.CurentDataSourceHolder.remove();
}
}

三、水平拆分数据分片规则

  1、根据数值范围

    按照时间区间或者ID区间来切分。例如:按日期将不同月甚至是日的数据分散到不同的库中;将userId为1~9999的记录分到第一个库,10000~20000的分到第二个库,以此类推。

  2、根据数值取模

    一般采用hash取模mod的切分方式,如将Order表根据userId字段切分到10个库中,余数为0的放到第一个库,余数为1的放到第二个库,依此类推。这样同一个用户的数据会分散到同一个库中。

end.

Spring动态数据源-AbstractRoutingDataSource的更多相关文章

  1. Spring动态数据源的配置

    Spring动态数据源 我们很多项目中业务都需要涉及到多个数据源,就是对不同的方法或者不同的包使用不同的数据源.最简单的做法就是直接在Java代码里面lookup需要的数据源,但是这种做法耦合性太高, ...

  2. Spring动态数据源实现读写分离

    一.创建基于ThreadLocal的动态数据源容器,保证数据源的线程安全性 package com.bounter.mybatis.extension; /** * 基于ThreadLocal实现的动 ...

  3. spring 动态数据源

    1.动态数据源:  在一个项目中,有时候需要用到多个数据库,比如读写分离,数据库的分布式存储等等,这时我们要在项目中配置多个数据库. 2.原理:   (1).spring 单数据源获取数据连接过程: ...

  4. spring动态数据源+事务

    今天在尝试配置spring的动态数据源和事务管理的时候,遇到了几处配置上的问题,在此记录下: 1.使用了spring的aop思想,实现了动态数据源的切换. 2.spring的事务管理,是基于数据源的, ...

  5. 【sping揭秘】21、Spring动态数据源的切换

    对于多个数据源的时候,我们如何切换不同的数据源进行数据库的操作呢? 当然我们可以直接定义2个DataSource,然后在每次获取connection的时候,从不同的DataSource中获取conne ...

  6. Spring 注解动态数据源设计实践

    Spring 动态数据源 动态数据源是什么?解决了什么问题? 在实际的开发中,同一个项目中使用多个数据源是很常见的场景.比如,一个读写分离的项目存在主数据源与读数据源. 所谓动态数据源,就是通过Spr ...

  7. Spring 下,关于动态数据源的事务问题的探讨

    开心一刻 毒蛇和蟒蛇在讨论谁的捕猎方式最高效. 毒蛇:我只需要咬对方一口,一段时间内它就会逐渐丧失行动能力,最后死亡. 蟒蛇冷笑:那还得等生效时间,我只需要缠住对方,就能立刻致它于死地. 毒蛇大怒:你 ...

  8. Spring 实现动态数据源切换--转载 (AbstractRoutingDataSource)的使用

    [参考]Spring(AbstractRoutingDataSource)实现动态数据源切换--转载 [参考] 利用Spring的AbstractRoutingDataSource解决多数据源的问题 ...

  9. spring AbstractRoutingDataSource实现动态数据源切换

    使用Spring 提供的 AbstractRoutingDataSource 实现 创建 AbstractRoutingDataSource 实现类,负责保存所有数据源与切换数据源策略:public ...

随机推荐

  1. MongoDB 基本命令备忘

    使用admin数据库: use admin 显示数据库: show dbs 创建用户,并制定该用户的角色: db.createUser({user: "root",pwd:&quo ...

  2. Openstack_SQLAlchemy_一对多关系表的多表插入实现

    目录 目录 Openstack 与 SQLAlchemy 一个多表插入的 Demo 小结 Openstack 与 SQLAlchemy SQLAlchemy 是 Python 语言下的一款开源软件,它 ...

  3. dropna()函数

    参数: axis:       default 0指行,1为列 how:       {‘any’, ‘all’}, default ‘any’指带缺失值的所有行;'all’指清除全是缺失值的 thr ...

  4. 如何让字典保持有序---Python数据结构与算法相关问题与解决技巧

    实际案例: 某编程竞赛系统,对参赛选手编程解体进行计时,选手完成题目后,吧该选手解体用时记录到字典中,以便赛后按选手名查询成绩 {'Lilei':(2,43),'HanMei':(5,52),'Jim ...

  5. OO第三单元单元总结

    目录 JML知识梳理 部署JMLUnitNG/JMLUnit 按照作业梳理自己的架构设计,并特别分析迭代中对架构的重构 按照作业分析代码实现的bug和修复情况 阐述对规格撰写和理解上的心得体会 JML ...

  6. dataTables使用的详细说明整理

    本文共三个部分:官网|基本使用|遇到的问题 一.官方网站:http://www.datatables.club/ 二.基本使用: 1.dataTables的引入及初始化 <!--第一步:引入Ja ...

  7. Mac入门--安装PHP扩展redis,swoole

    1 php7以下可以通过pecl安装PHP扩展 安装redis扩展 pecl install redis 安装swoole扩展 pecl install swoole 2 PHP7以上通过源码编译安装 ...

  8. Sentinel整合Dubbo限流实战(分布式限流)

    之前我们了解了 Sentinel 集成 SpringBoot实现限流,也探讨了Sentinel的限流基本原理,那么接下去我们来学习一下Sentinel整合Dubbo及 Nacos 实现动态数据源的限流 ...

  9. sql下的xml配置文件中特殊使用的sql语句编写

    1.使用服用的sql语句------------查询学生表所有字段 <sql id="selectAllStuAll"> select stu.id,stu.name, ...

  10. POJ-3122.Pie(二分法最大化平均值)

    二分法的主题思路就是逐步逼近,所以这道题的思路自然一目了然,做题思路也是... 本题大意:题主过生日,它买了N块半径为R[ i ],高为1的圆柱形蛋糕,现在他要将这N块蛋糕等分给F + 1个人,为了好 ...