高效开发的数据库访问利器!轻松完成多种数据库无缝对接

《开源精选》是我们分享 Github、Gitee 等开源社区中优质项目的栏目,包括技术、学习、实用与各种有趣的内容。本期推荐的是一个开发高效,维护高效,运行高效的数据库访问框架。

高效开发的数据库访问利器!轻松完成多种数据库无缝对接

BeetlSQL 的目标是提供开发高效,维护高效,运行高效的数据库访问框架,在一个系统多个库的情况下,提供一致的编写代码方式。支持如下数据平台:

  • • 传统数据库:MySQL、MariaDB、Oracle、Postgres、DB2、SQL Server、H2、SQLite、Derby、神通、达梦、华为高斯、人大金仓、PolarDB等

  • • 大数据:HBase、ClickHouse、Cassandar、Hive

  • • 物联网时序数据库:Machbase、TD-Engine、IotDB

  • • SQL查询引擎:Drill、Presto、Druid

  • • 内存数据库:ignite、CouchBase

BeetlSQL 不仅仅是简单的类似 MyBatis 或者是 Hibernate,或者是俩着的综合,BeetlSQL 远大理想是对标甚至超越 Spring Data,是实现数据访问统一的框架,无论是传统数据库,还是大数据,还是查询引擎或者时序库,内存数据库。

BeetlSQL 的架构

高效开发的数据库访问利器!轻松完成多种数据库无缝对接

除了 SQLManager 和 ClassAnnotations,任何一部分都可以扩展

BeetlSQL 依赖

<dependency>
    <groupId>com.ibeetl</groupId>
    <artifactId>beetlsql</artifactId>
    <version>3.${version}</version>
</dependency>

旧版本依赖

<dependency>
    <groupId>com.ibeetl</groupId>
    <artifactId>beetlsql</artifactId>
    <version>2.13.3.RELEASE</version>
</dependency>

适合用户

  • • 你不想把精力浪费在简单据库增删改查上?BeetlSQL  内置数据库的 CRUD 功能

  • • 你是属于以 SQL 为中心的程序员派别。BeetlSQL 提供了较好的 SQL 管理,以及内置大量 SQL

  • • 你是对代码可维护性有高要求的架构师?BeetlSQL 的设计目的就是尽可能提高数据库访问代码可维护性

  • • 平台级产品需要跨库,支持各种客户数据库的?BeetlSQL 支持各种库,程序员编写一次,能运行到各种数据库

  • • 系统需要连接多种库,比如连接商品库,连接订单库,或者设备基本信息在 MySQL,设备数据在 Clickhouse 里。BeetlSQL 很容易支持各种库,并能一统一的方式使用

  • • 系统初期单库单表,长期需要多库多表?BeetlSQL 很容易实现多库多表而不需要程序员过多关注

代码示例

例子1,内置方法,无需写 SQL 完成常用操作

UserEntity user  = sqlManager.unique(UserEntity.class,1);

user.setName("ok123");
sqlManager.updateById(user);

UserEntity newUser = new UserEntity();
newUser.setName("newUser");
newUser.setDepartmentId(1);
sqlManager.insert(newUser);

输出日志友好,可反向定位到调用的代码

┏━━━━━ Debug [user.selectUserAndDepartment] ━━━
┣ SQL:     select * from user where 1 = 1 and id=?
┣ 参数:     [1]
┣ 位置:     org.beetl.sql.test.QuickTest.main(QuickTest.java:47)
┣ 时间:     23ms
┣ 结果:     [1]
┗━━━━━ Debug [user.selectUserAndDepartment] ━━━

例子2 使用 SQL

String sql = "select * from user where id=?";
Integer id  = 1;
SQLReady sqlReady = new SQLReady(sql,new Object[id]);
List<UserEntity> userEntities = sqlManager.execute(sqlReady,UserEntity.class);
//Map 也可以作为输入输出参数
List<Map> listMap =  sqlManager.execute(sqlReady,Map.class);

例子3 使用模板 SQL

String sql = "select * from user where department_id=#{id} and name=#{name}";
UserEntity paras = new UserEntity();
paras.setDepartmentId(1);
paras.setName("lijz");
List<UserEntity> list = sqlManager.execute(sql,UserEntity.class,paras);

String sql = "select * from user where id in ( #{join(ids)} )";
List list = Arrays.asList(1,2,3,4,5); Map paras = new HashMap();
paras.put("ids", list);
List<UserEntity> users = sqlManager.execute(sql, UserEntity.class, paras);

例子4 使用 Query 类

支持重构

LambdaQuery<UserEntity> query = sqlManager.lambdaQuery(UserEntity.class);
List<UserEntity> entities = query.andEq(UserEntity::getDepartmentId,1)
                    .andIsNotNull(UserEntity::getName).select();

例子5 把数十行 SQL 放到 sql 文件里维护

//访问user.md#select
SqlId id = SqlId.of("user","select");
Map map = new HashMap();
map.put("name","n");
List<UserEntity> list = sqlManager.select(id,UserEntity.class,map);

高效开发的数据库访问利器!轻松完成多种数据库无缝对接

例子6 复杂映射支持

支持像 mybatis 那样复杂的映射

  • • 自动映射

@Data
@ResultProvider(AutoJsonMapper.class)
 public static class MyUserView {
        Integer id;
        String name;
        DepartmentEntity dept;
 }
  • • 配置映射,比 MyBatis 更容易理解,报错信息更详细

{
   "id": "id",
   "name": "name",
   "dept": {
       "id": "dept_id",
       "name": "dept_name"
   },
   "roles": {
       "id": "r_id",
       "name": "r_name"
   }
}

例子7 最好使用 mapper 来作为数据库访问类

@SqlResource("user") /*sql文件在user.md里*/
public interface UserMapper extends BaseMapper<UserEntity> {

    @Sql("select * from user where id = ?")
    UserEntity queryUserById(Integer id);

    @Sql("update user set name=? where id = ?")
    @Update
    int updateName(String name,Integer id);

    @Template("select * from user where id = #{id}")
    UserEntity getUserById(Integer id);

    @SpringData/*Spring Data风格*/
    List<UserEntity> queryByNameOrderById(String name);

    /**
     * 可以定义一个default接口
     * @return
     */
     default  List<DepartmentEntity> findAllDepartment(){
        Map paras = new HashMap();
        paras.put("exlcudeId",1);
        List<DepartmentEntity> list = getSQLManager().execute("select * from department where id != #{exlcudeId}",DepartmentEntity.class,paras);
        return list;
    }


    /**
     * 调用sql文件user.md#select,方法名即markdown片段名字
     * @param name
     * @return
     */
     List<UserEntity> select(String name);


    /**
     * 翻页查询,调用user.md#pageQuery
     * @param deptId
     * @param pageRequest
     * @return
     */
    PageResult<UserEntity>  pageQuery(Integer deptId, PageRequest pageRequest);
    
    @SqlProvider(provider= S01MapperSelectSample.SelectUserProvider.class)
    List<UserEntity> queryUserByCondition(String name);

    @SqlTemplateProvider(provider= S01MapperSelectSample.SelectUs
    List<UserEntity> queryUserByTemplateCondition(String name);

    @Matcher /*自己定义个Matcher注解也很容易*/
    List<UserEntity> query(Condition condition,String name);
}

你看到的这些用在 Mapper 上注解都是可以自定义,自己扩展的

例子8 使用 Fetch 注解

可以在查询后根据 Fetch 注解再次获取相关对象,实际上 @FetchOne 和 @FetchMany 是自定义的,用户可自行扩展

 @Data
    @Table(name="user")
    @Fetch
    public static class UserData {
        @Auto
        private Integer id;
        private String name;
        private Integer departmentId;
        @FetchOne("departmentId")
        private DepartmentData dept;
    }

    /**
     * 部门数据使用"b" sqlmanager
     */
    @Data
    @Table(name="department")
    @Fetch
    public static class DepartmentData {
        @Auto
        private Integer id;
        private String name;
        @FetchMany("departmentId")
        private List<UserData> users;
    }

例子9 不同数据库切换

可以自行扩展 ConditionalSQLManager 的 decide 方法,来决定使用哪个 SQLManager

SQLManager a = SampleHelper.init();
        SQLManager b = SampleHelper.init();
        Map<String, SQLManager> map = new HashMap<>();
        map.put("a", a);
        map.put("b", b);
        SQLManager sqlManager = new ConditionalSQLManager(a, map);

        //不同对象,用不同sqlManager操作,存入不同的数据库
        UserData user = new UserData();
        user.setName("hello");
        user.setDepartmentId(2);
        sqlManager.insert(user);

        DepartmentData dept = new DepartmentData();
        dept.setName("dept");
        sqlManager.insert(dept);

使用注解 @TargetSQLManager 来决定使用哪个 SQLManger

@Data
    @Table(name = "department")
    @TargetSQLManager("b")
    public static class DepartmentData {
        @Auto
        private Integer id;
        private String name;
    }

例子10 如果想给每个 sql 语句增加一个 sqlId 标识

这样好处是方便数据库 DBA 与程序员沟通

 public static class SqlIdAppendInterceptor implements  Interceptor{
        @Override
        public void before(InterceptorContext ctx) {
            ExecuteContext context = ctx.getExecuteContext();
            String jdbcSql = context.sqlResult.jdbcSql;
            String info  = context.sqlId.toString();
            //为发送到数据库的sql增加一个注释说明,方便数据库dba能与开发人员沟通
            jdbcSql = "/*"+info+"*/n"+jdbcSql;
            context.sqlResult.jdbcSql = jdbcSql;
        }
 }

例子11 代码生成框架

可以使用内置的代码生成框架生成代码何文档,也可以自定义的,用户可自行扩展 SourceBuilder类

 List<SourceBuilder> sourceBuilder = new ArrayList<>();
    SourceBuilder entityBuilder = new EntitySourceBuilder();
    SourceBuilder mapperBuilder = new MapperSourceBuilder();
    SourceBuilder mdBuilder = new MDSourceBuilder();
    //数据库markdown文档
    SourceBuilder docBuilder = new MDDocBuilder();

    sourceBuilder.add(entityBuilder);
    sourceBuilder.add(mapperBuilder);
    sourceBuilder.add(mdBuilder);
    sourceBuilder.add(docBuilder);
    SourceConfig config = new SourceConfig(sqlManager,sourceBuilder);
   //只输出到控制台
    ConsoleOnlyProject project = new ConsoleOnlyProject();
    String tableName = "USER";
    config.gen(tableName,project);

例子13 定义一个 Beetl 函数

GroupTemplate groupTemplate = groupTemplate();
        groupTemplate.registerFunction("nextDay",new NextDayFunction());

        Map map = new HashMap();
        map.put("date",new Date());
        String sql = "select * from user where create_time is not null and create_time<#{nextDay(date)}";
        List<UserEntity> count = sqlManager.execute(sql,UserEntity.class,map);

nextDay 函数是一个 Beetl 函数,非常容易定义,非常容易在 sql 模板语句里使用

public static class NextDayFunction implements Function {

        @Override
        public Object call(Object[] paras, Context ctx) {
            Date date = (Date) paras[0];
            Calendar c = Calendar.getInstance();
            c.setTime(date);
            c.add(Calendar.DAY_OF_YEAR, 1); // 今天+1天
            return c.getTime();
        }
    }

例子14 更多可扩展的例子

根据 ID 或者上下文自动分表,toTable 是定义的一个 Beetl 函数,

    static final String USER_TABLE="${toTable('user',id)}";
    @Data
    @Table(name = USER_TABLE)
    public static class MyUser {
        @AssignID
        private Integer id;
        private String name;
    }

定义一个 Jackson 注解,@Builder 是注解的注解,表示用 Builder 指示的类来解释执行,可以看到 BeetlSQL 的注解可扩展性就是来源于 @Build 注解

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.FIELD})
@Builder(JacksonConvert.class)
public @interface Jackson {

}

定义一个 @Tenant 放在 POJO 上,BeetlSQL 执行时候会给 SQL 添加额外参数,这里同样使用了 @Build 注解

/**
 * 组合注解,给相关操作添加额外的租户信息,从而实现根据租户分表或者分库
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Builder(TenantContext.class)
public @interface Tenant {

}

使用 XML 而不是 JSON 作为映射

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Builder(ProviderConfig.class)
public @interface XmlMapping {
    String path() default "";
}

参考源码例子 PluginAnnotationSample 了解如何定义自定的注解,实际上 BeetlSQL 有一半的注解都是通过核心注解扩展出来的

例子15 微服务事务

BeetlSQL 除了集成传统的事务管理器外,也提供 Saga 事务支持,支持多库事务和微服务事务。其原理是自动为每个操作提供反向操作,如 insert 的反向操作是 deleteById,并把这些操作作为任务交给 Saga—Server 调度。实现了通过 Kafka 作为客户端(各个 APP)与 SagaServer 交互的媒介保证任务可靠传递并最终被系统执行。

String orderAddUrl = "http://127.0.0.1:8081/order/item/{orderId}/{userId}/{fee}";
String userBalanceUpdateUrl = "http://127.0.0.1:8082/user/fee/{orderId}/{userId}/{fee}";
..........
SagaContext sagaContext = SagaContext.sagaContextFactory.current();
try {
  sagaContext.start(gid);
  //模拟调用俩个微服务,订单和用户
  rest.postForEntity(orderAddUrl, null,String.class, paras);
  rest.postForEntity(userBalanceUpdateUrl, null,String.class, paras);
  if (1 == 1) {
    throw new RuntimeException("模拟失败,查询saga-server 看效果");
  }
} catch (Exception e) {
  log.info("error " + e.getMessage());
  log.info("start rollback  " + e.getMessage());
  sagaContext.rollback();
  return e.getMessage();
}

以用户系统为例(源码是 DemoController),userBalanceUpdateUrl 对应如下扣费逻辑

@Autowired
UserMapper userMapper;
@Transactional(propagation= Propagation.NEVER)
public void update(String orderId,String userId,Integer fee){
  SagaContext sagaContext = SagaContext.sagaContextFactory.current();
  try{
    sagaContext.start(orderId);
    UserEntity  user  = userMapper.unique(userId);
    user.setBalance(user.getBalance()-fee);
    userMapper.updateById(user);
    sagaContext.commit();
  }catch (Exception e){
    sagaContext.rollback();
  }
}

这里的 UserMapper 实际上是 SagaMapper 子类(而不是 BaseMapper),会为每个操作提供反向操作

public interface SagaMapper<T> {
    /** sega 改造的接口**/
    @AutoMapper(SagaInsertAMI.class)
    void insert(T entity);

    @AutoMapper(SagaUpdateByIdAMI.class)
    int updateById(T entity);

    @AutoMapper(SagaDeleteByIdAMI.class)
    int deleteById(Object key);
}


传送门

开源协议:BSD-3-Clause

开源地址:https://gitee.com/xiandafu/beetlsql

-END-

原文始发于微信公众号(开源技术专栏):高效开发的数据库访问利器!轻松完成多种数据库无缝对接

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/71058.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!