BeetlSQL 3.3.0 發佈,新年快樂

  • BeetlSQL3 集成 ActFramework ,Act框架是一個非常強大的Web框架
  • 修復了自從3.x以來可能導致內置SQL找不到的BUG,建議升級
  • 增強了Clickhouse集成,Clickhouse 不支持“主鍵”,BeetlSQL爲CH提供自定義的MetaDataManager以支持@AssignId。
<dependency>
    <groupId>com.ibeetl</groupId>
    <artifactId>beetlsql</artifactId>
    <version>3.3.0-RELEASE</version>
</dependency>

BeetlSQL 的目標是提供開發高效,維護高效,運行高效的數據庫訪問框架,以我20年在電信,金融以及互聯網天天CRUD的經驗總結得來的框架,適用範圍廣。目前支持的數據庫如下

  • 傳統數據庫:MySQL,MariaDB,Oralce,Postgres,DB2,SQL Server,H2,SQLite,Derby,神通,達夢,華爲高斯,人大金倉,PolarDB 等
  • 大數據:HBase,ClickHouse,Cassandar,Hive
  • 物聯網時序數據庫:Machbase,TD-Engine,IotDB
  • SQL查詢引擎:Drill,Presto,Druid
  • 內存數據庫:ignite,CouchBase

編譯源碼

git clone https://gitee.com/xiandafu/beetlsql
mvn clean package
mvn clean install #如果想修改源碼

注意:BeetlSQL3 集成了Spring,以及支持大數據等,就算配置了國內鏡像,也可能需要很長時間下載大數據依賴包,爲了讓編譯快速通過,你需要進入pom.xml ,屏蔽sql-integration,sql-db-support,sql-jmh三個模塊

<modules>
<!--核心功能 -->
<module>sql-core</module>
<module>sql-mapper</module>
<module>sql-util</module>
<module>sql-fetech</module>
<!-- 打包到一起 -->
<module>beetlsql</module>
<module>sql-gen</module>
<module>sql-test</module>
<module>sql-samples</module>
<!-- 集成和擴展太多的數據庫,可以被屏蔽,以加速項目下載jar -->
<!--		<module>sql-integration</module>-->
<!--    <module>sql-jmh</module>-->
<!--		<module>sql-db-support</module>-->
</modules>

閱讀源碼例子

可以從模塊sql-samples中找得到所有例子,或者從sql-test 中運行單元測試例子,或者在sql-integration 中的各個框架單元測試中找到相關例子。所有例子都是基於H2內存數據庫,可以反覆運行

sql-samples爲例子

sql-samples 又包含了三個模塊大約100個例子

  • quickstart: BeetlSQL基礎使用例子,可以快速瞭解BeetlSQL3
  • usuage: BeetlSQL所有API和功能
  • plugin:BeetlSQL高級擴展實例

以usuage模塊爲例子,包含如下代碼

  • S01MapperSelectSample 15個例子, mapper中的查詢演示
  • S02MapperUpdateSample 11個例子, mapper中更新操作
  • S03MapperPageSample 3個例子,mapper中的翻頁查詢
  • S04QuerySample 9個例子,Query查詢
  • S05QueryUpdateSample 3個例子,Query完成update操作
  • S06SelectSample 14個例子,SQLManager 查詢API
  • S07InsertSample 8個例子,SQLManager 插入新數據API,主鍵生成
  • S08UpdateSample 6個例子,更新數據
  • S09JsonMappingSample 5個例子, json配置映射
  • S10FetchSample 2個例子,關係映射
  • S11BeetlFunctionSample 2個例子,自定義sql腳本的方法

代碼示例

例子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.RUNTIM@
@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);
}

BeetlSQL的架構

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章