全球快消息!自定义注解+AOPP 简化数据源的切换操作

发布时间:   来源:CSDN  

项目中经常会出现需要同时连接两个数据源的情况,这里还是演示基于MyBatis来配置两个数据源,并演示如何切换不同的数据源。


(资料图片)

网上的一些例子都写的有点冗余,这里我通过自定义注解+AOP的方式,来简化这种数据源的切换操作。

maven依赖

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950

UTF-8UTF-81.81.1.28.0.7-dmr2.1.81.0.5org.springframework.bootspring-boot-starter-aopmysqlmysql-connector-java${mysql-connector.version}runtimecom.alibabadruid${druid.version}com.baomidoumybatis-plus${mybatis-plus.version}com.baomidoumybatisplus-spring-boot-starter${mybatisplus-spring-boot-starter.version}org.springframework.bootspring-boot-starter-testtestorg.hamcresthamcrest-all1.3test

初始化数据库

这里我们需要创建两个数据库,初始化脚本如下:

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849

# -------------------------------------以下是pos业务库开始-------------------------------------------CREATE DATABASE IF NOT EXISTS pos default charset utf8 COLLATE utf8_general_ci;SET FOREIGN_KEY_CHECKS=0;USE pos;-- 后台管理用户表DROP TABLE IF EXISTS `t_user`;CREATE TABLE `t_user` (  `id`                        INT(11) PRIMARY KEY AUTO_INCREMENT COMMENT "主键ID",  `username`                  VARCHAR(32) NOT NULL COMMENT "账号",  `name`                      VARCHAR(16) DEFAULT "" COMMENT "名字",  `password`                  VARCHAR(128) DEFAULT "" COMMENT "密码",  `salt`                      VARCHAR(64) DEFAULT "" COMMENT "md5密码盐",  `phone`                     VARCHAR(32) DEFAULT "" COMMENT "联系电话",  `tips`                      VARCHAR(255) COMMENT "备注",  `state`                     TINYINT(1) DEFAULT 1 COMMENT "状态 1:正常 2:禁用",  `created_time`              DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT "创建时间",  `updated_time`              DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT "更新时间") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT="后台管理用户表";# 下面是pos数据库中的插入数据INSERT INTO `t_user` VALUES (1,"admin","系统管理员","123456","www", "17890908889", "系统管理员", 1, "2017-12-12 09:46:12", "2017-12-12 09:46:12");INSERT INTO `t_user` VALUES (2,"aix","张三","123456","eee", "17859569358", "", 1, "2017-12-12 09:46:12", "2017-12-12 09:46:12");# -------------------------------------以下biz业务库开始-------------------------------------------CREATE DATABASE IF NOT EXISTS pos default charset utf8 COLLATE utf8_general_ci;SET FOREIGN_KEY_CHECKS=0;USE biz;-- 后台管理用户表DROP TABLE IF EXISTS `t_user`;CREATE TABLE `t_user` (  `id`                        INT(11) PRIMARY KEY AUTO_INCREMENT COMMENT "主键ID",  `username`                  VARCHAR(32) NOT NULL COMMENT "账号",  `name`                      VARCHAR(16) DEFAULT "" COMMENT "名字",  `password`                  VARCHAR(128) DEFAULT "" COMMENT "密码",  `salt`                      VARCHAR(64) DEFAULT "" COMMENT "md5密码盐",  `phone`                     VARCHAR(32) DEFAULT "" COMMENT "联系电话",  `tips`                      VARCHAR(255) COMMENT "备注",  `state`                     TINYINT(1) DEFAULT 1 COMMENT "状态 1:正常 2:禁用",  `created_time`              DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT "创建时间",  `updated_time`              DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT "更新时间") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT="后台管理用户表";# 下面是biz数据库中的插入数据INSERT INTO `t_user` VALUES (1,"admin1","系统管理员","123456","www", "17890908889", "系统管理员", 1, "2017-12-12 09:46:12", "2017-12-12 09:46:12");INSERT INTO `t_user` VALUES (2,"aix1","张三","123456","eee", "17859569358", "", 1, "2017-12-12 09:46:12", "2017-12-12 09:46:12");

可以看到我创建了两个数据库pos和biz,同时还初始化了用户表,并分别插入两条初始数据。注意用户名数据不相同。

配置文件

接下来修改application.yml配置文件,如下:

1234567891011121314151617181920212223242526272829303132

###################  自定义配置  ###################xncoding:  muti-datasource-open: true #是否开启多数据源(true/false)###################  mybatis-plus配置  ###################mybatis-plus:  mapper-locations: classpath*:com/xncoding/pos/common/dao/repository/mapping/*.xml  typeAliasesPackage: >    com.xncoding.pos.common.dao.entity  global-config:    id-type: 0  # 0:数据库ID自增   1:用户输入id  2:全局唯一id(IdWorker)  3:全局唯一ID(uuid)    db-column-underline: false    refresh-mapper: true  configuration:    map-underscore-to-camel-case: true    cache-enabled: true #配置的缓存的全局开关    lazyLoadingEnabled: true #延时加载的开关    multipleResultSetsEnabled: true #开启的话,延时加载一个属性时会加载该对象全部属性,否则按需加载属性#默认数据源spring:  datasource:    url: jdbc:mysql://127.0.0.1:3306/pos?useSSL=false&autoReconnect=true&tinyInt1isBit=false&useUnicode=true&characterEncoding=utf8    username: root    password: 123456#多数据源biz:  datasource:    url: jdbc:mysql://127.0.0.1:3306/biz?useSSL=false&autoReconnect=true&tinyInt1isBit=false&useUnicode=true&characterEncoding=utf8    username: root    password: 123456

解释一下:

这里我添加了一个自定义配置项muti-datasource-open,用来控制是否开启多数据源支持。这个配置项后面我会用到。 接下来定义MyBatis的配置,最后定义了两个MySQL数据库的连接信息,一个是pos库,一个是biz库。

动态切换数据源

这里通过Spring的AOP技术实现数据源的动态切换。

多数据源的常量类:

1234

public interface DSEnum {    String DATA_SOURCE_CORE = "dataSourceCore";         //核心数据源    String DATA_SOURCE_BIZ = "dataSourceBiz";            //其他业务的数据源}

datasource的上下文,用来存储当前线程的数据源类型:

1234567891011121314151617181920212223242526

public class DataSourceContextHolder {    private static final ThreadLocalcontextHolder = new ThreadLocal();    /**     * @param dataSourceType 数据库类型     * @Description: 设置数据源类型     */    public static void setDataSourceType(String dataSourceType) {        contextHolder.set(dataSourceType);    }    /**     * @Description: 获取数据源类型     */    public static String getDataSourceType() {        return contextHolder.get();    }    /**     * @Description: 清除数据源类型     */    public static void clearDataSourceType() {        contextHolder.remove();    }}

定义动态数据源,继承AbstractRoutingDataSource:

1234567

public class DynamicDataSource extends AbstractRoutingDataSource {    @Override    protected Object determineCurrentLookupKey() {        return DataSourceContextHolder.getDataSourceType();    }}

接下来自定义一个注解,用来在Service方法上面注解使用哪个数据源:

1234567891011

/** * 多数据源标识 * * @author xiongneng */@Inherited@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD})public @interface DataSource {    String name() default "";}

最后,最核心的AOP类定义:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556

/** * 多数据源切换的aop * * @author xiongneng */@Aspect@Component@ConditionalOnProperty(prefix = "xncoding", name = "muti-datasource-open", havingValue = "true")public class MultiSourceExAop implements Ordered {    private Logger log = Logger.getLogger(this.getClass());    @Pointcut(value = "@annotation(com.xncoding.pos.common.annotion.DataSource)")    private void cut() {    }    @Around("cut()")    public Object around(ProceedingJoinPoint point) throws Throwable {        Signature signature = point.getSignature();        MethodSignature methodSignature = null;        if (!(signature instanceof MethodSignature)) {            throw new IllegalArgumentException("该注解只能用于方法");        }        methodSignature = (MethodSignature) signature;        Object target = point.getTarget();        Method currentMethod = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());        DataSource datasource = currentMethod.getAnnotation(DataSource.class);        if (datasource != null) {            DataSourceContextHolder.setDataSourceType(datasource.name());            log.debug("设置数据源为:" + datasource.name());        } else {            DataSourceContextHolder.setDataSourceType(DSEnum.DATA_SOURCE_CORE);            log.debug("设置数据源为:dataSourceCore");        }        try {            return point.proceed();        } finally {            log.debug("清空数据源信息!");            DataSourceContextHolder.clearDataSourceType();        }    }    /**     * aop的顺序要早于spring的事务     */    @Override    public int getOrder() {        return 1;    }}

这里使用到了注解@ConditionalOnProperty,只有当我的配置文件中muti-datasource-open=true的时候注解才会生效。

另外还有一个要注意的地方,就是order,aop的顺序一定要早于spring的事务,这里我将它设置成1,后面你会看到我将spring事务顺序设置成2。

配置类

首先有两个属性类:

DruidProperties连接池的属性类MutiDataSourcePropertiesbiz数据源的属性类

然后定义配置类:

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465

@Configuration@EnableTransactionManagement(order = 2)@MapperScan(basePackages = {"com.xncoding.pos.common.dao.repository"})public class MybatisPlusConfig {    @Autowired    DruidProperties druidProperties;    @Autowired    MutiDataSourceProperties mutiDataSourceProperties;    /**     * 核心数据源     */    private DruidDataSource coreDataSource() {        DruidDataSource dataSource = new DruidDataSource();        druidProperties.config(dataSource);        return dataSource;    }    /**     * 另一个数据源     */    private DruidDataSource bizDataSource() {        DruidDataSource dataSource = new DruidDataSource();        druidProperties.config(dataSource);        mutiDataSourceProperties.config(dataSource);        return dataSource;    }    /**     * 单数据源连接池配置     */    @Bean    @ConditionalOnProperty(prefix = "xncoding", name = "muti-datasource-open", havingValue = "false")    public DruidDataSource singleDatasource() {        return coreDataSource();    }    /**     * 多数据源连接池配置     */    @Bean    @ConditionalOnProperty(prefix = "xncoding", name = "muti-datasource-open", havingValue = "true")    public DynamicDataSource mutiDataSource() {        DruidDataSource coreDataSource = coreDataSource();        DruidDataSource bizDataSource = bizDataSource();        try {            coreDataSource.init();            bizDataSource.init();        } catch (SQLException sql) {            sql.printStackTrace();        }        DynamicDataSource dynamicDataSource = new DynamicDataSource();        HashMaphashMap = new HashMap<>();        hashMap.put(DSEnum.DATA_SOURCE_CORE, coreDataSource);        hashMap.put(DSEnum.DATA_SOURCE_BIZ, bizDataSource);        dynamicDataSource.setTargetDataSources(hashMap);        dynamicDataSource.setDefaultTargetDataSource(coreDataSource);        return dynamicDataSource;    }}

代码其实很好理解,我就不再多做解释了。

然后步骤跟普通的集成MyBatis是一样的,我简单的过一遍。

实体类

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748

@TableName(value = "t_user")public class User extends Model{private static final long serialVersionUID = 1L;    /**     * 主键ID     */    @TableId(value="id", type= IdType.AUTO)    private Integer id;    /**     * 账号     */    private String username;    /**     * 名字     */    private String name;    /**     * 密码     */    private String password;    /**     * md5密码盐     */    private String salt;    /**     * 联系电话     */    private String phone;    /**     * 备注     */    private String tips;    /**     * 状态 1:正常 2:禁用     */    private Integer state;    /**     * 创建时间     */    private Date createdTime;    /**     * 更新时间     */    private Date updatedTime;    // 省略getter/setter方法}

定义DAO

123

public interface UserMapper extends BaseMapper{}

定义Service

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950

@Servicepublic class UserService {    @Resource    private UserMapper userMapper;    /**     * 通过ID查找用户     * @param id     * @return     */    public User findById(Integer id) {        return userMapper.selectById(id);    }    /**     * 通过ID查找用户     * @param id     * @return     */    @DataSource(name = DSEnum.DATA_SOURCE_BIZ)    public User findById1(Integer id) {        return userMapper.selectById(id);    }    /**     * 新增用户     * @param user     */    public void insertUser(User user) {        userMapper.insert(user);    }    /**     * 修改用户     * @param user     */    public void updateUser(User user) {        userMapper.updateById(user);    }    /**     * 删除用户     * @param id     */    public void deleteUser(Integer id) {        userMapper.deleteById(id);    }}

这里唯一要说明的就是我在方法findById1()上面增加了注解@DataSource(name = DSEnum.DATA_SOURCE_BIZ),这样这个方法就会访问biz数据库。

注意,不加注解就会访问默认数据库pos。

测试

最后编写一个简单的测试,我只测试findById()方法和findById1()方法,看它们是否访问的是不同的数据源。

12345678910111213141516171819202122

@RunWith(SpringRunner.class)@SpringBootTestpublic class ApplicationTests {    private static final Logger log = LoggerFactory.getLogger(ApplicationTests.class);    @Resource    private UserService userService;    /**     * 测试增删改查     */    @Test    public void test() {        // 核心数据库中的用户id=1        User user = userService.findById(1);        assertThat(user.getUsername(), is("admin"));        // biz数据库中的用户id=1        User user1 = userService.findById1(1);        assertThat(user1.getUsername(), is("admin1"));    }}

运行测试,结果显示为green bar,成功!

相关文章Related

返回栏目>>