文档章节

从零开始写简易读写分离,不难嘛!

温安适
 温安适
发布于 03/10 00:12
字数 1651
阅读 4416
收藏 175
点赞 22
评论 22

最近在学习Spring boot,写了个读写分离。并未照搬网文,而是独立思考后的成果,写完以后发现从零开始写读写分离并不难!

我最初的想法是: 读方法走读库,写方法走写库(一般是主库),保证在Spring提交事务之前确定数据源.

 

保证在Spring提交事务之前确定数据源,这个简单,利用AOP写个切换数据源的切面,让他的优先级高于Spring事务切面的优先级。至于读,写方法的区分可以用2个注解。

但是如何切换数据库呢? 我完全不知道!多年经验告诉我

当完全不了解一个技术时,先搜索学习必要知识,之后再动手尝试。

                                                                                         --温安适 20180309

我搜索了一些网文,发现都提到了一个AbstractRoutingDataSource类。查看源码注释如下

/**
Abstract {@link javax.sql.DataSource} implementation that routes {@link #getConnection()}
 * calls to one of various target DataSources based on a lookup key. The latter is usually
 * (but not necessarily) determined through some thread-bound transaction context.
 *
 * @author Juergen Hoeller
 * @since 2.0.1
 * @see #setTargetDataSources
 * @see #setDefaultTargetDataSource
 * @see #determineCurrentLookupKey()
 */

AbstractRoutingDataSource就是DataSource的抽象,基于lookup key的方式在多个数据库中进行切换。重点关注setTargetDataSources,setDefaultTargetDataSource,determineCurrentLookupKey三个方法。那么AbstractRoutingDataSource就是Spring读写分离的关键了。

仔细阅读了三个方法,基本上跟方法名的意思一致。setTargetDataSources设置备选的数据源集合。 setDefaultTargetDataSource设置默认数据源,determineCurrentLookupKey决定当前数据源的对应的key。

但是我很好奇这3个方法都没有包含切换数据库的逻辑啊!我仔细阅读源码发现一个方法,determineTargetDataSource方法,其实它才是获取数据源的实现。源码如下:

    //切换数据库的核心逻辑
    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;
	}
    //之前的2个核心方法
	public void setTargetDataSources(Map<Object, Object> targetDataSources) {
		this.targetDataSources = targetDataSources;
	}
    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
		this.defaultTargetDataSource = defaultTargetDataSource;
	}

简单说就是,根据determineCurrentLookupKey获取的key,在resolvedDataSources这个Map中查找对应的datasource!,注意determineTargetDataSource方法竟然不使用的targetDataSources!

那一定存在resolvedDataSources与targetDataSources的对应关系。我接着翻阅代码,发现一个afterPropertiesSet方法(Spring源码中InitializingBean接口中的方法),这个方法将targetDataSources的值赋予了resolvedDataSources。源码如下:

	@Override
	public void afterPropertiesSet() {
		if (this.targetDataSources == null) {
			throw new IllegalArgumentException("Property 'targetDataSources' is required");
		}
		this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
		for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
			Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
			DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
			this.resolvedDataSources.put(lookupKey, dataSource);
		}
		if (this.defaultTargetDataSource != null) {
			this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
		}
	}

afterPropertiesSet 方法,熟悉Spring的都知道,它在bean实例已经创建好,且属性值和依赖的其他bean实例都已经注入以后执行。

也就是说调用,targetDataSources,defaultTargetDataSource的赋值一定要在afterPropertiesSet前边执行。

AbstractRoutingDataSource简单总结:

  1. AbstractRoutingDataSource,内部有一个Map<Object,DataSource>的域resolvedDataSources
  2. determineTargetDataSource方法通过determineCurrentLookupKey方法获得key,进而从map中取得对应的DataSource。
  3. setTargetDataSources 设置 targetDataSources
  4. setDefaultTargetDataSource 设置 defaultTargetDataSource,
  5. targetDataSources和defaultTargetDataSource 在afterPropertiesSet分别转换为resolvedDataSources和resolvedDefaultDataSource。
  6. targetDataSources,defaultTargetDataSource的赋值一定要在afterPropertiesSet前边执行。

进一步了解理论后,读写分离的方式则基本上出现在眼前了。(“下列方法不唯一”)

先写一个类继承AbstractRoutingDataSource,实现determineCurrentLookupKey方法,和afterPropertiesSet方法。afterPropertiesSet方法中调用setDefaultTargetDataSource和setTargetDataSources方法之后调用super.afterPropertiesSet。

之后定义一个切面在事务切面之前执行,确定真实数据源对应的key。但是这又出现了一个问题,如何线程安全的情况下传递每个线程独立的key呢?没错使用ThreadLocal传递真实数据源对应的key

ThreadLocal,Thread的局部变量,确保每一个线程都维护变量的一个副本

到这里基本逻辑就想通了,之后就是写了。

DataSourceContextHolder 使用ThreadLocal存储真实数据源对应的key

public class DataSourceContextHolder {  
    private static Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);
	//线程本地环境  
    private static final ThreadLocal<String> local = new ThreadLocal<String>();   
    public static void setRead() {  
        local.set(DataSourceType.read.name());  
        log.info("数据库切换到读库...");  
    }  
    public static void setWrite() {  
        local.set(DataSourceType.write.name());  
        log.info("数据库切换到写库...");  
    }  
    public static String getReadOrWrite() {  
        return local.get();  
    }  
}

DataSourceAopAspect 切面切换真实数据源对应的key,并设置优先级保证高于事务切面

@Aspect  
@EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true)  
@Component  
public class DataSourceAopAspect implements PriorityOrdered{

	 @Before("execution(* com.springboot.demo.mybatis.service.readorwrite..*.*(..)) "  
            + " and @annotation(com.springboot.demo.mybatis.readorwrite.annatation.ReadDataSource) ")  
    public void setReadDataSourceType() {  
        //如果已经开启写事务了,那之后的所有读都从写库读  
            DataSourceContextHolder.setRead();    
    }  
    @Before("execution(* com.springboot.demo.mybatis.service.readorwrite..*.*(..)) "  
            + " and @annotation(com.springboot.demo.mybatis.readorwrite.annatation.WriteDataSource) ")  
    public void setWriteDataSourceType() {  
        DataSourceContextHolder.setWrite();  
    }  
	@Override
	public int getOrder() {
		/** 
         * 值越小,越优先执行 要优于事务的执行 
         * 在启动类中加上了@EnableTransactionManagement(order = 10)  
         */  
		return 1;
	}
}

RoutingDataSouceImpl实现AbstractRoutingDataSource的逻辑

@Component
public class RoutingDataSouceImpl extends AbstractRoutingDataSource {
	
	@Override
	public void afterPropertiesSet() {
		//初始化bean的时候执行,可以针对某个具体的bean进行配置
		//afterPropertiesSet 早于init-method
		//将datasource注入到targetDataSources中,可以为后续路由用到的key
		this.setDefaultTargetDataSource(writeDataSource);
		Map<Object,Object>targetDataSources=new HashMap<Object,Object>();
		targetDataSources.put( DataSourceType.write.name(), writeDataSource);
		targetDataSources.put( DataSourceType.read.name(),  readDataSource);
		this.setTargetDataSources(targetDataSources);
		//执行原有afterPropertiesSet逻辑,
		//即将targetDataSources中的DataSource加载到resolvedDataSources
		super.afterPropertiesSet();
	}
	@Override
	protected Object determineCurrentLookupKey() {
		//这里边就是读写分离逻辑,最后返回的是setTargetDataSources保存的Map对应的key
		String typeKey = DataSourceContextHolder.getReadOrWrite();  
		Assert.notNull(typeKey, "数据库路由发现typeKey is null,无法抉择使用哪个库");
		log.info("使用"+typeKey+"数据库.............");  
		return typeKey;
	}
  	private static Logger log = LoggerFactory.getLogger(RoutingDataSouceImpl.class); 
	@Autowired  
	@Qualifier("writeDataSource")  
	private DataSource writeDataSource;  
	@Autowired  
	@Qualifier("readDataSource")  
	private DataSource readDataSource;  
}

基本逻辑实现完毕了就进行,通用设置,设置数据源,事务,SqlSessionFactory等

	@Primary
	@Bean(name = "writeDataSource", destroyMethod = "close")
	@ConfigurationProperties(prefix = "test_write")
	public DataSource writeDataSource() {
		return new DruidDataSource();
	}

	@Bean(name = "readDataSource", destroyMethod = "close")
	@ConfigurationProperties(prefix = "test_read")
	public DataSource readDataSource() {
		return new DruidDataSource();
	}

    	@Bean(name = "writeOrReadsqlSessionFactory")
	public SqlSessionFactory 
           sqlSessionFactorys(RoutingDataSouceImpl roundRobinDataSouceProxy) 
                                                           throws Exception {
		try {
			SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
			bean.setDataSource(roundRobinDataSouceProxy);
			ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
			// 实体类对应的位置
			bean.setTypeAliasesPackage("com.springboot.demo.mybatis.model");
			// mybatis的XML的配置
			bean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
			return bean.getObject();
		} catch (IOException e) {
			log.error("" + e);
			return null;
		} catch (Exception e) {
			log.error("" + e);
			return null;
		}
	}

    @Bean(name = "writeOrReadTransactionManager")
	public DataSourceTransactionManager transactionManager(RoutingDataSouceImpl 
              roundRobinDataSouceProxy) {
		//Spring 的jdbc事务管理器
		DataSourceTransactionManager transactionManager = new 
                  DataSourceTransactionManager(roundRobinDataSouceProxy);
		return transactionManager;
	}

其他代码,就不在这里赘述了,有兴趣可以移步完整代码

使用Spring写读写分离,其核心就是AbstractRoutingDataSource,源码不难,读懂之后,写个读写分离就简单了!。

AbstractRoutingDataSource重点回顾:

  1. AbstractRoutingDataSource,内部有一个Map<Object,DataSource>的域resolvedDataSources
  2. determineTargetDataSource方法通过determineCurrentLookupKey方法获得key,进而从map中取得对应的DataSource。
  3. setTargetDataSources 设置 targetDataSources
  4. setDefaultTargetDataSource 设置 defaultTargetDataSource,
  5. targetDataSources和defaultTargetDataSource 在afterPropertiesSet分别转换为resolvedDataSources和resolvedDefaultDataSource。
  6. targetDataSources,defaultTargetDataSource的赋值一定要在afterPropertiesSet前边执行。

这周确实有点忙,周五花费了些时间不过总算实现了自己的诺言。

完成承诺不容易,喜欢您就点个赞!

© 著作权归作者所有

共有 人打赏支持
温安适
粉丝 93
博文 21
码字总数 35287
作品 0
朝阳
后端工程师
加载中

评论(22)

xiaokek
xiaokek

引用来自“jiakme”的评论

ThreadLocal 有内存泄漏的风险,慎用.
ThreadLocal 正确的使用是没任何问题的.在线程干活前set,在干活的生命周期结束前remove,线程丢进线程池一点问题都没有.
spring mybatis这些框架大量使用ThreadLocal,比如管理请求上下文的RequestContextHolder,管理事务上下文的TransactionContextHolder.很多人也拿来做分页,几乎所有涉及线程上下文的都会用到ThreadLocal,所以他没毛病.,要怪就怪用不好.
jiakme
jiakme

引用来自“SimonYe”的评论

如果前面是个线程池,ThreadLocal 貌似会有问题

引用来自“温安适”的评论

感谢关注,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏。本例中由于每个线程仅仅持有一个枚举,并且持有数量不会增加,所以不会内存泄露,不过可以加入一个切面在事务提交之后,将ThreadLocal中的值清楚。

引用来自“jiakme”的评论

ThreadLocalMap的生命周期跟Thread一样长,但是它使用的是指向当前线程的 WeakReference对象作为key. GC下 ,key可能被回收导致内存泄漏.只有在 remove 等方法再次使用的时候,null key的项才被移除,中间的所有过程都是内存泄漏状态.

引用来自“温安适”的评论

这个也是ImportNew的文章中所说的吧,但是关于内存泄漏,其实这个示例中是不会影响的,因为仅仅持有一个枚举,对内存负担不大!

引用来自“jiakme”的评论

跟 ImportNew 有什么关系?我自己写的啊.你这里问题有的啊. tomcat 使用的是线程池,也就是存在线程存活周期长的情况,那么在 gc 后, WeakReference 被回收,而 spring 的bean 只会被初始化一次(单例情况下),那么后续事务如何获取DataSource 呢?

引用来自“温安适”的评论

推荐文章http://www.importnew.com/22039.html (ThreadLocal 为什么会内存泄漏。)

引用来自“jiakme”的评论

看这篇吧: http://ifeve.com/%E4%BD%BF%E7%94%A8threadlocal%E4%B8%8D%E5%BD%93%E5%8F%AF%E8%83%BD%E4%BC%9A%E5%AF%BC%E8%87%B4%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2/

引用来自“温安适”的评论

看了你给的文章,大家了解的是一致的,我这里并没有按照规范字使用完毕后进行remove,是偷懒了!从规范角度上说应该remove的。
不是偷懒不偷懒的问题.主要是什么时候 gc 不确定,也就导致数据失效时间不确定.我没仔细看过代码,不过你这边,直接配置一个单例bean或者直接加载配置文件并且保持不可变可行不.一般讲,读写分离,写数据库一般固定,但是读数据库可能会变动.但是单机部署的情况下,一般不会多连多个读从库.
温安适
温安适

引用来自“SimonYe”的评论

如果前面是个线程池,ThreadLocal 貌似会有问题

引用来自“温安适”的评论

感谢关注,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏。本例中由于每个线程仅仅持有一个枚举,并且持有数量不会增加,所以不会内存泄露,不过可以加入一个切面在事务提交之后,将ThreadLocal中的值清楚。

引用来自“jiakme”的评论

ThreadLocalMap的生命周期跟Thread一样长,但是它使用的是指向当前线程的 WeakReference对象作为key. GC下 ,key可能被回收导致内存泄漏.只有在 remove 等方法再次使用的时候,null key的项才被移除,中间的所有过程都是内存泄漏状态.

引用来自“温安适”的评论

这个也是ImportNew的文章中所说的吧,但是关于内存泄漏,其实这个示例中是不会影响的,因为仅仅持有一个枚举,对内存负担不大!

引用来自“jiakme”的评论

跟 ImportNew 有什么关系?我自己写的啊.你这里问题有的啊. tomcat 使用的是线程池,也就是存在线程存活周期长的情况,那么在 gc 后, WeakReference 被回收,而 spring 的bean 只会被初始化一次(单例情况下),那么后续事务如何获取DataSource 呢?

引用来自“温安适”的评论

推荐文章http://www.importnew.com/22039.html (ThreadLocal 为什么会内存泄漏。)

引用来自“jiakme”的评论

看这篇吧: http://ifeve.com/%E4%BD%BF%E7%94%A8threadlocal%E4%B8%8D%E5%BD%93%E5%8F%AF%E8%83%BD%E4%BC%9A%E5%AF%BC%E8%87%B4%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2/
看了你给的文章,大家了解的是一致的,我这里并没有按照规范字使用完毕后进行remove,是偷懒了!从规范角度上说应该remove的。
jiakme
jiakme

引用来自“SimonYe”的评论

如果前面是个线程池,ThreadLocal 貌似会有问题

引用来自“温安适”的评论

感谢关注,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏。本例中由于每个线程仅仅持有一个枚举,并且持有数量不会增加,所以不会内存泄露,不过可以加入一个切面在事务提交之后,将ThreadLocal中的值清楚。

引用来自“jiakme”的评论

ThreadLocalMap的生命周期跟Thread一样长,但是它使用的是指向当前线程的 WeakReference对象作为key. GC下 ,key可能被回收导致内存泄漏.只有在 remove 等方法再次使用的时候,null key的项才被移除,中间的所有过程都是内存泄漏状态.

引用来自“温安适”的评论

这个也是ImportNew的文章中所说的吧,但是关于内存泄漏,其实这个示例中是不会影响的,因为仅仅持有一个枚举,对内存负担不大!

引用来自“jiakme”的评论

跟 ImportNew 有什么关系?我自己写的啊.你这里问题有的啊. tomcat 使用的是线程池,也就是存在线程存活周期长的情况,那么在 gc 后, WeakReference 被回收,而 spring 的bean 只会被初始化一次(单例情况下),那么后续事务如何获取DataSource 呢?

引用来自“温安适”的评论

推荐文章http://www.importnew.com/22039.html (ThreadLocal 为什么会内存泄漏。)
看这篇吧: http://ifeve.com/%E4%BD%BF%E7%94%A8threadlocal%E4%B8%8D%E5%BD%93%E5%8F%AF%E8%83%BD%E4%BC%9A%E5%AF%BC%E8%87%B4%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2/
jiakme
jiakme

引用来自“SimonYe”的评论

如果前面是个线程池,ThreadLocal 貌似会有问题

引用来自“温安适”的评论

感谢关注,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏。本例中由于每个线程仅仅持有一个枚举,并且持有数量不会增加,所以不会内存泄露,不过可以加入一个切面在事务提交之后,将ThreadLocal中的值清楚。

引用来自“jiakme”的评论

ThreadLocalMap的生命周期跟Thread一样长,但是它使用的是指向当前线程的 WeakReference对象作为key. GC下 ,key可能被回收导致内存泄漏.只有在 remove 等方法再次使用的时候,null key的项才被移除,中间的所有过程都是内存泄漏状态.

引用来自“温安适”的评论

这个也是ImportNew的文章中所说的吧,但是关于内存泄漏,其实这个示例中是不会影响的,因为仅仅持有一个枚举,对内存负担不大!

引用来自“jiakme”的评论

跟 ImportNew 有什么关系?我自己写的啊.你这里问题有的啊. tomcat 使用的是线程池,也就是存在线程存活周期长的情况,那么在 gc 后, WeakReference 被回收,而 spring 的bean 只会被初始化一次(单例情况下),那么后续事务如何获取DataSource 呢?

引用来自“温安适”的评论

推荐文章http://www.importnew.com/22039.html (ThreadLocal 为什么会内存泄漏。)
我源代码都看完了,我还看这篇干嘛啊?
温安适
温安适

引用来自“SimonYe”的评论

如果前面是个线程池,ThreadLocal 貌似会有问题

引用来自“温安适”的评论

感谢关注,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏。本例中由于每个线程仅仅持有一个枚举,并且持有数量不会增加,所以不会内存泄露,不过可以加入一个切面在事务提交之后,将ThreadLocal中的值清楚。

引用来自“jiakme”的评论

ThreadLocalMap的生命周期跟Thread一样长,但是它使用的是指向当前线程的 WeakReference对象作为key. GC下 ,key可能被回收导致内存泄漏.只有在 remove 等方法再次使用的时候,null key的项才被移除,中间的所有过程都是内存泄漏状态.

引用来自“温安适”的评论

这个也是ImportNew的文章中所说的吧,但是关于内存泄漏,其实这个示例中是不会影响的,因为仅仅持有一个枚举,对内存负担不大!

引用来自“jiakme”的评论

跟 ImportNew 有什么关系?我自己写的啊.你这里问题有的啊. tomcat 使用的是线程池,也就是存在线程存活周期长的情况,那么在 gc 后, WeakReference 被回收,而 spring 的bean 只会被初始化一次(单例情况下),那么后续事务如何获取DataSource 呢?
推荐文章http://www.importnew.com/22039.html (ThreadLocal 为什么会内存泄漏。)
温安适
温安适

引用来自“SimonYe”的评论

如果前面是个线程池,ThreadLocal 貌似会有问题

引用来自“温安适”的评论

感谢关注,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏。本例中由于每个线程仅仅持有一个枚举,并且持有数量不会增加,所以不会内存泄露,不过可以加入一个切面在事务提交之后,将ThreadLocal中的值清楚。

引用来自“jiakme”的评论

ThreadLocalMap的生命周期跟Thread一样长,但是它使用的是指向当前线程的 WeakReference对象作为key. GC下 ,key可能被回收导致内存泄漏.只有在 remove 等方法再次使用的时候,null key的项才被移除,中间的所有过程都是内存泄漏状态.

引用来自“温安适”的评论

这个也是ImportNew的文章中所说的吧,但是关于内存泄漏,其实这个示例中是不会影响的,因为仅仅持有一个枚举,对内存负担不大!

引用来自“jiakme”的评论

跟 ImportNew 有什么关系?我自己写的啊.你这里问题有的啊. tomcat 使用的是线程池,也就是存在线程存活周期长的情况,那么在 gc 后, WeakReference 被回收,而 spring 的bean 只会被初始化一次(单例情况下),那么后续事务如何获取DataSource 呢?
实践出真知,若果有时间可以运行示例100回,看看后续是否有问题。AOP或每次请求都设置key的!
jiakme
jiakme

引用来自“SimonYe”的评论

如果前面是个线程池,ThreadLocal 貌似会有问题

引用来自“温安适”的评论

感谢关注,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏。本例中由于每个线程仅仅持有一个枚举,并且持有数量不会增加,所以不会内存泄露,不过可以加入一个切面在事务提交之后,将ThreadLocal中的值清楚。

引用来自“jiakme”的评论

ThreadLocalMap的生命周期跟Thread一样长,但是它使用的是指向当前线程的 WeakReference对象作为key. GC下 ,key可能被回收导致内存泄漏.只有在 remove 等方法再次使用的时候,null key的项才被移除,中间的所有过程都是内存泄漏状态.

引用来自“温安适”的评论

这个也是ImportNew的文章中所说的吧,但是关于内存泄漏,其实这个示例中是不会影响的,因为仅仅持有一个枚举,对内存负担不大!
跟 ImportNew 有什么关系?我自己写的啊.你这里问题有的啊. tomcat 使用的是线程池,也就是存在线程存活周期长的情况,那么在 gc 后, WeakReference 被回收,而 spring 的bean 只会被初始化一次(单例情况下),那么后续事务如何获取DataSource 呢?
温安适
温安适

引用来自“SimonYe”的评论

如果前面是个线程池,ThreadLocal 貌似会有问题

引用来自“温安适”的评论

感谢关注,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏。本例中由于每个线程仅仅持有一个枚举,并且持有数量不会增加,所以不会内存泄露,不过可以加入一个切面在事务提交之后,将ThreadLocal中的值清楚。

引用来自“jiakme”的评论

ThreadLocalMap的生命周期跟Thread一样长,但是它使用的是指向当前线程的 WeakReference对象作为key. GC下 ,key可能被回收导致内存泄漏.只有在 remove 等方法再次使用的时候,null key的项才被移除,中间的所有过程都是内存泄漏状态.
这个也是ImportNew的文章中所说的吧,但是关于内存泄漏,其实这个示例中是不会影响的,因为仅仅持有一个枚举,对内存负担不大!
jiakme
jiakme

引用来自“SimonYe”的评论

如果前面是个线程池,ThreadLocal 貌似会有问题

引用来自“温安适”的评论

感谢关注,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏。本例中由于每个线程仅仅持有一个枚举,并且持有数量不会增加,所以不会内存泄露,不过可以加入一个切面在事务提交之后,将ThreadLocal中的值清楚。
ThreadLocalMap的生命周期跟Thread一样长,但是它使用的是指向当前线程的 WeakReference对象作为key. GC下 ,key可能被回收导致内存泄漏.只有在 remove 等方法再次使用的时候,null key的项才被移除,中间的所有过程都是内存泄漏状态.
从零开始开发jvm语言(零)

目录 从零开始开发JVM语言(一)Latte 从零开始开发JVM语言(二)词法分析 从零开始开发JVM语言(三)特殊的Token结构 从零开始开发JVM语言(四)四则运算 从零开始开发JVM语言(五)语法分析...

wkgcass ⋅ 2016/05/31 ⋅ 2

探索MySQL高可用架构之MHA(5)

探索MySQL高可用架构之MHA(5) -----构建mysql高可用系列(共9篇) 上一篇文章介绍了本次架构的AB复制操作! 本篇文章主要介绍本次架构中的Atlas读写分离! 为什么要分库、分表、读写分离? 现在...

顺境其生 ⋅ 2015/07/22 ⋅ 0

concurrent包的同步控制工具

ReentrantLock 可重入锁,应用层面的锁,jdk6后性能和synchronized差不多,但是有更多的功能。 可以响应线程中断: 可以限时,这样可以避免死锁 ReentrantLock可以实现公平锁: 通过Conditi...

肥肥小浣熊 ⋅ 2017/11/06 ⋅ 0

mysql 分区/分表/读写分离/分片

当数据库性能出现问题的时候,首先需要找出瓶颈,是否进行了必要的优化,如:表和字段是否建的合理,索引是否建的好,sql语句有无优化等等。 随着站点规模越来越大,数据库压力越来越大,即使...

尚小胖 ⋅ 2016/12/05 ⋅ 0

Hibernate、Mybait,Mysql、Postgresql适用场景

传统系统 (1)单数据库,单表数据量<1000W,Hibernate+Mysql (2)单数据库,单表数据量>1000W,Hibernate+Postgresql 互联网系统 (1)主从数据库,读写分离,Hibernate+Mysql (2)垂直水平切分数...

GKTest ⋅ 2015/08/03 ⋅ 0

mysql主从以及读写分离(科普)

在实际的生产环境中,对数据库的读和写都在同一个数据库服务器中,是不能满足实际需求的。无论是在安全性、高可用性还是高并发等各个方面都是完全不能满足实际需求的。因此,通过主从复制的方...

hfisop ⋅ 05/17 ⋅ 0

mycat实现mysql读写分离实践

mycat是一个的数据库中间件,基于阿里开源的cobar产品而研发,由几个有志之士的牛人共同完成并开源。提供高可用性数据分片集群,自动故障切换,高可用性 ,支持读写分离,支持Mysql双主多从,...

rock912 ⋅ 2016/01/28 ⋅ 21

设计模式之代理模式之读写分离!!!

小伙伴们你们的小可爱逗比又上线了!!! 最近感觉带表情的文章看多了,写篇文章不放上几十个表情感觉自己都写不出来什么!!!原谅你们的小可爱放荡。。。不羁。。。爱谁谁!!! 好了好了,...

思梦教育 ⋅ 2017/11/14 ⋅ 0

2017双11技术揭秘—阿里巴巴数据库技术架构演进

作者:谌文涛(俞月) 每年电商双11大促对阿里技术人都是一次大考,对阿里数据库团队更是如此。经过9年的发展,双11单日交易额从2009年的0.5亿一路攀升到2017年的1682亿,秒级交易创建峰值达...

中间件小哥 ⋅ 2017/12/27 ⋅ 0

Uncode-DAL 1.0.20 发布,Java 通用数据访问层

Uncode-DAL 是 Java 通用数据访问组件,基于mybatis、spring jdbc、hibernate、mongo等ORM框架开发,同时支持基于多数据源的读写分离、主备切换、故障转移,自动恢复、负载均衡、缓存等。零开...

冶卫军 ⋅ 2017/02/21 ⋅ 1

没有更多内容

加载失败,请刷新页面

加载更多

下一页

vim基础-编辑模式-命令模式

编辑模式:可以编辑修改文件。编辑模式下 按“esc”键返回一般模式。 按一次“Insert”键 (一般在键盘回格键右边)作用和“i”一样表示“插入”。按两次“Insert”键表示“替换”,作用为:...

ZHENG-JY ⋅ 17分钟前 ⋅ 0

MaxCompute读取分析OSS非结构化数据的实践经验总结

摘要: 本文背景 很多行业的信息系统中,例如金融行业的信息系统,相当多的数据交互工作是通过传统的文本文件进行交互的。此外,很多系统的业务日志和系统日志由于各种原因并没有进入ELK之类...

阿里云云栖社区 ⋅ 21分钟前 ⋅ 0

Linux操作系统有何优势?Linux学习

  当今世界流行的操作系统有3大类,Linux、Mac OS和Windows操作系统,Linux操作系统因其开源、免费、跨平台、良好的界面等特性,深受广大程序员们的青睐!   Linux操作系统被广泛的应用于...

老男孩Linux培训 ⋅ 23分钟前 ⋅ 0

Spring Cloud Spring Boot mybatis分布式微服务云架构 开发Web应用

静态资源访问 在我们开发Web应用的时候,需要引用大量的js、css、图片等静态资源。 默认配置 Spring Boot默认提供静态资源目录位置需置于classpath下,目录名需符合如下规则: /static /pub...

itcloud ⋅ 27分钟前 ⋅ 0

6月19日任务 设置更改root密码、连接mysql、mysql常用命令

13.1 设置更改root密码 1. /usr/local/mysql/bin/mysql -uroot 设置环境变量 : export PATH=$PATH:/usr/local/mysql/bin/ 永久生效: vim /etc/profile 加入 export PATH=$PATH:/usr/local/m......

吕湘颖 ⋅ 29分钟前 ⋅ 0

MaxCompute读取分析OSS非结构化数据的实践经验总结

摘要: 本文背景 很多行业的信息系统中,例如金融行业的信息系统,相当多的数据交互工作是通过传统的文本文件进行交互的。此外,很多系统的业务日志和系统日志由于各种原因并没有进入ELK之类...

猫耳m ⋅ 30分钟前 ⋅ 0

Spring MVC controller,return重定向redirect:

@RequestMapping(value="/save",method=RequestMethod.POST)public String doSave(Course course) {log.debug("Info of Course");log.debug(ReflectionToStringBuilder.toStr......

颖伙虫 ⋅ 38分钟前 ⋅ 0

JavaSE——线程介绍

声明:本栏目所使用的素材都是凯哥学堂VIP学员所写,学员有权匿名,对文章有最终解释权;凯哥学堂旨在促进VIP学员互相学习的基础上公开笔记。 线程: 介绍:管线程叫多任务处理,首先你得知道...

凯哥学堂 ⋅ 41分钟前 ⋅ 0

ORM——使用spring jpa data实现逻辑删除

前言 在业务中是忌讳物理删除数据的,数据的这个对于一个IT公司可以说是最核心的资产,如果删除直接就物理删除,无疑是对核心资产的不重视,可能扯的比较远,本文最主要是想通过spring jpa ...

alexzhu592 ⋅ 47分钟前 ⋅ 0

CDN caching

Incapsula应用感知CDN使用智能分析和频率分析来动态缓存内容,并最大限度地提高效率。确保可直接从RAM获取最常访问的资源,而不依赖于较慢的访问机制。 1、 静态内容缓存 Incapsula缓存静态内...

上树的熊 ⋅ 50分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部