{"id":26873703,"url":"https://github.com/wayshall/dbm","last_synced_at":"2025-06-27T02:37:08.870Z","repository":{"id":41086463,"uuid":"86407844","full_name":"wayshall/dbm","owner":"wayshall","description":"a simple Java ORM Framework, based on spring-jdbc.","archived":false,"fork":false,"pushed_at":"2025-04-05T02:47:24.000Z","size":1640,"stargazers_count":8,"open_issues_count":1,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-05-07T10:53:53.552Z","etag":null,"topics":["dbm","java","orm","spring","spring-jdbc"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wayshall.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-03-28T02:52:33.000Z","updated_at":"2024-12-05T07:50:42.000Z","dependencies_parsed_at":"2023-02-08T23:31:27.153Z","dependency_job_id":"d2fee27a-85a3-43e9-86e2-5c139a2a9965","html_url":"https://github.com/wayshall/dbm","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/wayshall/dbm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wayshall%2Fdbm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wayshall%2Fdbm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wayshall%2Fdbm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wayshall%2Fdbm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wayshall","download_url":"https://codeload.github.com/wayshall/dbm/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wayshall%2Fdbm/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262179247,"owners_count":23271199,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dbm","java","orm","spring","spring-jdbc"],"created_at":"2025-03-31T09:19:56.585Z","updated_at":"2025-06-27T02:37:08.843Z","avatar_url":"https://github.com/wayshall.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dbm\n------\n基于spring jdbc实现的轻量级orm   \n\n项目github地址：[ dbm ]( https://github.com/wayshall/dbm )\n\n\n\n联系邮箱：  wayshall@qq.com\n\n\n\n## 目录\n- [特色](#特色)\n- [示例项目](#示例项目)\n- [要求](#要求)\n- [maven配置](#maven)\n- [一行代码启用](#一行代码启用)\n- [实体映射](#实体映射)\n- [id策略](#id策略)\n- [复合主键映射](#复合主键映射)\n- [其它特有的映射](#其它特有的映射)\n- [BaseEntityManager接口和QueryDSL](#BaseEntityManager接口和QueryDSL)\n- [CrudEntityManager接口](#crudentitymanager接口)\n- [DbmRepository动态sql查询接口](#DbmRepository动态sql查询接口)\n- [DbmRepository动态查询后缀函数支持](#DbmRepository动态查询后缀函数支持)\n- [sql片段支持](#sql片段支持)\n- [动态sql查询的语法和指令](#动态sql查询的语法和指令)\n- [用DbmRepository执行脚本](#用DbmRepository执行脚本)\n- [DbmRepository接口的多数据源支持](#dbmrepository接口的多数据源支持)\n- [DbmRepository接口对其它orm框架的兼容](#dbmrepository接口对其它orm框架的兼容)\n- [查询映射](#查询映射)\n- [复杂的嵌套查询映射](#复杂的嵌套查询映射)\n- [自定义实现DbmRepository接口](#自定义实现dbmrepository接口)\n- [枚举处理](#枚举处理)\n- [json映射](#json映射)\n- [敏感字段映射](#敏感字段映射)\n- [字段绑定](#字段绑定)\n- [从其它地方加载DbmRepository接口的sql](#从其它地方加载DbmRepository接口的sql)\n- [直接传入要执行的sql作为参数](#直接传入要执行的sql作为参数)\n- [其它映射特性](#其它映射特性)\n- [分页查询](#分页查询)\n- [批量插入](#批量插入)\n- [充血模型支持](#充血模型支持)\n- [参数配置](#参数配置)\n- [代码生成器](#代码生成器)\n- [辅助工具导出表结构为excell](#辅助工具导出表结构为excel)\n- [捐赠](#捐赠)\n\n\n## 特色\n- 基本的实体增删改查（单表）不需要生成样板代码和sql文件。\n\n- 返回结果不需要手动映射，会根据字段名称自动映射。\n\n- 支持sql语句和接口绑定风格的DAO，但sql不是写在丑陋的xml里，而是直接写在sql文件里，这样用eclipse或者相关支持sql的编辑器打开时，就可以语法高亮，更容易阅读。\n\n- 支持sql脚本修改后重新加载\n\n- 内置支持分页查询。\n\n- 接口支持批量插入\n\n- 使用Java8新增的编译特性，不需要使用类似@Param 的注解标注参数,当然你可以显式使用注解标注参数。\n\n- Repository接口（用注解@DbmRepository标注了的接口）支持默认方法\n\n- 支持多数据源绑定，可以为每个Repository接口指定具体的数据源\n\n- 支持不同的数据库绑定，Repository接口会根据当前绑定的数据源自动绑定加载对应数据库后缀的sql文件\n\n- 提供充血模型支持\n\n- 支持json映射，直接把数据库的json或者varchar类型（存储内容为json数据）的列映射为Java对象\n\n- 支持非int和String类型的枚举映射\n\n- 内置支持SnowFlake id生成算法\n\n- 支持敏感字段映射\n- Repository接口支持执行sql脚本\n\n   \n## 示例项目   \n单独使用dbm的示例项目\n[boot-dbm-sample](https://github.com/wayshall/boot-dbm-sample)\n\n\n## 要求\nJDK 1.8+\nspring 4.0+\n\n## maven\n当前snapshot版本：4.7.4-SNAPSHOT\n\n若使用snapshot版本，请添加snapshotRepository仓储：\n```xml\n\u003crepository\u003e\n     \u003cid\u003eoss\u003c/id\u003e\n     \u003curl\u003ehttps://oss.sonatype.org/content/repositories/snapshots/\u003c/url\u003e\n    \u003csnapshots\u003e\n        \u003cenabled\u003etrue\u003c/enabled\u003e\n    \u003c/snapshots\u003e\n\u003c/repository\u003e   \n```\n\n添加依赖：   \n```xml\n\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.onetwo4j\u003c/groupId\u003e\n    \u003cartifactId\u003eonetwo-dbm\u003c/artifactId\u003e\n    \u003cversion\u003e4.9.0-SNAPSHOT\u003c/version\u003e\n\u003c/dependency\u003e\n\n```\nspring的依赖请自行添加。\n\n## 一行代码启用\n在已配置好数据源的前提下，只需要在spring配置类（即有@Configuration注解的类）上加上注解@EnableDbm即可。\n```java     \n  \n\t@EnableDbm\n\t@Configuration\n\tpublic class SpringContextConfig {\n\t}   \n   \n```\n\n## 实体映射\n```java   \n@Entity   \n@Table(name=\"TEST_USER_AUTOID\")   \npublic class UserAutoidEntity {\n\n\t@Id\n\t@GeneratedValue(strategy=GenerationType.IDENTITY) \n\t@Column(name=\"ID\")\n\tprotected Long id;\n\t@Length(min=1, max=50)\n\tprotected String userName;\n\t@Length(min=0, max=50)\n\t@Email\n\tprotected String email;\n\tprotected String mobile;\n\tprotected UserStatus status;\n\n\t//省略getter和setter\n}   \n```\n### 注意这里用到了一些jpa的注解，含义和jpa一致：\n- @Entity，表示这是一个映射到数据库表的实体\n- @Table，表示这个实体映射的表\n- @Id，表示这是一个主键字段\n- @GeneratedValue(strategy=GenerationType.IDENTITY)，表示这个主键的值用数据库自增的方式生成，dbm目前只支持IDENTITY和SEQUENCE两种方式      \n- @Column，表示映射到表的字段，一般用在java的字段名和表的字段名不对应的时候   \n\njava的字段名使用驼峰的命名风格，而数据库使用下划线的风格，dbm会自动做转换   \n注意dbm并没有实现jpa规范，只是借用了几个jpa的注解，纯属只是为了方便。。。\n后来为了证明我也不是真的很懒，也写了和@Entity、@Table、@Column对应的注解，分别是：@DbmEntity（@Entity和@Table合一），@DbmColumn。。。\n\n\n- 注意：为了保持简单和轻量级，dbm的实体映射只支持单表，不支持多表级联映射。复杂的查询和映射请使用[DbmRepository接口](#dbmrepository接口)\n\n## id策略\ndbm支持jpa的GenerationType的id策略，此外还提供了通过@DbmIdGenerator自定义的策略：\n- GenerationType.IDENTITY   \n  使用数据库本身的自增策略\n- GenerationType.SEQUENCE   \n  使用数据库的序列策略（只支持oracle）\n- GenerationType.TABLE   \n  使用自定义的数据库表管理序列\n- GenerationType.AUTO   \n  目前的实现是：如果是mysql，则等同于GenerationType.IDENTITY，如果是oracle，则等同于GenerationType.SEQUENCE   \n- DbmIdGenerator   \n  dbm提供id生成注解，可通过配置 generatorClass 属性，配置自定义的id实现类，实现类必须实现CustomIdGenerator接口。dbm首先会通过尝试在spring context查找generatorClass类型的bean，如果找不到则通过反射创建实例。\n\n\n### 详细使用\n#### GenerationType.IDENTITY\n```Java\n@Entity\n@Table(name=\"t_user\")\npublic class UserEntity implements Serializable {\n\n\t@Id\n\t@GeneratedValue(strategy=GenerationType.IDENTITY) \n\tprotected Long id;\n}\n```\n\n\n#### GenerationType.TABLE\n```Java\n@Entity\n@Table(name=\"t_user\")\npublic class UserEntity implements Serializable {\n\n\t@Id\n\t@GeneratedValue(strategy = GenerationType.TABLE, generator=\"tableIdGenerator\")  \n\t@TableGenerator(name = \"tableIdGenerator\",  \n\t    table=\"gen_ids\",  \n\t    pkColumnName=\"gen_name\",  \n\t    valueColumnName=\"gen_value\",  \n\t    pkColumnValue=\"seq_test_user\",  \n\t    allocationSize=50\n\t)\n\tprotected Long id;\n}\n```\n\n也可以使用5.0新增的@DbmTableIdGenerator注解简化配置：\n```Java\n@Entity\n@Table(name=\"t_user\")\npublic class UserEntity implements Serializable {\n\n    @DbmTableIdGenerator(\"seq_test_user\")\n    Long id;\n}\n```\n\n\n#### GenerationType.SEQUENCE\n```Java\n@Entity\n@Table(name=\"t_user\")\npublic class UserEntity implements Serializable {\n\n\t@Id\n\t@GeneratedValue(strategy=GenerationType.SEQUENCE, generator=\"seqGenerator\") \n\t@SequenceGenerator(name=\"seqGenerator\", sequenceName=\"SEQ_TEST_USER\")\n\tprotected Long id;\n}\n```\n\n### DbmIdGenerator\n比如使用了dbm集成的snowflake策略，下面的配置使用了默认配置的snowflake，如果需要配置不同的datacenter和machine，建议自己实现CustomIdGenerator接口。\n```Java\n@Entity\n@Table(name=\"t_user\")\npublic class UserEntity implements Serializable {\n\n\t@Id  \n\t@GeneratedValue(strategy = GenerationType.AUTO, generator=\"snowflake\") \n\t@DbmIdGenerator(name=\"snowflake\", generatorClass=SnowflakeGenerator.class)\n\tprotected Long id;\n}\n```\n\n\n### @SnowFlakeId 注解\n\n4.7.4 版本后，使用内置的snowFlakeId生成主键id时，可直接使用 @SnowflakeId 简化配置：\n\n```Java\n@Entity\n@Table(name=\"t_user\")\npublic class UserEntity {\n\t@SnowflakeId \n\tprotected Long id;\n}\n```\n\n\n\n\n\n\n## 复合主键映射\njpa支持三种复合主键映射策略，dbm目前只支持一种： @IdClass 映射。\n映射方法如下：\n假设有一个表有两个主键：id1，id2。\n实体的Java代码如下：\n```Java\n@Data\n@Entity\n@Table(name=\"composite_table\")\n@IdClass(CompositeId.class)\npublic class CompositeEntity {\n\n\t@Id  \n\tLong id1;\n\t@Id\n\tLong id2;\n\n\t@Transient\n\tCompositeId id;\n\n\tpublic CompositeId getId() {\n\t\treturn new CompositeId(id1, id2);\n\t}\n\t\n\tpublic void setId(CompositeId id) {\n\t\tthis.id1 = id.getId1();\n\t\tthis.id2= id.getId2();\n\t}\n\t\n\t//....其它属性\n\n\t@Data\n\tpublic static class CompositeId implements Serializable {\n\t\tLong id1;\n\t\tLong id2;\n\t}\n}\n\n```\n解释：\n- 把需要映射为主键的实体属性都用 @Id 注解标注   \n- 另外创建一个复合主键的Pojo类CompositeId，属性为实体需要映射为主键的属性，名称类型一一对应，并实现 java.io.Serializable 接口   \n- 在实体类里用 @IdClass 注解标注为复合主键类为 CompositedId 类   \n- 实体的CompositeId属性不是必须的，只是为了更方便使用组合id，而且无需持久化，所以如果写的话，需要用 @Transient 注解标注\n\n\n复合主键实体的查找方法为：\n```Java\nCompositedId cid = new CompositedId(1, 1);\nCompositeEntity entity = baseEntityManager.load(CompositeEntity.class, cid);\n\nint deleteCount = baseEntityManager.removeById(CompositeEntity.class, entity.getId());\n```\n\n## 枚举处理\n\n### 枚举映射\ndbm支持jpa的@Enumerated枚举映射注解，使用方法和jpa一样，默认为EnumType.ORDINAL int值类型映射，可以通过注解属性指定为EnumType.STRING名称映射。\n\n但是，当枚举为EnumType.ORDINAL映射的时候，ordinal的值是从0开始根据定义时的先后顺序决定，这使得我们开发的时候很不方便，比如我有一个枚举类型，是需要映射为int类型，但是值并不是从0开始的，这时候就相当的尴尬，因为你既不能用默认为EnumType.ORDINAL,也不能用EnumType.STRING。\n\n所以dbm还另外增加了自定义的int值映射接口DbmEnumValueMapping，只要枚举类型实现了这个接口，就可以自定义返回实际的映射值，比如：\n```Java\n@Entity\n@Table(name=\"TEST_USER\")\npublic class UserEntity {\n\t@Id\n\tLong id;\n\t@Enumerated(EnumType.ORDINAL)\n\tUserGenders gender;\n\n\tpublic static enum UserGenders {\n\t\tFEMALE(\"女性\"),\n\t\tMALE(\"男性\");\n\t\t\n\t\tfinal private String label;\n\t\tprivate UserGenders(String label) {\n\t\t\tthis.label = label;\n\t\t}\n\t\tpublic String getLabel() {\n\t\t\treturn label;\n\t\t}\n\t}\n}\n```\n如果按照jpa的做法，枚举类型映射为@Enumerated(EnumType.ORDINAL)后，用户实体的gender属性对应的数据库列只能是0（FEMALE）和1（MALE）。\n在dbm里，你可以通过实现DbmEnumValueMapping接口，返回自定义的映射值，比如10（FEMALE）和11（MALE）。\n```Java\n@Entity\n@Table(name=\"TEST_USER\")\npublic class UserEntity {\n\t@Id\n\tLong id;\n\t@Enumerated(EnumType.ORDINAL)\n\tUserGenders gender;\n\n\tpublic static enum UserGenders implements DbmEnumValueMapping\u003cInteger\u003e {\n\t\tFEMALE(\"女性\", 10),\n\t\tMALE(\"男性\", 11);\n\t\t\n\t\tfinal private String label;\n\t\tfinal private int value;\n\t\tprivate UserGenders(String label, int value) {\n\t\t\tthis.label = label;\n\t\t\tthis.value = value;\n\t\t}\n\t\tpublic String getLabel() {\n\t\t\treturn label;\n\t\t}\n\t\t@Override\n\t\tpublic Integer getEnumMappingValue() {\n\t\t\treturn value;\n\t\t}\n\t\t\n\t}\n}\n```\n\n### 非int和String类型的枚举映射支持\n在jpa里，@Enumerated 注解支持int和String两种枚举值类型。\n在dbm里，只要属性的类型是枚举类型，并且实现了DbmEnumValueMapping接口，dbm就会自动处理枚举类型，不需要@Enumerated注解标记。\n而DbmEnumValueMapping是个泛型接口，可以支持任意类型的枚举值，只要数据值从数据库取回时可以和getEnumMappingValue()返回的值匹配上（eqauls）即可。\n比如项目比较奇葩，需要把枚举类型映射到Double类型：\n```java\n@Entity\n@Table(name=\"TEST_USER\")\n@Data\npublic class UserEntity {\n    @SnowflakeId\n    Long id;\n    UserGenders gender;\n}\nstatic enum UserGenders implements DbmEnumValueMapping\u003cDouble\u003e {\n        FEMALE(\"女性\", 0),\n        LADYBOY(\"人妖\", 0.5),\n        MALE(\"男性\", 1);\n        \n        final private String label;\n        final private double value;\n        private UserGenders(String label, double value) {\n            this.label = label;\n            this.value = value;\n        }\n        public String getLabel() {\n            return label;\n        }\n        @Override\n        public Double getEnumMappingValue() {\n            return value;\n        }\n}\n```\n\n\n### 枚举属性查询时的处理\n\n- 如果枚举实现了 DbmEnumValueMapping 接口，则取DbmEnumValueMapping#getMappingValue()方法所得的值\n- 通过Querys 和 BaseEntityManager 的api查询时，一般直接取枚举的name()方法所得的值\n- 如果是@DbmRepository 接口，并且用@Param注解指定了enumType属性，则根据配置的取相应的值，但是DbmEnumValueMapping接口优先级更高\n\n\n\n## json映射\n\n有时候，我们需要在数据库的某个字段里存储json格式的数据，又想在获取到数据后转为java对象使用，这时你可以使用 @DbmJsonField 注解，这个注解会在保存实体的时候把对象转化为json字符串，然后在取出数据的时候自动把字符串转化为对象。\n示例：\n\n```Java\nclass SimpleEntity {\n\t@DbmJsonField\n\tprivate ExtInfo extInfo;\n\n\t\n\tpublic static class ExtInfo {\n\t\tString address;\n\t\tList\u003cString\u003e phones;\n\t}\n}\n```\n\n如果该字段是泛型，需要保存类型信息，可以设置storeTyping属性为true\n\n```Java\nclass SimpleEntity {\n\t@DbmJsonField(storeTyping=true)\n\tprivate Map\u003cString, ConfigData\u003e configData;\n\n\t\n\tpublic static class ExtInfo {\n\t\tString address;\n\t\tList\u003cString\u003e phones;\n\t}\n}\n```\n\n需要添加依赖：\n\n```xml\n    \u003cdependency\u003e\n      \u003cgroupId\u003eorg.onetwo4j\u003c/groupId\u003e\n      \u003cartifactId\u003eonetwo-jackson\u003c/artifactId\u003e\n    \u003c/dependency\u003e\n```\n\n\n\n\n## 敏感字段映射\n\n### 加解密映射\n对于一些不适宜明文存储的字段信息，比如api密钥，存储的时候自动加密，获取的时候自动解密，此时可以使用@DbmEncryptField 注解。\n```Java\n@Entity\n@Table(name=\"TEST_MERCHANT\")\npublic class MerchantEntity implements Serializable {\n\t\n\n\t@Id\n\t@GeneratedValue(strategy=GenerationType.IDENTITY) \n\t@Column(name=\"ID\")\n\tprotected Long id;\n\t\n\t@DbmEncryptField\n\tprotected String apikey;\n}\n```\n在@DbmRepository 使用这个功能时，可以在插入的参数后面加上后缀函数：\n```sql\n/*****\n * @name: batchInsert\n * 批量插入     */\n    insert \n    into\n        test_merchant\n        (id, apikey) \n    values\n        (:id, :apikey?encrypt)\n```\n\n\n\n**注意**\n\n- dbm的敏感字段加密功能依赖jasypt\n\n- 你可以通过下面属性配置jasypt的StandardPBEStringEncryptor \n\n  ```yaml\n  dbm: \n      encrypt: \n          algorithm: PBEWithMD5AndTripleDES #默认加密算法\n          password: test #密钥\n  ```\n\n### 脱敏映射\n对于另一些字段，我们可能并不需要加解密，而只是在存储或者获取的时候，按照一定的规则脱敏。比如手机号码取出的时候自动对后面四位打上星号，或者邮件地址只显示第一个字符和@后面的字符，则可以使用 @DbmSensitiveField 注解进行脱敏映射。\n```Java\n@Entity\n@Table(name=\"TEST_USER\")\npublic class UserEntity implements Serializable {\n\t\n\n\t@Id\n\t@GeneratedValue(strategy=GenerationType.IDENTITY) \n\t@Column(name=\"ID\")\n\tprivate Long id;\n\t\n\tprivate String mobile;\n\t\n        @DbmBindValueToField(name=\"mobile\") //查询实体时，此字段的值来自mobile字段\n        @Transient //此字段无需保存到数据库\n\t@DbmSensitiveField(leftPlainTextSize=7, on=SensitiveOns.SELECT)\n\t// 保留手机号码只显示左边7位，如13612345678，取出脱敏后mobile的值为：1361234****\n\tprivate String mobileUnsensitive;\n\t\n\t@DbmSensitiveField(leftPlainTextSize=1, sensitiveIndexOf=\"@\",  on=SensitiveOns.SELECT)\n\t// 邮件地址左边保留一个长度的字符，@后面的字符都保留，其余用星号代替，如test@gmail.com，取出脱敏后为：t***@gmail.com\n\tprivate String email;\n}\n```\n\n**解释**\n\nDbmSensitiveField 属性解释如下：\n- on: 表示进行脱敏的时机，有两个选择：STORE（保存到数据库的时候），SELECT（从数据库获取出来转换为java对象的时候）\n- leftPlainTextSize: 脱敏时需要左边保持明文的字符长度\n- rightPlainTextSize: 脱敏时需要右边保持明文的字符长度\n- sensitiveIndexOf: 当不想整个字段进行脱敏的时候，此属性表示某个指定的字符索引作为脱敏的结束索引。比如邮件脱敏，@字符后面的保留时，此属性值可以写为\"@\"\n- replacementString: 替换敏感数据的字符串，默认为星号\n\n\n\n**注意**\n\n此功能从 4.7.4 版本开始支持\n\n\n\n\n### 字段绑定\n@DbmBindValueToField 注解可以帮某个字段的值绑定到另一个字段，绑定后，实体查询时，此字段的值将会取自绑定的值。例子可以参考 [脱敏映射](#脱敏映射) \n\n**注意**\n\n此功能从 4.7.4 版本开始支持\n\n\n\n\n## 其它特有的映射\n\n\n\n### @DbmFieldConvert注解\n@DbmFieldConvert 注解可自定义一个值转换器，用于从数据库表获取的字段值转换为Java对象的属性值，和把Java对象的属性值转换为数据库表的字段值。   \n@DbmJsonField 注解实际上是包装了@DbmField注解实现的。\n字段支持重复多个@DbmFieldConvert 注解\n\n\n\n\n## BaseEntityManager接口和QueryDSL\n大多数数据库操作都可以通过BaseEntityManager接口来完成。   \nBaseEntityManager可直接注入。   \n\n先来个简单的使用例子：\n```java    \n\n\t\n\t@Resource\n\tprivate BaseEntityManager entityManager;\n\n\t@Test\n\tpublic void testSample(){\n\t\tUserAutoidEntity user = new UserAutoidEntity();\n\t\tuser.setUserName(\"dbm\");\n\t\tuser.setMobile(\"1333333333\");\n\t\tuser.setEmail(\"test@test.com\");\n\t\tuser.setStatus(UserStatus.NORMAL);\n\t\t\n\t\t//save\n\t\tLong userId = entityManager.save(user).getId();\n\t\tassertThat(userId, notNullValue());\n\t\t\n\t\t//update\n\t\tString newMobile = \"13555555555\";\n\t\tuser.setMobile(newMobile);\n\t\tentityManager.update(user);\n\t\t\n\t\t//fetch by id\n\t\tuser = entityManager.findById(UserEntity.class, userId); \n\t\tassertThat(user.getMobile(), is(newMobile));\n\t\t\n\t\t//通过实体属性查找，下面的调用相当于sql条件： where mobile='13555555555' and status IN ('NORMAL', 'DELETE') and age\u003e18\n\t\tuser = entityManager.findOne(UserAutoidEntity.class, \n\t\t\t\t\t\t\t\t\t\t\"mobile\", newMobile,\n\t\t\t\t\t\t\t\t\t\t\"status:in\", Arrays.asList(UserStatus.NORMAL, UserStatus.DELETE),\n\t\t\t\t\t\t\t\t\t\t\"age:\u003e\", 18);\n\t\tassertThat(user.getId(), is(userId));\n\n\t\t//下面的调用相当于sql条件： where registerTime\u003e=:date1 and registerTime\u003c:date2\n\t\tentityManager.findList(UserEntity.class, \"registerTime:date in\", new Object[]{date1, date2})\n\t\t\n\t\t\n\t}\n```\nBaseEntityManager对象的find开头的接口，可变参数一般都是按键值对传入，相当于一个Map，键是实体对应的属性(+冒号+操作符，可选，不加默认就是=)，值是对应属性的条件值：   \n```Java\nentityManager.findOne(entityClass, propertyName1, value1, propertyName2, value2......);   \nentityManager.findList(entityClass, propertyName1, value1, propertyName2, value2......);\n```\nkey，value形式的参数最终会被and操作符连接起来。\n\n其中属性名和值都可以传入数组或者List类型的参数，这些多值参数最终会被or操作符连接起来，比如：\n- 属性名参数传入一个数组： \n```Java   \nentityManager.findList(entityClass, new String[]{propertyName1, propertyName2}, value1, propertyName3, value3);\n```\n最终生成的sql语句大概是：\n```sql\nselect t.* from table t where (t.property_name1=:value1 or t.property_name2=:value1) and t.property_name3=:value3\n```\n\n- 属性值参数传入一个数组： \n```Java   \nentityManager.findList(entityClass, propertyName1, new Object[]{value1, value2}, propertyName3, value3);\n```\n最终生成的sql语句大概是：\n```sql\nselect t.* from table t where (t.property_name1=:value1 or t.property_name1=:value2) and t.property_name3=:value3\n```\n\n- find 风格的api会对一些特殊参数做特殊的处理，比如 K.IF_NULL 属性是告诉dbm当查询值查找的属性对应的值为null或者空时，该如何处理，IfNull.Ignore表示忽略这个条件。 **\n比如：\n```Java   \nentityManager.findList(entityClass, propertyName1, new Object[]{value1, value2}, propertyName3, value3, K.IF_NULL, IfNull.Ignore);\n```\n那么，当value3（或者任何一个属性对应的值）为nul时，最终生成的sql语句大概是：\n```sql\nselect t.* from table t where (t.property_name1=:value1 or t.property_name1=:value2) \n```\nproperty_name3条件被忽略了。\n\n### 操作符\nBaseEntityManager的属性查询支持如下操作符：   \n=, \u003e, \u003c, !=, in, not in, date in, is null, like, not like\n\n### Query DSL API\ndbm还提供了一个专门用于构建查询的dsl api\n```Java\n\n//使用 querys dsl api\nUserAutoidEntity queryUser = Querys.from(entityManager, UserAutoidEntity.class)\n\t\t\t\t\t\t\t\t\t.where()\n\t\t\t\t\t\t\t\t\t\t.field(\"mobile\").is(newMobile)\n\t\t\t\t\t\t\t\t\t\t.field(\"status\").is(UserStatus.NORMAL)\n\t\t\t\t\t\t\t\t\t.end()\n\t\t\t\t\t\t\t\t\t.toQuery()\n\t\t\t\t\t\t\t\t\t.one();\nassertThat(queryUser, is(user));\n```\n\n注意：\n4.7.3后，query dsl api 已集成到 BaseEntityManager 接口，可以通过 BaseEntityManager 直接创建查询：\n```Java\npublic Optional\u003cUser\u003e findBy(String month, Long userId) {\n\t\treturn baseEntityManager.from(User.class)\n\t\t\t\t\t\t\t\t.where()\n\t\t\t\t\t\t\t\t\t.field(\"month\").is(month)\n\t\t\t\t\t\t\t\t\t.field(\"userId\").is(userId)\n\t\t\t\t\t\t\t\t.toQuery()\n\t\t\t\t\t\t\t\t.optionalOne();\n\t}\n```\n\n通过链式api和Java8 的 Stream api，你可以创建出这样的查询代码：\n```Java\npublic List\u003cUser\u003e findList(String month, Long userId) {\n\treturn baseEntityManager.from(DuesDetailEntity.class)\n\t\t\t\t\t\t.where()\n\t\t\t\t\t\t\t.field(\"duesMonth\").is(month)\n\t\t\t\t\t\t\t.field(\"userId\").is(userId)\n\t\t\t\t\t\t.toQuery()\n\t\t\t\t\t\t.list()\n\t\t\t\t\t\t.stream()\n\t\t\t\t\t\t.map(user -\u003e user.asBean(UserVO.class)) //把实体转换为VO\n\t\t\t\t\t\t.collect(Collectors.toList());\n}\n```\n\n动态条件和or 查询：\n```Java\n// 下面代码生成的sql条件：(age = 12 and userName like %test%) or (email like %qq.com and mobile=136666666) \npublic Optional\u003cUser\u003e findBy(String month, Long userId) {\n\t\treturn baseEntityManager.from(User.class)\n\t\t\t\t.where()\n                                .field(\"age\").is(12)\n                                .field(\"userName\").when(()-\u003euserName!=null).like(userName) // userName不为null的时候，userName条件才会被生成\n                                .or()\n                                    .field(\"email\").prelike(\"qq.com\")\n                                    .field(\"mobile\").is(\"13666666666\")\n\t\t\t\t.toQuery()\n\t\t\t\t.optionalOne();\n\t}\n```\n\n\n\n## CrudEntityManager接口\nCrudEntityManager是在BaseEntityManager基础上封装crud的接口，是给喜欢简单快捷的人使用的。   \nCrudEntityManager实例可在数据源已配置的情况下通过简单的方法获取：\n\n```java   \n@Entity   \n@Table(name=\"TEST_USER_AUTOID\")   \npublic class UserAutoidEntity {\n\n\tfinal static public CrudEntityManager\u003cUserAutoidEntity, Long\u003e crudManager = Dbms.obtainCrudManager(UserAutoidEntity.class);\n\n\t@Id\n\t@GeneratedValue(strategy=GenerationType.IDENTITY) \n\t@Column(name=\"ID\")\n\tprotected Long id;\n\t@Length(min=1, max=50)\n\tprotected String userName;\n\n\t//省略getter和setter\n}   \n```\n然后通过静态变量直接访问crud接口：   \n```Java    \n\n\tUserAutoidEntity.crudManager.save(entity);\n\tUserAutoidEntity user = UserAutoidEntity.crudManager.findOne(\"userName\", userName);\n\n```\n\n\n\n## DbmRepository动态sql查询接口\nDbmRepository接口支持类似mybatis的sql语句与接口绑定，但sql文件不是写在丑陋的xml里，而是直接写在sql文件里，这样用eclipse或者相关支持sql的编辑器打开时，就可以语法高亮，更容易阅读。\n\n### 1、定义一个接口   \n包名：test.dao   \n```java   \n@DbmRepository\npublic interface UserAutoidDao {\n\n\t@ExecuteUpdate\n\tpublic int removeByUserName(String userName);\n}\n\n```\n### 2、定义一个.jfish.sql文件\n在resource源码代码文件下新建一个目录：sql\n然后在sql目录里新建一个UserAutoidDao全类名的.jfish.sql文件，完整路径和文件为：\nsql/test.dao.UserAutoidDao.jfish.sql\n文件内容为：    \n\n```sql\n/*****\n * @name: removeByUserName\n * 批量删除\n */\n    delete from test_user_autoid \n        where 1=1 \n\t\t---这里的userName变量就是接口里的userName参数\n        [#if userName?has_content]\n\t\t\t---这里的userName命名查询参数也是接口里的userName参数\n         and user_name like :userName\n        [/#if]\n```\n\n\n解释：   \n- dbm会根据sql文件名去掉.jfish.sql后缀后作为类名，绑定对应的接口类，此处为：test.dao.UserAutoidDao    \n- @name: 表示此sql绑定的方法，此处表示会绑定到UserAutoidDao.removeByUserName方法    \n- \\[\\#if\\]...\\[/\\#if\\]，是freemarker的语法，表示条件判断。此处表示，如果userName的值不为空，才生成“user_name like ？” 这个条件   \n- :userName，spring jdbc的命名参数，和接口的方法参数绑定 \n- @ExecuteUpdate注解表示这个方法会以jdbc的executeUpdate方法执行，实际上可以忽略，因为dbm会识别update，insert，delete等前缀的方法名来判断。\n\n### 3、调用   \n```java\n\n@Service   \n@Transactional   \npublic class UserAutoidServiceImpl {\n\n\t@Resource\n\tprivate UserAutoidDao userAutoidDao;\n\n\tpublic int removeByUserName(){\n\t\treturn this.userAutoidDao.removeByUserName(\"%userName%\");\n\t}\n}\n\n```\n\n`\n   提示：如果你不想传入 \"%userName%\"，可以把sql文件里的命名参数“:userName”改成“:userName?likeString”试试，后面的?likeString是调用dbm内置的likeString方法，该方法会自动在传入的参数前后加上'%'。\n`\n`\n   注意：从4.7.3开始，dbm的 DbmRepository接口 支持Java8接口默认方法。\n`\n\n### 通过@Query直接在代码里写sql\n虽然本人不喜欢不推荐在代码里写sql，但实际开发中经常遇到很多人都是喜欢简单粗暴，直接在代码里通过注解写sql，所以，新版（4.5.2-SNAPSHOT+）的dbm提供了@Query来支持在代码里写sql。\n\n使用示例：\n```Java\n@DbmRepository //标记这是一个dbm的Repository接口\npublic interface UserDao {\n\t\n\t@Query(\"insert into test_user (id, email, gender, mobile, nick_name, password, status, user_name) \"\n\t\t\t+ \" values (:id, :email, :gender, :mobile, :nickName, :password, :status, :userName)\")\n\tint batchSaveUsers(List\u003cUserEntity\u003e users);\n\t\n\t@Query(value=\"select t.* from test_user t where 1=1 \"\n\t\t\t+ \"[#if userName?has_content] \"\n\t\t\t\t+ \"and t.user_name like :userName?likeString \"\n\t\t\t+ \"[/#if]\")\n\tPage\u003cUserEntity\u003e findUserPage(Page\u003cUserEntity\u003e page, String userName);\n\n}\n```\n\n## DbmRepository动态查询后缀函数支持\n\n### 无参数后缀函数\nDbmRepository的动态查询，命名参数支持后缀函数，以便于把一些参数值加工处理。\n比如使用like查询的时候，一般的查询片段如下：\n```sql\nwhere userName like :userName\n```\n若用户输入的userName参数是test，则此时是在前后添加模糊匹配符号的，实际参数应为：%test%.\ndbm提供了后缀函数支持来避免手工处理这种情况，所以在sql里，可以写成：\n```sql\nwhere userName like :userName?likeString\n```\n\n### 有参数后缀函数\n5.0版本后增加了参数支持\n目前Date类型的参数增加了两个带参数的后缀函数：minutes_ago, minutes_later.\n用于需要对时间参数类型处理，比如需要查询某个特定时间前后30分钟的数据时，可以这样写：\n```sql\nselect \n    d.*\nfrom \n    data d\nwhere\n    1=1 \n[#if request.uploadTime??]\n    and d.upload_time between :request.uploadTime?$30_minutes_ago and :request.uploadTime?$30_minutes_later\n[/#if]\n\n```\n\n### 全局后缀函数\n- preLikeString\n在参数值前面加上'%'，如userName=test，则实际值为：%test\n\n- postLikeString\n在参数值后面加上'%'，如userName=test，则实际值为：test%\n\n### Date类型后缀函数(DateTypeFuncSet)\n- dateString\n按 yyyy-MM-dd 格式化Date类型参数\n- dateTimeString\n按 yyyy-MM-dd HH:mm:ss 格式化Date类型参数\n- yyyyMMdd\n按 yyyyMMdd 格式化Date类型参数\n\n## sql片段支持\n有时候，两个查询方法的sql里，大部分是相同的（比如查询条件），只有小部分不同，如果写两份，就需要维护两份sql。这时候，你可以使用sql片段@fragment\n\n比如有两个sql查询\n\nsql1是findUserListLikeName:\n\n```sql\n/***\n * @name: findUserListLikeName\n */\nselect \n    usr.*\nfrom \n    test_user usr\nwhere \n    user_name like :userName?likeString\n```\n\nsql2是countUserLikeName:\n\n```sql\n/***\n * @name: countUserLikeName\n */\nselect \n    count(1)\nfrom \n    test_user usr\nwhere \n    user_name like :userName?likeString\n```\n\n两个方法的查询条件是一样的，只是select的数据不同，此时可以把相同的部分抽取出来：\n\n```sql\n/***\n * @name: queryUser\n * @fragment: subWhere\n */\nfrom \n    test_user usr\nwhere \n    user_name like :userName?likeString\n\n```\n\nfindUserListLikeName的sql可以改写为：\n\n```sql\n/***\n * @name: findUserListLikeName\n */\nselect \n    usr.*\n${fragment['subWhere']}\n```\n\ncountUserLikeName改写为：\n\n```sql\n/***\n * @name: countUserLikeName\n */\nselect \n    count(1)\n${fragment['queryUser.fragment.subWhere']}\n```\n\n${fragment['subWhere']} 和 ${fragment['queryUser.fragment.subWhere']} 均可引用到名为subWhere的sql片段，前者表示在queryUser这个查询下查找，后者则可以跨不同的命名查询查找sql片段\n\n\n\n\n## 动态sql查询的语法和指令\n\n### 常用指令\nsql模板使用的实际上是freemarker模板引擎，因此freemarker支持的语法都可以使用。\n一般比较常用到的指令如下：\n- if 指令\n```sql\n[#if 条件表达式]\n......\n[/#if]\n```\n- list 迭代指令\n```sql\n[#list 可迭代的变量 as item]\n......t.column_name = ${item.property1}\n[/#list]\n```\n条件表达式除了通常的逻辑判断外，还有一些比较常用到的表达式：\n- 双问号，用于判断一个变量是否存在\n```Java\n    [#if request.userName??]\n        t.user_name = :request.userName \n    [/#if]\n```\n- has_content，用于判断变量是否为null或者空白：\n```Java\n    [#if request.userName?has_content]\n        t.user_name = :request.userName?likeString\n    [/#if]\n```\n- trim?has_content，用于判断变量为null，并且非空白字符串：\n```Java\n    [#if request.userName?? \u0026\u0026 request.userName?trim?has_content]\n        t.user_name = :request.userName?likeString\n    [/#if]\n```\n或者使用5.0新增的方法：\n```Java\n    [#if isNotBlank(request.userNameList)]\n        t.user_name = :request.userName?likeString\n    [/#if]\n```\n- 判断list类型的变量是否你为空\n```Java\n    [#if request.userNameList?? \u0026\u0026 (request.userNameList?size \u003e 0)]\n        t.user_name in ( :request.userNameList )\n    [/#if]\n```\n或者使用5.0新增的方法：\n```Java\n    [#if isNotEmpty(request.userNameList)]\n        t.user_name in ( :request.userNameList )\n    [/#if]\n```\n\n### dbm扩展指令\n另外增加了一些特定的指令以帮助处理sql，包括：\n\n- @foreach\n- @str\n- @where\n- @set\n- @dateRange\n\n#### foreach指令\nforeach 遍历指令\n\n可以在sql，循环可遍历的参数，并用joiner连接起来，比如当传入ids是个列表，我们需要在sql进入in查询时：\n\n```sql\n/***\n * @name: findPermissions\n * @parser: template\n * \n */\n  select \n      t.*\n    from \n        data t\n   [#if ids??]\n    where\n        t.id in (\n\t        [@foreach list=ids joiner=', '; id, index]\n\t            #{id}\n\t        [/@foreach]\n        )\n   [/#if]\n```\n- list 属性：可遍历的参数\n- joiner 属性：连接字符\n- id：遍历的时候，引用每个正在遍历的元素的变量名\n- index：当前遍历的索引\n当然，这里只是为了演示foreach指令的用法，实际上，dbm的sql参数可以直接支持list参数类型，当传入的参数是个列表的时候，会自动分解参数。\n上面的语句实际上可直接写成： \n```sql\nselect \n      t.*\n    from \n        data t\n   [#if ids??]\n    where\n        t.id in ( :ids )\n   [/#if]\n```\n\n#### str指令\n@str 字符串指令\n\n可以在sql动态生成条件查询时，自动插入指定字符，同时去掉头尾多余的字符，比如动态插入where和去掉多余的and或者or：\n\n```sql\n/****\n * @name: findUsers\n */\n    select\n        *\n    from\n        TEST_USER u\n    [@str insertPrefix='where' trimPrefixs='and | or' trimSuffixs='and | or']\n        [#if query.userName?has_content]\n            u.user_name = :query.userName\n        [/#if]\n        [#if query.age??]\n            and u.age = :query.age\n        [/#if]\n        [#if query.status??]\n            and u.status = :query.status or \n        [/#if]\n    [/@str]\n```\n- insertPrefix 属性：当指令里面的sql条件不为空的时候，会自动把insertPrefix属性的字符串插入，这里就是where\n\n- trimPrefixs 属性：如果生成的sql片段以trimPrefixs指定的单词开始时，则会自动被去掉。支持指定多个单词，|为分隔符。\n\n- trimSuffixs 属性：如果生成的sql片段以trimSuffixs指定的单词结束时，则会自动被去掉。支持指定多个单词，|为分隔符。\n\n\n### where指令\nwhere指令可以在sql动态生成条件查询时，自动加上where，或者去掉多余的and或者or关键字，它是@str指令的包装。\n@str指令一节里的sql可以用where指令写成这样：\n\n```sql\n/****\n * @name: findUsersWithWhere\n */\n    select\n        *\n    from\n        TEST_USER u\n    [@where]\n        [#if query.userName?has_content]\n            u.user_name = :query.userName\n        [/#if]\n        [#if query.age??]\n            and u.age = :query.age\n        [/#if]\n        [#if query.status??]\n            and u.status = :query.status or \n        [/#if]\n    [/@where]\n```\n### set指令\nset  指令与where指令类似，只是@str指令的包装，用于sql更新语句：\n```sql\n/***\n * @name: updateUsersWithSet\n */\n    update\n        TEST_USER \n    [@set]\n        [#if query.userName?has_content]\n            user_name = :query.userName, \n        [/#if]\n        [#if query.age??]\n            age = :query.age, \n        [/#if]\n        [#if query.status??]\n            status = :query.status,\n        [/#if]\n    [/@set]\n    where \n        id = :query.id\n```\n\n### dateRange\n\n```sql\n        // 以天（date）为间隔，遍历输出从10月1日到11日（不包含）的日期，日期按照format格式化为字符串，format参数不写，则dateVar为Date类型对象\n   [@dateRange from='2014-10-01' to='2014-10-11' type='date' format='yyyyMMdd' joiner=' or '; dateVar, index]\n        t.date = '${dateVar}'\n   [/@dateRange]\n```\n\n### 其他特性\n\n\n- 支持通过特殊的注解参数进行查询分派：\n```Java\n@DbmRepository\npublic interface UserDao {\n\n\tpublic List\u003cUserVO\u003e findUserList(@QueryDispatcher String type);\n\n}\n```\ndbm会根据QueryDispatcher注解标记的特殊参数的值，分派到不同的sql。\n如果type==inner时，那么这个查询会被分派到findUserList(inner)；\n如果type==outer时，那么这个查询会被分派到findUserList(inner)\nsql文件：\n```sql\n/***\n * @name: findUserList(inner)\n */\nselect \n    usr.*\nfrom \n    inner_user usr\n\n\n/***\n * @name: findUserList(outer)\n */\nselect \n    usr.*\nfrom \n    outer_user usr\n```\n\n- in条件可以传入 Collection 类型的值，会自动解释为多个in参数\nDbmRepository接口：   \n```Java\n@DbmRepository\npublic interface UserDao {\n\n\tpublic List\u003cUserVO\u003e findUser(List\u003cString\u003e userNames);\n\n}\n```\nsql文件：   \n```sql\n/***\n * @name: findUser\n */\nselect \n    usr.*\nfrom \n    t_user usr\nwhere \n\tusr.user_name in ( :userNames )\n\n```\n注意：必须是Collection类型，不支持数组类型。\n\n- dbm默认会注入一些辅助函数以便在sql文件中调用，可通过_func前缀引用，比如${_func.dateAs(date, \"yyyy-MM-dd\")}格式化日期。通过QueryConfig注解扩展在sql文件使用的辅助函数集。\nsql文件：   \n```sql\n/***\n * @name:\n *  findUser\n */\nselect \n    usr.*\nfrom \n    t_user usr\nwhere \n\tusr.birthday=${_func.dateAs(date, \"yyyy-MM-dd\")}\n\n```\n\n- 支持Optional类型的返回值\n\n## 用DbmRepository执行脚本\n4.8.0版本后，DbmRepository支持执行sql脚本。\n只需要在DbmRepository的方法上加上注解@SqlScript，方法对应的sql即会被当做sql脚本执行。\n但需要注意：\n- 执行脚本必须返回void\n- 脚本无法设置 jdbc 参数\n\n如：\n```Java\n@DbmRepository\npublic interface SqlScriptDao {\n    @SqlScript\n    void createTables();\n}\n```\n对应的sql文件：\n```sql\n/**\n * @name: createTables\n */\nSET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS = 0;\n\n-- ----------------------------\n-- Table structure for wx_access_token\n-- ----------------------------\nDROP TABLE IF EXISTS `test_table`;\nCREATE TABLE `test_table`  (\n  `id` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,\n  `create_at` datetime(0) NOT NULL,\n  `update_at` datetime(0) NOT NULL,\n  PRIMARY KEY (`id`) USING BTREE\n) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact;\n\nSET FOREIGN_KEY_CHECKS = 1;\n```\n\n## DbmRepository接口的多数据源支持\nDbmRepository 查询接口还可以通过注解支持绑定不同的数据源，dataSource的值为spring bean的名称：\n```Java\n@DbmRepository(dataSource=\"dataSourceName1\")\npublic interface Datasource1Dao {\n}\n\n@DbmRepository(dataSource=\"dataSourceName2\", ignoreRegisterIfDataSourceNotFound=true)\npublic interface Datasource2Dao {\n}\n```\n\n- dataSource\n通过指定DataSource的Bean名称，让Dao使用所需数据源执行sql\n\n- ignoreRegisterIfDataSourceNotFound\n若指定了dataSource属性，却没有找到对应的dataSource Bean时，是否忽略注册此 DbmRepository Bean。\n此属性适用于某些特殊场景，比如某些共用的服务引用了此Dao，但在应用中并不会使用到此Dao查询数据，从而避免程序启动失败。\n\n\n## DbmRepository接口对其它orm框架的兼容\nDbmRepository 的查询接口是可以独立于dbm使用的，其它orm框架可以通过实现QueryProvideManager接口，然后通过 @DbmRepository 注解的queryProviderName或queryProviderClass属性指定特定的QueryProvideManager实现类。从而让DbmRepository查询接口使用其它orm框架，避免不同orm框架共存带来的一些副作用。    \n\ndbm内置了JPA（Hibernate）实现的QueryProvideManager。   \n但一个一个地把DbmRepository接口设置成相同的实现的QueryProvideManager实现的是不明智，只是没有意义的重复劳动，所以dbm另外提供了@EnableDbmRepository注解，单独激活和配置DbmRepository默认的QueryProvideManager。\n```Java\n@EnableDbmRepository(value=\"org.onetwo.common.hibernate.dao\", \n\t\t\t\t\t\tdefaultQueryProviderClass=HibernateJPAQueryProvideManager.class,\n\t\t\t\t\t\tautoRegister=true)\n\tpublic static class HibernateTestConfig {\n}\n```\n\n\n## 查询映射\nDbmRepository的查询映射无需任何xml配置，只需要遵循规则即可：   \n- 1、  Java类的属性名与sql查询返回的列名一致(不区分大小写)   \n- 2、  或者Java类的属性名采用驼峰命名，而列明采用下划线的方式分隔。如：userName对应user_name   \n默认的映射规则实际上和使用了@DbmRowMapper注解下的SMART_PROPERTY模式一致。\n详见：[注解@DbmRowMapper](#注解dbmrowmapper)\n\n举例：   \n### 创建一个DbmRepository接口\n```Java\n\n@DbmRepository\npublic interface CompanyDao {\n\tList\u003cCompanyVO\u003e findCompaniesByLikeName(String name);\n\tList\u003cCompanyVO\u003e findCompaniesByNames(Collection\u003cString\u003e names);\n}\n\npublic class CompanyVO {\n\tprotected Long id;\n\tprotected String name;\n\tprotected String description;\n\tprotected int employeeNumber;\n\n\t//省略getter和setter\n}\n```\n\n### 对应的sql文件CompanyDao.jfish.sql\n内容如下：   \n```sql\n/****\n * @name: findCompaniesByLikeName\n */\nselect \n    comp.id,\n    comp.name,\n    comp.description,\n    comp.employee_number\nfrom\n    company comp\nwhere\n    comp.name like :name?likeString\n    \n\n/****\n * @name: findCompaniesByNames\n */\nselect \n    comp.id,\n    comp.name,\n    comp.description,\n    comp.employee_number\nfrom\n    company comp\n[#if names?? \u0026\u0026 names?size\u003e0]\nwhere\n    comp.name in (:names)\n[/#if]\n```\n\n\n### 调用代码\n```Java\nList\u003cCompanyVO\u003e companies = this.companyDao.findCompaniesByLikeName(\"测试公司\");\ncompanies = this.companyDao.findCompaniesByNames(Collections.emptyList());\ncompanies = this.companyDao.findCompaniesByNames(Arrays.asList(\"测试公司-1\", \"测试公司-2\"));\n```\n\n\n## 复杂的嵌套查询映射\n有时，我们会使用join语句，查询出一个复杂的数据列表，比如包含了company、department和employee三个表。\n返回的结果集中，一个company对应多条department数据，而一条department数据又对应多条employee数据，我们希望把多条数据这样的数据最终只映射到一个VO对象里。这时候，你需要使用@DbmResultMapping和@DbmNestedResult两个注解，以指定VO的那些属性需要进行复杂的嵌套映射。\n\n举例如下：\n### 创建一个DbmRepository接口和相应的VO\n```Java\n\n@DbmRepository\npublic interface CompanyDao {\n\n\t@DbmResultMapping({\n\t\t\t@DbmNestedResult(property=\"departments.employees\", columnPrefix=\"emply_\", nestedType=NestedType.COLLECTION),\n\t\t\t@DbmNestedResult(property=\"departments\", id=\"id\", nestedType=NestedType.COLLECTION)\n\t})\n\tList\u003cCompanyVO\u003e findNestedCompanies();\n}\n\npublic class CompanyVO {\n\tprotected Long id;\n\tprotected String name;\n\tprotected String description;\n\tprotected int employeeNumber;\n\tprotected List\u003cDepartmentVO\u003e departments;\n\n\t//省略getter和setter\n}\n\npublic class DepartmentVO {\n\tprotected Long id;\n\tprotected String name;\n\tprotected Integer employeeNumber;\n\tprotected Long companyId;\n\tprotected List\u003cEmployeeVO\u003e employees;\n\t//省略getter和setter\n}\n\npublic class EmployeeVO  {\n\tprotected Long id;\n\tprotected String name;\n\tprotected Date joinDate;\n\t//省略getter和setter\n}\n```\n解释：   \n- @DbmResultMapping 注解表明，查询返回的结果需要复杂的嵌套映射\n- @DbmNestedResult 注解告诉dbm，返回的CompanyVO对象中，哪些属性是需要复杂的嵌套映射的。\nproperty用于指明具体的属性名称，columnPrefix用于指明，需要把返回的结果集中，哪些前缀的列都映射到property指定的属性里，默认会使用property。nestedType标识该属性的嵌套类型，有三个值，ASSOCIATION表示一对一的关联对象，COLLECTION表示一对多的集合对象，MAP也是一对多，但该属性的类型是个Map类型。\nid属性，用于指定嵌套对象的某个属性作为识别对象的唯一标识，配置了可一定程度上加快映射速度。\n\n### 对应的sql\n```sql\n/*****\n * @name: findNestedCompanies\n */\nselect \n    comp.*,\n    depart.id as departments_id,\n    depart.company_id as departments_company_id,\n    depart.`name` as departments_name,\n    emply.name as emply_name,\n    emply.join_date as emply_join_date,\n    emply.department_id as emply_department_id\nfrom \n    company comp\nleft join \n    department depart on comp.id=depart.company_id\nleft join\n    employee emply on emply.department_id=depart.id\n```\n\n\n### 调用\n```Java\nList\u003cCompanyVO\u003e companies = companyDao.findNestedCompanies();\n```\n\n- 注意：若嵌套类型为NestedType.COLLECTION，而容器的元素为简单类型，则把@DbmNestedResult 注解的id属性设置为“value”即可。\n\n\n## 分页查询\n\n### DbmRepository分页查询接口\nDbmRepository查询接口支持自动分页功能；\n需要分页的方法必须遵守下面的约定：\n- 接口其中一个参数必须是 org.onetwo.common.utils.Page 类型 或 org.onetwo.common.utils.PageRequest 类型\n- （可选）返回为org.onetwo.common.utils.Page 类型\n若参数为Page类型时，因为Page带有List类型的result属性，所以查询的list会设置到page参数的result属性里；\n若参数为PageRequest类型时，则返回类型必须为Page 类型\n\n### 自定义分页查询\n参看 [提供自定义分页的能力](https://github.com/wayshall/dbm/issues/45)\n\n\n## 自定义实现DbmRepository接口\ndbm的Repository查询接口采用了流行的只有接口没有实现类的风格，但有时你需要的查询，可能不只是写一条sql查询出来即可的，尽管你可以把这种逻辑处理定义到Service，但你又觉得这些是数据处理逻辑并不属于Service，并且你希望把这种实现也挂载到已经存在的Repository查询接口，没问题，dbm支持这种做法。\n比如，你已经有了一个名叫UserDao的Repository查询接口，然后你可以自顶一个CustomerUserDao接口：\n```Java\npublic interface CustomUserDao {\n\t\n\tint batchInsert(List\u003cUserTableIdEntity\u003e users);\n\n}\n\n```\n在同一个包路径下，你需要写一个CustomUserDao的实现类，实现类的命名规则是：自定义接口类名+Impl，即：CustomUserDaoImpl：\n```Java\n@Component\npublic class CustomUserDaoImpl implements CustomUserDao {\n\t@Autowired\n\tprivate BaseEntityManager baseEntityManager;\n\n\t@Override\n\tpublic int batchInsert(List\u003cUserTableIdEntity\u003e users) {\n\t\tCollection\u003cUserTableIdEntity\u003e dbusers = baseEntityManager.saves(users);\n\t\treturn dbusers.size();\n\t}\n\n}\n```\n然后再让UserDao继承你的扩展接口：\n```Java\n@DbmRepository\npublic interface UserDao extends CustomUserDao {\n\t\n\tList\u003cUserTableIdEntity\u003e findByUserNameLike(String userName);\n\n}\n\n```\n这样，当你注入UserDao，并调用batchInsert方法时，实际调用的就会是CustomUserDaoImpl的batchInsert方法了：\n```Java\npublic class CustomDaoTest {\n\t\n\t@Autowired\n\tprivate UserDao userDao;\n\t\n\t@Test\n\tpublic void test(){\n\t\tint total = 100;\n\t\tList\u003cUserTableIdEntity\u003e users = createUsers(total);\n\t\tint res = this.userDao.batchInsert(users);\n\t\tassertThat(res).isEqualTo(total);\n\t}\n\n}\n```\n\n\n\n## 批量插入\n\n在mybatis里，批量插入非常麻烦，我见过有些人甚至使用for循环生成value语句来批量插入的，这种方法插入的数据量如果很大，生成的sql语句以吨计，如果用jdbc接口执行这条语句，系统必挂无疑。   \n在dbm里，批量插入有几种方式。\n\n- 注意，批量操作不会触发 DbmEntityListener 接口的回调\n\n### 使用session接口的批量插入接口\n```java   \nList\u003cUserEntity\u003e userList = new ArrayList\u003c\u003e();\nuserList.add(user);\n...\nbaseEntityManager.getSessionFactory().getSession().batchInsert(userList)\n\n```\n\n### 使用DbmRepository查询批量插入\n在dbm里，使用编写sql的方式批量接口很简单。   \n\n定义接口：   \n```java   \n\npublic interface UserAutoidDao {\n\n\tpublic int batchInsert(List\u003cUserAutoidEntity\u003e users);\n}\n\n```\n\n然后定义sql：     \n\n```sql\n\n/*****\n * @name: batchInsert\n * 批量插入     */\n    insert \n    into\n        test_user_autoid\n        (birthday, email, gender, mobile, nick_name, password, status, user_name) \n    values\n        (:birthday, :email, :gender, :mobile, :nickName, :password?encrypt, :status.value, :userName)\n\n\n```\n**说明**\n- 方法名称以batchInsert、batchUpdate、batchSave开头命名即被视为批量操作，否则需要使用@ExecuteUpdate(isBatch=true)来注明该方法是批量操作\n- 因为是批量操作，第一个必须是Collection集合类型，若有多个参数且第一个参数不是集合类型，则必须使用@BatchObject标记集合类型的参数\n- values语句里面的userName等字段对应集合里面元素（对象）的属性\n\n### 批量插入或更新\ndbm也利用了mysql的on duplicate key update语法，支持批量插入或更新：\n```java   \nList\u003cUserEntity\u003e userList = new ArrayList\u003c\u003e();\nuserList.add(user);\n...\nbaseEntityManager.getSessionFactory().getSession().batchInsertOrUpdate(userList, 10000)\n\n```\n\n### 从其它地方加载DbmRepository接口的sql\n4.8.0 版本后DbmRepository接口的sql可以自定义加载方式。\nDbmRepository接口默认是自动绑定接口名称对应的 \".jfish.sql\"后缀的sql文件的，但有些场景，我们需要从其它地方加载sql。   \n这时，你可以通过@QueryName注解，标注命名查询的参数，动态设置查询名称（正常情况下，名称是类名+方法名），   \n使用@QuerySqlTemplateParser注解配置加载和解释sql的具体过程：\n```java\n@DbmRepository\npublic interface SqlExecutor {\n    @QuerySqlTemplateParser(SimpleSqlTemplateParser.class) // 使用SimpleSqlTemplateParser解释命名查询\n    \u003cT\u003e T executeSql(@QueryName String name, // 标记此参数是命名查询的名字参数\n                    UserStatus status, \n                    @QueryResultType Class\u003cT\u003e resultType);//对应查询的结果返回的类型\n\n}\n\npublic class SimpleSqlTemplateParser implements SqlTemplateParser {\n\n    @Override\n    public String parseSql(String name, Object context) {\n        if (\"countDisabledUser\".equals(name)) {\n            return \"select count(1) from test_user t where  t.status = :status\";\n        }\n        return name;\n    }\n    \n}\n\n\nSqlExecutor.executeSql(\"countDisabledUser\", // 执行名称为\"countDisabledUser\"的查询，而这个命名查询的sql就是SimpleSqlTemplateParser返回的sql\n                    UserStatus.DISABLED, \n                    Long.class); // countDisabledUser实际执行的是一条统计sql，所以这里返回Long类型\n```\n\n### 直接传入要执行的sql作为参数\n4.8.0 版本支持。\n在一些更加复杂和需要动态化的场景，sql可能不是从某个地方加载的，而是需要从参数传入，然后直接执行，类似于原始的jdbc的execteSql功能，但同时又需要使用dbm的sql解释和参数化功能，也是没问题的。\n\n```java\n@DbmRepository\npublic interface SqlExecutor {\n    \n    \u003cT\u003e T executeSql(@Sql String sql,\n                    UserStatus status, \n                    @QueryResultType Class\u003cT\u003e resultType, //对应查询的结果返回的类型\n                    @QueryParseContext Map\u003cString, Object\u003e ctx);\n\n}\n\nMap\u003cString, Object\u003e ctx = Maps.newHashMap();\nctx.put(\"now\", new NiceData());\nSqlExecutor.executeSql(\"select count(1) from test_user t where  t.status = :status and t.birthDay=${now.format('yyyy-MM-dd')}\", \n                    UserStatus.DISABLED, \n                    Long.class); // countDisabledUser实际执行的是一条统计sql，所以这里返回Long类型\n```\n\n## 其它映射特性\n\n\n\n### 注解@DbmRowMapper\n\n用于配置DbmRepository类的数据映射器，配置指定的mapper，默认为ENTITY模式。\n由于标注为实体的映射规则和Pojo默认的映射规则不一致，导致有时候某些查询返回需要用到两种规则时无法兼容，使用此注解的MIXTURE 混合模式可以兼容两种规则。\n\n- ENTITY模式\n使用EntryRowMapper映射器。\nEntryRowMapper会使用实体的风格映射，即：\n如果有@Column注解，则按照注解的映射匹配；\n如果没有使用注解，则把属性名称转为下划线匹配；\n\n- SMART_PROPERTY模式：\n使用DbmBeanPropertyRowMapper映射属性，即：\n自动把bean的属性名称转为小写和下划线两种方式去匹配sql返回的列值。\n此模式和不使用@DbmRowMapper注解时一致。\n\n- MIXTURE 混合模式：\n先匹配ENTITY模式，如果没有，则匹配SMART_PROPERTY模式\n\n\n## 充血模型支持   \n\ndbm对充血模型提供一定的api支持，如果觉得好玩，可尝试使用。   \n使用充血模型，需要下面几个步骤：\n### 1、需要在Configuration类配置model所在的包位置\n单独使用dbm的项目，只要model类在@EnableDbm注解所在的配置类的包（包括子包）下面即可，dbm会自动扫描。\n```Java\n\n@EnableDbm\npublic class DbmSampleApplication {\n}  \n```\n\n### 2、继承RichModel类\n```Java\n\n@Entity\n@Table(name=\"web_user\")\npublic class User extends RichModel\u003cUser, Long\u003e {\n}\n   \n```\n\n### 3、使用api\n```Java   \n//根据id查找实体   \nUser user = User.findById(id);   \n//保存实体   \nnew User().save();   \n//统计\nint count = User.count().intValue();   \n//查找, K.IF_NULL属性是告诉dbm当查询值userName为null或者空时，该如何处理。IfNull.Ignore表示忽略\nList\u003cUser\u003e users = User.findList(\"userName\", userName, K.IF_NULL, IfNull.Ignore);\n   \n```\n\n## 参数配置\n- logSql：是否打印执行的sql和时间，默认为true；\n需要同时配置日志文件：\n```xml\n\t\u003c!-- print dbm sql--\u003e\n    \u003clogger name=\"org.onetwo.dbm.core.internal.LogSqlInterceptor\" level=\"TRACE\"\u003e\n        \u003cappender-ref ref=\"logFile\" /\u003e\n    \u003c/logger\u003e\n```\n- watchSqlFile：是否监视sql文件，如果有修改则重新加载，默认为true；\n- useBatchOptimize：是否使用批量优化save和insert等api的操作，为true并且插入数量超过了设定的阈值，则会把此类api的循环插入优化为jdbc的批量插入，默认为true；\n- useBatchThreshold：批量插入的阈值，调用save和insert等api时，如果传入的集合数量超过了阈值，则自动转为批量插入，否则循环插入，默认为50；\n- processSizePerBatch：批量插入时，每次提交的数量，默认为10000；\n- enableSessionCache：是否启用会话缓存，默认为false；\n\n\n## 代码生成器\ndbm内置了一个代码生成器，可以根据模板生成下列文件：\n- 实体\n- service\n- controller\n- 基于freemarker的增删改查页面\n- 基于element-ui(vue)的增删改查页面\n\n使用示例：\n```Java\nDbmGenerator.createWithDburl(\"jdbc:mysql://localhost:3306/test?useUnicode=true\u0026characterEncoding=utf8\", \"root\", \"root\")\n\t\t\t\t\t.javaBasePackage(\"com.test.order\")//基础包名\n\t\t\t\t\t.pluginProjectDir(\"OrderPlugin\")//插件项目名称\n\t\t\t\t\t.webadminGenerator(\"t_order\")//要生成的表名\n\t\t\t\t\t\t.generateEntity()\n\t\t\t\t\t\t.generateServiceImpl()//配置在service.impl包下生成service类\n\t\t\t\t\t\t.generateController(BaseController.class)//配置在controller包下生成controller，并制定controller基类\n\t\t\t\t\t\t.generatePage()//生成freemarker的增删改查页面，配置在src/main/resources/META-INF/resources/webftls/${pluginName}下生成crud页面\n\t\t\t\t\t\t.generateVueCrud()//生成基于element-ui(vue)的增删改查页面，\n\t\t\t\t\t.end()\n\t\t\t\t\t.build()\n\t\t\t\t\t.generate();//生成文件\n```\n\n## 辅助工具导出表结构为excel\n可以通过ExcelExporter类导出表结构为excel\n\n```Java\n    TableExportParam params = new TableExportParam();\n    params.setExportFilePath(\"f:/test/表字段说明.xls\"); // 导出的excel文件保存路径\n    params.addTable(\"table1\", // 导出的表名\n            \"table2\");\n    params.setConfigurer(it -\u003e {\n        // 满足条件的字段才会被导出\n        it.setCondition(\"#column.name!='create_at' \u0026\u0026 #column.name!='update_at'\");\n    });\n    ExcelExporter export = ExcelExporter.create(dataSource);\n    export.exportTableShema(params); // 导出\n```\n\n\n## 待续。。。\n\n\n\n## 捐赠\n如果你觉得这个项目帮到了你，请用支付宝打赏一杯咖啡吧~~~   \n\n![支付宝](doc/alipay2.jpg)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwayshall%2Fdbm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwayshall%2Fdbm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwayshall%2Fdbm/lists"}