我写了一个数据库迁移工具,欢迎围观

206 阅读6分钟

介绍一下

前段时间因为工作需求,写了一个数据库迁移工具。现在开源出来,希望能帮助到需要的人。

我取名字为dbx,没错,看到这里您是不是想到了大名鼎鼎的datax?是的,您想的没错,我借鉴了他的名字(ps:毕竟谁都想出名嘛)

说了那么多,你可能要问:dbx能干什么?

emm... , dbx主要是完成关系型数据库之间各种异构数据源之间稳定高效的数据迁移功能,在满足基本的迁移功能下,还提供了其他高级特性。当然我使用了java注解的方式来描述他们之间的关系。

dbx有以下几大特性:

  • 使用java注解描述新旧数据库表之间的映射关系,简单明了。

  • 不仅仅支持数据迁移,还支持数据库表生成。

  • 抽象了一组数据库DDL公用描述,简化用户使用。

  • 支持生成sql(DDL脚本和数据脚本)语句脚本,无需目标库连接也可执行。

  • 通过事件方式提供了多个扩展节点,满足用户不同的需求。

既然是使用注解来构建映射关系的,那我们介绍一下要用到的的几个注解和相关接口。

  • MapperTable
    • String target 目标数据库表名称
    • String source 原始数据库表名称
    • String[] excludeFields 删减的字段。注意: 当字段名称发生了变化之后,需要用 MapperField 定义时,同时需要用excludeFields排除原始字段,否者将生成两个字段。
    • String[] includeFields 需要拷贝的字段。注意: 在默认拷贝数据库表的情况下,可无需设置值。
    • Class<?>[] children 指定关联的子类(关联表)的描述类。可以关联多个子类,dbx将在处理父表数据时同时处理子表数据,以父表单条数据为一个处理周期。
    • Class<? extends ValueFormat> customFormatValue() 用户自定义数据格式化借口类。
    • content 表备注信息
  • MapperField
    • String target 目标库表字段名称
    • String source 原始库表字段名称
    • String superSource 引用原始库父表的字段的 值和DDL定义
    • String superTarget 引用目标库父表的字段的 值和DDL定义。 但是当值设置时,需要保证父类的值已经被设置,慎用。
    • String defaultValue 默认生成值
    • String defaultFormatValue 平台提供的默认格式化数据的方法
      • ValueDefaultType.NONE 默认返回null
      • ValueDefaultType.UUID uuid
      • ValueDefaultType.UUID_NO_LINE uuid无连接线。
    • String ddl 字段DDL的描述
    • String id 指定field是否是id 类配置只能有一个配置该值为true 。其值代表意义等同于:ddl = @DdlConfig(dataType = "varchar(64)", content = "主键id" , primary=true , nullable = false)
  • DdlConfig 用于描述字段的DDL语句,比较好理解,这里不在表述。
  • UseDdl 在在多个类描述了同一个目标表映射关系时,需要指定一个类做表DDL生成类。
  • ValueFormat 数据格式化处理方式。

好戏开始了

说了那么多,感觉干干的,那么我们接下来看几个列子吧:

基本操作

首先我们支持最基本的数据迁移模型:表拷贝,表名称变更,表字段名称变更,增加删减字段等基本操作。

表拷贝:user_man(id,name,sex_m) => user_man(id,name,sex_m)

@MapperTable(target = "user_man", source = "user_man")
public static class UserMan {
}

表名称变更:user_man(id,name,sex_m) => user(id,name,sex_m)

@MapperTable(target = "user_man", source = "user_man")
public static class UserMan {
}

表字段删减:user_man(id,name,sex_m) => user_man(id,name)

@MapperTable(target = "user_man", source = "user_man", excludeFields = {"sex_m"})
public static class UserMan {
}
@MapperTable(target = "user_man", source = "user_man", includeFields = {"id","name"})
public static class UserMan {
}

增加字段:ser_man(id,name,sex_m) => user_man(id,name,sex_m,age)

@MapperTable(target = "user_man", source = "user_man")
// 增加字段,必须指定 ddl的值,不然不知道如何创建该字段,引擎会根据DdlConfig描述和目标表的数据库类型创建合适的字段。 defaultValue指定了程序给字段生成默认值20。
@MapperField(target = "age", defaultValue = "20", ddl = @DdlConfig(type = FieldJavaType.Integer, length = 2, content = "年龄"))
public static class UserMan {

}

修改字段:user_man(id,name,sex_m) => user_man(id,name_,sex_m)

@MapperTable(target = "user_man", source = "user_man")
// 将字段 name 修改为 name_ ,  你已可以加ddl属性来修改列属性,但是要符合数据库规范。
@MapperField(target = "name_", source = "name")
public static class UserMan {

}

what???你可能会说:

接下来我们来点更高级的,坐稳了。

高级特性

表合并:将多个表的数据合并到一个表中 。user_man(id,name,sex_m)user_woman(id,name,sex_w) =>
user(id,name,sex) 。将表user_man和user_woman的数据合并到user表,而且还需要变更字段名字 sex_m和sex_w的数据需要合并到sex中。

@MapperTable(target = "user", source = "user_man", excludeFields = {"sex_m"})
@MapperField(target = "sex", source = "sex_m")
@UseDdl // 1.使用UserMan的配置生成目标表 
public static class UserMan {
}

@MapperTable(target = "user", source = "user_woman", excludeFields = {"sex_w"})
@MapperField(target = "sex", source = "sex_w")
public static class UserWoman {
}

表拆分:将一个表的数据数据拆分到多个表中。比如你以前有个表多选字段是按照字符串拼接存储的,但是现在想拆分成主表和关联表就可以用该模型。 我们还是以user表举例。将表 user(id,name,sex,age)=> user(id,name,sex) 和 age(id, user_id , age)。

  • 以user为主表,(当然你也可以以age为主表生成)。 导入Age表的定义,主表排出 age 字段的生成
  • 创建一个User主类,用于描述User表之间的映射关系,并使用children讲需要生成的关联表定义与该表的定义关联起来
  • 子表取父表的ddl定义和数据
@MapperTable(target = "user", source = "user", children = {Age.class}, excludeFields = {"age"})
public static class User {

}

@MapperTable(target = "age")

@MapperField(target = "id", superSource = "id")
@MapperField(target = "user_id", superSource = "id")
@MapperField(target = "age", superSource = "age")
public static class Age {

}

那你说,我还想怎点更骚的怎么办?比如我想讲user表拆分成user表。然后年龄按性别分成age_man , age_woman 。三张表怎么办?

真有你的?这也能想出来,不过还难不倒我!

  • 使用UserFormat 来处理数据分流,根据TableRowValueMapperDefinition的id和数据上下文中的父源表的sex的属性进行判断数据处理。ValueExecState.NONE 表示不生成数据。

@MapperTable(target = "user", source = "user", children = {AgeMan.class,AgeWoman.class}, excludeFields = {"age"})
public static class User {
}

@MapperTable(target = "age_man", customFormatValue = UserFormat.class)
public static class AgeMan {
}

@MapperTable(target = "age_woman", customFormatValue = UserFormat.class)
public static class AgeWoman {
}

public static class UserFormat implements ValueFormat {
    String id = valueContext.getTableRowValueMapperDefinition().getId();
    String sexValue = valueContext.getParent().getSourceValue().get("sex");
    @Override
    public void prepare(ValueContext valueContext) {
        if (id.equals(AgeNan.class.getName()) && !sexValue.equals("男")) {
            valueContext.getValueState().setValueExecState(ValueExecState.NONE);
        }
        if (id.equals(AgeNv.class.getName()) && !sexValue.equals("女")) {
            valueContext.getValueState().setValueExecState(ValueExecState.NONE);
        }
    }
}

上面的只是对dbx的简单介绍。更详细的介绍和用法可以去git上看,传送门:gitee.com/small_broke…

希望能对您有一点点帮助。

一起进步

你好,我是啊Q,是一个爱技术爱生活的的程序员。

写程序几年了,现在想记录一下自己的程序员生活和故事,来掘金和大家交朋友。写的不好请多多包涵,支持一下。

我是啊Q,可掘金关注或私信我,我们一起进步。

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天,[点击查看活动详情](juejin.cn/post/719472…