# 数据模型 BeetlSQL的参数和出参支持POJO和Map。一般来说POJO跟容易维护,而Map不容易维护,尤其是类型,很可能不同数据库,Map的Value类型还可能不一样。而POJO映射则会强制转化为POJO定义的类型,或者使用注解,RowMapper,ResultSetMapper等方式等进一步扩展 BeetlSQL推荐使用POJO作为数据模型 ## POJO POJO的定义是普通java对象(相对于JavaEE的EJB来说的),java对象需要准守JavaBean规范,即提供getter和setter方法。如下是一个符合BeetlSQL的POJO ```java public class UserData{ Integer id; String name; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } ``` 下面的代码则不是POJO ```java public class UserData{ public Integer id; public String name; } ``` 下面的代码也不是POJO ```java public class UserData{ Integer id; public Integer getId() { return id; } public UserData setId(Integer id) { this.id = id; return this } } ``` 如果使用lombok,则只需要使用@Data注解 ```java @Data public class UserData{ Integer id; String name; } ``` ## 交集 这是一个非常重要的概念。BeetlSQL 在操作数据库的时候,默认情况只会处理POJO与数据库表(视图)的交集,比如,数据库表有id,user_name 俩个字段 而Java的POJO 有id和name。 因此(除非你使用@Cloumn说明),只有POJO的id属性和表的id字段对应上,这将作为BeetlSQL的所有操作基础元数据 当你调用insert方法的时候,POJO的id属性将被插入到id列,但name属性不做任何操作。 当你调用select方法的时候POJO的属性name无法赋值 ## @Table 注解 当要进行插入或者更新操作的时候,类名通过NameConversion隐喻了表名,也可使用@Table注解说明表名字 ```java @Table(name="sys_user") @Data public static class UserData{ } ``` 如果表在其他schema或者catalog里,可以加上前缀 ```java @Table(name="das10.sys_user") @Data public static class UserData{ } ``` @Table注解可以使用表达式,以实现动态分表 ```java static final String USER_TABLE="${toTable('sys_user',id)}"; @Data @Table(name = USER_TABLE) public class MyUser { @AssignID private Integer id; private String name; } ``` toTable是一个自定义函数,可以查看源码S6MoreDatabase,或者查看《BeetlSQL3 多库使用》 如果不是更新或者插入,而只是映射查询结果,则不需要@Table,你可以定义任意多的POJO来映射结果集,BeetlSQL3默认情况会采用POJO属性和结果集的交集来映射。比如MyUser对象,只有id和name,那么查询结果中的列名department_id不做映射 ## @ Column 注解 同@Table一样,如果NameConversion无法满足,可以使用@Column来标识属性对应的列名 ```java @Table(name="sys_user") public class TestUser { @Column("id") @AutoID Integer myId; @Column("name") String myName; Integer departmentId; } ``` 属性departmentId并未使用注解,这说明符合NameConversion ## 主键 @AutoID,作用于属性字段或者getter方法,告诉beetlsql,这是自增主键,对应于数据自增长 ```java @AutoID Integer myId; ``` @AssignID,作用于属性字段或者getter方法,告诉beetlsql,这是程序设定 ```java @AssignID Integer id; ``` 代码设定主键允许像@AssignID 传入id的生成策略以自动生成序列,BeetlSQL默认提供了一个snowflake算法,一个用于分布式环境的id生成器([https://github.com/twitter/snowflake](https://github.com/twitter/snowflake)) ```java @AssignID("simple") @AssignID() Long id; ``` simple 是beetlsql提供的一个默认的snowflake实现,你可以实现自己的id生成策略 ```java sqlManager.addIdAutonGen("uuid", new IDAutoGen(){ @Override public Object nextID(String params) { return UUID.randomUUID().toString(); } }); ``` ```java @AssignID("uuid") String id; ``` - @SeqID(name="xx_seq"),告诉beetlsql,这是序列主键,目前只有H2,Oracle和Postgres或者DB2使用序列主键,以源码单元测试为例子,如下DeviceDetail具有id,序列名称是`label_sequence` ```java @Data public abstract class BaseSeqIdEntity<ID> extends BaseEntity implements Serializable{ @SeqID(name="label_sequence") protected ID id; } @Data @Table(name="device_detail") public class DeviceDetail extends BaseSeqIdEntity<Integer>{ String json; } ``` IdTest的seqIdTest如下 ```java public class IdTest extends BaseTest { @Test public void seqIdTest(){ DeviceDetail data = new DeviceDetail(); data.setJson("{}"); sqlManager.insert(data); Assert.assertNotNull(data.getId()); System.out.println(data); } } ``` 执行后,可以看到H2的输出 ```java ┏━━━━━ Debug [deviceDetail.$insert] ━━━ ┣ SQL: insert into device_detail (ID ,JSON ) values (NEXT VALUE FOR label_sequence ,? ) ┣ 参数: [{}] ┣ 位置: org.beetl.sql.id.IdTest.seqIdTest(IdTest.java:47) ┣ 时间: 7ms ┣ 更新: [1] ┗━━━━━ Debug [deviceDetail.$insert] ━━━ ``` H2Style.getSeqValue返回了序列名称对应的求值语句,Oralce,Postgres类似,如下是DbStyle的getSeqValue实现 ```java public class H2Style extends AbstractDBStyle { @Override public String getSeqValue(String seqName) { return "NEXT VALUE FOR "+seqName; } } public class OracleStyle extends AbstractDBStyle { @Override public String getSeqValue(String seqName) { return seqName+".nextval"; } } public class OracleStyle extends AbstractDBStyle { @Override public String getSeqValue(String seqName) { return "nextval('" + seqName + "')"; } } ``` > 对于支持多种数据库的,这些annotation可以叠加在一起,但作为跨库更好的选择是使用@Assign,并自定义个id生成策略 ## RowMapper BeetlSQL完成默认的映射,你可以自定义一个RowmMapper子类,完成额外的映射 ```java public interface RowMapper<T> { /** * * @param obj 正常处理后的对象 * @param rs 结果集 * @param rowNum 处理的记录位置(第几条记录):可以只针对某一条记录做特殊处理 * @param config 注解相关配置,参考 {@link ProviderConfig} * @throws SQLException * @return T */ T mapRow(ExecuteContext ctx, Object obj, ResultSet rs, int rowNum, Annotation config) throws SQLException; } ``` RowMapper会在BeetlSQL默认映射结果集的基础上做额外处理,比如,有些未映射的也可以通过`ResultSet rs` 中调用获取 ```java public static class MyRowMapper implements RowMapper<UserVo>{ @Override public UserVo mapRow(ExecuteContext ctx, Object obj, ResultSet rs, int rowNum, Annotation config) throws SQLException { //内置的映射已经完成 UserVo vo = (UserVo)obj; //额外取得结果集 String col = rs.getString("col"); vo.setExtraAttribute(col); return vo; } } ``` 有俩种方式可以使用RowMapper,一种是通过在POJO上加上注解@RowProvider ```java @RowProvider(MyRowMapper.class) public class UserVo2 { //忽略其他属性 public void setExtraAttribute(String col){} } ``` 另外一种是SQLManager.rowMapper 方法,临时设置一次当前查询使用RowMapper(这种方式不常用) ```java sqlManager.rowMapper(MyRowMapper.class).select(sqlId,xxxx.class,paras); ``` 当查询结果返回后,rowMapper使用结束。除非再次调用rowMapper方法 ## ResultSetMapper 如果想自己完全掌控结果集映射,可以使用ResultSetMapper,定义如下 ```java public interface ResultSetMapper<T> { /** * 将数据库查询结果集ResultSet映射到一个对象上,对象通过target指定 * @param ctx * @param target * @param resultSet * @param config 实现了ProviderConfig注解的注解,如果没有,则为空 * @return */ public List<T> mapping(ExecuteContext ctx, Class target, ResultSet resultSet, Annotation config) throws SQLException; } ``` ExecuteContext代表了执行上下文,比如SqlId,当前的SQLManager,入参等,一般很少需要关注,除非有些高级需求,比如BeetlSQL提供的JSON映射就是用到了ExecuteContext 一个简单的实现如下 ```java public class MyResultSetMapper implements ResultSetMapper<ResultSetObject>{ @Override public List<ResultSetObject> mapping(ExecuteContext ctx, Class target, ResultSet resultSet, Annotation config) throws SQLException { List<ResultSetObject> list = new ArrayList<>(); while(resultSet.next()){ ResultSetObject obj = new ResultSetObject(); obj.setMyId(resultSet.getInt("id")); obj.setMyName(resultSet.getString("name")); list.add(obj); } return list; } } ``` 有俩种方法使用ResultSetMapper,第一种在POJO使用注解 ```java @Data @ResultProvider(MyResultSetMapper.class) public class ResultSetObject{ private Integer myId; private String myName; } ``` 或者使用SQLManager.resultSetMapper(Class resultSetMapperClass),临时设置一次当前查询采用的映射类 ## 混合模型 混合模型。兼具灵活性和更好的维护性。POJO可以实现Tail(尾巴的意思),或者继承TailBean,这样查询出的ResultSet 除了按照pojo进行映射外,无法映射的值将按照列表/值保存。如下一个混合模型: ```java /*混合模型*/ public User extends TailBean{ private int id ; private String name; private int roleId; /*以下是getter和setter 方法*/ } ``` 对于sql语句: ```markdown selectUser === select u.*,r.name r_name from user u left join role r on u.roleId=r.id ..... ``` 执行查询的时候 ```java List<User> list = sqlManager.select(sqlId,User.class,paras); for(User user:list){ System.out.println(user.getId()); System.out.println(user.get("rName")); } ``` 程序可以通过get方法获取到未被映射到pojo的值,也可以在模板里直接 ${user.rName} 显示(对于大多数模板引擎都支持) 另外一种更自由的实现混合模型的方法是在目标Pojo上采用注解@Tail,如果注解不带参数,则默认会调用set(String,Object) 方法来放置额外的查询属性,否则,依据注解的set参数来确定调用方法 ```java @Tail(set="addValue") public class User { private Integer id ; private Integer age ; public User addValue(String str,Object ok){ ext.put(str, ok); return this; } ``` ## Json配置映射 类似MyBatis通过xml提供映射,BeetlSQL3通过实现ResultSetMapper,提供了一个json格式映射 ```java private static final String USER_MAPPING = "{'id':'id','name':'name','deptName':'dept_name'}"; @Data @ResultProvider(JsonConfigMapper.class) @JsonMapper(USER_MAPPING) public static class UserInfo { Integer id; String name; String deptName; } ``` 对于UserInfo,使用了BeetlSQL3提供的JsonConfigMapper对象,JsonConfigMapper会读取@JsonMapper作为配置映射参数,这样,如果查询的SQL结果集是 ``` id,name,detp_name ``` 则会按照`USER_MAPPING`的配置映射到各自属性上 > @JsonMapper 实现了@ProviderConfig()注解,因此,这注解将会传给JsonConfigMapper。了解BeetlSQL3注解如何实现,可以参考《源码解读》 如果需要一对多的映射,也可以使用 ```java private static final String DEPT_MAPPING = "{'id':'id','name':'name','users':{'id':'u_id','name':'u_name'}}"; @Data @ResultProvider(JsonConfigMapper.class) @JsonMapper(DEPT_MAPPING) public static class DepartmentInfo { Integer id; String name; List<UserInfo> users; } ``` 对于DEPT_MAPPING配置,如果结果集满足 ```sql id,name,u_id,u_name ``` 则可以进行映射,并且,u_id,u_name,赋值给UserInfo对象, 此对象合并到**相同(id,name)**的DepartmentInfo的users属性上 JsonConfigMapper可以进行任意复杂的映射。 并将结果集合并 @JsonMapper提供了json配置,也可以指定一个sqlId作为配置,因此配置可以放到markdown文件里 ```java @ResultProvider(JsonConfigMapper.class) //@JsonMapper( // "{'id':'id','name':'name','users':{'id':'u_id','name':'u_name'}}") @sonMapper(resource ="dept.departmentJsonMapping") public class MyDepartment { Integer id; String name; List<MyUser> users; } ``` dept.md内容如下 ```markdown departmentJsonMapping === * MyDepartment ​```json { "id":"id", "name":"name", "users": { "id":"u_id", "name":"u_name" } } ​``` ``` ## Json自动映射 如果查询结果集跟java类定义匹配,则不需要显示的json配置,可以AutoJsonMapper ```java @Data @ResultProvider(AutoJsonMapper.class) public class MyUserView { Integer id; String name; DepartmentEntity dept; } ``` 如上配置,可以自动映射如下查询结果 ```java String sql = "select u.id ,u.name ,d.id `dept.id`,d.name `dept.name` " + " from sys_user u left join department d on d.id=u.department_id"; SQLReady ready = new SQLReady(sql); List<MyUserView> list = sqlManager.execute(ready,MyUserView.class); ``` 之所以成为AutoJsonMapper,是因为AutoJsonMapper会解析POJO类,生成一个json配置,类似如下 ```json { "id":"id", "name":"name", "dept": { "id":"dept.id", "name":"dept.name" } } ``` ## XML映射 未完成,期待3的某个版本实现,实现方式同Json配置映射,希望有人看了JsonConfigMapper或者AutoJsonMapper能参考实现一个 ## 自动Fetch 有时候查询结果出来后需要自动加载额外的数据,类似Hibernate 的关系映射。BeetlSQL3也支持这种自动抓取。不同hibernate的是,他不强制要求有外键关系 > 越来越多数据库设计不考虑外键,这样能提升一些性能。系统维护也好维护。 自动抓取通过@Fetch注解,提醒BeetlSQL3在执行完查询操作后有自动抓取需要完成,BeetlSQL3会解析此POJO的属性,如果一旦有@FetchMany或者@FetchOne,或者@FetchSql,则会执行查询操作 ```java @Data @Table(name="sys_order") @Fetch(level =2) public class CustomerOrder { @AutoID Integer id; String name; Integer customerId; @FetchOne(value="customerId") Customer customer; } @Data @Fetch(level = 2) @Table(name="sys_customer") public class Customer { @AutoID Integer id; String name; @FetchMany("customerId") List<CustomerOrder> order; } @Table(name="sys_order") @Fetch(level =2) public class CustomerOrder2 { @AutoID Integer id; String name; Integer customerId; @FetchSql("select * from sys_customer where id =#{customerId}") Customer customer; @FetchSql("select * from sys_customer s where s.id =#{customerId} order by s.id desc") List<Customer> customers; } ``` @Fetch的level属性表示抓取数据的深度,默认是一层,CustomerOrder设定为2,则不仅仅会自动抓取Customer数据,也会抓取Customer的CustomerOrder数据。如果CustomerOrder设定为3,那么,还会从CustomerOrder再次抓取Customer,实现3层抓取 > BeetlSQL3在Fetch过程中把已经抓取的过数据放入内存里,如果类似数据一旦曾经抓取过,则不会再从数据库里获取。因此不需要担心出现死循环.同时,缓存有利于性能优化,不需要查询数据库 @FetchOne 表示抓取一个,其value值是POJO的一个属性名,该属性名对应的值作为需要·抓取对象的主键,因此CustomerOrder的@FetchOne注解表明了需要使用CustomerOrder.customerId属性值作为主键来查询Customer。因此BeetlSQL3会发起类似如下查询 ```java Integer customerId = getAttrValue(customerOrderIns,"customerId") Customer customer = sqlMqnager.unique(Customer,customerId); ``` 对于Customer对象,需要自动抓取多个CustomerOrder,注解@FetchMany("customerId") 告诉BeetlSQL3.启用模板查询功能查询CustomerOrder,模板的key是“customerId”(也就是CustomerOrder属性customerId),值是POJO的主键,就是Customer.id.因此BeetlSQL会发起类似入如下查询 ```java Integer customerId = getPrimakeyValue(customerIns); CustomerOrder template = new CustomerOrder(); tempalte.setCustomerId(customerId); List<CustomerOrder> list = sqlManager.template(tempalte); ``` BeetlSQL3 的FetchOne操作会进行合并查询,比如查询所有CustomerOrder ```java List<CustomerOrder> orders = sqlManager.all(); ``` 在BeetlSQL3 进行自动抓取的时候,并不会逐一抓取Customer对象,而是调用sqlManager.selectByIds 一次性抓取所有Customer,提高性能呢 当使用自动Fetch,设置level=2的时候,出现A引用B,B又引用A的时候,需要特别设计A和B对象的hashcode方法和equals方法,避免出现无限循环,比如 ```java @Fetch(level = 2) @Table(name="sys_customer") @EqualsAndHashCode(of="id") public class Customer { @AutoID Integer id; String name; @FetchMany("customerId") List<CustomerOrder> order; } ``` 如果没有`@EqualsAndHashCode(of="id")` 那么Customer的hashcode方法包括CustomerOrder,而CustomerOrder又包含Customer,这样导致hashcode无需循环,出现StackOverflowError。 这并不是BeetlSQL的问题 ## AttributeConvert 可以自定义一个属性注解,BeetlSQL上遇到此属性注解,将按照属性注解的执行类去执行映射,比如对手机号码的入库加密,出库解密。比如对JSON对象序列化成字符串到数据库,然后从数据库反序列成成对象。同其他BeetlSQL扩展注解机制类似,实现一个扩展注解,需要使用@Builder注解申明其执行类 ```java @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD, ElementType.FIELD}) @Builder(Base64Convert.class) public static @interface Base64 { } ``` 如上申明一个@Encrypt,用于字段在入库加密,出库解密。其实现类使用@Builder注解申明,本例其执行类是Base64Convert。 执行类必须是一个AttributeConvert的子类 ```java public static class Base64Convert implements AttributeConvert { Charset utf8 = Charset.forName("UTF-8"); public Object toDb(ExecuteContext ctx, Class cls, String name, Object pojo) { String value= (String) BeanKit.getBeanProperty(dbValue,name); byte[] bs = java.util.Base64.getEncoder().encode(value.getBytes(utf8)); return new String(bs,utf8); } public Object toAttr(ExecuteContext ctx, Class cls, String name, ResultSet rs, int index) throws SQLException { String value = rs.getString(index); return new String(java.util.Base64.getDecoder().decode(value),utf8); } } ``` toDb方法用于将属性转化为列,pojo指入库的POJO对象,name是指其属性名称,可以调用BeetlSQL3提供的类BeanKit.getBeanProperty获取对象属性值 toAttr将数据库转化为属性 ```java @Table(name="sys_user") @Data public class UserData{ @AutoID Integer id; @Base64 String name; } ``` 如下是定义了一个@Update注解 ```java @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.METHOD, ElementType.FIELD}) @Builder(UpdateTimeConvert.class) public @interface UpdateTime { } public class UpdateTimeConvert implements AttributeConvert { @Override public Object toDb(ExecuteContext ctx, Class cls,String name, Object pojo){ Date now = new Date(); BeanKit.setBeanProperty(pojo,now,name); return now; } } ``` 这样,在每次入库操作的时候,都取得最新的时间。并调用BeanKit.setBeanProperty赋值给pojo对象,并返回当前时间 。BeetlSQL3通过返回的当前时间做入库操作,因此调用BeanKit.setBeanProperty 不是必须操作。但POJO对象还需要有一个一致的值。 ## BeanConvert 可以为POJO定义一个注解,在sql准备参数前,调用此API,得到一个新Bean,用于参数设定。BeanConvert定义如下 ```java @Plugin public interface BeanConvert { /** * 返回入库之前的对象 * @param ctx * @param obj * @param an 注解信息,可以提供额外参数 * @return */ default Object before(ExecuteContext ctx, Object obj, Annotation an){ return obj; } /** * 返回查询结果后的对象 * @param ctx * @param obj * @param an * @return */ default Object after(ExecuteContext ctx, Object obj, Annotation an){ return obj; } } ``` 比如以AttributeConvert的例子作为说明,可以定义如下BeanEncrypt, 其执行类BeanStringConvert, 注解有个attr方法,标识需要加密的字段 ```java @Retention(RetentionPolicy.RUNTIME) @Target(value = {ElementType.TYPE}) @Builder(BeanStringConvert.class) public @interface BeanEncrypt { String attr(); } ``` BeanStringConvert的实现如下(仅仅简单修改了需要加密的字段,添加一个时间戳) ```java public class BeanStringConvert implements BeanConvert{ public Object before(ExecuteContext ctx, Object obj, Annotation an){ BeanEncrypt beanEncrypt = (BeanEncrypt)an; String attrName = beanEncrypt.attr(); String attrValue = (String)BeanKit.getBeanProperty(obj,attrName); String encryptAttrValue = attrValue+"-"+System.currentTimeMillis(); BeanKit.setBeanProperty(obj,encryptAttrValue,attrName); return obj; } public Object after(ExecuteContext ctx, Object obj, Annotation an){ BeanEncrypt beanEncrypt = (BeanEncrypt)an; String attrName = beanEncrypt.attr(); String encryptAttrValue = (String)BeanKit.getBeanProperty(obj,attrName); String attrValue = encryptAttrValue.split("-")[0]; BeanKit.setBeanProperty(obj,attrValue,attrName); return obj; } } ``` ```java @Table(name="sys_user") @Data @BeanEncrypt( attr="name") public static class UserEntity2{ @Auto Long id ; String name; } ``` ## 枚举 BeetlSQL3默认情况会调用枚举的name方法转化为字符串,存入数据库。当从数据库取出字符串的时候,调用枚举的valueOf方法得到其枚举 ```java public class UserData{ @AutoID Integer id; Name name; } /*使用枚举名存库*/ enum Name{ Li, Zhang; } ``` BeetlSQL3也支持自定义枚举存入数据库的值,使用@EnumValue标注在枚举的属性字段上,如下枚举Name2,取值是属性`str` ```java public enum Name2{ Li("li"), Zhang("zhang"); @EnumValue String str; Name2(String str){ this.str = str; } public String getStr() { return str; } public void setStr(String str) { this.str = str; } } ``` 如果枚举来自第三方,无法使用@EnumValue,则可以使用@EnumMapping,如上Name2如果来自第三方,则可以在POJO中@EnumValue ```java @Table(name="sys_user") @Data public static class UserData3{ @AutoID Integer id; @EnumMapping("str") Name2 name; } ``` > 关于枚举,可以参考源码单元测试EnumSelectTest ## 其他注解 * @UpdateIgnore 作用于属性上,当使用内置的更新语句的时候,会忽略此字段 * @InsertIgnore 作用于属性上,当使用内置的插入语句的时候,会忽略此字段 * @LogicDelete,作用在属性上,告诉BeetlSQL,deleteById语句 生成更新语句,并设置此属性字段为LogicDelete指定的值 ```java @Data @Table(name="sys_user") public class SysUser{ @AutoId Integer id; String name; @LogicDelete(1) Integer flag; } ``` * @Version 注解@Version作用在类型为int,long的属性或者getter方法上,用于乐观锁实现。 ~~~java public class Credit implements Serializable{ private Integer id ; private Integer balance ; @Version private Integer version ; ~~~ 当调用内置的updateById,或者updateTemlateById的时候,被@Version注解的字段将作为where条件的一部分 ~~~ ┏━━━━━ Debug [credit._gen_updateTemplateById] ━━━ ┣ SQL: update `credit` set `balance`=?, `version`=`version`+1 where `id` = ? and `version` = ? ┣ 参数: [15, 1, 5] ┣ 位置: org.beetl.sql.test.QuickTest.main(QuickTest.java:38) ┣ 时间: 4ms ┣ 更新: [1] ┗━━━━━ Debug [credit._gen_updateTemplateById] ━━━ ~~~ * @View注解 在BeetlSQL内置查询语句里,返回的结果集是POJO和列的交集,使用@View注解,可以进一步限定内置SQL语句需要返回的列 如下MyUser对象,有三个字段,id和name,以及photo。当sqlManager发起内置的查询的时候,这三个字段都会返回结果集,如果你想在某些查询下排除photo字段,可以使用@View ```java @Data @Table(name="user") public class MyUser { static interface Simple{} static interface Complex{} @AssignID @View(Simple.class,Complex.class) private Integer id; @View(Simple.class) private String name; @View(Complex.class) private byte[] photo; } ``` 如上name属性,当view是Simple.class的时候将返回,photo属性则只在view是Complex.class的返回。 id则总是返回。 SQLManager.viewType指定此次查询的view,如下指定Simple ```java MyUser cacheItem = SQLManager.viewType(MyUser.Simple).single(MyUser.class,1) ``` 如下指定Complex ```java MyUser userWithPohot = SQLManager.viewType(MyUser.Complex).single(MyUser.class,1) ``` * @TargetSQLManager 如果SQLManager是ConditionalSQLManager子类,则可以在POJO中指明使用哪个SQLManager ```java @Table(name="sys_user") @TargetSQLManager("sysSqlmanager") @Data public class DbUser1 { @AssignID private Long id; private String name; } ``` 由于ConditionalSQLManager涉及了多库,因此这部分将在《BeetlSQL3 多库使用》信息说明 > 源码S6MoreDatabase.conditional 演示了ConditionalSQLManager使用