BeetlSQL 3 目前正在研发过程,预计9月能发版。相比于BeetlSQL2,有非常多的改进,本博客会用一部分介绍BeetlSQL3的功能,另外一部分介绍如何定制Beetl3。
BeetSql是一个全功能DAO工具, 同时具有Hibernate 优点 & Mybatis优点功能,适用于承认以SQL为中心,同时又需求工具能自动能生成大量常用的SQL的应用
文档较长,可以点击右侧的目录导航到你想关注的内容
目前BeetlSQL3 还有大量工作需要完善,欢迎留言给我,可以加入开发或者测试
BeetlSQL3 特点
- 派别:SQL为中心
- 内置常见增删改查功能,节省项目50%工作量
- 强化SQL管理,通过md文件管理sql,使用Beetl模板编写复杂sql
- 简单SQL可以通过Query类链式API完成
- 全面支持跨数据库平台
- 支持NOSQL,如ClickhHouse,Elastic,Hive等
- 支持SQL查询引擎,如Apache Drill,Presto等
- 支持一对一,一对多等常见的映射。
- 可以使用约定习俗映射,复杂查询结果支持通过json配置映射到POJO
- 提供idea插件
- 其他
- 具备代码生成功能,提供代码生成框架
- 最大程度减少数据库重构对项目造成的影响
- 最大程度减少数据库切换对项目造成的影响
- 支持多数据源,数据源包含传统数据库,NOSQL,SQL查询引擎,且可以根据规则使用数据源
- 内置主从支持
- 提供丰富的扩展功能,80%的功能都可以自行扩展,打造自己个性化的数据库发访问框架,扩展适应新的数据库&NOSQL&查询引擎
数据库工具的痛点
- 开发效率低,如mybatis,还需要搭配plus工具才能提高开发效率,而JOOQ这样的又不适合复杂访问
- 无SQL管理,遇到复杂的sql特别难维护,比如在Java里拼写sql,遇到调整就麻烦
- 跨数据库平台,即使Hibenerate,也完全做不到跨数据库
- 缺少数据库和NOSQL无缝切换很难,比如一部分业务要无缝切换到NOSQL上
- 数据库重构对代码影响非常大,数据库列修改,增加要改很多代码
- 难以调试数据库访问代码
BeetlSQL不仅仅知道所有这些痛点,而且能很好的解决这些痛点
BeetlSQL3 例子
所有例子都可以从 https://gitee.com/xiandafu/beetlsql/tree/3.0/sql-samples/sql-sample-quickstart 看到和运行
- S1QuickStart.SQLMananger API,Query类,Mapper使用,基本的CRUD映射
- S2MappingSample: 如何把结果集映射到Java对象,通过注解,通过json配置,或者约定习俗进行复杂映射,通过自定义注解来扩展映射方式
- S3PageSample: 翻页和范围查询
- S4Other: 其他常用操作示例,一些常见的like,in,batch操作
- S5Fetch:自动fetch功能 ,在查询对象后,还可以自动fetch其他对象,类似JPA的ORM,但ORM对CRUD影响过大,fetch功能则简单很多
- S6MoreSource: 非常方便的实现多数据源,每个实体可以标记自己的数据源;或者简单一个主从数据库的例子;或者分表例子;或者分库+分表。
- S7CodeGen: 使用BeetlSQL生成代码,SQL语句和数据库文档
用户能在2小时内浏览完所有例子并基本掌握BeetlSQL的用法
基础例子
1/** 2 * 入门 演示内置SQLManager用法和BaseMapper用法,项目中更推荐使用BaseMapper,而不是较为底层的SQLManager 3 * @author xiandafu 4 * 5 */ 6 7public class S1QuickStart { 8 9 SQLManager sqlManager; 10 UserMapper mapper = null; 11 12 public S1QuickStart(SQLManager sqlManager) { 13 this.sqlManager = sqlManager; 14 mapper = sqlManager.getMapper(UserMapper.class); 15 } 16 17 public static void main(String[] args) throws Exception { 18 SQLManager sqlManager = SampleHelper.getSqlManager(); 19 S1QuickStart quickStart = new S1QuickStart(sqlManager); 20 quickStart.baseSqlManager(); 21 quickStart.executeSql(); 22 quickStart.executeTemplate(); 23 quickStart.query(); 24 quickStart.mapper(); 25 quickStart.sqlResource(); 26 } 27 28 /** 29 * 使用内置sqlManager方法 30 */ 31 32 public void baseSqlManager(){ 33 UserEntity user = sqlManager.unique(UserEntity.class,1); 34 35 user.setName("ok123"); 36 sqlManager.updateById(user); 37 38 UserEntity newUser = new UserEntity(); 39 newUser.setName("newUser"); 40 newUser.setDepartmentId(1); 41 sqlManager.insert(newUser); 42 43 44 UserEntity template = new UserEntity(); 45 template.setDepartmentId(1); 46 List<UserEntity> list = sqlManager.template(template); 47 48 49 50 } 51 52 53 //执行sql语句方法 54 public void executeSql(){ 55 String sql = "select * from user where id=?"; 56 Integer id = 1; 57 SQLReady sqlReady = new SQLReady(sql,new Object[id]); 58 List<UserEntity> userEntities = sqlManager.execute(sqlReady,UserEntity.class); 59 60 String updateSql = "update department set name=? where id =?"; 61 String name="lijz"; 62 SQLReady updateSqlReady = new SQLReady(updateSql,new Object[]{name,id}); 63 sqlManager.executeUpdate(updateSqlReady); 64 65 66 } 67 68 //执行sql模板语句 69 public void executeTemplate(){ 70 71 { 72 String sql = "select * from user where department_id=#{id} and name=#{name}"; 73 UserEntity paras = new UserEntity(); 74 paras.setDepartmentId(1); 75 paras.setName("lijz"); 76 List<UserEntity> list = sqlManager.execute(sql,UserEntity.class,paras); 77 } 78 79 { 80 //或者使用Map作为参数 81 String sql = "select * from user where department_id=#{myDeptId} and name=#{myName}"; 82 Map paras = new HashMap(); 83 paras.put("myDeptId",1); 84 paras.put("myName","lijz"); 85 List<UserEntity> list = sqlManager.execute(sql,UserEntity.class,paras); 86 } 87 88 { 89 //使用Beetl模板语句 90 String sql = "select * from user where 1=1 \n" + 91 "-- @if(isNotEmpty(myDeptId)){\n" + 92 " and department_id=#{myDeptId}\t\n" + 93 "-- @}\n" + 94 "and name=#{myName}"; 95 96 97 Map paras = new HashMap(); 98 paras.put("myDeptId",1); 99 paras.put("myName","lijz"); 100 List<UserEntity> list = sqlManager.execute(sql,UserEntity.class,paras); 101 102 } 103 104 105 106 } 107 108 public void query(){ 109 { 110 Query<UserEntity> query = sqlManager.query(UserEntity.class); 111 List<UserEntity> entities = query.andEq("department_id",1) 112 .andIsNotNull("name").select(); 113 } 114 { 115 //使用LambdaQuery,能很好的支持数据库重构 116 LambdaQuery<UserEntity> query = sqlManager.lambdaQuery(UserEntity.class); 117 List<UserEntity> entities = query.andEq(UserEntity::getDepartmentId,1) 118 .andIsNotNull(UserEntity::getName).select(); 119 } 120 121 122 123 } 124 125 126 /** 127 * 最常用的方式,编写一个Mapper类,mapper方法提供数据库访问接口,beetlsql提供丰富的beetlsql实现 128 */ 129 public void mapper(){ 130 131 132// 内置BaseMapper方法调用 133 List<UserEntity> list = mapper.all(); 134 boolean isExist = mapper.exist(2); 135 UserEntity me = mapper.unique(1); 136 me.setName("newName"); 137 mapper.updateById(me); 138 139 //调用其他方法 140 UserEntity user = mapper.getUserById(1); 141 UserEntity user2 = mapper.queryUserById(2); 142 mapper.updateName("newName2",2); 143 144 List<UserEntity> users = mapper.queryByNameOrderById("newName2"); 145 146 List<DepartmentEntity> depts = mapper.findAllDepartment(); 147 148 } 149 150 /** 151 * 对于复杂sql语句,比如几十行,甚至几百行的sql模板语句,放到markdown文件里是个不错的想法 152 * 参考sql/user.md#select 153 */ 154 public void sqlResource(){ 155 SqlId id = SqlId.of("user","select"); 156 //or SqlId id = SqlId.of("user.select"); 157 Map map = new HashMap(); 158 map.put("name","n"); 159 List<UserEntity> list = sqlManager.select(id,UserEntity.class,map); 160 161 UserMapper mapper = sqlManager.getMapper(UserMapper.class); 162 mapper.select("n"); 163 164 } 165 166} 167
结果集映射
1/** 2 * 演示如何将数据库查询结果映射到java对象上 3 * 4 * @author xiandafu 5 */ 6 7public class S2MappingSample { 8 9 SQLManager sqlManager; 10 UserMapper mapper =null; 11 12 public S2MappingSample(SQLManager sqlManager) { 13 this.sqlManager = sqlManager; 14 mapper = sqlManager.getMapper(UserMapper.class); 15 } 16 17 public static void main(String[] args) throws Exception { 18 SQLManager sqlManager = SampleHelper.getSqlManager(); 19 S2MappingSample mappingSample = new S2MappingSample(sqlManager); 20 mappingSample.column(); 21 mappingSample.toMap(); 22 mappingSample.view(); 23 mappingSample.mappingProvider(); 24 mappingSample.jsonConfig(); 25 mappingSample.autoMapping(); 26 mappingSample.myAttributeAnnotation(); 27 } 28 29 /** 30 * 使用@Column注解,或者按照NameConversion来自动映射 31 */ 32 public void column() { 33 MyUser user = sqlManager.unique(MyUser.class, 1); 34 35 } 36 37 /** 38 * 可以把查询结果转化成Map,在java中,注意,滥用map作为也业务对象是非常糟糕设计 39 */ 40 public void toMap() { 41 SQLReady sqlReady = new SQLReady("select id,name from user"); 42 List<Map> list = sqlManager.execute(sqlReady, Map.class); 43 44 } 45 46 47 /** 48 * 49 */ 50 public void view() { 51 //映射所有列 52 TestUser user = sqlManager.unique(TestUser.class, 1); 53 54 //映射只有一个KeyInfo标注的属性,本例子中department属性不在查询结果范围里 55 TestUser keyInfo = sqlManager.viewType(TestUser.KeyInfo.class).unique(TestUser.class, 1); 56 } 57 58 /** 59 * 使用额外的映射类来映射 60 */ 61 public void mappingProvider() { 62 //运行时刻指定一个映射类 63 TestUser testUser = sqlManager.rowMapper(MyRowMapper.class).unique(TestUser2.class, 1); 64 //使用@RowProvider注解为类指定一个Mapper,这个更常用 65 TestUser testUse2 = sqlManager.unique(TestUser2.class, 1); 66 } 67 68 69 70 /** 71 * 使用json 配置来映射,类似mybatis的xml配置 72 */ 73 public void jsonConfig() { 74 String sql = "select d.id id,d.name name ,u.id u_id,u.name u_name " + 75 " from department d join user u on d.id=u.department_id where d.id=?"; 76 Integer deptId = 1; 77 SQLReady ready = new SQLReady(sql,new Object[]{deptId}); 78 List<DepartmentInfo> list = sqlManager.execute(ready,DepartmentInfo.class); 79 System.out.println(list.toString()); 80 81 } 82 83 84 /** 85 * 使用json 配置来映射,类似mybatis的xml配置 86 */ 87 public void autoMapping() { 88 List<MyUserView> list = mapper.allUserView(); 89 System.out.println(list); 90 91 } 92 93 /** 94 * 自定义一个属性注解Base64,用于编码和解码属性字段 95 */ 96 public void myAttributeAnnotation(){ 97 98 UserData userData = new UserData(); 99 userData.setName("123456"); 100 sqlManager.insert(userData); 101 UserData data = sqlManager.unique(UserData.class,userData.getId()); 102 System.out.println("user name "+data.getName()); 103 104 UserEntity entity = sqlManager.unique(UserEntity.class,userData.getId()); 105 System.out.println("db value "+entity.getName()); 106 107 } 108 109 /** 110 * 演示使用Column 注解映射java属性与表列名, 111 */ 112 @Data 113 @Table(name="user") 114 public static class MyUser { 115 @Column("id") 116 @AutoID 117 Integer myId; 118 @Column("name") 119 String myName; 120 } 121 122 123 @Data 124 @Table(name="user") 125 public static class TestUser { 126 public static interface KeyInfo { 127 } 128 129 @Column("id") 130 @AutoID 131 @View(KeyInfo.class) 132 Integer myId; 133 134 @Column("name") 135 @View(KeyInfo.class) 136 String myName; 137 138 Integer departmentId; 139 } 140 141 142 @RowProvider(MyRowMapper.class) 143 public static class TestUser2 extends TestUser { 144 145 } 146 147 /** 148 * 使用json配置来映射,如果映射配置过长,建议放到文件中,使用resource说明配置路径 149 * 150 */ 151 @Data 152 @ResultProvider(JsonConfigMapper.class) 153// @JsonMapper( 154// "{'id':'id','name':'name','users':{'id':'u_id','name':'u_name'}}") 155 @org.beetl.sql.annotation.entity.JsonMapper(resource ="user.departmentJsonMapping") 156 public static class DepartmentInfo { 157 Integer id; 158 String name; 159 List<UserInfo> users; 160 } 161 162 @Data 163 public static class UserInfo { 164 Integer id; 165 String name; 166 } 167 168 169 /** 170 * 如果数据库查询的结果与类定义一致,也可以使用AutoJsonMapper 171 */ 172 @Data 173 @ResultProvider(AutoJsonMapper.class) 174 public static class MyUserView { 175 UserInfo user; 176 DepartmentEntity dept; 177 } 178 179 180 181 public static class MyRowMapper implements RowMapper<TestUser> { 182 183 @Override 184 public TestUser mapRow(ExecuteContext ctx, Object obj, ResultSet rs, int rowNum, Annotation config) throws SQLException { 185 TestUser testUser = (TestUser) obj; 186 testUser.setMyName(testUser.getMyName() + "-" + System.currentTimeMillis()); 187 return testUser; 188 189 } 190 } 191 192 193 194 195 @Table(name="user") 196 @Data 197 public static class UserData{ 198 @AutoID 199 Integer id; 200 @Base64 201 String name; 202 } 203 204 205 @Retention(RetentionPolicy.RUNTIME) 206 @Target(value = {ElementType.METHOD, ElementType.FIELD}) 207 @Builder(Base64Convert.class) 208 public static @interface Base64 { 209 210 } 211 212 213 /** 214 * 自定义一个注解,实现把属性字段加密存入数据库,取出的时候解密 215 */ 216 public static class Base64Convert implements AttributeConvert { 217 Charset utf8 = Charset.forName("UTF-8"); 218 219 public Object toDb(ExecuteContext ctx, Class cls, String name, Object dbValue) { 220 221 String value= (String) BeanKit.getBeanProperty(dbValue,name); 222 byte[] bs = java.util.Base64.getEncoder().encode(value.getBytes(utf8)); 223 return new String(bs,utf8); 224 225 } 226 public Object toAttr(ExecuteContext ctx, Class cls, String name, ResultSet rs, int index) throws SQLException { 227 String value = rs.getString(index); 228 return new String(java.util.Base64.getDecoder().decode(value),utf8); 229 } 230 } 231 232 233} 234
翻页查询
1public class S3PageSample { 2 3 SQLManager sqlManager; 4 UserMapper mapper =null; 5 6 public S3PageSample(SQLManager sqlManager) { 7 this.sqlManager = sqlManager; 8 mapper = sqlManager.getMapper(UserMapper.class); 9 } 10 11 public static void main(String[] args) throws Exception { 12 SQLManager sqlManager = SampleHelper.getSqlManager(); 13 S3PageSample page = new S3PageSample(sqlManager); 14 page.baseRange(); 15 page.page(); 16 page.jdbcPage(); 17 page.resourceSqlPage(); 18 page.resourceGroupSqlPage(); 19 } 20 21 /** 22 * 范围查询 23 */ 24 public void baseRange(){ 25 List<UserEntity> all = mapper.all(); 26 long count = mapper.allCount(); 27 28 UserEntity template = new UserEntity(); 29 template.setDepartmentId(1); 30 UserEntity user1 = mapper.templateOne(template); 31 32 33 34 35 } 36 37 /** 38 * 翻页查询,使用模板sql 39 */ 40 41 public void page(){ 42 43 /** 44 * sql模板语句的page函数能自动把sql模板语句转为为求总数语句 45 */ 46 String sql = "select #{page('*')} from user where department_id=#{id}"; 47 PageRequest request = DefaultPageRequest.of(1,10); 48 Map map = new HashMap<>(); 49 map.put("id",1); 50 PageResult pr = sqlManager.executePageQuery(sql,UserEntity.class,map,request); 51 //强制转化为DefaultPageResult, 52 DefaultPageResult pageResult = (DefaultPageResult)pr; 53 printPageResult(pageResult); 54 55 } 56 57 /** 58 * 直接使用jdbc sql 59 */ 60 public void jdbcPage(){ 61 62 /** 63 * 解析jdbc sql语句,生成求总数语句 64 */ 65 String sql = "select * from user where department_id=?"; 66 PageRequest request = DefaultPageRequest.of(1,10); 67 SQLReady sqlReady = new SQLReady(sql,new Object[]{1}); 68 PageResult pr = sqlManager.execute(sqlReady,UserEntity.class,request); 69 DefaultPageResult pageResult = (DefaultPageResult)pr; 70 printPageResult(pageResult); 71 72 } 73 74 75 /** 76 * 翻页查询通常很复杂,SQL很长,把sql语句放到sql文件里是个好办法,也是最常用的办法 77 */ 78 public void resourceSqlPage(){ 79 PageRequest request = DefaultPageRequest.of(1,10); 80 PageResult pr = mapper.pageQuery(1,request); 81 DefaultPageResult pageResult = (DefaultPageResult)pr; 82 printPageResult(pageResult); 83 } 84 85 /** 86 * 对分组语句进行翻页查询,需要嵌套在子查询里,比如 87 * <pre> 88 * select count(1),name from user group by name 89 * </pre> 90 * 如上分组提供给beetlsql的时候,应该编写成 91 * <pre> 92 * select #{page()} from ( select count(1),name from user group by name ) a 93 * </pre> 94 * 95 */ 96 public void resourceGroupSqlPage(){ 97 PageRequest request = DefaultPageRequest.of(1,10); 98 99 PageResult pr = mapper.pageQuery2(1,request); 100 DefaultPageResult pageResult = (DefaultPageResult)pr; 101 printPageResult(pageResult); 102 } 103 104 105 public void printPageResult(DefaultPageResult pageResult){ 106 System.out.println(pageResult.getPage()); 107 System.out.println(pageResult.getPageSize()); 108 System.out.println(pageResult.getTotal()); 109 System.out.println(pageResult.getTotalPage()); 110 System.out.println(pageResult.getResult()); 111 } 112 113}
演示like,batchUpdate,in 操作
1public class S4Other { 2 3 SQLManager sqlManager; 4 UserMapper mapper = null; 5 6 public S4Other(SQLManager sqlManager) { 7 this.sqlManager = sqlManager; 8 mapper = sqlManager.getMapper(UserMapper.class); 9 } 10 11 public static void main(String[] args) throws Exception { 12 SQLManager sqlManager = SampleHelper.getSqlManager(); 13 S4Other others = new S4Other(sqlManager); 14 others.like(); 15 others.in(); 16 others.batch(); 17 others.sqlResult(); 18 19 } 20 21 /** 22 * like 23 */ 24 public void like() { 25 String sql = "select * from user where name like #{name}"; 26 Map paras = new HashMap(); 27 String name = "%li%"; 28 paras.put("name", name); 29 List<UserEntity> users = sqlManager.execute(sql, UserEntity.class, paras); 30 //同样效果 31 sql = "select * from user where name like #{'%'+name+'%'}"; 32 paras = new HashMap(); 33 name = "li"; 34 paras.put("name", name); 35 users = sqlManager.execute(sql, UserEntity.class, paras); 36 37 //同样小姑 38 SQLReady sqlReady = new SQLReady("select * from user where name like ?" 39 ,new Object[]{"%"+name+"%"}); 40 users = sqlManager.execute(sqlReady,UserEntity.class); 41 42 43 } 44 45 /** 46 * in 47 */ 48 public void in() { 49 //使用beetlsql提供的join函数,接受一个list变量 50 String sql = "select * from user where id in ( #{join(ids)} )"; 51 List list = Arrays.asList(1,2,3,4,5); 52 Map paras = new HashMap(); 53 paras.put("ids", list); 54 List<UserEntity> users = sqlManager.execute(sql, UserEntity.class, paras); 55 56 } 57 58 /** 59 * batch 60 */ 61 public void batch() { 62 //批量插入 63 UserEntity user1 = new UserEntity(); 64 user1.setName("b1"); 65 user1.setDepartmentId(1); 66 67 UserEntity user2 = new UserEntity(); 68 user2.setName("b2"); 69 user2.setDepartmentId(1); 70 71 72 //根据组件批量更新 73 List<UserEntity> data = Arrays.asList(user1,user2); 74 sqlManager.insertBatch(UserEntity.class,data); 75 76 data.get(1).setName("bb11"); 77 sqlManager.updateByIdBatch(data); 78 79 //循环删除,执行多次 80 data.stream().forEach(userEntity -> mapper.deleteById(userEntity.getId())); 81 82 } 83 84 /** 85 * 不执行,只得到sql语句和参数 86 */ 87 public void sqlResult(){ 88 89 Map map = new HashMap(); 90 map.put("name","li"); 91 SQLResult sqlResult = sqlManager.getSQLResult(SqlId.of("user","select"),map); 92 String targetJdbc = sqlResult.jdbcSql; 93 Object[] paras = sqlResult.toObjectArray(); 94 System.out.println(targetJdbc); 95 System.out.println(Arrays.asList(paras)); 96 97 } 98 99 100}
自动fetch
1/** 2 * 演示自动fetch,类似orm,但不同于orm,CRUD在ORM概念下过于复杂, 3 * BeetlSQL的fetch没有那么多复杂概念,仅仅是加载对象后看看还有没有需要再加载的对象 4 * 5 * 6 * @author xiandafu 7 */ 8 9public class S5Fetch { 10 11 SQLManager sqlManager; 12 13 14 public S5Fetch(SQLManager sqlManager) { 15 this.sqlManager = sqlManager; 16 } 17 18 public static void main(String[] args) throws Exception { 19 //为了简单起见,俩个sqlManager都来自同一个数据源,实际是不同数据库,甚至是NOSQL 20 SQLManager sqlManager = SampleHelper.init(); 21 S5Fetch fetch = new S5Fetch(sqlManager); 22 fetch.fetchOne(); 23 fetch.fetchMany(); 24 } 25 26 /** 27 * 28 */ 29 public void fetchOne(){ 30 UserData user = sqlManager.unique(UserData.class,1); 31 System.out.println(user.getDept()); 32 33 //fetchOne 会合并查询提高性能 34 List<UserData> users = sqlManager.all(UserData.class); 35 System.out.println(users.get(0).getDept()); 36 } 37 38 public void fetchMany(){ 39 DepartmentData dept = sqlManager.unique(DepartmentData.class,1); 40 System.out.println(dept.getUsers()); 41 } 42 43 44 /** 45 * 用户数据使用"a" sqlmanager 46 */ 47 @Data 48 @Table(name="user") 49 @Fetch 50 public static class UserData { 51 @Auto 52 private Integer id; 53 private String name; 54 private Integer departmentId; 55 @FetchOne("departmentId") 56 private DepartmentData dept; 57 } 58 59 /** 60 * 部门数据使用"b" sqlmanager 61 */ 62 @Data 63 @Table(name="department") 64 @Fetch 65 public static class DepartmentData { 66 @Auto 67 private Integer id; 68 private String name; 69 @FetchMany("departmentId") 70 private List<UserData> users; 71 } 72 73}
多数据库
可能是BeetlSQL最不好理解的部分,然而,应该还是比其他DAO工具更容易实现和理解
1/** 2 * <ui> 3 * <li> 4 * 演示多数据源操作中的ConditionalSQLManager,按照条件决定使用哪个SQLManager 5 * ConditionalSQLManager.decide方法决定使用哪个SQLManager, 6 * decide默认会读取目标对象的TargetSQLManager注解来决定,SQLManager的有些api参数没有目标对象,则使用默认SQLManager 7 * </li> 8 * <li> 9 * 演示user分表操作,动态表名实现分表 10 * </li> 11 * <li> 12 * 演示user分库操作,根据条件决定数据访问哪个数据库,使用了{@link ConditionalConnectionSource} 13 * </li> 14 * </ui> 15 16 * 17 * 18 * 注意:分库分表最好使用中间件 19 * @author xiandafu 20 */ 21 22public class S6MoreDatabase { 23 24 public S6MoreDatabase() { 25 } 26 27 public static void main(String[] args) throws Exception { 28 29 S6MoreDatabase moreSource = new S6MoreDatabase(); 30 moreSource.conditional(); 31 moreSource.masterSlave(); 32 moreSource.multipleTables(); 33 moreSource.multipleDataBaseAndTables(); 34 } 35 36 /** 37 * 多数据源协作 38 */ 39 public void conditional() { 40 41 SQLManager a = SampleHelper.init(); 42 SQLManager b = SampleHelper.init(); 43 Map<String, SQLManager> map = new HashMap<>(); 44 map.put("a", a); 45 map.put("b", b); 46 SQLManager sqlManager = new ConditionalSQLManager(a, map); 47 48 //不同实体,用不同sqlManager操作,存入不同的数据库 49 UserData user = new UserData(); 50 user.setName("hello"); 51 user.setDepartmentId(2); 52 sqlManager.insert(user); 53 54 DepartmentData dept = new DepartmentData(); 55 dept.setName("dept"); 56 sqlManager.insert(dept); 57 } 58 59 60 /** 61 * 普通一主多从 62 */ 63 public void masterSlave(){ 64 //为了简单起见,主从库都走同一个数据库 65 DataSource master = SampleHelper.mysqlDatasource(); 66 DataSource slave1 = SampleHelper.mysqlDatasource(); 67 DataSource slave2 = SampleHelper.mysqlDatasource(); 68 ConnectionSource source = ConnectionSourceHelper.getMasterSlave(master,new DataSource[]{slave1,slave2}); 69 70 SQLManagerBuilder builder = new SQLManagerBuilder(source); 71 builder.setNc(new UnderlinedNameConversion()); 72 builder.setInters(new Interceptor[]{new DebugInterceptor()}); 73 builder.setDbStyle(new MySqlStyle()); 74 SQLManager sqlManager = builder.build(); 75 76 //更新操作走主库 77 UserData user = new UserData(); 78 user.setName("a"); 79 user.setDepartmentId(1); 80 sqlManager.insert(user); 81 //查询走从库 82 sqlManager.unique(UserData.class,1); 83 84 85 } 86 87 /** 88 * 单库分表操作,user对象的{@code @Table}注解是逻辑表达式 89 * <pre>{@code 90 * @Table(name="${toTable('user',id)}" 91 * public class User{ 92 * 93 * } 94 * }</pre> 95 * toTable方法是一个自定义注册的beetl方法,在运行的时候会根据id换算出真实表 96 * 97 * 对于beetlsql所有内置方法,都可以自动分表,但你自己的sql,也要类似使用 98 * {@code ${toTable('user',id)}} 99 * @see TableChoice 100 */ 101 public void multipleTables(){ 102 103 SQLManager sqlManager = getSQLManager4MultipleTables(); 104 //使用user表 105 sqlManager.deleteById(MyUser.class,199); 106 MyUser user = new MyUser(); 107 user.setName("abc"); 108 user.setId(199); 109 sqlManager.insert(user); 110 111 //使用user_1表. 为了简单起见,分表逻辑返回的目标表还是user表 112 MyUser user2 = new MyUser(); 113 user2.setName("abc"); 114 user2.setId(1500); 115 sqlManager.insert(user2); 116 117 } 118 119 /** 120 * 分库分布表操作,同{@link #multipleTables()} 方法,但增加如果id超过一定限额,走另外一个数据库 121 * 核心还是需要定义一个分库分表逻辑 122 * @see TableAndDataBaseChoice 123 */ 124 public void multipleDataBaseAndTables(){ 125 126 SQLManager sqlManager = getSQLManager4MultipleDatBase(); 127 sqlManager.deleteById(MyUser.class,199); 128 MyUser user = new MyUser(); 129 user.setName("abc"); 130 user.setId(199); 131 sqlManager.insert(user); 132 133 //这条记录使用第二个库的user表 134 sqlManager.deleteById(MyUser.class,2900); 135 MyUser user2 = new MyUser(); 136 user2.setName("abc"); 137 user2.setId(2900); 138 sqlManager.insert(user2); 139 140 } 141 142 protected SQLManager getSQLManager4MultipleTables(){ 143 SQLManager sqlManager = SampleHelper.getSqlManager(); 144 //告诉sqlManager遇到USER_TABLE这个不存在的表不慌,他是个虚表,真实表是user 145 sqlManager.addVirtualTable("user",USER_TABLE); 146 BeetlTemplateEngine templateEngine = (BeetlTemplateEngine)sqlManager.getSqlTemplateEngine(); 147 // 注册一个方法来实现映射到多表的逻辑 148 templateEngine.getBeetl().getGroupTemplate().registerFunction("toTable", new Function(){ 149 150 @Override 151 public Object call(Object[] paras, Context ctx) { 152 String tableName = (String)paras[0]; 153 Integer id = (Integer)paras[1]; 154 //使用分表逻辑 155 TableChoice tableChoice = new TableChoice(); 156 return tableChoice.getTableName(tableName,id); 157 158 } 159 }); 160 return sqlManager; 161 } 162 163 /** 164 * 分表选择逻辑 165 */ 166 public static class TableChoice{ 167 public String getTableName(String tableName,Integer id){ 168 if(id<1000){ 169 return tableName; 170 }else{ 171 //根据需要返回另外一个表,比如tableName+"_1" 172 return tableName; 173// return tableName+"_1"; 174 } 175 } 176 } 177 178 /** 179 * 分库选择逻辑,用户自由实现分表分库逻辑, 180 */ 181 public static class TableAndDataBaseChoice{ 182 public String getTableName(ExecuteContext executeContext,String tableName,Integer id){ 183 184 if(id<1000){ 185 return tableName; 186 }else if(id<2000){ 187 return tableName+"_1"; 188 }else{ 189 //如果继续大,设置一个标记,进入另外一个数据库cs2库的user表 190 191 executeContext.setContextPara(FLAG,"cs2"); 192 if(id<3000){ 193 return tableName; 194 }else{ 195 return tableName+"_1"; 196 } 197 198 } 199 200 } 201 } 202 203 204 205 private static final String FLAG ="connectionSource"; 206 protected SQLManager getSQLManager4MultipleDatBase(){ 207 //为了测试方便,假设指向同一个数据库 208 DataSource db1 = SampleHelper.mysqlDatasource(); 209 ConnectionSource cs1 = ConnectionSourceHelper.getSingle(db1); 210 DataSource db2 = SampleHelper.mysqlDatasource(); 211 ConnectionSource cs2 = ConnectionSourceHelper.getSingle(db2); 212 Map<String,ConnectionSource> datas = new HashMap<>(); 213 datas.put("cs1",cs1); 214 datas.put("cs2",cs2); 215 // 配置策略 216 ConditionalConnectionSource.Policy policy = new ConditionalConnectionSource.Policy() { 217 @Override 218 public String getConnectionSourceName(ExecuteContext ctx, boolean isUpdate) { 219 String name = (String)ctx.getContextPara(FLAG); 220 if(name!=null){ 221 return name; 222 }else{ 223 // 如果没有设置,则返回一个默认库 224 return "cs1"; 225 } 226 } 227 228 @Override 229 public String getMasterName() { 230 return "cs1"; 231 } 232 }; 233 234 235 ConditionalConnectionSource ds = new ConditionalConnectionSource(policy,datas); 236 // 初始化sqlManager,使用ConditionalConnectionSource 237 SQLManagerBuilder builder = new SQLManagerBuilder(ds); 238 builder.setNc(new UnderlinedNameConversion()); 239 builder.setInters(new Interceptor[]{new DebugInterceptor()}); 240 builder.setDbStyle(new MySqlStyle()); 241 SQLManager sqlManager = builder.build(); 242 // 申明一个虚表 "${toTable('user',id)}",实际上是user表 243 sqlManager.addVirtualTable("user",USER_TABLE); 244 BeetlTemplateEngine templateEngine = (BeetlTemplateEngine)sqlManager.getSqlTemplateEngine(); 245 // 注册一个方法来实现映射到多表的逻辑 246 templateEngine.getBeetl().getGroupTemplate().registerFunction("toTable", new Function(){ 247 248 @Override 249 public Object call(Object[] paras, Context ctx) { 250 String tableName = (String)paras[0]; 251 Integer id = (Integer)paras[1]; 252 ExecuteContext executeContext = (ExecuteContext)ctx.getGlobal(ExecuteContext.NAME); 253 //使用分库逻辑 254 TableAndDataBaseChoice choice = new TableAndDataBaseChoice(); 255 return choice.getTableName(executeContext,tableName,id); 256 257 } 258 }); 259 260 return sqlManager; 261 262 } 263 264 265 266 267 268 269 270 271 /** 272 * 用户数据使用"a" sqlmanager 273 */ 274 @Data 275 @Table(name = "user") 276 @TargetSQLManager("a") 277 public static class UserData { 278 @Auto 279 private Integer id; 280 private String name; 281 private Integer departmentId; 282 } 283 284 /** 285 * 部门数据使用"b" sqlmanager 286 */ 287 @Data 288 @Table(name = "department") 289 @TargetSQLManager("b") 290 public static class DepartmentData { 291 @Auto 292 private Integer id; 293 private String name; 294 } 295 296 297 static final String USER_TABLE="${toTable('user',id)}"; 298 @Data 299 @Table(name = USER_TABLE) 300 public static class MyUser { 301 @AssignID 302 private Integer id; 303 private String name; 304 } 305 306 307 308 309} 310
代码生成框架
演示代码生成框架,以及生成代码和数据库文档
1/** 2 * 演示beetlsql 代码生成框架 3 * 4 * @author xiandafu 5 */ 6 7public class S7CodeGen { 8 9 SQLManager sqlManager; 10 11 12 public S7CodeGen(SQLManager sqlManager) { 13 this.sqlManager = sqlManager; 14 initGroupTemplate(); 15 } 16 17 protected void initGroupTemplate(){ 18 //指定模板文件路径,正常情况下,不需要要指定,默认在classpath:templates,但idea的环境读取不到 19 GroupTemplate groupTemplate = BaseTemplateSourceBuilder.getGroupTemplate(); 20 String root = System.getProperty("user.dir"); 21 //代码模板在sql-gen,你可以指定自己的模板路径 22 String templatePath = root+"/sql-gen/src/main/resources/templates/"; 23 FileResourceLoader resourceLoader = new FileResourceLoader(templatePath); 24 groupTemplate.setResourceLoader(resourceLoader); 25 } 26 27 public static void main(String[] args) throws Exception { 28 //为了简单起见,俩个sqlManager都来自同一个数据源,实际是不同数据库,甚至是NOSQL 29 SQLManager sqlManager = SampleHelper.init(); 30 S7CodeGen gen = new S7CodeGen(sqlManager); 31 gen.genCode(); 32 gen.genDoc(); 33 gen.genAllDoc(); 34 } 35 36 /** 37 * 代码生成,生成实体,mapper代码 38 */ 39 public void genCode(){ 40 List<SourceBuilder> sourceBuilder = new ArrayList<>(); 41 SourceBuilder entityBuilder = new EntitySourceBuilder(); 42 SourceBuilder mapperBuilder = new MapperSourceBuilder(); 43 SourceBuilder mdBuilder = new MDSourceBuilder(); 44 45 sourceBuilder.add(entityBuilder); 46 sourceBuilder.add(mapperBuilder); 47 sourceBuilder.add(mdBuilder); 48 49 SourceConfig config = new SourceConfig(sqlManager,sourceBuilder); 50 //如果有错误,抛出异常而不是继续运行1 51 EntitySourceBuilder.getGroupTemplate().setErrorHandler(new ReThrowConsoleErrorHandler() ); 52 53 ConsoleOnlyProject project = new ConsoleOnlyProject(); 54 String tableName = "USER"; 55 config.gen(tableName,project); 56 } 57 58 59 /** 60 * 生成数据库文档 61 */ 62 public void genDoc(){ 63 List<SourceBuilder> sourceBuilder = new ArrayList<>(); 64 SourceBuilder docBuilder = new MDDocBuilder(); 65 66 sourceBuilder.add(docBuilder); 67 68 SourceConfig config = new SourceConfig(sqlManager,sourceBuilder); 69 //如果有错误,抛出异常而不是继续运行1 70 EntitySourceBuilder.getGroupTemplate().setErrorHandler(new ReThrowConsoleErrorHandler() ); 71 72 ConsoleOnlyProject project = new ConsoleOnlyProject(); 73 String tableName = "USER"; 74 config.gen(tableName,project); 75 } 76 77 78 /** 79 * 生成数据库文档 80 */ 81 public void genAllDoc(){ 82 List<SourceBuilder> sourceBuilder = new ArrayList<>(); 83 SourceBuilder docBuilder = new MDDocBuilder(); 84 sourceBuilder.add(docBuilder); 85 86 SourceConfig config = new SourceConfig(sqlManager,sourceBuilder); 87 //如果有错误,抛出异常而不是继续运行1 88 EntitySourceBuilder.getGroupTemplate().setErrorHandler(new ReThrowConsoleErrorHandler() ); 89 90 StringOnlyProject project = new StringOnlyProject(); 91 config.genAll(project); 92 String output = project.getContent(); 93 System.out.println(output); 94 } 95 96} 97
BeetlSQL3 架构(节选)

Mapper定制
使用或者定制BeetlSQL,可以从Mapper开始,BeetlSQL提供了BaseMapper接口,作为推荐的使用BeetlSQL首选方式
1public interface UserMapper extends BaseMapper<User>{ 2 3}
这里,我们假定一个User对象,我们希望对其增删改查,对应数据库user表。
如果你还不熟悉BeetSQL用法,阅读《BeetlSQL使用指南》
BaseMapper提供了内置的CRUD等若干方法,满足50%的常用DAO操作,选取如下4个方法
1public interface BaseMapper<T> { 2 3 @AutoMapper(InsertAmi.class) 4 void insert(T entity); 5 6 @AutoMapper(UpdateByIdAmi.class) 7 int updateById(T entity); 8 9 @AutoMapper(UniqueAmi.class) 10 T unique(Object autoKey); 11 12 @AutoMapper(SingleAmi.class) 13 T single(Object autoKey); 14 15} 16
- insert 插入User到相应的表里
- updateById,更新User,按照主键id作为条件更新
- unique,按照主键查找User,如果未找到,抛出异常
- single,同unique,如果未找到,返回null
这四个内置方法,都使用了注解@AutoMapper,定义如下
1@Target({java.lang.annotation.ElementType.METHOD}) 2@Retention(RetentionPolicy.RUNTIME) 3public @interface AutoMapper { 4 Class<? extends MapperInvoke> value() ; 5}
AutoMapper需要提供一个类,必须是MapperInvoke的子类,此子类真正负责执行Dao操作。我们以InsertAmi为例子,代码如下
1public class InsertAmi implements MapperInvoke { 2 @Override 3 public Object call(SQLManager sm, Class entityClass, Method m, Object[] args) { 4 int ret = sm.insert(args[0]); 5 return ret; 6 } 7}
可以看到InsertAmi非常简单,仅仅调用SQLManager的insert方法,SQLManager是BeetlSQL提供的核心类.流程如下

如果你想定义自己的通用Mapper,可以定义任意一个接口,或者继承已经做好的BaseMapper,使用@AutoMapper标明其实现类即可。
除了AutoMapper注解,BeetlSQL也提供了其他注解,甚至是可以自定义注解,解释如下
BeetlSQL提供了@Sql注解,允许同时提供sql语句
1@Sql("select * from user where id= ?) 2public User queryById(Integer id);
解释@Sql注解的也是一个MapperInvoke子类,位于org.beetl.sql.mapper.ready包下,
1public class SelectSQLReadyMI extends BaseSQLReadyMI { 2 boolean isSingle = false; 3 public SelectSQLReadyMI(String sql,Class targetType,boolean isSingle){ 4 this.sql = sql; 5 this.targetType = targetType; 6 this.isSingle = isSingle; 7 } 8 9 @Override 10 public Object call(SQLManager sm, Class entityClass, Method m, Object[] args) { 11 SQLReady sqlReady = new SQLReady(this.getSql(),args); 12 List list = sm.execute(sqlReady,this.getTargetType()); 13 if(isSingle){ 14 return list.isEmpty()?null:list.get(0); 15 }else{ 16 return list; 17 } 18 } 19}
可以看到,SelectSQLReadyMI的call方法仍然非常简单,调用SQLManager的jdbc查询方法。SelectSQLReadyMI的构造是通过MapperMethodParser.parse方法,稍后会介绍这个类
如果你想利用BeetlSQL实现自定义注解,比如你觉得提供sql语句繁琐,你更喜欢SpringData那种根据方法名来推测sql语句,类似如下
1@SpringData 2public User queryByNameAndAge(String name,Integer a);
Spring Data 是一个Spring封装的DAO框架,它会根据dao的方法命名来判断,如上方法,表示查询用户(query),条件是通过name属性,因此sql是 select * from user where name=? and age=?
为了使BeetlSQL能识别SpringData注解,且能使用MapperInvoke执行,你可以定义SpringData注解,如下
1@Target({java.lang.annotation.ElementType.METHOD}) 2@Retention(RetentionPolicy.RUNTIME) 3@Builder(SpringDataBuilder.class) 4public @interface SpringData { 5 6}
这里,@SpringData注解又使用了BeetlSQL的@Builder注解,这表明@SpringData注解的解释是通过此类来定义的,BeetlSQL期望SpringDataBuilder实现MapperExtBuilder接口,能解析当前方法,返回一个MapperInvoke接口,类似如下
1public class SpringDataBuilder implements MapperExtBuilder { 2 @Override 3 public MapperInvoke parse(Class entity, Method m) { 4 ...... 5 } 6} 7
MapperExtBuilder 用于解析Mapper定义的方法,转化为对应的sql查询(通常是调用SQLMananager API),系统已经提供了SpringDataBuilder和ProviderMapperExtBuilder .
@Builder注解是BeetlSQL一个很强大的注解,它是注解的注解,表明了注解的含义,我们在后面还能看到很多注解都采用了@Builder这个方法,比如Mapper中的SqlProvider, Bean的注解扩展@UpdateTime,以及关联查询的@FetchOne,@FetchMany
Mapper类图如下

数据库表到Java对象
BeetlSQL提供多种方式实现数据库映射,包括
- 约定习俗,指定NameConversion
- 通过@Table和@Column注解
- 通过ViewType 只映射一部分结果集
- 通过RowMapper自定义行映射,想在如上映射结果基础上,在定制映射结果
- 通过ResultSetMapper 自定义结果集映射,这有包含了@JsonMapper 的实现JsonConfigMapper和AutoJsonMapper俩种复杂结果集映射,类似MyBatis通过XML配置映射
映射完毕后,可以通过AttributeConvert或者BeanConvert再次对映射结果处理。比如加密字段的解密,或者字符串变成json操作
在返回结果集前,BeetlSQL还会查看是否有@Fetch标签,进行额外数据的抓取
NameConversion
NameConversion 定义了如何把Java名字转化为数据库名字,或者相反
1public abstract String getTableName(Class<?> c); 2public abstract String getColName(Class<?> c,String attrName); 3public abstract String getPropertyName(Class<?> c,String colName); 4
NameConversion 的子类内置了DefaultNameConversion,即不做任何改变。UnderlinedNameConversion,把下划线去掉,其后字母大写。最为常用,也符合数据库设计规范,使用UnderlinedNameConversion
重写NameConversion需要考虑读取@Table和@Cloumn注解,可以复用NameConversion.getAnnotationColName,getAnnotationAttrName和getAnnotationTableName,如下是UnderlinedNameConversion的实现
1@Override 2public String getTableName(Class<?> c) { 3 String name = getAnnotationTableName(c); 4 if(name!=null){ 5 return name; 6 } 7 return StringKit.enCodeUnderlined(c.getSimpleName()); 8} 9 10@Override 11public String getColName(Class<?> c,String attrName) { 12 String col = super.getAnnotationColName(c,attrName); 13 if(col!=null){ 14 return col; 15 } 16 return StringKit.enCodeUnderlined(attrName); 17} 18 19@Override 20public String getPropertyName(Class<?> c,String colName) { 21 String attrName = super.getAnnotationAttrName(c,colName); 22 if(attrName!=null){ 23 return attrName; 24 } 25 return StringKit.deCodeUnderlined(colName.toLowerCase()); 26}
ViewType
ViewType 类似Jackson的@View注解,在BeetlSQL查询过程中,查询被VIewType申明的字段,如下TestUser,属性myId和myName被@View注解标注,因此sqlManager指定viewType为KeyInfo.class的时候,仅仅查询此俩列
1 TestUser keyInfo = sqlManager.viewType(TestUser.KeyInfo.class).unique(TestUser.class, 1); 2 3@Data 4public static class TestUser { 5 public static interface KeyInfo { 6 } 7 8 @Column("id") 9 @AutoID 10 @View(KeyInfo.class) 11 Integer myId; 12 13 @Column("name") 14 @View(KeyInfo.class) 15 String myName; 16 17 Integer departmentId; 18} 19
VIewType会影响代码生成,因此对于TestUser对象来说,根据主键查询会有俩条内置sql语句生成,参考代码AbstractDBStyle
1public SQLSource genSelectByIds(Class<?> cls,Class viewType) { 2 ConcatContext concatContext = this.createConcatContext(); 3 Select select = concatContext.select(); 4 appendIdsCondition(cls,select); 5 select.from(cls); 6 if(viewType!=null){ 7 select.all(cls,viewType); 8 }else{ 9 select.all(); 10 } 11 return new SQLTableSource(select.toSql()); 12}
对于普通的sql'语句,也可以只映射部分查询结果,而不需要映射所有结果集,比如某些大字段(TODO,未完成)
RowMapper
RowMapper 可以在BeetlSQL默认的映射规则基础上,添加用户自定义映射,RowMapper可以通过SQLManager传入,或者通过POJO上的注解来申明,比如
1@RowProvider(MyRowMapper.class) 2public static class TestUser2 extends TestUser { 3 4}
所有查询结果映射到TestUser2后,还需要执行MyRowMapper接口
@RowProvider注解定义如下
1@Retention(RetentionPolicy.RUNTIME) 2@Target(ElementType.TYPE) 3public @interface RowProvider { 4 Class<? extends RowMapper> value(); 5}
ResultSetMapper
ResultSetMapper对象相当于告诉BeetlSQL,不需要BeetlSQL来映射,交给ResultSetMapper来实现,比如一个select join结果需要映射到复杂的对象上(比如一个用户有多个角色,属于多个组织),BeetlSQL自带了JsonConfigMapper实现,用json来申明如何映射,类似MyBatis用xml来申明如何映射
1String sql = "select d.id id,d.name name ,u.id u_id,u.name u_name " + 2 " from department d join user u on d.id=u.department_id where d.id=?"; 3Integer deptId = 1; 4SQLReady ready = new SQLReady(sql,new Object[]{deptId}); 5List<DepartmentInfo> list = sqlManager.execute(ready,DepartmentInfo.class); 6 7@Data 8@ResultProvider(JsonConfigMapper.class) 9@JsonMapper( 10 "{'id':'id','name':'name','users':{'id':'u_id','name':'u_name'}}") 11public static class DepartmentInfo { 12 Integer id; 13 String name; 14 List<UserInfo> users; 15}
注解ResultProvider提供了一个ResultSetMapper实现类,@JsonMapper是一个配置注解,与ResultProvider搭档,提供额外配置,JsonMapper支持配置在java代码里,或者通过文件配置
Pojo类上所有注解都在
ClassAnnotation类上存放,ResultProvider和JsonMapper 被缓存在ClassAnnotation类里,因为JsonMapper注解被ProviderConfig注解所申明,所以他俩是一对一1@ProviderConfig() 2public @interface JsonMapper { 3 String value() default ""; 4 String resource() default ""; 5} 6ClassAnnotation 不仅仅寻找ResultProvider注解,也寻找使用了@ProviderConfig()的注解,并作为配置注解放在一起。BeetlSQL大量使用这种注解的注解,来提供扩展机制
JsonConfigMapper定义如下
1public class JsonConfigMapper extends ConfigJoinMapper { 2 protected AttrNode parse(ExecuteContext ctx, Class target, ResultSetMetaData rsmd, Annotation config){ 3 4 } 5}
ConfigJoinMapper 是基类,他会根据AttrNode描述来做映射,因此JsonConfigMapper只需要读取config注解申明的配置,然后转化成AttrNode即可,如果你想让配置是yml或者xml,可以实现parse方法即可
AttributeConvert
AttributeConvert用于属性转化,定义如下
1public default Object toAttr(ExecuteContext ctx, Class cls,String name, ResultSet rs, int index) throws SQLException { 2 return rs.getObject(index); 3} 4public default Object toDb(ExecuteContext ctx, Class cls,String name, Object dbValue) { 5 return dbValue; 6} 7
toAttr用于把数据库转化成属性值,比如数据库字符串转成Java的json对象,toDb则是把属性值在存入数据库之前转成合适的值,比如json对象转成字符串
在定义了AttributeConvert类后,需要在定义一个注解,这样,beetlsql遇到此注解,将按照上述机制执行,注解的注解仍然使用@Builder 来完成,Builder接受一个AttributeConvert子类
1@Retention(RetentionPolicy.RUNTIME) 2@Target(value = {ElementType.METHOD, ElementType.FIELD}) 3@Builder(Base64Convert.class) 4public static @interface Base64 { 5 6} 7
因此,可以自pojo上使用此注解
1@Table(name="user") 2@Data 3public static class UserData{ 4 @AutoID 5 Integer id; 6 @Base64 7 String name; 8}
所有关于pojo的注解都在
ClassAnnotation里维护
一个简单的实现Base64注解实现如下,这样保证name字段存入数据库是经过base64加密,取出是base64解密
1public static class Base64Convert implements AttributeConvert { 2 Charset utf8 = Charset.forName("UTF-8"); 3 4 public Object toDb(ExecuteContext ctx, Class cls, String name, Object dbValue) { 5 6 String value= (String) BeanKit.getBeanProperty(dbValue,name); 7 byte[] bs = java.util.Base64.getEncoder().encode(value.getBytes(utf8)); 8 return new String(bs,utf8); 9 10 } 11 public Object toAttr(ExecuteContext ctx, Class cls, String name, ResultSet rs, int index) throws SQLException { 12 String value = rs.getString(index); 13 return new String(java.util.Base64.getDecoder().decode(value),utf8); 14 } 15}
BeanConvert
BeanConvert同AttributeConvert类似,但用于整个Bean,BeanConvert定义如下
1public interface BeanConvert { 2 public default Object before(ExecuteContext ctx, Object obj, Annotation an){ 3 return obj; 4 } 5 public default Object after(ExecuteContext ctx, Object obj, Annotation an){ 6 return obj; 7 } 8}