第一部分 简介
JDBFly
是一个基于JAVA
的持久层开发框架,包含两部分内容:Mybatis
增强、数据库版本跟踪。在简化常规开发的同时屏蔽数据库的差异,通过JDBFly
使开发者更加关注业务本身,如雄鹰般在天空自由翱翔,从繁琐重复的持久层编码中解放出来。
1.1 特性
- 侵入小:对
Mybatis
只做增强,对原有原生代码不会产生影响,仅需调整少量JDBFly
配置代码 - 损耗小:启动即会自动注入内置
Mapper
,提供基本CRUD
,无额外性能损耗 - 强大的CRUD操作:内置通用
Mapper
,仅仅通过少量配置即可实现单表大部分CRUD
操作,更有强大的条件构造器,满足各类使用需求 - 支持多种条件查询: 通过字符串或
Lambda
表达式,方便的编写各类查询条件,开发者可自由使用 - 多种主键策略:支持
6
种主键策略(JDBC自增主键
、方言SQL
、自定义SQL
、动态自定义SQL
、JAVA代码
、主键标识
),可自由配置 - 支持
JPA
方法名称解析:支持JPA
形式通过方法名称定义Mapper
方法 - 支持自定义Mapper扩展:提供自定义
Mapper
入口,快速实现常用方法,并可根据实际业务需要自由组合内置Mapper
- 动态表名:提供自定义动态表名方法,完美解决多租户分表需求
- 乐观锁:内置乐观锁实现,提供并发处理的快速实现
- 自定义XML标签扩展:开发者可自由扩展原生
Mybatis
标签,定制符合自身业务的标签,内置了常用多数据库适配的函数标签 - 多数据库适配:支持
MySQL
、MariaDB
、Oracle
、达梦
等多种数据库,针对特殊数据库提供方言接口可有开发者扩展 - 多种集成方式:支持原生
Java
项目、Spring
、SpringBoot
项目集成 - 数据库版本跟踪:提供通用数据类型,无需关心具体数据库数据映射关系,对数据库版本自动维护升级
1.2 支持数据库
标准功能部分支持所有标准数据库,全功能已经测试通过数据库有:MySql
、MariaDB
、H2
、Oracle
、PostgreSQL
、达梦
。
1.3 实现原理
1.3.1 Mybatis
增强
JDBFly
提供了一些通用的方法,这些通用方法是以接口的形式提供的。接口和方法都使用了泛型,使用该通用方法的接口需要指定泛型的类型。通过Java
反射可以得到接口泛型的类型信息,即对应实体类的信息。在实体类上通过特殊的注解完成实体类与数据库关系的映射。
得到了Mapper
方法、实体类映射关系之后,通过生成符合Mybatis
规范的XML
,最终再交由Mybatis
解析处理。默认情况下JDBFly
不对Mybatis
生成的MappedStatement
进行修改,但是这个规则并不是强制的,开发者可以自定义构建器进行特殊的处理。JDBFly
修改了MapperAnnotationBuilder
默认行为,在Mybatis
解析完Mapper
接口以及关联的XML
文件后,进行后置处理,增加自定义增强Mapper
的扩展处理。
为了实现对Mybatis
默认标签的扩展,JDBFly
重写了EntityResolver
,将静态DTD
文件转换为Java
实体,在运行时动态生成DTD
规则,允许开发者扩展自己的节点解析器的时候修改DTD
实体相关内容。
1.3.2 数据库版本跟踪
JDBFly
对flyway
进行了二次封装,扩展了针对不同数据库匹配相应脚本的方法。
1.4 安装
使用JDbFly
可以直接下载源代码编译或者下载已经编译的jar
文件,如果您是使用maven
来构建项目,也可以直接在pom.xml
中添加JDBFly
的坐标:
<!-- http://mvnrepository.com/artifact/com.jianggujin/JDBFly -->
<dependency>
<groupId>com.jianggujin</groupId>
<artifactId>JDBFly</artifactId>
<version>最新版本</version>
</dependency>
如果使用快照SNAPSHOT
版本需要添加仓库,且版本号为快照版本 点击查看最新快照版本号
<repository>
<id>snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
注意:引入
JDBFly
之后请不要再次引入MyBatis
、MyBatis-Spring
、mybatis-boot-starter
,以避免因版本差异导致的问题。
第二部分 Mybatis
增强
2.1 快速开始
2.1.1 JAVA
集成
2.1.1.1 从XML
中构建 SqlSessionFactory
每个基于MyBatis
的应用都是以一个SqlSessionFactory
的实例为核心的,JDBFly
也同样如此。与直接使用Mybatis
的区别在于SqlSessionFactory
的实例的构建方式,JDBFly
需要将SqlSessionFactoryBuilder
替换为JSqlSessionFactoryBuilder
。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new JSqlSessionFactoryBuilder().build(inputStream);
XML
配置文件中包含了对MyBatis
系统的核心设置,包括获取数据库连接实例的数据源(DataSource
)以及决定事务作用域和控制方式的事务管理器(TransactionManager
),示例如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.jianggujin.dbfly.test.mapper" />
</mappers>
</configuration>
2.1.1.2不使用XML
构建SqlSessionFactory
除了使用XML
创建配置之外,JDBFly
也可以像Mybatis
一样直接从Java
代码创建。
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new JConfiguration(environment);
configuration.addMappers("com.jianggujin.dbfly.mapper");
SqlSessionFactory sqlSessionFactory = new JSqlSessionFactoryBuilder().build(configuration);
这里需要注意的是与XML
创建配置不同的地方,除了需要将SqlSessionFactoryBuilder
替换为JSqlSessionFactoryBuilder
之外,还需要将Configuration
替换为JConfiguration
,实际上通过XML
方式最终获得的配置类也是JConfiguration
。
JSqlSessionFactoryBuilder
是SqlSessionFactoryBuilder
的子类,JConfiguration
是Configuration
的子类,在JDBFly
环境下获得的Configuration
都应该是JConfiguration
。
2.1.2 Spring
集成
2.1.2.1 通过XML
配置
<bean id="sqlSessionFactory" class="com.jianggujin.dbfly.spring.JSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.jianggujin.dbfly.test.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
上面是默认的配置,在配置sqlSessionFactory
时需要将SqlSessionFactoryBean
替换为JSqlSessionFactoryBean
,其他配置与直接使用Mybatis
一样,有些时候需要一些特殊的配置,可能会需要自定义Configuration
,这时候,一定要使用JConfiguration
替换Mybatis
的Configuration
。
<bean id="configuration" class="com.jianggujin.dbfly.mybatis.JConfiguration"/>
<bean id="sqlSessionFactory" class="com.jianggujin.dbfly.spring.JSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configuration" ref="configuration"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.jianggujin.dbfly.test.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
2.1.2.2 通过@JMapperScan
注解配置
Mybatis
官方提供的注解为MapperScan
,在使用注解进行配置的时候,开发者需要使用JDBFly
提供的JMapperScan
替换官方注解。二者注解属性相同。
@Configuration
@JMapperScan("com.jianggujin.dbfly.test.mapper")
public class AppConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().addScript("schema.sql").build();
}
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactory = new JSqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
return sessionFactory.getObject();
}
}
2.1.3 SpringBoot
集成
2.1.3.1 自动装配
JDBFly
支持SpringBoot
自动装配,为了降低集成与迁移成本,JDBFly
的自动装配参数与直接使用mybatis-boot-starter
保持一致,只是在其基础上增加了JDBFly
特有的配置,如果不需要特殊处理,则配置与mybatis-boot-starter
相同。需要注意的是,如果没有结合@JMapperScan
注解直接使用自动装配,自动装配会从SpringBoot
基础包开始扫描,需要在Mapper
接口上增加@Mapper
注解,否则MyBatis
无法判断扫描哪些接口。
例如在properties
配置中:
mybatis.configuration.default-enum-type-handler=org.apache.ibatis.type.EnumOrdinalTypeHandler
mybatis.type-aliases-package=com.jianggujin.dbfly.test.entity
mybatis.dbfly.style=camelhump
mybatis.dbfly
开头的是JDBFly
特有的配置
2.1.2.2 通过@JMapperScan
注解配置
在SpringBoot
环境中同样可以使用@JMapperScan
注解,可以与自动装配配合使用,在不使用该注解的情况下,Mybatis
从基础包开始扫描,通过@JMapperScan
注解可以限定扫描范围,提升扫描装配效率,也是比较推荐的用法。
2.2 对象关系映射
集成完JDBFly
之后,需要做的事情就是将项目中的实体类与数据库中的表进行映射。在JDBFly
中提供了实体与表映射的注解,此章节仅介绍常规的通用注解,对于有特殊功能的注解,比如主键策略、动态表名相关注解将在对应章节介绍。
比如在数据库中有人员信息表:
CREATE TABLE user_info (
id INt AUTO_INCREMENT COMMENT '主键',
user_no VARCHAR(8) NOT NULL COMMENT '人员编号',
user_name VARCHAR(8) NOT NULL COMMENT '人员姓名',
PRIMARY KEY (id)
)
则该表对应的Java
实体如下:
Public class UserInfo {
@JUseGeneratedKeys
private Integer id;
private String userNo;
private String userName;
// 省略gettre和setter
}
@JNameStyle
注解
描述:用于配置Java
实体与数据库表、Java
实体属性与数据库列之间名称的转换关系。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 枚举类型JStyle ,指定转换规则,可选值如下: normal :原值camelhump :驼峰转下划线uppercase :转换为大写lowercase :转换为小写camelhumpAndUppercase :驼峰转下划线大写形式camelhumpAndLowercase :驼峰转下划线小写形式 |
示例:
@JNameStyle(JStyle.camelhump)
private String userId; // => user_id
@JTable
注解
描述:用于指定Java
实体对应的数据库表名称,该注解优先级最高,使用该注解之后名称转换规则
与动态表名
将失效,以value
的值为准。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 对应数据库表名称 |
schema | 否 | 可选schema |
示例:
@JTable("TUC_USER") // TUC_USER
public class User {
// ...
}
@JColumn
注解
描述:用于指定Java
实体属性对应的数据库列名称,该注解优先级最高,使用该注解之后名称转换规则
与将失效,以value
的值为准。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 对应数据库列名称 |
示例:
@JColumn("user_id")
private String userId; // => user_id
@JExcludeInsert
注解
描述:用于在插入操作的时候排除对应属性,即执行通用Mapper
插入方法的时候,添加@JExcludeInsert
注解的属性将会被忽略。
示例:
@JExcludeInsert
private String userId;
@JExcludeSelect
注解
描述:用于在查询操作的时候排除对应属性,即执行通用Mapper
查询方法的时候,添加@JExcludeSelect
注解的属性将会被忽略。
示例:
@JExcludeSelect
private String userId;
@JExcludeUpdate
注解
描述:用于在修改操作的时候排除对应属性,即执行通用Mapper
修改方法的时候,添加@JExcludeUpdate
注解的属性将会被忽略。
示例:
@JExcludeUpdate
private String userId;
@JTransient
注解
描述:用于忽略Java
实体属性,通常情况下用于在Java
实体中冗余属性,但实际该属性没有对应的列,不需要处理,该注解与直接在属性上添加transient
关键字效果是一样的。如果将该注解放在Mapper
的方法上面,默认解析器将忽略该方法。
示例:
@JTransient
private String userId;
@JJdbcType
注解
描述:用于指定Java
实体属性对应的Jdbc
类型,当某些情况Java
实体属性无法被正确识别转换的时候需要添加该注解。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 枚举类型JdbcType ,可选值如下: ARRAY 、BIT 、TINYINT 、SMALLINT 、INTEGER BIGINT 、FLOAT 、REAL 、DOUBLE 、NUMERIC DECIMAL 、CHAR 、VARCHAR 、LONGVARCHAR 、DATE TIME 、TIMESTAMP 、BINARY 、VARBINARY 、LONGVARBINARY NULL 、OTHER 、BLOB 、CLOB 、BOOLEAN CURSOR 、UNDEFINED 、NVARCHAR 、NCHAR 、NCLOB STRUCT 、JAVA_OBJECT 、DISTINCT 、REF 、DATALINK ROWID 、LONGNVARCHAR 、SQLXML 、DATETIMEOFFSET |
示例:
@JJdbcType(JdbcType.VARCHAR)
private String userId;// => #{userId, jdbcType=VARCHAR}
@JTypeHandler
注解
描述:用于指定Java
实体属性对应的TypeHandler
,一般用于特殊类型的转换,比如枚举
的处理。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 类型处理器的实现类 |
示例:
@JTypeHandler(EnumOrdinalTypeHandler.class)
private TrueFalse isDeleted;// => #{userId, typeHandler=org.apache.ibatis.type.EnumOrdinalTypeHandler}
@JUseJavaType
注解
描述:用于告知通用Mapper
,在处理该Java
实体属性的时候是否添加javaType
配置
示例:
@JUseJavaType
private String userId; // => #{userId, javaType=java.lang.String}
@JUseNotEmpty
注解
描述:用于当字符串类型的Java
实体属性作为条件时是否增加!=''
的判断。
示例:
@JUseNotEmpty
private String userId; // <if test="userId != null and userId != ''">...</if>
@JOrder
注解
描述:用于标注Java
实体属性为排序属性,在通用Mapper
进行默认的查询操作时会将其作为排序条件拼接在最终的SQL
语句中。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 否 | 枚举类型JOrderStrategy ,指定排序的策略,默认值为 ASC ,可选值如下:ASC :升序DESC :降序 |
priority | 否 | 排序的优先级,默认值为1 ,值越小,将会优先处理排在排序 SQL 的前面。 |
示例:
@JOrder
private String userId; // => user_id asc
@JOverwriteGlobal
注解
描述:用于覆盖全局的配置,比如在JDBFly
中可以指定排除某些Java
实体属性在查询的时候不查询,但是在部分Java
实体中存在特例,可以通过在对应实体上添加该注解以覆盖全局的配置。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 指定需要覆盖的配置对应的注解类 |
示例:
@JOverwriteGlobal(JExcludeUpdate.class)
private String userId;
2.3 内置Mapper
JDBFly
对常用的CRUD
操作做了通用Mapper
的封装,开发者只需要将自己的Mapper
继承JDBFly
内置的通用Mapper
即可拥有相应的操作方法。比如有一个用户信息实体UserInfo
,需要对该实体进行增删改查的操作,开发者只需要定义一个Mapper
继承JDBFly
通用Mapper
即可。
public interface UserInfoMapper extends JBaseMapper<UserInfo> {
}
JBaseMapper
提供了常用操作的很多方法,有些时候并不需要这么多的操作,针对这种场景,开发者只需要按需选择继承相应的Mapper
,同时JDBFly
允许开发者自己编写Mapper
方法与内置通用Mapper
方法在一个Mapper
中混合使用,默认情况下,开发者自己的方法优先级高于默认方法。
2.3.1 通用内置Mapper
按照具体的操作分类,内置通用Mapper
分类如下:
-
插入类
Mapper 说明 JInsertMapper
保存一个实体, null
的属性也会保存,不会使用数据库默认值JInsertSelectiveMapper
保存一个实体, null
的属性不会保存,会使用数据库默认值 -
删除类
Mapper 说明 JDeleteByConditionMapper
根据 Condition
条件删除数据JDeleteByIdMapper
根据主键字段进行删除,方法参数必须包含完整的主键属性 JDeleteMapper
根据实体属性作为条件进行删除,查询条件使用等号 -
修改类
Mapper 说明 JUpdateByConditionMapper
根据 Condition
条件修改JUpdateByIdMapper
根据主键更新实体全部字段, null
值会被更新JUpdateSelectiveByConditionMapper
根据 Condition
条件修改,仅会修改!=null
的数据JUpdateSelectiveByIdMapper
根据主键更新属性不为 null
的值 -
查询类
Mapper 说明 JSelectByConditionMapper
根据 Condition
条件进行查询JSelectOneMapper
根据实体中的属性进行查询,只能有一个返回值,
有多个结果是抛出异常,查询条件使用等号JSelectOneByConditionMapper
根据 Condition
条件进行查询,只能有一个返回值,
有多个结果是抛出异常JSelectMapper
根据实体中的属性值进行查询,查询条件使用等号 JSelectCountMapper
根据实体中的属性查询总数,查询条件使用等号 JSelectCountAllMapper
查询总数 JSelectCountByConditionMapper
根据 Condition
条件进行查询总数JSelectByIdMapper
根据主键字段进行查询,
方法参数必须包含完整的主键属性,查询条件使用等号JSelectAllMapper
查询全部结果 JExistsMapper
根据实体中的属性判断数据是否存在,查询条件使用等号 JExistsByIdMapper
根据主键字段判断数据是否存在,
方法参数必须包含完整的主键属性,查询条件使用等号JExistsByConditionMapper
根据 Condition
条件判断数据是否存在
上面介绍的Mapper
为最小的操作单位,JDBFly
按照对应的操作分类提供了汇总的基础Mapper
:JBaseInsertMapper
、JBaseDeleteMapper
、JBaseUpdateMapper
、JBaseSelectMapper
,分别对应了插入
、删除
、修改
、查询
的操作。在大部分情况下,开发者仅需要继承基础的JBaseMapper
,该Mapper
包含了所有通用的内置方法。
2.3.2 扩展内置Mapper
扩展内置Mapper
无法保证通用性,开发者按需选择。
Mapper | 说明 |
---|---|
JInsertListMapper | 批量保存实体,null 的属性也会保存,不会使用数据库默认值 |
JDeleteByIdsMapper | 根据主键集合删除 |
JSelectByIdsMapper | 根据主键集合查询 |
JSelectOneOptionalByConditionMapper | 根据Condition 条件进行查询,只能有一个返回值,有多个结果是抛出异常,需要依赖 mybatis3.5+ |
JSelectOneOptionalMapper | 根据实体中的属性进行查询,只能有一个返回值, 有多个结果是抛出异常,查询条件使用等号, 需要依赖 mybatis3.5+ |
JSelectOptionalByIdMapper | 根据主键字段进行查询, 方法参数必须包含完整的主键属性,查询条件使用等号, 需要依赖 mybatis3.5+ |
JSelectPageAllMapper | 查询全部结果,支持分页 |
JSelectPageByConditionMapper | 根据Condition 条件进行查询,支持分页 |
JSelectPageMapper | 根据实体中的属性值进行查询,查询条件使用等号,支持分页 |
JBasePageMapper | 基础Mapper ,包含分页 |
2.4 方法名称解析
使用内置的通用Mapper
,可以简化开发,但是有些时候也会有点麻烦,比如通过固定属性作为条件、查询部分属性等,为了进一步简化对数据库的操作,JDBFly
支持像JPA
那样通过方法名称定义相关的操作。如果需要开启方法名称解析,只需要让对应的Mapper
继承JMethodMapper
即可,当然JBaseMapper
已经默认开启了方法名称解析。
public interface UserInfoMapper extends JMethodMapper<UserInfo> {
findUserIdAndUserNameByid(Integer id);
}
2.4.1 命名规则
使用方法名称解析功能,Mapper方法需要按照指定的命名规范才能被正常识别与解析,目前方法名称解析仅支持查询和删除,对应的方法名命名规则如下:
查询:[(select|find|read|get|query)[Distinct][Exclude][selectProperties]By][whereProperties][OrderBy(orderProperties)]
删除:(delete|remove)By[whereProperties]
各部分说明:
- ():表示必选项
- []:表示可选项
- Distinct:存在该关键词将为查询语句添加
DISTINCT
- Exclude:存在该关键词表示后面的查询属性为需要排除的
- selectProperties:该表达式表示需要查询的属性,不存在该部分则默认查询全部属性,属性首字母大写且属性与属性之间需要使用
And
分隔 - whereProperties:该表达式表示
where
条件需要使用的属性,分段条件之间通过Or
或And
拼接,Or
优先级大于And
,分段条件格式为:peoperty[Keyword]
,属性首字母大写,需要注意的是对应方法形参数量必须与拆分后的条件参数数量一致 - orderProperties:该表达式表示
Order by
部分需要使用的属性,属性首字母大写,多个排序条件之间使用Asc
或Desc
分隔,如果该部分表达式不存在则是使用默认的排序配置
作为条件可用关键词:
关键词 | 示例 | SQL代码段 |
---|---|---|
IsNotNull 、NotNull | findByIdIsNotNull 、findByIdNotNull | id is not null |
IsNull 、Null | findByIdIsNull 、findByIdNull | id is null |
IsNot 、Not 、NotEquals 、Ne | findByIdIsNot 、findByIdNot 、findByIdNotEquals findByIdNe | id <> ?1 |
Is 、Equals 、Eq | findByIdIs 、findByIdEquals findByIdEq | id = ?1 |
IsGreaterThan 、GreaterThan 、Gt | findByIdIsGreaterThan 、findByIdGreaterThan 、findByIdGt | id > ?1 |
IsGreaterThanEqual 、GreaterThanEqual 、Ge | findByIdIsGreaterThanEqual 、findByIdGreaterThanEqual 、findByIdGe | id >= ?1 |
IsLessThan 、LessThan 、Lt | findByIdIsLessThan 、findByIdLessThan 、findByIdLt | id < ?1 |
IsLessThanEqual 、LessThanEqual 、Le | findByIdIsLessThanEqual 、findByIdLessThanEqual 、findByIdLe | id <= ?1 |
IsBefore 、Before | findByIdIsBefore 、findByIdBefore | id < ?1 |
IsBeforeEqual 、BeforeEqual | findByIdIsBeforeEqual 、findByIdBeforeEqual | id <= ?1 |
IsAfter 、After | findByIdIsAfter 、findByIdAfter | id > ?1 |
IsAfterEqual 、AfterEqual | findByIdIsAfterEqual 、findByIdAfterEqual | id >= ?1 |
IsNotIn 、NotIn | findByIdIsNotIn 、findByIdNotIn | id not in (?1) |
IsIn 、In | findByIdIsIn 、findByIdIn | id in (?1) |
IsBetween 、Between | findByIdIsBetween 、findByIdBetween | id between ?1 and ?2 |
IsNotBetween 、NotBetween | findByIdIsNotBetween 、findByIdNotBetween | id not between ?1 and ?2 |
IsLike 、Like | findByIdIsLike 、findByIdLike | id like ?1 |
IsNotLike 、NotLike | findByIdIsNotLike 、findByIdNotLike | id not like ?1 |
2.4.2 使用注解解析
在使用方法名称解析的时候,在某些情况下可能会导致方法名称冗长,可以在方法上面搭配注解来缩短方法名称。存在注解优先级大于方法名称解析结果且不同部分之间互不影响。
public interface UserInfoMapper extends JMethodMapper<UserInfo> {
@JProperty(exclude = { "id" })
@JOrderBy(value = { @JOrderProperty(value = "userId", strategy = JOrderStrategy.DESC) })
List<UserInfo> findAll();
}
@JProperty
注解
描述:用于配置选择或排除的列对应的属性,等价于查询的[(select|find|read|get|query)[Distinct][Exclude][selectProperties]By]
部分。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 否 | 用于指定选择的列对应的属性, 优先级高于 exclude ,如果与exclude 都为空则查询全部属性。 |
exclude | 否 | 用于指定排除选择的列对应的属性 |
示例:
@JProperty({"id", "userName"})
public User findByUserId(String userId);
@JWhere
注解
描述:用于配置条件部分的属性以及条件,等价于查询或删除的[whereProperties]
部分。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 一组动态条件,属性值为@JCriteria 注解 |
示例:
@JWhere(@JCriteria({ @JCriterion("id"), @JCriterion("usrName") }))
User selectIdAndUserNameBy(Integer id, String userName);
@JCriteria
注解
描述:用于配置一个动态条件的分组内容。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 动态条件,属性值为@JCriterion 注解 |
isOr | 否 | 是否为OR 条件 |
示例:
@JWhere(@JCriteria({ @JCriterion("id"), @JCriterion("usrName") }))
User selectIdAndUserNameBy(Integer id, String userName);
@JCriterion
注解
描述:用于配置一个动态条件。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 属性名称 |
isOr | 否 | 是否为OR 条件 |
type | 否 | 枚举类型JType ,指定排序的策略,默认值为 EQUALS ,可选值如下:IS_NOT_NULL 、IS_NULL 、NOT_EQUALS EQUALS 、GREATER_THAN 、GREATER_THAN_EQUAL LESS_THAN 、LESS_THAN_EQUAL 、BEFORE BEFORE_EQUAL 、AFTER 、AFTER_EQUAL NOT_IN 、IN 、NOT_BETWEEN BETWEEN 、NOT_LIKE 、LIKE |
示例:
@JWhere(@JCriteria({ @JCriterion("id"), @JCriterion("usrName") }))
User selectIdAndUserNameBy(Integer id, String userName);
@JOrderBy
注解
描述:用于在查询的时候使用的排序条件,等价于查询的[OrderBy(orderProperties)]
部分。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 排序属性,属性值为@JOrderProperty 注解 |
示例:
@JOrderBy(@JCriteria("id"))
User selectIdAndUserNameBy(Integer id, String userName);
@JOrderProperty
注解
描述:用于配置排序属性以及排序策略。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 排序属性 |
strategy | 否 | 枚举类型JOrderStrategy ,指定排序的策略,默认值为 ASC ,可选值如下:ASC :升序DESC :降序 |
示例:
@JOrderBy(@JCriteria("id"))
User selectIdAndUserNameBy(Integer id, String userName);
2.5 条件构造器
在JDBFly
内置的通用Mapper
中,以ByCondition
结尾的方法允许开发者通过复杂的条件进行操作。
JDBFly
默认不支持MBG
生成Example
,如果需要可由开发者自行扩展
JCondition condition = new JCondition(UserInfo.class);
condition.and().andEqualTo("id", 1);
List<UserInfo> list = mapper.selectByCondition(condition);
2.5.1 动态条件
通过JCondition
对象的and
或or
方法可以获得JCriteria
对象,利用该对象可以实现复杂的条件的动态组合。对应方法后缀效果如下:
方法后缀 | 参考SQL代码段 |
---|---|
IsNull | column is null |
IsNotNull | column is not null |
EqualTo 、Et | column = value |
NotEqualTo 、Ne | column <> value |
GreaterThan 、Gt | column > value |
GreaterThanOrEqualTo 、Ge | column >= value |
LessThan 、Lt | column < value |
LessThanOrEqualTo 、Le | column <= value |
In | column in (value) |
NotIn | column not in (value) |
Between | column between value1 and value2 |
NotBetween | column not between value1 and value2 |
Like | column like value |
LikeLeft | column like '%value' |
LikeRight | column like 'value%' |
NotLike | column not like value |
NotLikeLeft | column not like '%value' |
NotLikeRight | column not like 'value%' |
Condition | 手写条件,以实际为准 |
EqualTo | 将不为空的字段参数作为相等查询条件 |
AllEqualTo | 将所有字段参数作为相等查询条件,如果字段为null ,则为is null |
and
或or
的前缀表示生成的条件使用AND
或者OR
,方法后缀规则一致,不再重复赘述。除了上述通过and
或or
前缀开头的方法构造动态条件以外,还可以通过更简单的链式调用,方法功能与上述方法后缀一致,仅需要将首字母变为小写,默认情况下链式调用的链接方式与获得条件对象时一致,如果需要更改,仅需要调用and()
或or()
方法切换当前链式调用状态。比如有如下条件:
condition.and().andEqualTo("id", 1).andLikeRight("userName", "jiang");
可以简写为:
condition.and().equalTo("id", 1).likeRight("userName", "jiang");
部分条件方法存在简化同义方法,比如上面的例子可以进一步简化为:
condition.and().eq("id", 1).likeRight("userName", "jiang");
2.5.2 排序
JCondition condition = new JCondition(UserInfo.class);
condition.orderBy("id").asc().orderBy("userId").desc();
mapper.selectByCondition(condition));
排序有两种方式,第一种:使用JCondition
对象的orderBy
方法,通过链式调用构建排序规则;第二种方式:使用JCondition
对象的setOrderBySql
方法直接设置排序SQL
片段。
2.5.3 去重
JCondition condition = new JCondition(UserInfo.class);
condition.setDistinct(true);
mapper.selectByCondition(condition));
去重需要调用JCondition
对象的setDistinct
方法并设置为true
。
2.5.4 排他锁
JCondition condition = new JCondition(UserInfo.class);
condition.setForUpdate(true);
mapper.selectByCondition(condition));
排他锁需要调用JCondition
对象的setsetForUpdate
方法并设置为true
。
2.5.5 查询或排除列
JCondition condition = new JCondition(UserInfo.class);
condition.selectProperties("id", "userId");
mapper.selectByCondition(condition));
JCondition
中提供查询属性和排除属性的方法,分别为selectProperties
和excludeProperties
。查询属性优先级高于排除属性,如果不设置,则默认查询全部属性。
2.5.6 Lambda
形式调用
JLambdaCondition<UserInfo> condition = new JLambdaCondition<>(UserInfo.class);
condition.lambdaAnd().andEqualTo(UserInfo::getId, 1);
mapper.selectByCondition(condition);
JCondition
为开发者提供了灵活的设置方式,但是通过字符串形式编写条件,容易出现编写错误、或者实体类升级之后,属性修改不同步出现一些错误,JDBFly
同时提供了JLambdaCondition
,可以通过lambda
表达式定义属性,将错误提升到编译阶段,提前暴露问题,JLambdaCondition
同时提供了通过lambda
调用以及JCondition
的所有能力,使用更为灵活。
2.6 主键策略
JDBFly
中提供了6
种主键策略,可由开发者自由组合使用。每种主键策略对应一个注解,多个注解联合使用时优先选择优先级别较高的注解配置,忽略优先级别低的注解。
@JUseGeneratedKeys
注解
描述:第一优先级主键策略,表示是否使用JDBC
方式获取主键,其效果等价于在XML
文件中使用useGeneratedKeys="true"
的形式,同一个实体类中只允许在一个属性上使用该注解。
示例:
@JUseGeneratedKeys
private Integer id;
@JUseIdentityDialect
注解
描述:第二优先级主键策略,根据方言配置的数据库类型取回主键,其效果等价于在XML
文件中使用selectKey
,同一个实体类中只允许在一个属性上使用该注解。
示例:
以内置的MySQL
方言为例,假设有如下实例配置:
Public class UserInfo {
@JUseIdentityDialect
private Integer id;
private String userNo;
private String userName;
// 省略gettre和setter
}
则会生成如下XML
片段:
<insert>
<selectKey keyProperty="id" order="AFTER" resultType="int">
SELECT LAST_INSERT_ID()
</selectKey>
</insert>
@JUseSql
注解
描述:第三优先级主键策略,根据配置的获取主键的SQL
与主键生成顺序获取主键,其效果等价于在XML
文件中使用selectKey
,同一个实体类中只允许在一个属性上使用该注解。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 取主键的SQL |
order | 否 | 枚举类型JKeyOrder ,指定主键生成顺序,默认值为 BEFORE ,可选值如下:AFTER :insert 后执行SQL BEFORE :insert 前执行SQL |
示例:
@JUseSql(value = "SELECT LAST_INSERT_ID()", order = JKeyOrder.AFTER)
private Integer id;
@JUseSqlGenerator
注解
描述:第四优先级主键策略,结合SQL
生成器JSqlGenerator
动态生成获取主键的SQL
与主键生成顺序获取主键,其效果等价于在XML
文件中使用selectKey
,同一个实体类中只允许在一个属性上使用该注解。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | SQL 生成器实现类 |
order | 否 | 枚举类型JKeyOrder ,指定主键生成顺序,默认值为 BEFORE ,可选值如下:AFTER :insert 后执行SQL BEFORE :insert 前执行SQL |
示例:
@JUseSqlGenerator(IdSqlGenerator.class)
private Integer id;
@JUseIdGenerator
注解
描述:第五优先级主键策略,通过Java
方式生成主键,需要和主键生成器JIdGenerator
结合使用,执行插入操作之前会自动将生成的主键注入到对应实体中。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 主键生成器实现类 |
示例:
@JUseSqlGenerator(IdGenerator.class)
private Integer id;
@JId
注解
描述:第六优先级主键策略,仅仅标记当前属性为主键,具体操作由开发者通过代码控制。
示例:
@JId
private String userId;
2.7 动态表名
在多租户的场景下,可能会需要按照租户进行分表,JDBFly
允许开发者动态指定表名,前文中的@JTable
注解用于指定静态表名,当需要动态表名处理的时候需要使用@JUseDynamicTableName
注解。需要注意的是@JUseDynamicTableName
注解不能与@JTable
注解同时使用,否则动态表名不生效。
JDBFly
中默认提供了一种动态表名实现JDynamicTableNameGenerator
,需要实体类实现JDynamicTableName
接口,在对该实体进行数据库操作时,会将表名修改为实际返回的表名。
@JUseDynamicTableName
注解
描述:用于配置指定实体使用动态表名。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 动态表名生成器的实现类 |
name | 否 | 默认的表名,等价于使用静态表名的形式 |
schema | 否 | 可选schema |
示例:
@JUseDynamicTableName(JDynamicTableNameGenerator.class)
public class User {
// ...
}
2.8 乐观锁
当要修改一条记录的时候,希望与数据库记录同步没有被其他人修改的时候,可以使用JDBFly
提供的乐观锁功能。开发者仅需要在开乐观锁的属性上增加@JVersion
注解。在使用JDBFly
内置方法进行修改操作时,会自动设置新的版本号,需要注意的是在使用乐观锁功能时不应该将乐观锁属性排除查询。
JDBFly
内置了一种默认的乐观锁版本号生成器实现JDefaultVersionGenerator
,支持的数据类型有:Integer
、Long
、Timestamp
、Date
。Integer
、Long
会在原有数据基础上加1
,Timestamp
、Date
会取当前时间。
@JVersion
注解
描述:版本号,用于乐观锁,一个实体类中只能存在一个,不能与主键策略一起使用,优先级高于JGeneratedValue
。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 版本号生成器实现类 |
示例:
@JVersion
private Integer version;
2.9 自动生成属性值
通常情况下,创建数据库表的时候会冗余创建时间与最后一次修改时间,对应再进行相关插入、修改操作的时候需要手动设置相关时间的数据,JDBFly
提供了@JGeneratedValue
注解用于自动生成指定属性的数据。需要注意的是@JGeneratedValue
注解与主键策略注解或@JVersion
注解共同使用无效,仅会在insert
、update
操作下生效,且受是否允许添加与修改限制。
JDBFly
内置了一种默认的日期类型数据的生成器实现JDateValueGenerator
,支持的数据类型有:Date
、java.sql.Timestamp
、java.sql.Date
、java.sql.Time
、String
,使用String
数据类型的时候需要结合@JFormat
注解指定格式化模板,默认模板为:yyyyMMddHHmmss
。
@JVersion
注解
描述:自动生成指定属性的数据,与主键策略注解共同使用无效,仅会在insert
、update
操作下生效,且受是否允许添加与修改限制。
属性:
属性名 | 必填 | 说明 |
---|---|---|
value | 是 | 数据生成器实现类 |
示例:
@JGeneratedValue(JDateValueGenerator.class)
private Date createTime;
2.10 自定义Mapper
扩展
当JDBFly
内置通用Mapper
不满足开发需求的时候,开发者可以按照规范自定义自己业务相关的Mapper
。这里我们用一个内置的Mapper
来介绍自定义Mapper
扩展的步骤,本质上是一样的,只不过内置Mapper
是已经写好的,仅此而已。
2.10.1 编写Mapper
接口
我们以JSelectAllMapper
为例,首先我们需要创建JSelectAllMapper
接口,并增加泛型参数,形如:
public interface JSelectAllMapper<T> {
}
一般情况下,建议开发者在一个Mapper接口中只做一件事情,接下来就需要在刚刚创建的接口中添加对应的Mapper方法。
public interface JSelectAllMapper<T> {
@JMappedStatementProvider(type = JBaseSelectProvider.class)
List<T> selectAll();
}
可以看到在定义的selectAll
方法上使用了@JMappedStatementProvider
注解,该注解用于指定生成此方法XML
片段的服务实现类与对应实现方法。@JMappedStatementProvider
注解包含两个属性type
和method
,type
用于指定生成此方法XML
片段的服务实现类;method
用于指定对应的实现方法,该方法可以是静态方法,也可以是实例方法,但是方法形参必须是(JConfiguration configuration, Class<?> mapperClass, Method method, Document document, Element mapperElement)
,否则无法解析,method
属性的默认值为dynamicMethod
,表示动态映射方法,比如例子中服务实现类是JBaseSelectProvider
,Mapper
方法名称为selectAll
,那么对应的实现方法就是JBaseSelectProvider#selectAll(JConfiguration configuration, Class<?> mapperClass, Method method, Document document, Element mapperElement)
,若不使用动态映射方法,则可以指定方法名称。
2.10.2 编写XML
片段实现方法
有了Mapper
接口与实现方法的映射关系,接下来就是编写真正的实现方法。
public class JBaseSelectProvider {
public void selectAll(JConfiguration configuration, Class<?> mapperClass, Method method, Document document,
Element mapperElement) {
JEntity entity = JProviderHelper.resolveEntity(configuration, mapperClass, method);
String resultMap = JProviderHelper.checkAndCreateResultMap(configuration, document, mapperElement, entity);
Element selectElement = JElementBuilder.buildSelectElement(document, method, resultMap, null);
JProviderHelper.selectAllColumns(document, selectElement, entity);
JProviderHelper.fromTable(configuration, document, selectElement, entity);
JProviderHelper.orderByDefault(document, selectElement, entity);
mapperElement.appendChild(selectElement);
}
}
实现方法的本质上就是通过Java
的Dom
方式编辑Mapper
对应的XML
,开发者只需要按照方法生成对应的Element
之后将其添加到根元素mapperElement
中即可。
2.11 自定义MappedStatement
构造器
JDBFly
内置的构造器支持XML与方法名称的解析,如果实际业务有更加个性化的需求,或者开发者想提供自己的解析方式,JDBFly
支持自定义的JMappedStatementBuilder
,不过并不建议这样做。
2.11.1 编写构造器实现
public interface JMappedStatementBuilder {
/**
* 解析
*
* @param configuration
* @param mapperClass
*/
void parse(JConfiguration configuration, Class<?> mapperClass);
/**
* 判断是否支持
*
* @param configuration
* @param mapperClass
* @return
*/
boolean support(JConfiguration configuration, Class<?> mapperClass);
}
自定义MappedStatement
构造器,开发者需要实现JMappedStatementBuilder
接口,并重写parse
与support
方法,JDBFly
在解析Mapper
接口时会从MappedStatement构造器注册器
获取所有已注册的MappedStatement
构造器,通过support
方法验证构造器是否支持对应Mapper
接口,如支持则会执行parse
真正的解析,即使Mapper
接口被构造器解析之后,当查找到后续构造器依然支持解析该Mapper
,会再次解析,建议开发者除非有必要对MappedStatement
对象进行二次处理,否则应该先判断是否有未解析的方法,避免多构造器重复处理。
String namespace = mapperClass.getName();
if (configuration.isResourceLoaded(namespace)) {
return;
}
Method[] methods = JMybatisUtils.filterUnParseMethods(configuration, mapperClass);
if (methods == null || methods.length == 0) {
return;
}
2.11.2 注册构造器实现
开发完自己的构造器之后,需要将构造器加入JDBFly
的配置JDBFlyConfiguration
中,通过调用JDBFlyConfiguration
的getMappedStatementBuilderRegistry()
方法可以获得构造器的注册器,通过注册器的addMappedStatementBuilder
添加自定义的构造器,构造器名称默认取实现类的简单类名,出现同名时后添加的构造器将覆盖已有构造器。
2.12 自定义XML
标签扩展
自定义XML
标签用于Mybatis
提供的默认标签不满足开发需要的场景,比如在JDBFly
中,为了屏蔽数据库的差异,对于一些常用函数进行了XML
标签的扩展,这部分标签全部以fn_
开头,开发者只需要使用内置的扩展标签即可实现不同数据库的差异屏蔽,当然,JDBFly
无法穷举所有的实际开发场景,所以有类似需求的时候,开发者可以通过JDBFly
进行原生Mybatis
标签的扩展。
2.12.1 编写节点解析器
开发者需要实现JNodeHandler
接口来开发自定义节点解析器,该接口有一个需要实现的方法handleNode
和一个accept
默认方法。
public interface JNodeHandler extends Consumer<JDTD> {
void handleNode(JXMLScriptBuilder builder, XNode nodeToHandle, List<SqlNode> targetContents);
@Override
default void accept(JDTD dtd) {
}
}
handleNode
方法用于解析标签并生成相应的SqlNode
,以原生的bind
标签的解析器为例:
public class JBindHandler implements JNodeHandler {
@Override
public void handleNode(JXMLScriptBuilder builder, XNode nodeToHandle, List<SqlNode> targetContents) {
final String name = nodeToHandle.getStringAttribute("name");
final String expression = nodeToHandle.getStringAttribute("value");
final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
targetContents.add(node);
}
}
JBindHandlerder
的实现与Mybatis
官方的是一致的,只是类名与实现接口改变,通过JDBFly
进行自定义XML
标签扩展与Mybatis
的内置实现相同,通过解析标签属性与内容生成SqlNode
,开发者也可以根据实际情况创建自己的SqlNode
。
accept
方法用于对DTD
的验证,因为JDBFly
扩展了XML
标签,在解析XML
的时候会对XML
文档的合法性进行校验,而自定义标签是Mybatis
无法识别的,所以在JDBFly
中将静态的DTD
文件转变为Java
对象,可以动态进行修改,当开发者扩展了XML
标签之后,需要实现accept
方法以添加或修改为符合实际节点的规则。
public class JFnHandler implements JNodeHandler {
@Override
public void handleNode(JXMLScriptBuilder builder, XNode nodeToHandle,
List<SqlNode> targetContents) {
JConfiguration configuration = (JConfiguration) builder.getConfiguration();
JDialect dialect = configuration.getDBFlyConfiguration().getDialect();
if (!dialect.handleFnNode(builder, nodeToHandle, targetContents)) {
throw new JDBFlyException("无法解析{}函数标签", nodeToHandle.getName());
}
}
@Override
public void accept(JDTD dtd) {
JDTDItem empty = new JDTDEmpty();
JDTDPCData PCDATA = new JDTDPCData();
/****** 字符串函数 ******/
// 参数第一个字符的ASCII码
dtd.add(new JDTDElement("fn_ascii", PCDATA, new JDTDAttribute("str", "CDATA")));
// 字符串拼接
dtd.add(new JDTDElement("fn_concat", new JDTDMixed(new JDTDName("value", JDTDCardinal.ONEMANY))));
// 返回字符串的字符数
dtd.add(new JDTDElement("fn_char_length", PCDATA, new JDTDAttribute("str", "CDATA")));
// 返回字符串的字节数
dtd.add(new JDTDElement("fn_bit_length", PCDATA, new JDTDAttribute("str", "CDATA")));
// 返回指定的子字串的位置
dtd.add(new JDTDElement("fn_position", empty, new JDTDAttribute("sub", "CDATA"),
new JDTDAttribute("str", "CDATA")));
// 把字串str里出现地所有子字串from替换成子字串to
dtd.add(new JDTDElement("fn_replace", empty, new JDTDAttribute("str", "CDATA"),
new JDTDAttribute("from", "CDATA"), new JDTDAttribute("to", "CDATA")));
// 从字符串str的start位置截取长度为length的子字符串
dtd.add(new JDTDElement("fn_sub_str", empty, new JDTDAttribute("str", "CDATA"),
new JDTDAttribute("start", "CDATA"), new JDTDAttribute("length", "CDATA")));
// 将字符串转换为大写
dtd.add(new JDTDElement("fn_upper", PCDATA, new JDTDAttribute("str", "CDATA")));
// 将字符串转换为小写
dtd.add(new JDTDElement("fn_lower", PCDATA, new JDTDAttribute("str", "CDATA")));
// 去掉字符串开始和结尾处的空格
dtd.add(new JDTDElement("fn_trim", PCDATA, new JDTDAttribute("str", "CDATA")));
// 去掉字符串开始处的空格
dtd.add(new JDTDElement("fn_ltrim", PCDATA, new JDTDAttribute("str", "CDATA")));
// 去掉字符串结尾处的空格
dtd.add(new JDTDElement("fn_rtrim", PCDATA, new JDTDAttribute("str", "CDATA")));
/****** 数字函数 ******/
/****** 日期函数 ******/
/****** 类型转换函数 ******/
// 日期格式化
dtd.add(new JDTDElement("fn_date_format", PCDATA, new JDTDAttribute("format", "CDATA", JDTDDecl.REQUIRED),
new JDTDAttribute("date", "CDATA")));
// 字符串转日期
dtd.add(new JDTDElement("fn_str_to_date", PCDATA, new JDTDAttribute("format", "CDATA", JDTDDecl.REQUIRED),
new JDTDAttribute("str", "CDATA")));
/****** 其他函数 ******/
dtd.add(new JDTDElement("fn_nvl", empty, new JDTDAttribute("exp1", "CDATA", JDTDDecl.REQUIRED),
new JDTDAttribute("exp2", "CDATA", JDTDDecl.REQUIRED)));
/****** 辅助标签 ******/
// 用于分隔参数
dtd.add(new JDTDElement("value", PCDATA));
String[] names = { "select", "insert", "selectKey", "update", "delete", "sql", "trim", "where", "set",
"foreach", "when", "otherwise", "if" };
for (String name : names) {
JDTDElement element = dtd.get(name);
JDTDMixed mixed = (JDTDMixed) element.getContent();
mixed.add("fn_ascii", "fn_concat", "fn_char_length", "fn_bit_length", "fn_position", "fn_replace",
"fn_sub_str", "fn_upper", "fn_lower", "fn_trim", "fn_ltrim", "fn_rtrim", "fn_date_format",
"fn_str_to_date", "fn_nvl");
}
}
}
2.12.2 注册节点解析器
开发完自己的节点解析器之后,需要将节点解析器设置到JXMLScriptBuilder
中,通过调用JXMLScriptBuilder
的setNodeHandler
方法可以将节点解析器加入到脚本构建器中。因为节点解析器是针对节点进行解析的,所以在设置节点解析器的时候需要告诉JDBFly
解析器可以解析的标签名称,有两种实现方式:
第一种:节点解析器实现JCustomNodeHandler
接口的nodeNames
方法,返回值为可以解析的XML
标签名称数组。
第二种:取节点解析器的简单类名,如果名称以Handler
结尾将自动截断,并将类名首字母小写作为可以解析的XML标签名称,如:If -> if
、IfHandler -> if
。
需要注意的是如果多个节点解析器同时支持一个XML
标签,后添加的会覆盖之前添加的。
2.12.3 开发阶段验证XML文档
自定义的XML
标签在运行阶段解析的时候需要动态生成,防止解析验证失败,同样的,开发好的自定义标签在开发阶段编辑XML
的时候,开发工具默认会提供辅助操作,比如智能提示、错误验证等,这时候自定义的标签会出现问题,虽然不影响最终程序运行,但是总会让开发者心情不美丽,这时候可以通过生成新的DTD
文件,通过指定URI
的方式将XML
验证的DTD
文件指向自己修改后的文件,避免开发工具直接从网络获取官方DTD
文件。开发者可通过如下代码生成最终的修改后的DTD
文件。
JDTD dtd = new JDBFlyMapperDTDFactory().createDTD();
dtd.write(new PrintWriter(new FileWriter("mybatis-3-mapper.dtd")));
如果使用
XML
方式配置Mybatis
也可以通过该种方式,只需要将JDBFlyMapperDTDFactory
替换为JDBFlyConfigDTDFactory
即可
2.12.4 内置扩展标签
2.12.4.1 函数类
2.12.4.1.1 字符串函数
fn_ascii
描述:返回字符串第一个字符的ASCII
码。
属性:
属性名 | 必填 | 说明 |
---|---|---|
str | 否 | 字符串内容,属性与元素文本二选一,优先元素文本 |
示例:
<fn_ascii>'jianggujin'</fn_ascii> <!-- 106 -->
<fn_ascii str="'jianggujin'"/> <!-- 106 -->
fn_concat
描述:字符串拼接,内部包含value
标签,一个标签表示一个待拼接字符串,应至少包含两个非空value
标签。
示例:
<fn_concat><value>'jiang'</value><value>'gujin'</value></fn_concat>
<!-- 'jianggujin' -->
fn_char_length
描述:返回字符串的字符数。
属性:
属性名 | 必填 | 说明 |
---|---|---|
str | 否 | 字符串内容,属性与元素文本二选一,优先元素文本 |
示例:
<fn_char_length>'jianggujin'</fn_char_length> <!-- 10 -->
<fn_char_length str="'jianggujin'"/> <!-- 10 -->
fn_bit_length
描述:返回字符串的字节数。
属性:
属性名 | 必填 | 说明 |
---|---|---|
str | 否 | 字符串内容,属性与元素文本二选一,优先元素文本 |
示例:
<fn_bit_length>'jianggujin'</fn_bit_length> <!-- 10 -->
<fn_bit_length str="'jianggujin'"/> <!-- 10 -->
fn_position
描述:返回字符串在目标字符串中的位置。
属性:
属性名 | 必填 | 说明 |
---|---|---|
sub | 是 | 子字符串内容 |
str | 是 | 目标字符串内容 |
示例:
<fn_position sub=“gu” str=“jianggujin”/> <!-- 6 -->
fn_replace
描述:把字符串str
里出现地所有子字符串from
替换成子字符串to
。
属性:
属性名 | 必填 | 说明 |
---|---|---|
str | 是 | 源字符串 |
from | 是 | 需要替换的字符串 |
to | 是 | 替换的结果字符串 |
示例:
<fn_replace str=“jianooujin” from="oo" to="gg"/> <!-- 'jianggujin' -->
fn_sub_str
描述:从字符串str
的start
位置截取长度为length
的子字符串。
属性:
属性名 | 必填 | 说明 |
---|---|---|
str | 是 | 待截取的字符串 |
start | 是 | 开始位置 |
`length | 是 | 截取长度 |
示例
<fn_sub_str str=“jianggujin” start="6" length="2"/> <!-- 'gu' -->
fn_upper
描述:将字符串转换为大写。
属性:
属性名 | 必填 | 说明 |
---|---|---|
str | 是 | 字符串内容,属性与元素文本二选一,优先元素文本 |
示例:
<fn_upper>'jianggujin'</fn_upper> <!-- 'JIANGGUJIN' -->
<fn_upper str="'jianggujin'"/> <!-- 'JIANGGUJIN' -->
fn_lower
描述:将字符串转换为小写。
属性:
属性名 | 必填 | 说明 |
---|---|---|
str | 是 | 字符串内容,属性与元素文本二选一,优先元素文本 |
示例:
<fn_lower>'JIANGGUJIN'</fn_lower> <!-- 'jianggujin' -->
<fn_lower str="'JIANGGUJIN'"/> <!-- 'jianggujin' -->
fn_trim
描述:去掉字符串开始和结尾处的空格。
属性:
属性名 | 必填 | 说明 |
---|---|---|
str | 是 | 字符串内容,属性与元素文本二选一,优先元素文本 |
示例:
<fn_trim>' jianggujin '</fn_trim> <!-- 'jianggujin' -->
<fn_trim str="' jianggujin '"/> <!-- 'jianggujin' -->
fn_ltrim
描述:去掉字符串开始和结尾处的空格。
属性:
属性名 | 必填 | 说明 |
---|---|---|
str | 是 | 字符串内容,属性与元素文本二选一,优先元素文本 |
示例:
<fn_ltrim>' jianggujin '</fn_ltrim> <!-- 'jianggujin ' -->
<fn_ltrim str="' jianggujin '"/> <!-- 'jianggujin ' -->
fn_rtrim
描述:去掉字符串结尾处的空格。
属性:
属性名 | 必填 | 说明 |
---|---|---|
str | 是 | 字符串内容,属性与元素文本二选一,优先元素文本 |
示例:
<fn_rtrim>' jianggujin '</fn_rtrim> <!-- ' jianggujin' -->
<fn_rtrim str="' jianggujin '"/> <!-- ' jianggujin' -->
2.12.4.1.2 数字函数函数
2.12.4.1.3 日期函数函数
2.12.4.1.4 类型转换函数
fn_date_format
描述:日期格式化。
属性:
属性名 | 必填 | 说明 |
---|---|---|
format | 是 | 字符串内容,属性与元素文本二选一,优先元素文本 |
str | 是 | 字符串内容,属性与元素文本二选一,优先元素文本 |
示例:
<fn_date_format format="yyyyMMddHHmmss">Now()</fn_date_format>
<fn_date_format format="yyyyMMddHHmmss" date="Now()"/>
fn_str_to_date
描述:字符串转日期
属性:
属性名 | 必填 | 说明 |
---|---|---|
format | 是 | 字符串内容,属性与元素文本二选一,优先元素文本 |
str | 是 | 字符串内容,属性与元素文本二选一,优先元素文本 |
示例:
<fn_str_to_date format="yyyyMMddHHmmss">'20201106144233'</fn_str_to_date>
<fn_str_to_date format="yyyyMMddHHmmss" str="'20201106144233'"/>
2.12.4.1.2 其他函数
fn_nvl
描述:如果exp1
的值不为NULL
,则返回exp1
,否则返回exp2
。
属性:
属性名 | 必填 | 说明 |
---|---|---|
format | 是 | 字符串内容,属性与元素文本二选一,优先元素文本 |
str | 是 | 字符串内容,属性与元素文本二选一,优先元素文本 |
示例:
<fn_nvl exp1="amount" exp2="0"/>
2.12.4.2 操作类
page
描述:分页标签
属性:
属性名 | 必填 | 说明 |
---|---|---|
name | 否 | 分页数据对象名称,如果该属性为空, 自动从参数中查找类型为 JPage 的对象,且其他属性配置无效,如果该属性设置为: $root ,等价于设置:_parameter |
startRow | 否 | 指定开始行属性名称,默认:startRow |
endRow | 否 | 指定结束行属性名称,默认:endRow |
pageSize | 否 | 指定每页数据量属性名称,默认:pageSize |
示例:
<page>select id from test</page>
<page name="page">select id from test</page>
alias
描述:别名,自动添加AS alias
,屏蔽不同数据库别名差异,例如Oracle
数据库会自动为其添加双引号。
属性:
属性名 | 必填 | 说明 |
---|---|---|
name | 否 | 别名,默认取节点内容 |
as | 否 | 是否添加AS 关键字,默认true ,可选值如下:true :添加false :不添加 |
示例:
<alias>name</alias> // => name AS name
2.13 自定义方言扩展
方言是JDBFly
中比较重要的部分,主要用于屏蔽数据库差异,提供针对不同数据库的个性化处理。
2.13.1 编写方言实现
编写自己的方言需要实现JDialect
接口,对接口中相关方法进行扩展实现即可。
public interface JDialect {
/**
* 查询或生成主键的SQL以及生成顺序,如不支持则返回null
*
* @return
*/
JIdentity getIdentity();
/**
* 处理函数节点, 返回{@code true}表示支持并处理,{@code false}表示不支持
*
* @param builder
* @param nodeToHandle
* @param targetContents
* @return
*/
boolean handleFnNode(JXMLScriptBuilder builder, XNode nodeToHandle, List<SqlNode> targetContents);
/**
* 处理分页节点, 返回{@code true}表示支持并处理,{@code false}表示不支持
*
* @param builder
* @param nodeToHandle
* @param targetContents
* @return
*/
boolean handlePageNode(JXMLScriptBuilder builder, XNode nodeToHandle, List<SqlNode> targetContents);
/**
* 处理批量插入,返回{@code true}表示支持并处理,{@code false}表示不支持
*
* @param configuration
* @param mapperClass
* @param method
* @param document
* @param mapperElement
* @param entity
* @return
*/
boolean handleInsertListMapperMethod(JConfiguration configuration, Class<?> mapperClass, Method method,
Document document, Element mapperElement, JEntity entity);
/**
* 判断当前方言是否支持指定数据库元数据
*
* @return
* @throws SQLException
*/
boolean support(DatabaseMetaData metaData) throws SQLException;
}
正常情况下,对方言的扩展实现建议开发者直接继承JAbstarctDialect
,在JAbstarctDialect
抽象化类中提供了一些通用的处理。
2.13.2 注册方言实现
开发完自己的方言之后,需要将方言加入JDBFly
的配置JDBFlyConfiguration
中,通过调用JDBFlyConfiguration
的getDialectRegistry()
方法可以获得方言的注册器,通过注册器的addDialect
添加自定义的方言实现,方言名称默认取实现类的简单类名,出现同名时后添加的方言将覆盖已有方言。
除了通过方言注册器添加方言之外,开发者还可以直接通过JDBFlyConfiguration
的setDialect
方法设置指定的方言实例,二者区别在于直接设置的方言实例将作为后续JDBFly
直接使用的方言,否则将通过注册器查找支持的方言实现。
2.14 JDBFly
配置说明
JDBFly
的特殊配置都是通过JDBFlyConfiguration
进行配置,该配置实例可以通过JConfiguration
类的getDBFlyConfiguration()
方法获得。在JDBFlyConfiguration
中可以通过配置的set
方法进行设置或者通过Properties
的形式进行配置,下面将介绍可以使用的配置属性以及说明。
描述:用于配置Java
实体与数据库表、Java
实体属性与数据库列之间名称的转换关系。
属性:
属性名 | 必填 | 说明 |
---|---|---|
dialect | 否 | 指定方言实现类,会自动初始化为方言实例 |
dialects | 否 | 需要注册的方言实现类,多个类之间使用, 分隔,会将其注册到方言注册器中 |
style | 否 | 枚举类型JStyle ,表名、属性名列名转换风格,默认 camelhump ,可选值如下:normal :原值camelhump :驼峰转下划线uppercase :转换为大写lowercase :转换为小写camelhumpAndUppercase :驼峰转下划线大写形式camelhumpAndLowercase :驼峰转下划线小写形式 |
useJavaType | 否 | 是否设置javaType ,默认false |
notEmpty | 否 | 是否增加空字符串!='' 判断,仅当 Java 类型为字符串时生效,默认false |
exludeSelects | 否 | 全局需要排除的不需要查询的列,多个属性之间使用, 分隔 |
excludeInserts | 否 | 全局需要排除的不需要插入的列,多个属性之间使用, 分隔 |
excludeUpdates | 否 | 全局需要排除的不需要修改的列,多个属性之间使用, 分隔 |
valueGeneratorProperties | 否 | 全局Value 生成器的列,多个属性之间使用, 分隔 |
valueGeneratorClass | 否 | 全局Value 生成器实现类 |
schema | 否 | 全局schema |
JDBFly的特殊配置除了可以通过
JDBFlyConfiguration
配置,在JConfiguration
中也提供了快速的配置方法,二者是等价的。
第三部分 数据库版本跟踪
第四部分 附录
附录一:扩展Mapper的DTD文件
<?xml version="1.0" encoding="UTF-8" ?>
<!ELEMENT mapper (cache-ref | cache | resultMap* | parameterMap* | sql* | insert* | update* | delete* | select*)+>
<!ATTLIST mapper
namespace CDATA #IMPLIED
>
<!ELEMENT cache-ref EMPTY>
<!ATTLIST cache-ref
namespace CDATA #REQUIRED
>
<!ELEMENT cache (property*)>
<!ATTLIST cache
type CDATA #IMPLIED
eviction CDATA #IMPLIED
flushInterval CDATA #IMPLIED
size CDATA #IMPLIED
readOnly CDATA #IMPLIED
blocking CDATA #IMPLIED
>
<!ELEMENT parameterMap (parameter+)?>
<!ATTLIST parameterMap
id CDATA #REQUIRED
type CDATA #REQUIRED
>
<!ELEMENT parameter EMPTY>
<!ATTLIST parameter
property CDATA #REQUIRED
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
mode (IN | OUT | INOUT) #IMPLIED
resultMap CDATA #IMPLIED
scale CDATA #IMPLIED
typeHandler CDATA #IMPLIED
>
<!ELEMENT resultMap (constructor?,id*,result*,association*,collection*,discriminator?)>
<!ATTLIST resultMap
id CDATA #REQUIRED
type CDATA #REQUIRED
extends CDATA #IMPLIED
autoMapping (true | false) #IMPLIED
>
<!ELEMENT constructor (idArg*,arg*)>
<!ELEMENT id EMPTY>
<!ATTLIST id
property CDATA #IMPLIED
javaType CDATA #IMPLIED
column CDATA #IMPLIED
jdbcType CDATA #IMPLIED
typeHandler CDATA #IMPLIED
>
<!ELEMENT result EMPTY>
<!ATTLIST result
property CDATA #IMPLIED
javaType CDATA #IMPLIED
column CDATA #IMPLIED
jdbcType CDATA #IMPLIED
typeHandler CDATA #IMPLIED
>
<!ELEMENT idArg EMPTY>
<!ATTLIST idArg
javaType CDATA #IMPLIED
column CDATA #IMPLIED
jdbcType CDATA #IMPLIED
typeHandler CDATA #IMPLIED
select CDATA #IMPLIED
resultMap CDATA #IMPLIED
name CDATA #IMPLIED
>
<!ELEMENT arg EMPTY>
<!ATTLIST arg
javaType CDATA #IMPLIED
column CDATA #IMPLIED
jdbcType CDATA #IMPLIED
typeHandler CDATA #IMPLIED
select CDATA #IMPLIED
resultMap CDATA #IMPLIED
name CDATA #IMPLIED
>
<!ELEMENT collection (constructor?,id*,result*,association*,collection*,discriminator?)>
<!ATTLIST collection
property CDATA #REQUIRED
column CDATA #IMPLIED
javaType CDATA #IMPLIED
ofType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
select CDATA #IMPLIED
resultMap CDATA #IMPLIED
typeHandler CDATA #IMPLIED
notNullColumn CDATA #IMPLIED
columnPrefix CDATA #IMPLIED
resultSet CDATA #IMPLIED
foreignColumn CDATA #IMPLIED
autoMapping (true | false) #IMPLIED
fetchType (lazy | eager) #IMPLIED
>
<!ELEMENT association (constructor?,id*,result*,association*,collection*,discriminator?)>
<!ATTLIST association
property CDATA #REQUIRED
column CDATA #IMPLIED
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
select CDATA #IMPLIED
resultMap CDATA #IMPLIED
typeHandler CDATA #IMPLIED
notNullColumn CDATA #IMPLIED
columnPrefix CDATA #IMPLIED
resultSet CDATA #IMPLIED
foreignColumn CDATA #IMPLIED
autoMapping (true | false) #IMPLIED
fetchType (lazy | eager) #IMPLIED
>
<!ELEMENT discriminator (case+)>
<!ATTLIST discriminator
column CDATA #IMPLIED
javaType CDATA #REQUIRED
jdbcType CDATA #IMPLIED
typeHandler CDATA #IMPLIED
>
<!ELEMENT case (constructor?,id*,result*,association*,collection*,discriminator?)>
<!ATTLIST case
value CDATA #REQUIRED
resultMap CDATA #IMPLIED
resultType CDATA #IMPLIED
>
<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT typeAlias EMPTY>
<!ATTLIST typeAlias
alias CDATA #REQUIRED
type CDATA #REQUIRED
>
<!ELEMENT select (#PCDATA | include | trim | where | set | foreach | choose | if | bind | fn_ascii | fn_concat | fn_char_length | fn_bit_length | fn_position | fn_replace | fn_sub_str | fn_upper | fn_lower | fn_trim | fn_ltrim | fn_rtrim | fn_date_format | fn_str_to_date | fn_nvl | alias | page)*>
<!ATTLIST select
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
resultMap CDATA #IMPLIED
resultType CDATA #IMPLIED
resultSetType (FORWARD_ONLY | SCROLL_INSENSITIVE | SCROLL_SENSITIVE) #IMPLIED
statementType (STATEMENT | PREPARED | CALLABLE) #IMPLIED
fetchSize CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true | false) #IMPLIED
useCache (true | false) #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
resultOrdered (true | false) #IMPLIED
resultSets CDATA #IMPLIED
>
<!ELEMENT insert (#PCDATA | selectKey | include | trim | where | set | foreach | choose | if | bind | fn_ascii | fn_concat | fn_char_length | fn_bit_length | fn_position | fn_replace | fn_sub_str | fn_upper | fn_lower | fn_trim | fn_ltrim | fn_rtrim | fn_date_format | fn_str_to_date | fn_nvl | alias | page)*>
<!ATTLIST insert
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true | false) #IMPLIED
statementType (STATEMENT | PREPARED | CALLABLE) #IMPLIED
keyProperty CDATA #IMPLIED
useGeneratedKeys (true | false) #IMPLIED
keyColumn CDATA #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
>
<!ELEMENT selectKey (#PCDATA | include | trim | where | set | foreach | choose | if | bind | fn_ascii | fn_concat | fn_char_length | fn_bit_length | fn_position | fn_replace | fn_sub_str | fn_upper | fn_lower | fn_trim | fn_ltrim | fn_rtrim | fn_date_format | fn_str_to_date | fn_nvl | alias | page)*>
<!ATTLIST selectKey
resultType CDATA #IMPLIED
statementType (STATEMENT | PREPARED | CALLABLE) #IMPLIED
keyProperty CDATA #IMPLIED
keyColumn CDATA #IMPLIED
order (BEFORE | AFTER) #IMPLIED
databaseId CDATA #IMPLIED
>
<!ELEMENT update (#PCDATA | selectKey | include | trim | where | set | foreach | choose | if | bind | fn_ascii | fn_concat | fn_char_length | fn_bit_length | fn_position | fn_replace | fn_sub_str | fn_upper | fn_lower | fn_trim | fn_ltrim | fn_rtrim | fn_date_format | fn_str_to_date | fn_nvl | alias | page)*>
<!ATTLIST update
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true | false) #IMPLIED
statementType (STATEMENT | PREPARED | CALLABLE) #IMPLIED
keyProperty CDATA #IMPLIED
useGeneratedKeys (true | false) #IMPLIED
keyColumn CDATA #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
>
<!ELEMENT delete (#PCDATA | include | trim | where | set | foreach | choose | if | bind | fn_ascii | fn_concat | fn_char_length | fn_bit_length | fn_position | fn_replace | fn_sub_str | fn_upper | fn_lower | fn_trim | fn_ltrim | fn_rtrim | fn_date_format | fn_str_to_date | fn_nvl | alias | page)*>
<!ATTLIST delete
id CDATA #REQUIRED
parameterMap CDATA #IMPLIED
parameterType CDATA #IMPLIED
timeout CDATA #IMPLIED
flushCache (true | false) #IMPLIED
statementType (STATEMENT | PREPARED | CALLABLE) #IMPLIED
databaseId CDATA #IMPLIED
lang CDATA #IMPLIED
>
<!ELEMENT include (property+)?>
<!ATTLIST include
refid CDATA #REQUIRED
>
<!ELEMENT bind EMPTY>
<!ATTLIST bind
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT sql (#PCDATA | include | trim | where | set | foreach | choose | if | bind | fn_ascii | fn_concat | fn_char_length | fn_bit_length | fn_position | fn_replace | fn_sub_str | fn_upper | fn_lower | fn_trim | fn_ltrim | fn_rtrim | fn_date_format | fn_str_to_date | fn_nvl | alias | page)*>
<!ATTLIST sql
id CDATA #REQUIRED
lang CDATA #IMPLIED
databaseId CDATA #IMPLIED
>
<!ELEMENT trim (#PCDATA | include | trim | where | set | foreach | choose | if | bind | fn_ascii | fn_concat | fn_char_length | fn_bit_length | fn_position | fn_replace | fn_sub_str | fn_upper | fn_lower | fn_trim | fn_ltrim | fn_rtrim | fn_date_format | fn_str_to_date | fn_nvl | alias | page)*>
<!ATTLIST trim
prefix CDATA #IMPLIED
prefixOverrides CDATA #IMPLIED
suffix CDATA #IMPLIED
suffixOverrides CDATA #IMPLIED
>
<!ELEMENT where (#PCDATA | include | trim | where | set | foreach | choose | if | bind | fn_ascii | fn_concat | fn_char_length | fn_bit_length | fn_position | fn_replace | fn_sub_str | fn_upper | fn_lower | fn_trim | fn_ltrim | fn_rtrim | fn_date_format | fn_str_to_date | fn_nvl | alias | page)*>
<!ELEMENT set (#PCDATA | include | trim | where | set | foreach | choose | if | bind | fn_ascii | fn_concat | fn_char_length | fn_bit_length | fn_position | fn_replace | fn_sub_str | fn_upper | fn_lower | fn_trim | fn_ltrim | fn_rtrim | fn_date_format | fn_str_to_date | fn_nvl | alias | page)*>
<!ELEMENT foreach (#PCDATA | include | trim | where | set | foreach | choose | if | bind | fn_ascii | fn_concat | fn_char_length | fn_bit_length | fn_position | fn_replace | fn_sub_str | fn_upper | fn_lower | fn_trim | fn_ltrim | fn_rtrim | fn_date_format | fn_str_to_date | fn_nvl | alias | page)*>
<!ATTLIST foreach
collection CDATA #REQUIRED
item CDATA #IMPLIED
index CDATA #IMPLIED
open CDATA #IMPLIED
close CDATA #IMPLIED
separator CDATA #IMPLIED
>
<!ELEMENT choose (when*,otherwise?)>
<!ELEMENT when (#PCDATA | include | trim | where | set | foreach | choose | if | bind | fn_ascii | fn_concat | fn_char_length | fn_bit_length | fn_position | fn_replace | fn_sub_str | fn_upper | fn_lower | fn_trim | fn_ltrim | fn_rtrim | fn_date_format | fn_str_to_date | fn_nvl | alias | page)*>
<!ATTLIST when
test CDATA #REQUIRED
>
<!ELEMENT otherwise (#PCDATA | include | trim | where | set | foreach | choose | if | bind | fn_ascii | fn_concat | fn_char_length | fn_bit_length | fn_position | fn_replace | fn_sub_str | fn_upper | fn_lower | fn_trim | fn_ltrim | fn_rtrim | fn_date_format | fn_str_to_date | fn_nvl | alias | page)*>
<!ELEMENT if (#PCDATA | include | trim | where | set | foreach | choose | if | bind | fn_ascii | fn_concat | fn_char_length | fn_bit_length | fn_position | fn_replace | fn_sub_str | fn_upper | fn_lower | fn_trim | fn_ltrim | fn_rtrim | fn_date_format | fn_str_to_date | fn_nvl | alias | page)*>
<!ATTLIST if
test CDATA #REQUIRED
>
<!ELEMENT fn_ascii (#PCDATA)>
<!ATTLIST fn_ascii
str CDATA #IMPLIED
>
<!ELEMENT fn_concat (value+)>
<!ELEMENT fn_char_length (#PCDATA)>
<!ATTLIST fn_char_length
str CDATA #IMPLIED
>
<!ELEMENT fn_bit_length (#PCDATA)>
<!ATTLIST fn_bit_length
str CDATA #IMPLIED
>
<!ELEMENT fn_position EMPTY>
<!ATTLIST fn_position
sub CDATA #IMPLIED
str CDATA #IMPLIED
>
<!ELEMENT fn_replace EMPTY>
<!ATTLIST fn_replace
str CDATA #IMPLIED
from CDATA #IMPLIED
to CDATA #IMPLIED
>
<!ELEMENT fn_sub_str EMPTY>
<!ATTLIST fn_sub_str
str CDATA #IMPLIED
start CDATA #IMPLIED
length CDATA #IMPLIED
>
<!ELEMENT fn_upper (#PCDATA)>
<!ATTLIST fn_upper
str CDATA #IMPLIED
>
<!ELEMENT fn_lower (#PCDATA)>
<!ATTLIST fn_lower
str CDATA #IMPLIED
>
<!ELEMENT fn_trim (#PCDATA)>
<!ATTLIST fn_trim
str CDATA #IMPLIED
>
<!ELEMENT fn_ltrim (#PCDATA)>
<!ATTLIST fn_ltrim
str CDATA #IMPLIED
>
<!ELEMENT fn_rtrim (#PCDATA)>
<!ATTLIST fn_rtrim
str CDATA #IMPLIED
>
<!ELEMENT fn_date_format (#PCDATA)>
<!ATTLIST fn_date_format
format CDATA #REQUIRED
date CDATA #IMPLIED
>
<!ELEMENT fn_str_to_date (#PCDATA)>
<!ATTLIST fn_str_to_date
format CDATA #REQUIRED
str CDATA #IMPLIED
>
<!ELEMENT fn_nvl EMPTY>
<!ATTLIST fn_nvl
exp1 CDATA #REQUIRED
exp2 CDATA #REQUIRED
>
<!ELEMENT value (#PCDATA)>
<!ELEMENT alias (#PCDATA | include | trim | where | set | foreach | choose | if | bind | fn_ascii | fn_concat | fn_char_length | fn_bit_length | fn_position | fn_replace | fn_sub_str | fn_upper | fn_lower | fn_trim | fn_ltrim | fn_rtrim | fn_date_format | fn_str_to_date | fn_nvl)*>
<!ATTLIST alias
name CDATA #IMPLIED
as (true | false) #IMPLIED
>
<!ELEMENT page (#PCDATA | include | trim | where | set | foreach | choose | if | bind | fn_ascii | fn_concat | fn_char_length | fn_bit_length | fn_position | fn_replace | fn_sub_str | fn_upper | fn_lower | fn_trim | fn_ltrim | fn_rtrim | fn_date_format | fn_str_to_date | fn_nvl | alias)*>
<!ATTLIST page
name CDATA #IMPLIED
startRow CDATA #IMPLIED
endRow CDATA #IMPLIED
pageSize CDATA #IMPLIED
>
附录二:扩展Config的DTD文件
<?xml version="1.0" encoding="UTF-8" ?>
<!ELEMENT configuration (properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?,dbfly?)>
<!ELEMENT databaseIdProvider (property*)>
<!ATTLIST databaseIdProvider
type CDATA #REQUIRED
>
<!ELEMENT properties (property*)>
<!ATTLIST properties
resource CDATA #IMPLIED
url CDATA #IMPLIED
>
<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT settings (setting+)>
<!ELEMENT setting EMPTY>
<!ATTLIST setting
name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ELEMENT typeAliases (typeAlias*,package*)>
<!ELEMENT typeAlias EMPTY>
<!ATTLIST typeAlias
type CDATA #REQUIRED
alias CDATA #IMPLIED
>
<!ELEMENT typeHandlers (typeHandler*,package*)>
<!ELEMENT typeHandler EMPTY>
<!ATTLIST typeHandler
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
handler CDATA #REQUIRED
>
<!ELEMENT objectFactory (property*)>
<!ATTLIST objectFactory
type CDATA #REQUIRED
>
<!ELEMENT objectWrapperFactory EMPTY>
<!ATTLIST objectWrapperFactory
type CDATA #REQUIRED
>
<!ELEMENT reflectorFactory EMPTY>
<!ATTLIST reflectorFactory
type CDATA #REQUIRED
>
<!ELEMENT plugins (plugin+)>
<!ELEMENT plugin (property*)>
<!ATTLIST plugin
interceptor CDATA #REQUIRED
>
<!ELEMENT environments (environment+)>
<!ATTLIST environments
default CDATA #REQUIRED
>
<!ELEMENT environment (transactionManager,dataSource)>
<!ATTLIST environment
id CDATA #REQUIRED
>
<!ELEMENT transactionManager (property*)>
<!ATTLIST transactionManager
type CDATA #REQUIRED
>
<!ELEMENT dataSource (property*)>
<!ATTLIST dataSource
type CDATA #REQUIRED
>
<!ELEMENT mappers (mapper*,package*)>
<!ELEMENT mapper EMPTY>
<!ATTLIST mapper
resource CDATA #IMPLIED
url CDATA #IMPLIED
class CDATA #IMPLIED
>
<!ELEMENT package EMPTY>
<!ATTLIST package
name CDATA #REQUIRED
>
<!ELEMENT dbfly (dialect?,style?,useJavaType?,notEmpty?,exludeSelects?,excludeInserts?,excludeUpdates?,valueGeneratorProperties?,valueGeneratorClass?)>
<!ELEMENT dialect EMPTY>
<!ATTLIST dialect
name CDATA #REQUIRED
>
<!ELEMENT style EMPTY>
<!ATTLIST style
name (normal | camelhump | uppercase | lowercase | camelhumpAndUppercase | camelhumpAndLowercase) "camelhump"
>
<!ELEMENT useJavaType EMPTY>
<!ATTLIST useJavaType
value (true | false) "false"
>
<!ELEMENT notEmpty EMPTY>
<!ATTLIST notEmpty
value (true | false) "false"
>
<!ELEMENT exludeSelects EMPTY>
<!ATTLIST exludeSelects
property CDATA #REQUIRED
>
<!ELEMENT excludeInserts EMPTY>
<!ATTLIST excludeInserts
property CDATA #REQUIRED
>
<!ELEMENT excludeUpdates EMPTY>
<!ATTLIST excludeUpdates
property CDATA #REQUIRED
>
<!ELEMENT valueGeneratorProperties EMPTY>
<!ATTLIST valueGeneratorProperties
property CDATA #REQUIRED
>
<!ELEMENT valueGeneratorClass EMPTY>
<!ATTLIST valueGeneratorClass
name CDATA #REQUIRED
>