一, Spring 使用过程中的踩坑记录
image
Spring 通过注解使用多数据源
坑:@Autowired 按 byType 自动注入, 而 @Resource 则默认按 byName 自动注入,@Primary 是优先选择.
例如, 在项目中是有两个 Redis 源, 这两个 Redis Bean 分别为 dataRedisTemplate 和 redisTemplate.
Redis Bean1:dataRedisTemplate,clusterNodes 为 ${data-Redis.cluster.nodes}
- @Bean(name = "dataRedisTemplate")
- public RedisTemplate dataRedisTemplate() {
- RedisTemplate template = new RedisTemplate();
- template.setConnectionFactory(sessionLettuceConnectionFactory);
- Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
- ObjectMapper om = new ObjectMapper();
- om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
- om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
- jackson2JsonRedisSerializer.setObjectMapper(om);
- template.setKeySerializer(new StringRedisSerializer());
- template.setValueSerializer(jackson2JsonRedisSerializer);
- template.setHashKeySerializer(jackson2JsonRedisSerializer);
- template.setHashValueSerializer(new StringRedisSerializer());
- template.afterPropertiesSet();
- return template;
- }
- // factory
- @Resource
- @Qualifier(value = "dataLettuceConnectionFactory")
- private RedisConnectionFactory dataLettuceConnectionFactory;
- // clusterNodes
- @Value("${spring.data-redis.cluster.nodes}")
- private String clusterNodes;
Redis Bean2:redisTemplate,clusterNodes 为 ${Redis.cluster.nodes}
- @Primary
- @Bean(name = "redisTemplate")
- public RedisTemplate<String, Object> redisTemplate() {
- RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
- redisTemplate.setConnectionFactory(lettuceConnectionFactory);
- redisTemplate.setKeySerializer(new StringRedisSerializer());
- Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
- ObjectMapper objectMapper = new ObjectMapper();
- objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
- objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
- jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
- redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
- redisTemplate.setHashKeySerializer(new StringRedisSerializer());
- redisTemplate.setHashValueSerializer(new StringRedisSerializer());
- redisTemplate.afterPropertiesSet();
- return redisTemplate;
- }
- // factory
- @Resource
- @Qualifier(value = "lettuceConnectionFactory")
- private RedisConnectionFactory lettuceConnectionFactory;
- // clusterNodes
- @Value("${spring.redis.cluster.nodes}")
- private String clusterNodes;
在一个应用中要把数据放入到 "Redis Bean1:dataRedisTemplate" 对应的 Redis 中, 于是我在这个应用中使用方式如下:
- @Autowired
- private RedisTemplate dataRedisTemplate;
- // 根据 key 获取数据
- Object obj = dataRedisTemplate.opsForValue().get(key);
实际上使用的是 "Redis Bean2:redisTemplate" 对应的 Redis.
破解方式一: 把 @Autowired 换成 @Resource 注解. 如下:
- @Autowired
- private RedisTemplate dataRedisTemplate;
- // 把 @Autowired 换成 @Resource
- @Resource
- private RedisTemplate dataRedisTemplate;
@Autowired 和 @Resource 最大的区别就是:@Autowired 按 byType 自动注入, 而 @Resource 则默认按 byName 自动注入.
这里还需要注意一个注解 @Primary, 官方的说明如下:
Indicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency. If exactly one 'primary' bean exists among the candidates, it will be the autowired value.
@Primary 优先方案, 被注解的实现, 优先被注入
通常情况下 @Autowired 是通过 byType 的方法注入的, 可是在多个实现类的时候, byType 的方式不再是唯一, 而需要通过 byName 的方式来注入, 而这个 name 默认就是根据变量名来的.
也就是说, 如果没有在 redisTemplate() 上面增加 @Primary 的话是没有问题的, 因为有多个实现时,@Autowired 是会通过 byName 的方式来注入的, 但是按照上面说的, 因为有了 @Primary,@Autowired 注解会优先使用 Bean redisTemplate.
还有一种解决方案是增加 @Qualifier(value = "dataRedisTemplate"), 如下:
- @Autowired
- private RedisTemplate dataRedisTemplate;
- // 换成: 增加 @Qualifier(value = "dataRedisTemplate")
- @Autowired
- @Qualifier(value = "dataRedisTemplate")
- private RedisTemplate dataRedisTemplate;
Spring 事务 @Transactional 失效问题
坑: 若同一类中的其他没有 @Transactional 注解的方法内部调用有 @Transactional 注解的方法, 有 @Transactional 注解的方法的事务被忽略, 不会发生回滚.
- FooService.class
- public interface FooService {
- void insertRecord();
- void insertThenRollback() throws Exception;
- void invokeInsertThenRollback() throws Exception;
- void invokeInsertThenRollbackTwo() throws Exception;
- }
- FooServiceImpl.class
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.jdbc.core.JdbcTemplate;
- import org.springframework.stereotype.Component;
- import org.springframework.transaction.annotation.Transactional;
- @Component
- public class FooServiceImpl implements FooService {
- @Autowired
- private JdbcTemplate jdbcTemplate;
- @Autowired
- private FooService fooService;
- @Override
- @Transactional
- public void insertRecord() {
- jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('AAA')");
- }
- @Override
- @Transactional(rollbackFor = Exception.class)
- public void insertThenRollback() throws Exception {
- jdbcTemplate.execute("INSERT INTO FOO (BAR) VALUES ('BBB')");
- throw new Exception();
- }
- @Override
- public void invokeInsertThenRollback() throws Exception {
- insertThenRollback();
- }
- @Override
- public void invokeInsertThenRollbackTwo() throws Exception {
- fooService.insertThenRollback();
- }
- }
执行
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.CommandLineRunner;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.context.annotation.AdviceMode;
- import org.springframework.jdbc.core.JdbcTemplate;
- import org.springframework.transaction.annotation.EnableTransactionManagement;
- @SpringBootApplication
- @EnableTransactionManagement(mode = AdviceMode.PROXY)
- @Slf4j
- public class DeclarativeTransactionDemoApplication implements CommandLineRunner {
- @Autowired
- private FooService fooService;
- @Autowired
- private JdbcTemplate jdbcTemplate;
- public static void main(String[] args) {
- SpringApplication.run(DeclarativeTransactionDemoApplication.class, args);
- }
- @Override
- public void run(String... args) throws Exception {
- // 标记 1: 输出 AAA 1
- fooService.insertRecord();
- log.info("AAA {}",jdbcTemplate.queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='AAA'", Long.class));
- // 标记 2: 输出 BBB 0, 事务生效
- try {
- fooService.insertThenRollback();
- } catch (Exception e) {
- log.info("BBB {}",jdbcTemplate.queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='BBB'", Long.class));
- }
- // 标记 3: 输出 BBB 1, 事务未生效
- //*** 这个地方就是最容易踩坑的地方!!!***
- try {
- fooService.invokeInsertThenRollback();
- } catch (Exception e) {
- log.info("BBB {}",jdbcTemplate.queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='BBB'", Long.class));
- }
- // 标记 4: 输出 BBB 1, 事务生效 (如果把标记 3 的代码注释掉, 输出 BBB 0)
- //*** 这是避免踩坑的一种方式 ***
- try {
- fooService.invokeInsertThenRollbackTwo();
- } catch (Exception e) {
- log.info("BBB {}",jdbcTemplate.queryForObject("SELECT COUNT(*) FROM FOO WHERE BAR='BBB'", Long.class));
- }
- }
- }
二, RocketMQ 使用过程中的踩坑记录
image
Rocket 默认开启了 VIP 通道导致 10909failed 问题
坑: Rocket 默认开启了 VIP 通道, VIP 通道端口为 10911-2=10909. 若 Rocket 服务器未启动端口 10909, 则报 connect to <:10909> failed.
解决方案: 不走 VIP 通道.
- producer.setVipChannelEnabled(false);
- consumer.setVipChannelEnabled(false);
Rocket instanceName 参数未做配置导致重复消费问题
坑: 一个是 Rocket 如果没有配置 instanceName, 那么会使用 pid 做 instanceName, 如果 instanceName 一样会重复消费, 因为集群消费模式是按 instanceName 做为唯一消费实例.
查看源码, 如果没有指定 instanceName 默认会把 pid 做为 instanceName, 如下:
- if (this.instanceName.equals("DEFAULT")) {
- this.instanceName = String.valueOf(UtilAll.getPid());
- }
- public static int getPid() {
- RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
- String name = runtime.getName(); // format: "pid@hostname"
- try {
- return Integer.parseInt(name.substring(0, name.indexOf('@')));
- }
- catch (Exception e) {
- return -1;
- }
- }
解决方案: 运维配置有 $MQ_INSTANCE_NAME 环境变量, 不同机器不一样, 所以可以使用: mq.consumer.instanceName:${MQ_INSTANCE_NAME: 默认值} 进行配置.
- @Value("${rocketmq.consumer.instanceName:${MQ_INSTANCE_NAME:fota}}")
- private String clientInstanceName;
- consumer.setInstanceName(clientInstanceName);
来源: http://www.jianshu.com/p/2c5b900acb86