分庫分表實(shí)踐-springboot+mybatis+druid+shardingsphere

0 前言

0.1 Sharding-Sphere

Sharding-Sphere是一套開源的分布式數(shù)據(jù)庫中間件解決方案組成的生態(tài)圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar這3款相互獨(dú)立的產(chǎn)品組成。他們均提供標(biāo)準(zhǔn)化的數(shù)據(jù)分片、讀寫分離、柔性事務(wù)和數(shù)據(jù)治理功能,可適用于如Java同構(gòu)、異構(gòu)語言、容器、云原生等各種多樣化的應(yīng)用場(chǎng)景。
Sharding-Sphere定位為關(guān)系型數(shù)據(jù)庫中間件,旨在充分合理地在分布式的場(chǎng)景下利用關(guān)系型數(shù)據(jù)庫的計(jì)算和存儲(chǔ)能力,而并非實(shí)現(xiàn)一個(gè)全新的關(guān)系型數(shù)據(jù)庫。

0.2 Sharding-JDBC

我們這邊主要用的是里面的Sharding-JDBC:

定位為輕量級(jí)Java框架,在Java的JDBC層提供的額外服務(wù)。 它使用客戶端直連數(shù)據(jù)庫,以jar包形式提供服務(wù),無需額外部署和依賴,可理解為增強(qiáng)版的JDBC驅(qū)動(dòng),完全兼容JDBC和各種ORM框架。

  • 適用于任何基于Java的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
  • 基于任何第三方的數(shù)據(jù)庫連接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
  • 支持任意實(shí)現(xiàn)JDBC規(guī)范的數(shù)據(jù)庫。目前支持MySQL,Oracle,SQLServer和PostgreSQL。
系統(tǒng)架構(gòu)圖
  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 分片設(shè)計(jì)

邏輯表結(jié)構(gòu):

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

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

數(shù)據(jù)庫結(jié)構(gòu)如下圖:

數(shù)據(jù)庫結(jié)構(gòu)

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

具體的配置參數(shù)含義見官方文檔
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. 測(cè)試代碼

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注解實(shí)現(xiàn)弱XA

  • 完全支持非跨庫事務(wù),例如:僅分表,或分庫但是路由的結(jié)果在單庫中。
  • 完全支持因邏輯異常導(dǎo)致的跨庫事務(wù)。例如:同一事務(wù)中,跨兩個(gè)庫更新。更新完畢后,拋出空指針,則兩個(gè)庫的內(nèi)容都能回滾。
  • 不支持因網(wǎng)絡(luò)、硬件異常導(dǎo)致的跨庫事務(wù)。例如:同一事務(wù)中,跨兩個(gè)庫更新,更新完畢后、未提交之前,第一個(gè)庫宕機(jī),則只有第二個(gè)庫數(shù)據(jù)提交。

3.問題

3.1問題描述

版本號(hào)
springboot 1.5.10.RELEASE
sharding-jdbc 3.0.0.M2

用spring自帶的@Transactional 報(bào)錯(cuò):

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問題原因

觸發(fā)事務(wù)的時(shí)候會(huì)調(diào)用TransactionListener.listen中的:

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

從ThreadLocal中獲得的TransactionContext中的transactionManager為空
springboot啟動(dòng)的時(shí)候,加載了TransactionLoader,里面的doXaTransactionConfiguration是set了ThreadLocal中的transactionManager,但是set只對(duì)當(dāng)前線程的ThreadLocal變量有效,所以導(dǎo)致在有新線程起來的時(shí)候再初始化TransactionContext時(shí),transactionManager就為空。

3.3 問題解決

修改源碼中的TransactionContext類

    private TransactionManager transactionManager = new WeakXaTransactionManager();

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容