SpringBoot + Mybatis + atomikos realiza transacciones distribuidas

Antecedentes: con el desarrollo continuo del negocio y la duplicación de la cantidad de datos, el rendimiento de una sola base de datos crea un cuello de botella. Podemos dividir la base de datos (es decir, la subbase de datos y la subtabla que solemos llamar). La partición mencionada aquí se refiere a la física Después de la partición, diferentes bibliotecas pueden estar en diferentes servidores. En este momento, el ACID de una sola base de datos ya no puede adaptarse a esta situación. En este entorno de clúster ACID, es casi difícil garantizar el ACID del clúster. Logre, o incluso si puede lograr que la eficiencia y el rendimiento se reduzcan en gran medida, esta vez utilizaremos transacciones distribuidas

¿Para qué sirve Atomikos? 

Atomikos es un administrador de transacciones de código abierto que proporciona servicios de valor agregado para la plataforma Java. Se utiliza principalmente para manejar transacciones entre bases de datos. Por ejemplo, una determinada instrucción está escrita en la biblioteca A y la biblioteca B, y la empresa requiere la escritura de la biblioteca A y la biblioteca B. La operación debe ser atómica, y atomikos se puede utilizar en este momento.

Para una implementación específica, mire hacia abajo ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

1. Introducir paquetes jar relacionados con transacciones distribuidas

        <!-- 阿里 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.4</version>
        </dependency>
        <!--分布式事务-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>

Dos, configuración de la propiedad application.yml

spring:
  datasource:
    pre :
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/test1?useUnicode=true&amp;characterEncoding=utf8&amp;characterResultSets=utf8
      user: users
      password: 123456
    sit :
      driver-class-name: com.mysql.jdbc.Driver
      url: jdbc:mysql://localhost:3306/test2?useUnicode=true&amp;characterEncoding=utf8&amp;characterResultSets=utf8
      user: userbyuer
      password: 123456

Tres, configure la fuente de datos

 Cree dos fuentes de datos de Atomikos

@Configuration
public class DataSourceConfig {

    // 将这个对象放入spring容器中(交给Spring管理)
    @Bean
    // 读取 application.yml 中的配置参数映射成为一个对象
    @ConfigurationProperties(prefix = "spring.datasource.pre")
    public XADataSource getDataSource1(){
        // 创建XA连接池
        return new MysqlXADataSource();
    }

    /**
     * 创建Atomikos数据源
     * 注解@DependsOn("druidXADataSourcePre"),在名为druidXADataSourcePre的bean实例化后加载当前bean
     * @param xaDataSource
     * @return
     */
    @Bean
    @DependsOn("getDataSource1")
    @Primary
    public DataSource dataSourcePre(@Qualifier("getDataSource1") XADataSource xaDataSource){
        //这里的AtomikosDataSourceBean使用的是spring提供的
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(xaDataSource);
        return atomikosDataSourceBean;
    }


    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.sit")
    public XADataSource getDataSource2(){
        // 创建XA连接池
        return new MysqlXADataSource();
    }

    @Bean
    @DependsOn("getDataSource2")
    public DataSource dataSourceSit(@Qualifier("getDataSource2") XADataSource xaDataSource){
        //这里的AtomikosDataSourceBean使用的是spring提供的
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(xaDataSource);
        return atomikosDataSourceBean;
    }
}
  • La clase de configuración sitSqlSessionTemplate de la fuente de datos 1

/**
 * 数据源Config1
 */
@Configuration
@MapperScan(basePackages = {"com.xiateng.dao.userbuyer"}, sqlSessionTemplateRef = "sitSqlSessionTemplate")
public class MybatisSitConfig {

    @Autowired
    @Qualifier("dataSourceSit")
    private DataSource dataSource;

    /**
     * 创建 SqlSessionFactory
     * @return
     * @throws Exception
     */
    @Bean
    @Primary
    public SqlSessionFactory sitSqlSessionFactory() throws Exception{
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        // 设置mybatis的xml所在位置
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().
                getResources("classpath*:com/xiateng/mapper/userbuyer/*.xml"));
        return bean.getObject();
    }

    /**
     * 通过 SqlSessionFactory 来创建 SqlSessionTemplate
     * @param sqlSessionFactory
     * @return
     */
    @Bean
    @Primary
    public SqlSessionTemplate sitSqlSessionTemplate(@Qualifier("sitSqlSessionFactory") SqlSessionFactory sqlSessionFactory){
        // SqlSessionTemplate是线程安全的,可以被多个DAO所共享使用
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

Clase de entidad relacionada con la fuente de datos 1:

public class TUserBuyer {
    private Long userId;
    private Integer bond;

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public Integer getBond() {
        return bond;
    }

    public void setBond(Integer bond) {
        this.bond = bond;
    }
}

Interfaz de Dao relacionada con la fuente de datos 1:

public interface TUserBuyerMapper {
    int updateByPrimaryKeySelective(TUserBuyer record);
}

La interfaz dao relacionada con la fuente de datos 1 implementa xml:

<update id="updateByPrimaryKeySelective" parameterType="com.xiateng.entity.TUserBuyer">
    update t_user_buyer
    <set>
      <if test="bond != null">
        bond = #{bond,jdbcType=INTEGER},
      </if>
    </set>
    where user_id = #{userId,jdbcType=BIGINT}
  </update>
 
  • La clase de configuración sitSqlSessionTemplate de la fuente de datos 2

/**
 * 数据源Config2
 */
@Configuration
@MapperScan(basePackages = {"com.xiateng.dao.user"}, sqlSessionTemplateRef = "preSqlSessionTemplate")
public class MybatisPreConfig {

    @Autowired
    // @Qualifier表示查找Spring容器中名字为 preDataSource 的对象
    @Qualifier("dataSourcePre")
    private DataSource dataSource;

    /**
     * 创建 SqlSessionFactory
     * @return
     * @throws Exception
     */
    @Bean
    @Primary
    public SqlSessionFactory preSqlSessionFactory() throws Exception{
        // 用来创建 SqlSessionFactory 等同于下面配置
//        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
//            <property name="dataSource" ref="dataSource" />
//            <property name="mapperLocations" value="classpath:mybatis-mapper/*.xml"/>
//        </bean>
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        // 设置mybatis的xml所在位置(扫描mybatis的相关xml文件,装配到容器中)
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().
                getResources("classpath*:com/xiateng/mapper/user/*.xml"));
        return bean.getObject();
    }

    /**
     * 通过 SqlSessionFactory 来创建 SqlSessionTemplate
     * @param sqlSessionFactory
     * @return
     */
    @Bean
    @Primary
    public SqlSessionTemplate preSqlSessionTemplate(@Qualifier("preSqlSessionFactory") SqlSessionFactory sqlSessionFactory){
        // SqlSessionTemplate是线程安全的,可以被多个DAO所共享使用
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

Clases de entidad relacionadas con la fuente de datos 2:

package com.xiateng.entity;

public class TtUser {
    private Long id;
    private Long updatedBy;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
    public Long getUpdatedBy() {
        return updatedBy;
    }

    public void setUpdatedBy(Long updatedBy) {
        this.updatedBy = updatedBy;
    }
}

 Interfaz dao relacionada con la fuente de datos 2:

public interface TtUserMapper {
    int updateByPrimaryKeySelective(TtUser record);
}

 Xml de implementación de interfaz relacionada con la fuente de datos 2:

<update id="updateByPrimaryKeySelective" parameterType="com.xiateng.entity.TtUser">
    update t_user
    <set>
      <if test="updatedBy != null">
        updated_by = #{updatedBy,jdbcType=BIGINT}
      </if>
    </set>
    where id = #{id,jdbcType=BIGINT}
  </update>

Cuatro, escritura de código de capa de servicio

public interface TUserService {

    int transactionalTest() throws Exception;
}
@Service
public class TUserServiceImpl implements TUserService{
    @Autowired
    private TtUserMapper ttUserMapper;
    @Autowired
    private TUserBuyerMapper tUserBuyerMapper;
    /**
     * 实现多数据库操作
     * @return
     */
    @Transactional
    public int transactionalTest() throws Exception{
        // sit(数据源1)
        TUserBuyer tUserBuyer = new TUserBuyer();
        tUserBuyer.setUserId(0L);
        tUserBuyer.setBond(444);
        tUserBuyerMapper.updateByPrimaryKeySelective(tUserBuyer);
        // pre(数据源2)
        TtUser ttUser = new TtUser();
        ttUser.setId(1L);
        ttUser.setUpdatedBy(444L);
        ttUserMapper.updateByPrimaryKeySelective(ttUser);
        //模拟异常
        int a = 1/0;
        return 1;
    }
}

Cinco, código de capa de controlador

   @RequestMapping(value = "/tUserList1")
    @ResponseBody
    public Map<String, Object> tUserList1(){
        Map<String, Object> map = new HashMap<>();
        int result = 0;
        try {
            result = tUserService.transactionalTest();
        } catch (Exception e) {
            e.printStackTrace();
            map.put("result","系统异常!");
            return map;
        }
        map.put("result",result);
        return map;
    }

Resultado de la prueba: si simulamos una excepción, los datos de la base de datos no han cambiado, lo que indica que se realizó una operación de reversión cuando ocurrió la excepción y se logró el efecto esperado.

Finalmente, proporciono la dirección de origen de github: https://github.com/xiatengGG/springboot-atomikos

Supongo que te gusta

Origin blog.csdn.net/qq_43037478/article/details/111592101
Recomendado
Clasificación