- 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,任何一部分都可以擴展