{"id":38599407,"url":"https://github.com/pugwoo/nimble-orm","last_synced_at":"2026-01-17T08:32:05.362Z","repository":{"id":35755447,"uuid":"40034703","full_name":"pugwoo/nimble-orm","owner":"pugwoo","description":"一个基于Spring jdbcTemplate的灵活轻量级的ORM","archived":false,"fork":false,"pushed_at":"2025-12-24T15:38:46.000Z","size":1889,"stargazers_count":46,"open_issues_count":0,"forks_count":18,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-12-26T05:44:49.653Z","etag":null,"topics":["java","jdbc","mysql","orm","spring"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pugwoo.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG","contributing":null,"funding":null,"license":"LICENSE","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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2015-08-01T04:02:52.000Z","updated_at":"2025-10-20T14:31:58.000Z","dependencies_parsed_at":"2023-09-24T11:38:55.480Z","dependency_job_id":"50b26a75-2f30-4eb0-a754-4105f84fe508","html_url":"https://github.com/pugwoo/nimble-orm","commit_stats":null,"previous_names":[],"tags_count":75,"template":false,"template_full_name":null,"purl":"pkg:github/pugwoo/nimble-orm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pugwoo%2Fnimble-orm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pugwoo%2Fnimble-orm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pugwoo%2Fnimble-orm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pugwoo%2Fnimble-orm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pugwoo","download_url":"https://codeload.github.com/pugwoo/nimble-orm/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pugwoo%2Fnimble-orm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28504364,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T06:57:29.758Z","status":"ssl_error","status_checked_at":"2026-01-17T06:56:03.931Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["java","jdbc","mysql","orm","spring"],"created_at":"2026-01-17T08:32:05.110Z","updated_at":"2026-01-17T08:32:05.347Z","avatar_url":"https://github.com/pugwoo.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# nimble-orm\n\n# 支持的数据库\n\n| 数据库        | 支持情况                          |\n|------------|-------------------------------|\n| MySQL      | 支持                            |\n| PostgreSQL | 支持                            |\n| Clickhouse | 支持。jdbc版本最高支持至0.7.2，勿用0.8.x版本 |\n| TiDB       | 支持                            |\n\n这是一个基于Spring JdbcTemplate的小工具，帮助开发者简单地完成增删改查。为什么还需要在众多存在的ORM，如MyBatis/Hibernate的情况下再写一个ORM呢？\n\n1. Hibernate因为比较复杂难以使用好，互联网公司大都没有采用，用得多的是MyBatis。而MyBatis的xml文件中，会出现大量相同的列名。增删一个列或修改一个列名，对于xml文件都是很大的变动。有一种方式是用MyBatis的Generator生成xml，但是这样的xml文件如果修改过，下次生成就会覆盖，很容易出错。因此，这种xml方式维护sql，虽然足够灵活，但也非常繁琐。\n\n2. MyBatis对null值的处理不够灵活。例如只想更新非null值，或只插入非null值，MyBatis的写法会出现很多判断语句，大量的重复列名出现。\n\n3. MyBatis提供一种定义接口，然后去xml中实现同名标记的sql，就可以暴露为可用DAO的方式。这种方式我并不推荐，在简单的增删改查功能下，这种方式没有问题，因为一个DAO接口对应于sql并不是只有一条，特别是复杂的DAO接口。如果一条SQL就是一个DAO接口的话，那组装多个sql的逻辑就暴露到service层，这样service层会变得复杂。\n\n# 使用nimble-orm的优势\n\n1. **为互联网频繁的表变动而生。** 表名、字段名，仅在代码中出现一次。修改表名只需要改一处地方，修改字段名，仅需改字段注解一次及where子句中涉及的字段名。增加字段只需增加一个表成员。修改量相比MyBatis大大减少。\n\n2. **实用主义者，注重简单实用的接口。** 分页接口、软删除标记位全面支持、数据库乐观锁CAS写法、事务手动回滚、支持SOA远程方式的跨数据库关联查询等。定义完DO之后，无需增加额外配置，调用接口即可。\n\n3. **贫血模型，纯粹的POJO。**  不需要继承指定类或实现接口，纯粹的POJO，适合于各种序列化场景。喜欢Spring就会喜欢nimble-orm。\n\n4. **没有潜规则约定，一切注解配置，老项目迁移成本极低。** 不会约定类成员变量和表字段名的关系，全部需要通过配置指定，老项目不规则的表名字段名，不会影响新代码的命名。强制指定配置，这种“麻烦”会收获到后续代码运维上的很多便利。欠下的技术债总是要还的，何不一开始就描述清楚点？而xml或其它文件格式写sql，导致几个文件来回切换的编码，一个修改另外一个忘记修改就出错的情况，不再需要了，代码内聚，让阅读和维护更简单。\n\n## Get Started 示例\n\n把代码下载后，可以以Maven项目方式导入到IDE中，数据库链接配置在`src/test/resources/jdbc.properties`中，数据库测试建表语句在`create-table-mysql.sql`中。建好数据库和表之后，就可以执行`TestDBHelper.java`中的例子。\n\n在Java DAO层，一般一个DO对象和一张数据库表是一一对应的。你只需要为DO对象加上一些注解，主要注解哪个表和哪些列，就可以方便地使用DBHelper进行增删改查了。\n\n```java\n// 这个一个标准的POJO，除了注解，没有引入任何nimble-orm的东西，不要求继承或实现任何东西。\n@Table(\"t_student\")\npublic class StudentDO extends IdableSoftDeleteBaseDO { // 这里用不用继承都是可以的，用继承可以框定一些规范（在业务系统中，有些每个表都有的公共字段可以放在父类中）\n\n/** IdableSoftDeleteBaseDO也是单纯的POJO，无需继承或实现任何东西。其内容是：\n  @Column(value = \"id\", isKey = true, isAutoIncrement = true)\n  private Long id;\n  \n  // 软删除标记为，0 未删除，1已删除\n  @Column(value = \"deleted\", softDelete = {\"0\", \"1\"})\n  private Boolean deleted;\n  \n  @Column(value = \"create_time\", setTimeWhenInsert = true)\n  private Date createTime;\n  \n  @Column(value = \"update_time\", setTimeWhenUpdate = true, setTimeWhenInsert = true)\n  private Date updateTime;\n*/\n\t\t\n\t@Column(\"name\")\n\tprivate String name;\n\t\n\t@Column(\"age\")\n\tprivate Integer age;\n\t\n\t// 标准的 getters / setters，如果提供，nimble-orm就会用；如果没提供，就会直接反射设置字段值\n}\n```\n\n项目只需要maven引入：\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.pugwoo\u003c/groupId\u003e\n    \u003cartifactId\u003enimble-orm\u003c/artifactId\u003e\n    \u003cversion\u003e1.8.1\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n同时nimble-orm是基于jdbcTemplate的，nimble-orm需要拿到配置好的jdbcTemplate和namedJdbcTemplate，dataSource请自行提供，不作限制:\n\n```xml\n\t\u003c!-- 配置JdbcTemplate --\u003e\n\t\u003cbean id=\"jdbcTemplate\" class=\"org.springframework.jdbc.core.JdbcTemplate\"\u003e\n\t\t\u003cproperty name=\"dataSource\" ref=\"dataSource\" /\u003e\n\t\u003c/bean\u003e\n\n\t\u003cbean id=\"dbHelper\" class=\"com.pugwoo.dbhelper.impl.SpringJdbcDBHelper\"\u003e\n\t    \u003cproperty name=\"jdbcTemplate\" ref=\"jdbcTemplate\" /\u003e\n\t\u003c/bean\u003e\n```\n\nnimble-orm提供了spring-boot-starter接入方式，详见：https://github.com/pugwoo/nimbleorm-spring-boot-starter\n\n## 一些高级好玩特性介绍（部分）\n\n**数据库用到软删除标记，每次都要写where deleted=0 ?**\n\n在nimble-orm中，你只需要为软删除字段注解上：\n\n```java\n  /**软删除标记，false未删除，true已删除*/\n  @Column(value = \"deleted\", softDelete = {\"0\", \"1\"})\n  private Boolean deleted;\n```\n\n然后增删改查，nimble-orm的接口都会自动处理好软删除的事。例如调用dbHelper.delete方法，会自动update软删除位为1，不会真的删除；例如调用dbHelper.getPage时，会自动给where条件加上deleted=0条件，不会把软删除的查出来。除了DO注解外，你完全不需要在代码中出现deleted字样。\n\n**关联查询**\n\n假设一个场景，StudentDO学生表，有表字段school_id，外键关联SchoolDO表。如果没有类似@OneToOne之类的关联查询，那么需要手动写代码查两次：先查StudentDO拿到school_id，再通过school_id去查SchoolDO表，拿到之后设置到studentDO中。\n\n这个动作可以通过注解让nimble-orm来做，由于我们约定DO是和数据库表一一对应，所以这个关联的类应该叫StudentVO，它继承自StudentDO：\n\n```java\npublic class StudentVO extends StudentDO {\n  \n  // 关联查询时，请务必确保关联字段在Java是相同类型，否则java的equals方法会判断为不相等。\n  // 为了解决这种情况，dbhelper采用一种折中方案，当类型不同时，都转化成string进行判断，同时给出WARN日志\n  @RelatedColumn(localColumn = \"school_id\", remoteColumn = \"id\")\n  private SchoolDO schoolDO;\n\n}\n```\n\n这样就可以了，查询时接口都一模一样，只是把StudentDO.class的地方换成StudentVO.class即可。\n\n**Join查询**\n\n其实和关联查询相似，但有些表有where查询条件时，必须得用join来查询。nimble-orm采用一种巧妙的方式，在不新增接口的情况下，满足了这个需求，目前只支持2个表关联，更多的表关联应该尽量避免。\n\n同样的，定义个VO，使用查询接口一模一样：\n\n```java\n@JoinTable(joinType = JoinTypeEnum.LEFT_JOIN, on = \"t1.school_id=t2.id\")\npublic class StudentSchoolJoinVO {\n\n  @JoinLeftTable\n  private StudentDO studentDO;\n  \n  @JoinRightTable\n  private SchoolDO schoolDO;\n  \n}\n```\n\n上面定义的StudentSchoolJoinVO即表达了`select t1.*,t2.* from t_student t1 left join t_school t2 on t1.school_id=t2.id where t1.deleted=0 and (t2.deleted=0 or t2.deleted is null)`的基本语句。\n\n**拦截器**\n\n通过拦截器，可以截获所有dbhelper对数据库的增删改查操作，可以在拦截器的统一切面中，对系统的所有操作进行记录，方便安全审计。需要注意的是，拦截器对所有的操作生效，牵一发而动全身，需要仔细编码和测试，特别避免在拦截器中使用带相同拦截器的dbhelper而导致数据库操作死循环。\n\n## 关于数据库字段到Java字段的映射关系\n\n强烈建议java POJO不要使用基础类型(如int long)，同时引用类型也不要设置默认值。该建议同样适用于MyBatis，因为：\n\n1. 无法表达数据库NULL数值。\n\n2. 执行相关updateNotNull操作时会埋坑，有些值会一直被默认值覆盖。\n\n其它：\n\n1. 当数据库字段是NULL，而java POJO的字段是基础类型时，会转换成0。数据库字段不支持enum，推荐使用String来表达。\n\n2. 一般来说，Java中的Boolean字段会对应数据库的tinyint(1)字段，和C语言保持一致，对于tinyint(4)，0表示false，非0表示true(包括负数)。当然dbHelper也支持mysql bit的类型。\n\n## 其它技巧和说明\n\n1. 对于用distinct查询数据库某些列的枚举值，可以使用group by代替，性能是等价的，推荐给group by字段加索引。\n\n## 一些思考和选择\n\n\u003cdetails\u003e\n  \u003csummary\u003e为什么insert和update默认只插入非null的值，而单独提供一个insertWithNull和updateWithNull的方法呢?\u003c/summary\u003e\n\n\u003e 答：对于null值，如果写入到数据库，那肯定是null，同时无法使用数据库的默认值。因此null值是没有必要默认就写入数据库的。同理，update也保持一致的做法。\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e为什么@Column的注解要显示指定value值，不做自动根据字段名配置映射成驼峰形式或下划线形式？\u003c/summary\u003e\n\n\u003e 答：`约定优于配置`是一种很常用的指导方向。但需要明确的是，适合约定的内容是什么？它应该是大家乐于接受的，表达明确的，且不易改变的，能支持绝大多数的功能的。例如我们约定linux操作系统的文件夹路径用`/`分隔，就是一个好的约定，在整个linux操作系统内，没有额外的需求需要用另外一个符号来表示。相反的，像一些web框架，约定url到方法的映射关系，可以支持驼峰或下划线的多种混合格式时，就是一个差的约定，例如Java中sayHi的方法，就默认支持url中`say_hi`、`sayHi`，`say_Hi`、`SayHi`等多种不同方式的映射。约定有一个副作用是，失去了追查系统实现的线索，例如你维护一个老系统，前端过来请求`say_Hi`，你怎样快速准确地找到这个url对应的方法？显然你也只能猜测去查找，java中是sayHi，或者say_Hi?。所以Spring MVC明确注解`@RequestMapping`就是一个很不错的方式，它明确了前后端映射的准确对应关系。同样的道理，`@Column`要求明确写value值。\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003e为什么查询参数当SQL写为`a=?`且传入的参数值是null时，不自动转换为`a is null`?\u003c/summary\u003e\n\n\u003e 答：首先，在实际开发过程中，传入查询参数明确为null是极少见的情况，更多的情况是参数有异常（例如参数名称写错导致其值为null，或者前端忘记传递过来导致其值为null）且没有过滤导致。其次，null是有歧义的、不够明确的值，在查询语句中，它即可以表示不需要查询该字段，也可以表示为查询该字段为null的值。在这种有歧义的情况下，最好的设计就是让框架把这种更有可能是人为失误的情况报出异常（通常可以在测试阶段发现），而非自动转换为`a is null`来隐藏一个问题，埋下一个大坑。\n\n\u003c/details\u003e\n\n## 注意事项\n\n1. 如果需要传入的参数是Object...（例如getAll之类的方法），那么当单个参数需要传入多个值时，强烈使用List来传。因为如果使用Object[]来传，Object...本身就是Object[]类型，当只有单个Object[]的时候，就只会取Object[]的第一个参数作为参数，这样就有错误，而且是语义错误，很隐蔽。有一种hack的方式，但不推荐，在传入Object[]参数后面，再加上一个任意类型的参数，让Java不要认为参数是Object...。\n\n2. 参数列表中不能出现null，否则会报`org.springframework.dao.InvalidDataAccessApiUsageException: No value supplied for the SQL parameter 'param1': No value registered for key 'param1'`\n\n3. 对于内部类是由@RelatedColumn注解的@Table及其子类的，需要修饰为`public static class`，否则dbhelper访问不到，将导致数据转换成对象失败，从而返回null对象。详见测试代码中`SchoolWithInnerClassVO.java`例子。\n\n4. 虽然mysql数据库支持使用单引号和双引号来表示字符串，但在SQL标准中，定义的是单引号，并没有双引号。因此，nimble-orm为了处理规范和简单，只支持单引号表示字符串。\n\n5. nimble-orm支持多数据源，在多数据源事务管理器的情况下，nimble-orm仍能正常工作。\n\n6. 不建议使用java.sql.Timestamp/java.sql.Date/java.sql.Time类型，推荐使用LocalDate和LocalDateTime类型。目前clickhouse 0.4.6驱动仍不能正确处理java.sql.Timestamp类型。\n\n7. 不建议使用clickhouse jdbc 0.8.x版本。该版本存在2个致命问题：1) 不再支持update/delete SQL 2) ResultSet的findColumn返回的值一直是0，这个值应该\u003e=1. 0.7.x以下的版本没有这2个问题。建议充分测试clickhouse驱动的兼容性再升级clickhouse驱动。","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpugwoo%2Fnimble-orm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpugwoo%2Fnimble-orm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpugwoo%2Fnimble-orm/lists"}