SpringBoot+Mybatis多数据源配置,以及加入事物控制

有时候为了让数据分割,不让数据库压力太大,数据会放在两个数据源中(两个逻辑库),但是我又不想为一个数据库搭建一个微服务,这个时候就要让一个Web服务管理两个数据源。SpringBoot中就能配置双数据源,接下来我们就来配置多数据源。

SpringBoot多数据源配置:

有两个数据源就要配置两个DataSource数据源,要使用Mybatis,也要有两个SqlSessionFactory,同时也要有两个SqlSessionTemplate

配置文件:application.properties

######################################################## test1 database
spring.datasource.test1.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
spring.datasource.test1.username=root
spring.datasource.test1.password=root
spring.datasource.test1.driver-class-name=com.mysql.cj.jdbc.Driver
#自动提交 默认值true
spring.datasource.test1.auto-commit=true
#此属性控制客户端(即您)将等待来自池的连接的最大毫秒数。 默认值30000(30秒)
spring.datasource.test1.connection-timeout=30000
#此属性控制允许连接在池中闲置的最长时间。 默认值600000(10分钟),此设置仅适用于minimumIdle定义为小于maximumPoolSize。
spring.datasource.test1.idle-timeout=600000
#此属性控制池中连接的最大生存期。 1800000(30分钟)
spring.datasource.test1.max-lifetime=1800000
#该属性控制HikariCP尝试在池中维护的最小空闲连接数。不推荐使用这个,默认是和maximum-pool-size相等
spring.datasource.test1.minimum-idle=100
#此属性控制池允许达到的最大大小,包括空闲和正在使用的连接。默认值10
spring.datasource.test1.maximum-pool-size=100
#mybatis对应的映射文件路径
spring.datasource.test1.mapper-locations=classpath:mapper1/*.xml
#mybatis对应的实体类
spring.datasource.test1.type-aliases-package=com.wangye.spbootmybatis1.model1


######################################################## test2 database
spring.datasource.test2.url=jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&useSSL=false
spring.datasource.test2.username=root
spring.datasource.test2.password=root
spring.datasource.test2.driver-class-name=com.mysql.cj.jdbc.Driver
#自动提交 默认值true
spring.datasource.test2.auto-commit=true
#此属性控制客户端(即您)将等待来自池的连接的最大毫秒数。 默认值30000(30秒)
spring.datasource.test2.connection-timeout=30000
#此属性控制允许连接在池中闲置的最长时间。 默认值600000(10分钟),此设置仅适用于minimumIdle定义为小于maximumPoolSize。
spring.datasource.test2.idle-timeout=600000
#此属性控制池中连接的最大生存期。 1800000(30分钟)
spring.datasource.test2.max-lifetime=1800000
#该属性控制HikariCP尝试在池中维护的最小空闲连接数。不推荐使用这个,默认是和maximum-pool-size相等
spring.datasource.test2.minimum-idle=100
#此属性控制池允许达到的最大大小,包括空闲和正在使用的连接。默认值10
spring.datasource.test2.maximum-pool-size=100
#mybatis对应的映射文件路径
spring.datasource.test2.mapper-locations=classpath:mapper2/*.xml
#mybatis对应的实体类
spring.datasource.test2.type-aliases-package=com.wangye.spbootmybatis1.model2

为了加载这些配置,我们要使用如下配置类:

这是Datasource2的属性值:

@Configuration
@ConfigurationProperties(prefix = "spring.datasource.test1")
public class Datasource1Properties {

    private String url;

    private String username;

    private String password;

    private String driverClassName;

    private Boolean autoCommit;

    private Long connectionTimeout;

    private Long idleTimeout;

    private Long maxLifetime;

    private Integer minimumIdle;

    private Integer maximumPoolSize;

    private String mapperLocations;

    private String typeAliasesPackage;

    get 和 set方法
}

这是Datasource2的属性值:

@Configuration
@ConfigurationProperties(prefix = "spring.datasource.test2")
public class Datasource2Properties {

    private String url;

    private String username;

    private String password;

    private String driverClassName;

    private Boolean autoCommit;

    private Long connectionTimeout;

    private Long idleTimeout;

    private Long maxLifetime;

    private Integer minimumIdle;

    private Integer maximumPoolSize;

    private String mapperLocations;

    private String typeAliasesPackage;

    get 和 set 方法
}

Datasource1的配置和第一个SqlSessionFactory以及第一个SqlSessionTemplate

@Configuration
@MapperScan(basePackages = "com.wangye.spbootmultidatasource.mapper1", sqlSessionFactoryRef = "test1SqlSessionFactory")
public class Datasource1Configuration {

    private Datasource1Properties datasourceProperties;

    @Autowired
    public void setDatasourceProperties(Datasource1Properties datasourceProperties) {
        this.datasourceProperties = datasourceProperties;
    }

    @Bean(name = "test1DataSource")
    @Primary// 表示这个数据源是默认数据源 // 读取application.properties中的配置参数映射成为一个对象
    public DataSource getDateSource1() {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setJdbcUrl(datasourceProperties.getUrl());
        hikariConfig.setUsername(datasourceProperties.getUsername());
        hikariConfig.setPassword(datasourceProperties.getPassword());
        hikariConfig.setDriverClassName(datasourceProperties.getDriverClassName());
        hikariConfig.setAutoCommit(datasourceProperties.getAutoCommit());
        hikariConfig.setConnectionTimeout(datasourceProperties.getConnectionTimeout());
        hikariConfig.setIdleTimeout(datasourceProperties.getIdleTimeout());
        hikariConfig.setMaxLifetime(datasourceProperties.getMaxLifetime());
        hikariConfig.setMinimumIdle(datasourceProperties.getMinimumIdle());
        hikariConfig.setMaximumPoolSize(datasourceProperties.getMaximumPoolSize());
        return new HikariDataSource(hikariConfig);
    }

    @Bean(name = "test1SqlSessionFactory")
    @Primary // 表示这个数据源是默认数据源 // @Qualifier表示查找Spring容器中名字为test1DataSource的对象
    public SqlSessionFactory test1SqlSessionFactory(@Qualifier("test1DataSource") DataSource datasource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(datasource);
        // 设置mybatis的xml所在位置
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(datasourceProperties.getMapperLocations()));
        bean.setTypeAliasesPackage(datasourceProperties.getTypeAliasesPackage());
        return bean.getObject();
    }

    @Bean("test1SqlSessionTemplate")
    @Primary
    public SqlSessionTemplate test1sqlsessiontemplate(
            @Qualifier("test1SqlSessionFactory") SqlSessionFactory sessionfactory) {
        return new SqlSessionTemplate(sessionfactory);
    }

    /**
     * 为 test1DataSource 配置事务管理器
     * @param test1DataSource test2数据源
     * @return DataSourceTransactionManager 事务管理器
     */
    @Bean // 这里方法名会设置为@Bean的名字
    @Primary
    public PlatformTransactionManager test1TransactionManager(@Qualifier("test1DataSource")DataSource test1DataSource) {
        return new DataSourceTransactionManager(test1DataSource);
    }
}

Datasource2的配置和第一个SqlSessionFactory以及第一个SqlSessionTemplate

@Configuration
@MapperScan(basePackages = "com.wangye.spbootmultidatasource.mapper2", sqlSessionFactoryRef = "test2SqlSessionFactory")
public class Datasource2Configuration {

    private Datasource2Properties datasourceProperties;

    @Autowired
    public void setDatasourceProperties(Datasource2Properties datasourceProperties) {
        this.datasourceProperties = datasourceProperties;
    }

    @Bean(name = "test2DataSource")// 读取application.properties中的配置参数映射成为一个对象
    public DataSource getDateSource2() {
        HikariConfig hikariConfig = new HikariConfig();
        hikariConfig.setJdbcUrl(datasourceProperties.getUrl());
        hikariConfig.setUsername(datasourceProperties.getUsername());
        hikariConfig.setPassword(datasourceProperties.getPassword());
        hikariConfig.setDriverClassName(datasourceProperties.getDriverClassName());
        hikariConfig.setAutoCommit(datasourceProperties.getAutoCommit());
        hikariConfig.setConnectionTimeout(datasourceProperties.getConnectionTimeout());
        hikariConfig.setIdleTimeout(datasourceProperties.getIdleTimeout());
        hikariConfig.setMaxLifetime(datasourceProperties.getMaxLifetime());
        hikariConfig.setMinimumIdle(datasourceProperties.getMinimumIdle());
        hikariConfig.setMaximumPoolSize(datasourceProperties.getMaximumPoolSize());
        return new HikariDataSource(hikariConfig);
    }

    @Bean(name = "test2SqlSessionFactory")// @Qualifier表示查找Spring容器中名字为test1DataSource的对象
    public SqlSessionFactory test2SqlSessionFactory(@Qualifier("test2DataSource") DataSource datasource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(datasource);
        // 设置mybatis的xml所在位置
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(datasourceProperties.getMapperLocations()));
        bean.setTypeAliasesPackage(datasourceProperties.getTypeAliasesPackage());
        return bean.getObject();
    }

    @Bean("test2SqlSessionTemplate")
    public SqlSessionTemplate test2sqlsessiontemplate(@Qualifier("test2SqlSessionFactory") SqlSessionFactory sessionfactory) {
        return new SqlSessionTemplate(sessionfactory);
    }

    /**
     * 为test2DataSource配置事务管理器
     * @param test2DataSource test2DataSource数据源
     * @return PlatformTransactionManager事务管理器
     */
    @Bean // 这里方法名会设置为@Bean的名字
    public PlatformTransactionManager test2TransactionManager(@Qualifier("test2DataSource")DataSource test2DataSource) {
        return new DataSourceTransactionManager(test2DataSource);
    }

}

接下来就看看项目目录结构吧:

接下来就来测试一下:

UserMapper1.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.wangye.spbootmultidatasource.mapper1.UserMapper1">

    <select id="getUserById1" resultType="com.wangye.spbootmultidatasource.model1.User1">
        select
            id as id,
            name as name,
            age as age
        from
            user
        where
            id = #{id}
    </select>

    <insert id="addUser" parameterType="com.wangye.spbootmultidatasource.model1.User1">
        insert into
            user
        (name,age)
        values (
        #{name},#{age}
        )
    </insert>

</mapper>

 UserMapper2.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.wangye.spbootmultidatasource.mapper2.UserMapper2">

    <select id="getUserById1" resultType="com.wangye.spbootmultidatasource.model2.User2">
        select
            id as id,
            name as name,
            age as age
        from
            user
        where
            id = #{id}
    </select>

    <insert id="addUser" parameterType="com.wangye.spbootmultidatasource.model2.User2">
        insert into
            user
        (name,age)
        values (
        #{name},#{age}
        )
    </insert>
</mapper>

UserMapper1.java

@Repository
public interface UserMapper1 {

    User1 getUserById1(@Param("id") Long id);

    @Select({"<script>",
            "select",
            "   id as id,",
            "   name as name,",
            "   age as age",
            "from",
            "   user",
            "<where>",
            "   <if test ='id != null'>",
            "   and",
            "       id = #{id}",
            "   </if>",
            "</where>",
            "</script>"})
    User1 getUserById(@Param("id") Long id);

    void addUser(User1 user1);
}

UserMapper2.java:

@Repository
public interface UserMapper2 {

    User2 getUserById1(@Param("id") Long id);

    @Select({"<script>",
            "select",
            "   id as id,",
            "   name as name,",
            "   age as age",
            "from",
            "   user",
            "<where>",
            "   <if test ='id != null'>",
            "   and",
            "       id = #{id}",
            "   </if>",
            "</where>",
            "</script>"})
    User2 getUserById(@Param("id") Long id);

    void addUser(User2 user2);
}

User1.java:

public class User1 {

    private Long id;

    private String name;

    private Integer age;

    set 和 get 方法
}

User2.java:

public class User2 {

    private Long id;

    private String name;

    private Integer age;

    set 和 get 方法
}

UserService.java:

@Service
public class UserService {

    private UserMapper1 userMapper1;

    @Autowired
    public void setUserMapper1(UserMapper1 userMapper1) {
        this.userMapper1 = userMapper1;
    }

    @Autowired
    public void setUserMapper2(UserMapper2 userMapper2) {
        this.userMapper2 = userMapper2;
    }

    private UserMapper2 userMapper2;

    public String addUser(){
        User1 user1 = new User1();
        user1.setName("小四");
        user1.setAge(15);
        userMapper1.addUser(user1);

        User2 user2 = new User2();
        user2.setName("小屋");
        user2.setAge(16);
        userMapper2.addUser(user2);
        return "success";
    }
    
}

UserController.java

@RestController
public class UserController {

    private UserService userService;

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    @RequestMapping("/addUser")
    public String addUser(){
        return userService.addUser();
    }
}

对了还没有贴数据库,贴一下:

接下来所有的事情都做完了,那么就来测试一下接口:

localhost:8080/addUser。

发现会返回success

多数据源下事物管理:

事物管理默认管理的是@Primary修饰的Datasource,也就是我们的test1DataSource。如果要切换事物管理器,使用@Transactional的时候指定value值:下面两个方法抛出异常了,所以数据不会添加成功。

    @Transactional(value = "test1TransactionManager")
    public String adUserTransaction1(){
        User1 user1 = new User1();
        user1.setName("小四");
        user1.setAge(15);
        userMapper1.addUser(user1);
        int i = 1 / 0;
        return "success";
    }
    @Transactional(value = "test2TransactionManager")
    public String adUserTransaction2(){
        User2 user2 = new User2();
        user2.setName("小留");
        user2.setAge(16);
        userMapper2.addUser(user2);
        int i = 1 / 0;
        return "success";
    }

 向上面这样就能为单个数据源控制事物。

注意下面代码:下面代码虽然不能插入成功,是因为@Transactional默认的value值的@Primary注解修饰的数据源事物管理器,代码在插入第二个数据源的时候就已经异常了,不会往下面执行,所以回滚的是test1DataSource的事务

    @Transactional
    public String addUserToTwoDataSource1(){
        User1 user1 = new User1();
        user1.setName("小四");
        user1.setAge(15);
        userMapper1.addUser(user1);
        int i = 1 / 0 ;
        User2 user2 = new User2();
        user2.setName("小屋");
        user2.setAge(16);
        userMapper2.addUser(user2);
        return "success";
    }

 再看看下面的代码:下面会往test2DataSource中插入数据,不会向test1DataSource中插入,因为在插入test1DataSource前就已经抛异常了,代码不会往下执行了。所以数据库会往test2DataSource插入一条数据。

    @Transactional
    public String addUserToTwoDataSource2(){
        User2 user2 = new User2();
        user2.setName("小屋");
        user2.setAge(16);
        userMapper2.addUser(user2);
        int i = 1 / 0 ;

        User1 user1 = new User1();
        user1.setName("小四");
        user1.setAge(15);
        userMapper1.addUser(user1);
        return "success";
    }

接下来在看看下面两个方法的代码:上面已经说过了,@Transactional默认的@Value值是@Primary修饰的事务管理器,所以下面两个方法都会往test2DataSource中插入一条数据,但是test1DataSource中会回滚,不插如任何数据

    @Transactional
    public String addUserToTwoDataSource3(){
        User2 user2 = new User2();
        user2.setName("小屋");
        user2.setAge(16);
        userMapper2.addUser(user2);


        User1 user1 = new User1();
        user1.setName("小四");
        user1.setAge(15);
        userMapper1.addUser(user1);
        int i = 1 / 0 ;
        return "success";
    }
    @Transactional
    public String addUserToTwoDataSource4(){
        User1 user1 = new User1();
        user1.setName("小四");
        user1.setAge(15);
        userMapper1.addUser(user1);

        User2 user2 = new User2();
        user2.setName("小屋");
        user2.setAge(16);
        userMapper2.addUser(user2);
        int i = 1 / 0 ;
        return "success";
    }

 

那么怎么实现一个@Transactional控制两个数据源呢,答案是不行,因为这个是普通的事物管理器,如果是实现了XA协议,并且实现了两段提交的,三段提交的,就能一个@Transactional控制两个或者三个数据源提交事物。MySQL是支持XA协议的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值