一、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接口。。
一维度分片策略:使用一个字段属性作为分片策略的计算参数;
多维度分片策略:同时使用多个分片策略计算参数,不同的方法指定不同的分片策略;
实例代码:
(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
准备回家过年,提前预祝各位走过路过的大佬春节快乐,阖家欢乐!
共同學習,寫下你的評論
評論加載中...
作者其他優質文章
