美文网首页
分库分表实践-springboot+mybatis+druid+

分库分表实践-springboot+mybatis+druid+

作者: Rediculous | 来源:发表于2019-07-25 10:19 被阅读0次

    0 前言

    0.1 Sharding-Sphere

    Sharding-Sphere是一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar这3款相互独立的产品组成。他们均提供标准化的数据分片、读写分离、柔性事务和数据治理功能,可适用于如Java同构、异构语言、容器、云原生等各种多样化的应用场景。
    Sharding-Sphere定位为关系型数据库中间件,旨在充分合理地在分布式的场景下利用关系型数据库的计算和存储能力,而并非实现一个全新的关系型数据库。

    0.2 Sharding-JDBC

    我们这边主要用的是里面的Sharding-JDBC:

    定位为轻量级Java框架,在Java的JDBC层提供的额外服务。 它使用客户端直连数据库,以jar包形式提供服务,无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架。

    • 适用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
    • 基于任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
    • 支持任意实现JDBC规范的数据库。目前支持MySQL,Oracle,SQLServer和PostgreSQL。
    系统架构图
    1. 配置
      1.1 springboot+mybatis+druid

      1.2 sharding-spheres
      pom.xml引入:
    <dependency>
        <groupId>io.shardingsphere</groupId>
        <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
        <version>3.0.0.M2</version>
    </dependency>
    

    1.3 分片设计

    逻辑表结构:

    CREATE TABLE `t_order` (
      `order_id` int(11) NOT NULL,
      `user_id` int(11) DEFAULT NULL,
      PRIMARY KEY (`order_id`)
    )
    

    按照order_id进行范围分片(order_id < 100的在shard1,其他的在shard2);
    再按user_id进行hash分片(user_id % 2 分表落在t_order_0和t_order_1上)

    数据库结构如下图:

    数据库结构

    application.properties

    server.port=9001
    
    mybatis.mapper-locations=classpath:mapper/*.xml
    
    sharding.jdbc.datasource.names=ds0,ds1
    
    sharding.jdbc.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource
    sharding.jdbc.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
    sharding.jdbc.datasource.ds0.url=jdbc:mysql://localhost:3306/shard1?useSSL=false
    sharding.jdbc.datasource.ds0.username=root
    sharding.jdbc.datasource.ds0.password=password
    
    sharding.jdbc.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
    sharding.jdbc.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
    sharding.jdbc.datasource.ds1.url=jdbc:mysql://localhost:3306/shard2?useSSL=false
    sharding.jdbc.datasource.ds1.username=root
    sharding.jdbc.datasource.ds1.password=password
    
    sharding.jdbc.config.sharding.tables.t_order.actual-data-nodes=ds$->{0..1}.t_order_$->{0..1}
    sharding.jdbc.config.sharding.tables.t_order.database-strategy.standard.sharding-column=order_id
    sharding.jdbc.config.sharding.tables.t_order.database-strategy.standard.precise-algorithm-class-name=com.joey.springboot.shardingdemo.conf.OrderShardingAlgorithm
    sharding.jdbc.config.sharding.tables.t_order.table-strategy.inline.sharding-column=user_id
    sharding.jdbc.config.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_$->{user_id % 2}
    sharding.jdbc.config.sharding.tables.t_order.key-generator-column-name=order_id
    
    sharding.jdbc.config.sharding.props.sql.show=true
    

    具体的配置参数含义见官方文档
    http://shardingsphere.io/document/current/cn/manual/sharding-jdbc/configuration/config-spring-boot/

    其中OrderShardingAlgorithm配置类为:

    package com.joey.springboot.shardingdemo.conf;
    
    import io.shardingsphere.core.api.algorithm.sharding.PreciseShardingValue;
    import io.shardingsphere.core.api.algorithm.sharding.standard.PreciseShardingAlgorithm;
    
    import java.util.Collection;
    
    public class OrderShardingAlgorithm implements PreciseShardingAlgorithm {
    
        @Override
        public String doSharding(Collection availableTargetNames, PreciseShardingValue shardingValue) {
            if(shardingValue.getValue().compareTo(100) > 0) {
                return "ds1";
            }
            return "ds0";
        }
    }
    
    
    1. 测试代码

    controller:

    package com.joey.springboot.shardingdemo.controller;
    
    import com.joey.springboot.shardingdemo.dao.Order;
    import com.joey.springboot.shardingdemo.service.OrderService;
    import io.shardingsphere.core.jdbc.core.connection.ShardingConnection;
    import io.shardingsphere.core.jdbc.core.transaction.TransactionLoader;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.transaction.annotation.Transactional;
    import org.springframework.web.bind.annotation.*;
    
    import javax.sql.DataSource;
    import java.sql.SQLException;
    
    @RestController
    public class OrderController {
    
        @Autowired
        OrderService orderService;
    
        @Autowired
        DataSource dataSource;
    
        @RequestMapping("/order/{id}")
        public Order getOrderById(@PathVariable int id) {
            System.out.println(orderService.getOrderById(id));
            return orderService.getOrderById(id);
        }
    
        @RequestMapping(value="/order", method= RequestMethod.POST)
        @ResponseBody
        @Transactional(rollbackFor = Exception.class)
        public Order addOrder(@RequestBody Order order) {
            orderService.addOrder(order);
            Order order1 = new Order();
            order1.setOrderId(order.getOrderId()+100);
            order1.setUserId(order.getUserId()+1);
            orderService.addOrder(order1);
            return orderService.getOrderById(order.getOrderId());
        }
    }
    
    

    service:

    package com.joey.springboot.shardingdemo.service;
    
    import com.joey.springboot.shardingdemo.dao.Order;
    import com.joey.springboot.shardingdemo.dao.mapper.OrderMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class OrderService {
    
        @Autowired
        private OrderMapper orderMapper;
    
        public Order getOrderById(int id) {
            return orderMapper.selectByPrimaryKey(id);
        }
    
    
        public int addOrder(Order order) {
            return orderMapper.insert(order);
        }
    }
    
    

    model:

    package com.joey.springboot.shardingdemo.dao;
    
    public class Order {
        private Integer orderId;
    
        private Integer userId;
    
        public Integer getOrderId() {
            return orderId;
        }
    
        public void setOrderId(Integer orderId) {
            this.orderId = orderId;
        }
    
        public Integer getUserId() {
            return userId;
        }
    
        public void setUserId(Integer userId) {
            this.userId = userId;
        }
    }
    

    mapper:

    package com.joey.springboot.shardingdemo.dao.mapper;
    
    import com.joey.springboot.shardingdemo.dao.Order;
    
    public interface OrderMapper {
        int deleteByPrimaryKey(Integer orderId);
    
        int insert(Order record);
    
        int insertSelective(Order record);
    
        Order selectByPrimaryKey(Integer orderId);
    
        int updateByPrimaryKeySelective(Order record);
    
        int updateByPrimaryKey(Order record);
    }
    

    mapper.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.joey.springboot.shardingdemo.dao.mapper.OrderMapper">
      <resultMap id="BaseResultMap" type="com.joey.springboot.shardingdemo.dao.Order">
        <id column="order_id" jdbcType="INTEGER" property="orderId" />
        <result column="user_id" jdbcType="INTEGER" property="userId" />
      </resultMap>
      <sql id="Base_Column_List">
        order_id, user_id
      </sql>
      <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
        select 
        <include refid="Base_Column_List" />
        from t_order
        where order_id = #{orderId,jdbcType=INTEGER}
      </select>
      <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
        delete from t_order
        where order_id = #{orderId,jdbcType=INTEGER}
      </delete>
      <insert id="insert" parameterType="com.joey.springboot.shardingdemo.dao.Order">
        insert into t_order (order_id, user_id)
        values (#{orderId,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER})
      </insert>
      <insert id="insertSelective" parameterType="com.joey.springboot.shardingdemo.dao.Order">
        insert into t_order
        <trim prefix="(" suffix=")" suffixOverrides=",">
          <if test="orderId != null">
            order_id,
          </if>
          <if test="userId != null">
            user_id,
          </if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
          <if test="orderId != null">
            #{orderId,jdbcType=INTEGER},
          </if>
          <if test="userId != null">
            #{userId,jdbcType=INTEGER},
          </if>
        </trim>
      </insert>
      <update id="updateByPrimaryKeySelective" parameterType="com.joey.springboot.shardingdemo.dao.Order">
        update t_order
        <set>
          <if test="userId != null">
            user_id = #{userId,jdbcType=INTEGER},
          </if>
        </set>
        where order_id = #{orderId,jdbcType=INTEGER}
      </update>
      <update id="updateByPrimaryKey" parameterType="com.joey.springboot.shardingdemo.dao.Order">
        update t_order
        set user_id = #{userId,jdbcType=INTEGER}
        where order_id = #{orderId,jdbcType=INTEGER}
      </update>
    </mapper>
    

    这样可以方便地使用@Transactional注解实现弱XA

    • 完全支持非跨库事务,例如:仅分表,或分库但是路由的结果在单库中。
    • 完全支持因逻辑异常导致的跨库事务。例如:同一事务中,跨两个库更新。更新完毕后,抛出空指针,则两个库的内容都能回滚。
    • 不支持因网络、硬件异常导致的跨库事务。例如:同一事务中,跨两个库更新,更新完毕后、未提交之前,第一个库宕机,则只有第二个库数据提交。

    3.问题

    3.1问题描述

    版本号
    springboot 1.5.10.RELEASE
    sharding-jdbc 3.0.0.M2

    用spring自带的@Transactional 报错:

    Could not dispatch event: io.shardingsphere.core.transaction.listener.TransactionListener@6e43ddd6 to public void io.shardingsphere.core.transaction.listener.TransactionListener.listen(io.shardingsphere.core.transaction.event.TransactionEvent) throws java.sql.SQLException
    

    3.2问题原因

    触发事务的时候会调用TransactionListener.listen中的:

    TransactionManager transactionManager = TransactionContextHolder.get().getTransactionManager();
    

    从ThreadLocal中获得的TransactionContext中的transactionManager为空
    springboot启动的时候,加载了TransactionLoader,里面的doXaTransactionConfiguration是set了ThreadLocal中的transactionManager,但是set只对当前线程的ThreadLocal变量有效,所以导致在有新线程起来的时候再初始化TransactionContext时,transactionManager就为空。

    3.3 问题解决

    修改源码中的TransactionContext类

        private TransactionManager transactionManager = new WeakXaTransactionManager();
    
    

    相关文章

      网友评论

          本文标题:分库分表实践-springboot+mybatis+druid+

          本文链接:https://www.haomeiwen.com/subject/kvecrctx.html