Contents
springboot mybatis aop 读写分离
注解
-
定义两个注解
1 2import java.lang.annotation.ElementType; 3import java.lang.annotation.Retention; 4import java.lang.annotation.RetentionPolicy; 5import java.lang.annotation.Target; 6 7/** 8 * 主库可读写 9 */ 10@Target(ElementType.METHOD) 11@Retention(RetentionPolicy.RUNTIME) 12public @interface Master { 13}
1import java.lang.annotation.ElementType; 2import java.lang.annotation.Retention; 3import java.lang.annotation.RetentionPolicy; 4import java.lang.annotation.Target; 5 6/** 7 * 从库可读 8 */ 9@Target(ElementType.METHOD) 10@Retention(RetentionPolicy.RUNTIME) 11public @interface Slave { 12}
数据库配置
1 2spring: 3 datasource: 4 master: 5 jdbc-url: jdbc:mysql://192.168.1.22:3307/test 6 username: root 7 password: 123456 8 driver-class-name: com.mysql.cj.jdbc.Driver 9 slave1: 10 jdbc-url: jdbc:mysql://192.168.1.22:3307/test 11 username: root # 只读账户 12 password: 123456 13 driver-class-name: com.mysql.cj.jdbc.Driver 14 slave2: 15 jdbc-url: jdbc:mysql://192.168.1.22:3307/test 16 username: root # 只读账户 17 password: 123456 18 driver-class-name: com.mysql.cj.jdbc.Driver 19
-
定义 数据源
1 2public enum DBTypeEnum { 3 MASTER, SLAVE1, SLAVE2; 4} 5
mybatis 配置
数据源配置
1import org.springframework.beans.factory.annotation.Qualifier; 2import org.springframework.boot.context.properties.ConfigurationProperties; 3import org.springframework.boot.jdbc.DataSourceBuilder; 4import org.springframework.context.annotation.Bean; 5import org.springframework.context.annotation.Configuration; 6 7import javax.sql.DataSource; 8import java.util.HashMap; 9import java.util.Map; 10 11@Configuration 12public class DataSourceConfig { 13 @Bean 14 @ConfigurationProperties("spring.datasource.master") 15 public DataSource masterDataSource() { 16 return DataSourceBuilder.create().build(); 17 } 18 19 @Bean 20 @ConfigurationProperties("spring.datasource.slave1") 21 public DataSource slave1DataSource() { 22 return DataSourceBuilder.create().build(); 23 } 24 25 @Bean 26 @ConfigurationProperties("spring.datasource.slave2") 27 public DataSource slave2DataSource() { 28 return DataSourceBuilder.create().build(); 29 } 30 31 @Bean 32 public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource, 33 @Qualifier("slave1DataSource") DataSource slave1DataSource, 34 @Qualifier("slave2DataSource") DataSource slave2DataSource) { 35 Map<Object, Object> targetDataSources = new HashMap<>(); 36 targetDataSources.put(DBTypeEnum.MASTER, masterDataSource); 37 targetDataSources.put(DBTypeEnum.SLAVE1, slave1DataSource); 38 targetDataSources.put(DBTypeEnum.SLAVE2, slave2DataSource); 39 RoutingDataSource routingDataSource = new RoutingDataSource(); 40 routingDataSource.setDefaultTargetDataSource(masterDataSource); 41 routingDataSource.setTargetDataSources(targetDataSources); 42 return routingDataSource; 43 } 44 45} 46
mybatis 配置
1 2 3import org.apache.ibatis.session.SqlSessionFactory; 4import org.mybatis.spring.SqlSessionFactoryBean; 5import org.springframework.context.annotation.Bean; 6import org.springframework.context.annotation.Configuration; 7import org.springframework.core.io.support.PathMatchingResourcePatternResolver; 8import org.springframework.jdbc.datasource.DataSourceTransactionManager; 9import org.springframework.transaction.PlatformTransactionManager; 10import org.springframework.transaction.annotation.EnableTransactionManagement; 11 12import javax.annotation.Resource; 13import javax.sql.DataSource; 14 15@Configuration 16@EnableTransactionManagement 17public class MyBatisConfig { 18 19 @Resource(name = "routingDataSource") 20 private DataSource routingDataSource; 21 22 @Bean 23 public SqlSessionFactory sqlSessionFactory() throws Exception { 24 SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); 25 sqlSessionFactoryBean.setDataSource(routingDataSource); 26// sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml")); 27 return sqlSessionFactoryBean.getObject(); 28 } 29 30 @Bean 31 public PlatformTransactionManager platformTransactionManager() { 32 return new DataSourceTransactionManager(routingDataSource); 33 } 34} 35
动态数据源
1import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; 2import org.springframework.lang.Nullable; 3 4public class RoutingDataSource extends AbstractRoutingDataSource { 5 @Nullable 6 @Override 7 protected Object determineCurrentLookupKey() { 8 return DBContextHolder.get(); 9 } 10} 11
数据源切换
1 2import java.util.concurrent.atomic.AtomicInteger; 3 4public class DBContextHolder { 5 private static final ThreadLocal<DBTypeEnum> contextHolder = new ThreadLocal<>(); 6 7 private static final AtomicInteger counter = new AtomicInteger(-1); 8 9 public static void set(DBTypeEnum dbType) { 10 contextHolder.set(dbType); 11 } 12 13 public static DBTypeEnum get() { 14 return contextHolder.get(); 15 } 16 17 public static void remove() { 18 contextHolder.remove(); 19 } 20 21 public static void master() { 22 set(DBTypeEnum.MASTER); 23 System.out.println("切换到master"); 24 } 25 26 public static void slave() { 27 // 轮询 28 int index = counter.getAndIncrement() % 2; 29 if (counter.get() > 9999) { 30 counter.set(-1); 31 } 32 if (index == 0) { 33 set(DBTypeEnum.SLAVE1); 34 System.out.println("切换到slave1"); 35 } else { 36 set(DBTypeEnum.SLAVE2); 37 System.out.println("切换到slave2"); 38 } 39 } 40 41} 42
aop 切面
1import org.aspectj.lang.annotation.After; 2import org.aspectj.lang.annotation.Aspect; 3import org.aspectj.lang.annotation.Before; 4import org.springframework.stereotype.Component; 5 6@Aspect 7@Component 8public class DataSourceAop { 9 @Before("@annotation(com.example.demo.annotation.Master)") 10 public void master() { 11 DBContextHolder.master(); 12 } 13 14 @Before("@annotation(com.example.demo.annotation.Slave)") 15 public void slave() { 16 DBContextHolder.slave(); 17 } 18 19 @After("@annotation(com.example.demo.annotation.Slave)||@annotation(com.example.demo.annotation.Master)") 20 public void afterSwitchDB() { 21 DBContextHolder.remove(); 22 } 23}
使用方式
1 2 3@Service 4public class UserServiceImpl implements UserService { 5 @Autowired 6 private UserMapper userMapper; 7 8 /** 9 * 主库写入数据 10 * @param user 11 * @return 12 */ 13 @Override 14 @Master 15 public int save(User user) { 16 return userMapper.save(user); 17 } 18 19 /** 20 * 从库查询数据 21 * @return 22 */ 23 @Override 24 @Slave 25 public User get() { 26 return userMapper.get(); 27 } 28} 29 30
1 2public interface UserMapper { 3 4 @Insert("insert into t_test(name)values(#{name})") 5 int save(User user); 6 7 @Select("select * from t_test where id>=(select floor(rand() * (select max(id) from t_test))) order by id limit 1") 8 User get(); 9} 10
test
1@SpringBootTest 2class DemoApplicationTests { 3 @Autowired 4 private UserService userService; 5 6 @Test 7 void testDb() throws InterruptedException { 8 for (int i = 0; i < 10; i++) { 9 User user = userService.get(); 10 System.out.println(user); 11 TimeUnit.SECONDS.sleep(1); 12 user.setName(UUID.randomUUID().toString()); 13 userService.save(user); 14 } 15 } 16 17}
1切换到slave2 2User{id=8, name='e87f057f-9dc9-4f59-8ff4-3a01682487d0'} 3切换到master 4切换到slave1 5User{id=2, name='d786167b-d29f-49eb-bb1d-dc48f01f761c'} 6切换到master 7切换到slave2 8User{id=6, name='c680a686-3ede-419d-bd66-de82966d5f96'} 9切换到master 10切换到slave1 11User{id=5, name='07d2a8d4-3414-49a0-bc37-065a1688a55f'} 12切换到master 13切换到slave2 14User{id=3, name='bac785d9-743d-48db-9b74-c2d51ba878fa'} 15切换到master 16切换到slave1 17User{id=7, name='0c3866a0-208b-4530-8c8d-9502dea753ff'} 18切换到master 19切换到slave2 20User{id=3, name='bac785d9-743d-48db-9b74-c2d51ba878fa'} 21切换到master 22切换到slave1 23User{id=8, name='e87f057f-9dc9-4f59-8ff4-3a01682487d0'} 24切换到master
-