为了账号安全,请及时绑定邮箱和手机立即绑定

进阶—极速分布式ORM框架---Mango(二)

一、Mango实现数据表分片

表分片通常也被称为分表,散表。

当某张表的数据量很大时,sql执行效率都会变低,这时通常会把大表拆分成多个小表,以提高sql执行效率。 我们将这种大表拆分成多个小表的策略称之为表分片。

要实现表分片,需要定义一个表分片类,这个类要实现 TableShardingStrategy 接口中的 getTargetTable 方法。

getTargetTable方法是表分片策略的核心,共两个输入参数,输出则为最终需要访问的表名字,所以我们通过实现getTargetTable方法计算最终需要访问的表名字。

第1个参数table为@DB注解中table参数所定义的全局表名。

第2个参数是自定义传入的参数,这里我们使用uid计算如何分表,所以第2个参数是uid。

需要注意的是,第2个参数是一个泛型参数,这里由于uid是整形数字,所以类型定义为Integer。

实例代码

(1)OrderTableShardingStrategy.java

package com.lhf.mango.tableShard;

import org.jfaster.mango.sharding.TableShardingStrategy;

/**
 * @ClassName: OrderTableShardingStrategy
 * @Desc: 实现表分片,需要实现TableShardingStrategy接口
 * getTargetTable方法是表分片策略的核心,共两个输入参数,输出则为最终需要访问的表名字,所以我们通过实现getTargetTable方法计算最终需要访问的表名字。
 *
 * 第1个参数table为@DB注解中table参数所定义的全局表名,这里是t_order。
 *
 * 第2个参数是自定义传入的参数,这里由于我们要使用uid计算如何分表,所以第2个参数是uid
 * @Author: liuhefei
 * @Date: 2018/12/21 11:29
 */
public class OrderTableShardingStrategy implements TableShardingStrategy<Integer> {

    /**
     * uid小于等于1000时,使用t_order_0表
     * uid大于1000时,时使用t_order_1表
     * @param table
     * @param uid
     * @return
     */
    public String getTargetTable(String table, Integer uid) {
        int num = uid <= 100 ? 0 : 1;
        return table + "_" + num;
    }
}

(2)OrderTableShardingMain.java

package com.lhf.mango.tableShard;

import com.google.common.collect.Lists;
import com.lhf.mango.dao.TableShardingOrderDao;
import com.lhf.mango.entity.Order;
import com.lhf.mango.util.RandomUtils;
import org.jfaster.mango.datasource.DriverManagerDataSource;
import org.jfaster.mango.operator.Mango;

import javax.sql.DataSource;
import java.util.List;

/**
 * @ClassName: OrderTableShardingMain
 * @Desc: 实现表分片
 * @Author: liuhefei
 * @Date: 2018/12/21 11:36
 */
public class OrderTableShardingMain {

    public static void main(String[] args){

        //定义数据源
        String driverClassName = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/mango_example?useSSL=false";
        String username = "root";
        String passowrd = "root";
        DataSource ds = new DriverManagerDataSource(driverClassName,url, username, passowrd);
        Mango mango = Mango.newInstance(ds);  //使用数据源初始化mango
        TableShardingOrderDao tableShardingOrderDao = mango.create(TableShardingOrderDao.class);

        Order order = new Order();

        List<Integer> uids = Lists.newArrayList(1, 2, 10020, 10086);
        for (Integer uid : uids) {
            String id = RandomUtils.randomStringId(10); // 随机生成10位字符串ID
            order.setId(id);
            order.setUid(uid);
            order.setPrice(100);
            order.setStatus(1);
            tableShardingOrderDao.addOrder(order);
            System.out.println(tableShardingOrderDao.getOrdersByUid(uid));
        }
    }


}

二、Mango实现数据库分片


数据库分片通常也被称为分库,散库等。

当我们在某个库中,把某张大表拆分成多个小表后还不能满足性能要求,这时我们需要把一部分拆分的表挪到另外一个库中,以提高sql执行效率。

要实现数据库分片,需要定义一个数据库分片类,这个类要实现DatabaseShardingStrategy 接口中的 getDataSourceFactoryName 方法。

getDataSourceFactoryName方法是数据库分片策略的核心,返回最终请求的数据源工厂名称。

而getDataSourceFactoryName方法的输入参数是一个自定义传入的参数,这里由于我们要使用uid计算如何分库,所以参数为uid。

需要注意的是,输入参数是一个泛型参数,这里由于uid是整形数字,所以类型定义为Integer。

我们使用 @Sharding 注解中的databaseShardingStrategy参数,将数据库分片策略与DAO接口进行绑定。

引入了 @ShardingStrategy 接口,实现@ShardingStrategy接口等与同时实现@DatabaseShardingStrategy接口与@TableShardingStrategy接口。。

 

一维度分片策略:使用一个字段属性作为分片策略的计算参数;

多维度分片策略:同时使用多个分片策略计算参数,不同的方法指定不同的分片策略;

https://img1.sycdn.imooc.com//5c51467e000196b604230227.jpg

实例代码

(1)OrderDatabaseShardingStrategy.java

package com.lhf.mango.databaseShard;

import org.jfaster.mango.sharding.DatabaseShardingStrategy;

/**
 * @ClassName: OrderDatabaseShardingStrategy
 * @Desc:  数据库分片策略
 * 数据库要要实现分片,需要实现DatabaseShardingStrategy 接口中的 getDataSourceFactoryName 方法。
 * getDataSourceFactoryName方法是数据库分片策略的核心,返回最终请求的数据源工厂名称。
 * 而getDataSourceFactoryName方法的输入参数是一个自定义传入的参数,这里由于我们要使用uid计算如何分库,所以参数为uid
 *
 * 在 初始化数据库源工厂 中我们定义了dsf0,dsf1,dsf2共3个数据源工厂,分别对应db0,db1,db2这3个数据库。
 * 所以当我们想访问db0中的t_order表时,我们只需要让getDataSourceFactoryName方法返回dsf0;
 * 当我们想访问db1中的t_order表时,我们只需要让getDataSourceFactoryName方法返回dsf1,并以此类推。
 * @Author: liuhefei
 * @Date: 2018/12/21 14:14
 */
public class OrderDatabaseShardingStrategy implements DatabaseShardingStrategy<Integer> {

    public String getDataSourceFactoryName(Integer uid) {

        return "dsf" + uid % 3;
    }
}

(2)OrderDatabaseShardingMain.java

package com.lhf.mango.databaseShard;

import com.google.common.collect.Lists;
import com.lhf.mango.dao.DatabaseShardingOrderDao;
import com.lhf.mango.entity.Order;
import com.lhf.mango.util.RandomUtils;
import org.jfaster.mango.datasource.DataSourceFactory;
import org.jfaster.mango.datasource.DriverManagerDataSource;
import org.jfaster.mango.datasource.SimpleDataSourceFactory;
import org.jfaster.mango.operator.Mango;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName: OrderDatabaseShardingMain
 * @Desc: 数据库分片通常也被称为分库,散库等。
 * 当我们在某个库中,把某张大表拆分成多个小表后还不能满足性能要求,这时我们需要把一部分拆分的表挪到另外一个库中,以提高sql执行效率。
 * 3个独立数据库db0,db1,db2中,各有1张t_order表,在读写t_order表时,我们按照用户ID(后续简称uid)纬度进行数据库分片:
 *
 * uid模3为0的请求落在数据库db0
 * uid模3为1的请求落在数据库db1
 * uid模3为2的请求落在数据库db2
 *
 * @Author: liuhefei
 * @Date: 2018/12/21 14:14
 */
public class OrderDatabaseShardingMain {
    public static void main(String[] args){
        String driverClassName = "com.mysql.jdbc.Driver";
        String username = "root";
        String password = "root";

        int dbCount = 3;
        //初始化数据源工厂
        /**
         * 名字为dsf0的数据源工厂连接数据库mongo_example0
         * 名字为dsf1的数据源工厂连接数据库mongo_example1
         * 名字为dsf2的数据源工厂连接数据库mongo_example2
         */
        List<DataSourceFactory> dsfs = new ArrayList<DataSourceFactory>();
        for(int i=0;i<dbCount;i++){
            String name = "dsf" + i;
            String url = "jdbc:mysql://localhost:3306/mango_example" + i + "?useSSL=false";
            System.out.println("name = " + name + "  url = " + url);
            DataSource ds = new DriverManagerDataSource(driverClassName, url, username, password);
            DataSourceFactory dsf = new SimpleDataSourceFactory(name, ds);
            dsfs.add(dsf);
        }
        Mango mango = Mango.newInstance(dsfs);

        DatabaseShardingOrderDao orderDao = mango.create(DatabaseShardingOrderDao.class);
        List<Integer> uids = Lists.newArrayList(1,2,3,4,5,6,7,8,9);
        for(Integer uid : uids){
            String id = RandomUtils.randomStringId(10);   //随机生成10位字符串ID
            Order order = new Order();
            order.setId(id);
            order.setUid(uid);
            order.setPrice(1000);
            order.setStatus(1);

            orderDao.addOrder(order); //插入订单
            System.out.println(orderDao.getOrdersByUid(uid));   //查看订单
        }
    }

}

如果想要同时实现分表和分库功能,需要实现ShardingStrategy接口。

ShardingStrategy接口同时继承DatabaseShardingStrategy, TableShardingStrategy接口,然后分别实现getDataSourceFactoryName方法和getTargetTable方法即可。

关于更多分表分库的实例,在这里不再展示,后面会放到github上。

三、Mango函数式声明

我们需要将java中的列表,集合,自定义类映射到关系型数据库的某个字段中,直接插入取出显然是不行的,这时我们就需要使用函数式调用功能,将这些数据库不能识别的类型转化为数据库能够识别的类型插入,后续取出时再通过函数式调用转化为java中的复杂类型。

1)列表与字符串互转

@Getter(IntegerListToStringFunction.class)  将整型列表转化为字符串

@Setter(StringToIntegerListFunction.class)   将字符串转化为整型列表

 

2)枚举与数字互转

@Getter(EnumToIntegerFunction.class)   将枚举对象转化为数字

@Setter(IntegerToEnumFunction.class)    将数字转化为枚举对象

 

3)复杂类与字符串互转

@Getter(ObjectToGsonFunction.class)  将任意对象转化为json字符串

@Setter(GsonToObjectFunction.class)   json字符串转化为任意对象

实例代码:


package com.lhf.mango.entity;

import org.jfaster.mango.annotation.Getter;
import org.jfaster.mango.annotation.Setter;
import org.jfaster.mango.invoker.function.IntegerListToStringFunction;
import org.jfaster.mango.invoker.function.StringToIntegerListFunction;

import java.util.List;

/**
 * @ClassName: Teacher
 * @Desc:  老师表,老师与学生的关系是一对多
 * @Author: liuhefei
 * @Date: 2019/1/30 14:50
 */
public class Teacher {
    private Integer id;

    private String name;

    private List<Integer> studentIds;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Getter(IntegerListToStringFunction.class)   //将整型列表转化为字符串
    public List<Integer> getStudentIds() {
        return studentIds;
    }

    @Setter(StringToIntegerListFunction.class)  //将字符串转化整型列表
    public void setStudentIds(List<Integer> studentIds) {
        this.studentIds = studentIds;
    }
}

四、Mango拦截器

拦截器是mango框架中一个非常重要的功能,它为mango框架提供了扩展插件的可能。

 

当我们为mango框架添加拦截器后,最终执行的SQL会依次通过这些拦截器后再被执行。 所以我们可以通过拦截器对最终执行的SQL进行各种操作,如修改SQL,记录SQL等。

 

通过自定义拦截器,我们可以为mango框架扩展出许许多多的插件,最为常用的 分页查询 插件就可以通过拦截器来实现。

 

mango框架将拦截器分为了两类:查询拦截器与更新拦截器。

查询拦截器:所有查询请求最终需要执行的SQL都需要依次通过查询拦截器处理。

要实现一个自己的查询拦截器,需要定义一个类(MyQueryInterceptor),并继承 QueryInterceptor 类;

在初始化mango对象时,需要调用addInterceptor方法将我们的拦截器MyQueryInterceptor添加到了mango对象中。

public class MyQueryInterceptor extends QueryInterceptor {

       public void interceptQuery(

                     BoundSql boundSql,

                     List<Parameter> parameters,

                     DataSource dataSource) {

 

              System.out.println("sql: " + boundSql.getSql());

              System.out.println("args: " + boundSql.getArgs());

       }

}

QueryInterceptor中的interceptQuery方法有3个输入参数:

1、BoundSql封装了最终将要被执行的SQL

2、List<Parameter>封装了被执行方法的参数列表信息

3、DataSource则是SQL执行时所使用的数据源

 

更新拦截器:所有更新请求最终需要执行的SQL都需要依次通过更新拦截器处理。

要实现一个自己的更新拦截器,需要定义一个类(MyUpdateInterceptor),并继承继承 UpdateInterceptor 类。

在初始化mango对象时,我们调用addInterceptor方法将我们的拦截器(MyUpdateInterceptor)添加到了mango对象中。

public class MyUpdateInterceptor extends UpdateInterceptor {

 

       public void interceptUpdate(

                     BoundSql boundSql,

                     List<Parameter> parameters,

                     SQLType sqlType,

                     DataSource dataSource) {

 

              System.out.println("sql: " + boundSql.getSql());

              System.out.println("args: " + boundSql.getArgs());

       }

}

UpdateInterceptor中的interceptUpdate方法有4个输入参数:

1、BoundSql封装了最终将要被执行的SQL

2、List<Parameter>封装了被执行方法的参数列表信息

3、SQLType封装了被执行SQL的类型

4、DataSource则是SQL执行时所使用的数据源

五、Mango集成cache

mango自身不依赖任何缓存工具,mango对外提供SimpleCacheHandler抽象类,你只需继承SimpleCacheHandler抽象类,并实现其中的缓存操作代码(memcached,redis,直接内存等均可),就能享受mango带来的缓存操作便利。

CacheHandler抽象类一共有4个需要实现的抽象方法,它们分别对应着封装缓存的操作:

(1)Object get(String key, Type type) ,根据单个key值从缓存中查找数据,type为返回对象的类型

(2)Map<String, Object> getBulk(Set<String> keys, Type type) ,根据多个key值从缓存中查找数据,返回key-value对应的map,type为map中value对象的类型

(3)void set(String key, Object value, int exptimeSeconds),向缓存中设置数据,其中exptimeSeconds为缓存失效时间,单位为秒。

(4)void delete(String key) ,根据单个key值从缓存中删除数据。

 

初始化mango对象

DataSource ds = new DriverManagerDataSource(driverClassName, url, username, password);

Mango mango = Mango.newInstance(ds);

mango.setCacheHandler(new MockRedisHandler());

正常初始化mango对象后,只需要通过setCacheHandler方法传入一个实现了CacheHandler接口的对象即可,这里我们使用的是自定义的Redis实现的MockRedisHandler。

实例代码:

package com.lhf.mango.dao;

import com.lhf.mango.entity.User;
import org.jfaster.mango.annotation.*;
import org.jfaster.mango.operator.cache.Hour;

import java.util.List;

/**
 * @ClassName: UserCacheDao
 * @Desc:  Mango缓存使用
 * @Cache表示需要使用缓存,参数prefix表示key前缀,比如说传入uid=1,那么缓存中的key就等于user_1,参数expire表示缓存过期时间,Hour.class表示小时,配合后面的参数num=2表示缓存过期的时间为2小时。
 * @CacheBy用于修饰key后缀参数,在delete,update,getUser方法中@CacheBy都是修饰的uid,所以当传入uid=1时,缓存中的key就等于user_1。
 * @CacheIgnored表示该方法不操作缓存。需要注意的是,如果使用了@Cache注解,@CacheBy和@CacheIgnored二者必须有一个存在。
 *
 * @Author: liuhefei
 * @Date: 2018/12/21 18:15
 */
@DB
@Cache(prefix = "user", expire = Hour.class, num = 2)
public interface UserCacheDao {

    @CacheIgnored   //不使用缓存
    @SQL("insert into user(name, age, gender, money, update_time) values(:name, :age, :gender, :money, :updateTime)")
    public int insert(User user);

    @SQL("insert into user(name, age, gender, money, update_time) values(:1.name, :1.age, :1.gender, :1.money, :1.updateTime)")
    public int insertUser(@CacheBy("name") User user);  //使用User对象的name属性作为key后缀

    @SQL("delete from user where id =:1")
    public int delete(@CacheBy int id );

    @SQL("update user set name = :2 where id =:1")
    public int update(@CacheBy int id, String name);

    @SQL("select * from user where id=:1")
    public User getUser(@CacheBy int id);

    @SQL("select * from user where id in (:1)")
    public List<User> getUserList(@CacheBy List<Integer> ids);

    @SQL("select id, name from user where id=:1 and name=:2")
    public User getByUidAndName(@CacheBy int id, @CacheBy String name); //同时使用id和name两个字段来做缓存

}

今天分享就到这里,2019年1月30日于深圳。后续还会继续更新关于Mango的相关知识,相关代码后面会放到github上,地址:https://github.com/JavaCodeMood

准备回家过年,提前预祝各位走过路过的大佬春节快乐,阖家欢乐!

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消