# jdbc-plus **Repository Path**: LeaJone/jdbc-plus-master ## Basic Information - **Project Name**: jdbc-plus - **Description**: 强大又简单易用的跨数据库ORM框架,用java语言开发,支持oracle, mysql, sqlserver等数据库无感知切换,不再手写sql脚本,让程序员在繁杂易错的sql脚本的书写中解脱出来,全部用lambda表达式来实现查询,编译期就能将95%以上的脚本问题发现,同时支持多表连接查询。提供跨数据库分页功能,提供拦截器支持,并做了充分的单元测试和spring boot集成 - **Primary Language**: Java - **License**: GPL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 6 - **Created**: 2021-03-13 - **Last Updated**: 2021-03-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # jdbc-plus #### 介绍 跨数据库ORM框架 jdbc-plus不仅仅是简单的类似MyBatis或者是Hibernate,或者是俩者的综合,jdbc-plus远大理想是实现跨数据库的CRUD统一, 并且支持多表连接的lambda表达式查询 #### 软件架构 最简单最强大的ORM框架jdbcplus 1. 实体的CRUD直接用泛型实现,完全不需要定义dao层和service层,用公共方法即可调用 2. 可由实体自动建表,建索引,建外键,生成字段约束 3. 可由表反向生成实体 4. 实体注解简单,使用简单 5. 跨数据库, mysql, oracle,sqlserver数据库无感知切换 6. 程序支持数据源的跨数据库备份和还原 7. 主键可由策略自动生成 8. 多表连接查询全部用户函数式编程实现 9. 多表查询的子查询也全部用函数式编程实现 10. 提供自动检测数据库结构是否一致和自动更新数据库结构功能 11. 不再直接手写SQL,所以编译期能将95%以上的脚本问题不一致问题发现 12. 项目后期维护更简单,修改字段名只需修改注解 13. 拦截器支持,所有表的CRUD可统一处理 14. 提供实体新增时自动填充功能和默认值功能 15. 跨数据库分页功能支持 16. 提供批量插入和批量更新功能 17. 充分的单元测试和spring boot集成 18. 多次运用在实际项目中,大大提高了项目开发和维护成本 19. jdbc-plus写DDL语句就像是直接写Java代码,非常方便 20. 不需要拼接各种字符串来拼接SQL语句(拼接SQL多痛苦在用JDBC的时候经历过,少一个空格都会折磨死) 21. 不用去写各种ResultSet、PrepareStatement 有关的代码 22. 同样支持字符串拼接SQL(不提倡拼接SQL,可能引起SQL注入攻击) 23. 领域模型开发,建议先建实体添加注解,自动生成数据库表结构 24. 高性能,实体反射得到的信息被缓存到内存中,执行CRUD性能和纯JDBC脚本实现方法基本相同 25. 扩展性强,已经实现mysql, oracle,sqlserver方言,后期可考虑添加其它数据库方言支持 26. 安全性, 前端传入SQL攻击脚本无用,只要不在后台直接拼接SQL脚本即可防止SQL注入攻击 27. 易用性,上手简单快捷,不再手写CRUD操作的sql脚本代码和,减少大部分service层和dao层类定义 28. 可维护性,项目后期维护成本低,项目运行期BUG比用mybatis或者纯jdbc脚本少得多 29. 运用常用设计模式,提高框架扩展性和性能。如数据库方言用策略模式,对象生产用工厂模式,单例模式,享元模式,模板方法模式,代理模式,拦截器模式,建造者模式,链式方法模式 ## 组织结构 ``` lua jdbc-plus-master ├── jdbc-plus-common -- 通用代码模块 ├── jdbc-plus-core -- 核心代码实现模块 ├── jdbc-plus-generator -- 代码生成器和反向生成数据表模块 ├── jdbc-plus-boot-starter -- 与spring-boot集成模块 ├── jdbc-plus-boot-starter-test -- 与spring-boot集成后实例测试模块 ├── jdbc-plus-test -- 单元测试模块 ``` ## 适合用户 * 你不想把精力浪费在简单据库增删改查上?jdbc-plus 内置数据库的CRUD功能 * 你是对代码可维护性有高要求的架构师?jdbc-plus的设计目的就是尽可能提高数据库访问代码可维护性 * 平台级产品需要跨库,支持各种客户数据库的?jdbc-plus 支持各种库,程序员编写一次,能运行到各种数据库 ## 阅读源码例子 可以从模块`jdbc-plus-test`和`jdbc-plus-boot-starter-test`中找得到所有例子,运行单元测试例子。 所有例子都是基于mysql, sqlServer, oracle数据库,可以反复运行 ## 代码示例 ### 例子1,内置方法,无需写SQL完成用户表常用CRUD操作 ```java @ApiOperation(value = "业务创建", notes = "业务创建", produces = "application/json") @PostMapping(value = "/add") public R create(@RequestBody @Valid SysUser user) { DaoUtil.BASE.add(user); return R.success("成功"); } @ApiOperation(value = "新增多条数据", notes = "新增多条数据", produces = "application/json") @PostMapping(value = "/addList") public R addList(@RequestBody @Valid List list) { DaoUtil.BASE.addList(list); return R.success("新增多条成功"); } @ApiOperation(value = "根据id修改", notes = "根据id修改", produces = "application/json") @PostMapping(value = "/editById") public R editById(@RequestBody @Valid SysUser user) { try { DaoUtil.BASE.update(user); return R.success("成功"); } catch (Exception e) { log.error("根据id修改业务模块异常", e); return R.fail(ResultCode.FAILURE); } } @ApiOperation(value = "通过id删除", notes = "根据id删除一条或者多条数据") @DeleteMapping(value = "/deleteById") public R deleteById(@RequestParam(name = "id", required = true) String id) { try { DaoUtil.BASE.delete(id, SysUser.class); return R.success("删除成功"); } catch (Exception e) { log.error(e.toString(), e); return R.fail("删除失败,原因:" + e.toString()); } } @ApiOperation(value = "根据ids批量删除", notes = "根据ids批量删除") @DeleteMapping(value = "/deleteBatch") public R deleteBatch(@RequestParam(name = "ids", required = true) String ids) { try { String[] idSplit = ids.split(","); List pkList = Arrays.asList(idSplit); // 删除多条数据 DaoUtil.BASE.delete(pkList, SysUser.class); return R.success("批量删除成功"); } catch (Exception e) { log.error(e.toString(), e); return R.fail("删除失败,原因:" + e.toString()); } } @ApiOperation(value = "分页列表查询", notes = "分页列表查询") @PostMapping(value = "/page") public R> queryPage() { EntityWrap wrap = new EntityWrap<>(SysUser.class); wrap.lambda().like(SysUser::getUsername, "小明"); IPage page = new Page<>(1, 10); IPage pageList = DaoUtil.BASE.page(page, wrap); return R.data(pageList); } @ApiOperation(value = "查询列表", notes = "根据条件查询列表数据") @PostMapping(value = "/queryList") public R> queryList() { EntityWrap wrap = new EntityWrap<>(SysUser.class); wrap.lambda().like(SysUser::getUsername, "小明"); List list = DaoUtil.BASE.find(wrap); return R.data(list); } ``` 内置接口的完整方法如下 ``` public interface IBaseDao { /** * 新增实体 * * @param entity * @param * @return */ String add(M entity); /** * 新增或者修改 *

实体主键为空时新增,实体主键非空先查找是否存在,存在则更新非空值,不存在则新增

* * @param entity * @param * @return */ boolean addOrUpdate(M entity); /** * 新增或者修改实体列表 * * @param entityList * @param * @return */ boolean addOrUpdateList(Collection entityList); /** * 复制实体列表 * * @param entityList * @param * @return */ boolean copyList(List entityList); /** * 新增实体列表 * * @param entityList * @param * @return */ boolean addList(List entityList); /** * 只更新实体中非空字段, 实体主键属性值不可为空 * * @param entity * @param * @return */ boolean update(M entity); /** * 按条件更新实体中非空字段 * * @param entity * @param wrap * @param * @return */ boolean update(M entity, EntityWrap wrap); /** * 按条件更新实体中指定字段 * * @param updateWrap 指定的字段 * @param entityWrap 条件 * @param * @return */ boolean update(UpdateWrap updateWrap, EntityWrap entityWrap); /** * 更新字段 * * @param columnMap * @param wrap * @param * @return */ boolean updateByMap(Map columnMap, EntityWrap wrap); /** * 更新实体列表 * * @param entityList * @param * @return */ boolean updateList(Collection entityList); /** * 更新实体中所有字段, 实体主键属性值不可为空 * * @param entity * @param * @return */ boolean updateAll(M entity); /** * 根据id删除实体 * * @param id * @param entityClass * @param * @return */ boolean delete(String id, Class entityClass); /** * 根据id列表删除实体 * * @param ids * @param entityClass * @param * @return */ boolean delete(Collection ids, Class entityClass); /** * 根据条件删除实体 * * @param wrap * @param * @return */ boolean delete(EntityWrap wrap); /** * 删除全部实体 * * @param entityClass * @param * @return */ boolean deleteAll(Class entityClass); /** * 根据id物理删除实体 * * @param id * @param entityClass * @param * @return */ boolean realDel(String id, Class entityClass); /** * 根据id列表物理删除实体 * * @param ids * @param entityClass * @param * @return */ boolean realDel(Collection ids, Class entityClass); /** * 根据条件物理删除实体 * * @param wrap * @param * @return */ boolean realDel(EntityWrap wrap); /** * 物理删除全部实体 * * @param entityClass * @param * @return */ boolean realDelAll(Class entityClass); /** * 获取一个实体 * * @param id * @param entityClass * @param * @return */ M get(String id, Class entityClass); /** * 根据条件获取一个实体 * * @param wrap * @param * @return */ M get(EntityWrap wrap); /** * 查询id对应的实体是否存在 * * @param id * @param entityClass * @param * @return */ boolean exists(String id, Class entityClass); /** * 根据条件查询实体是否存在 * * @param wrap * @param * @return */ boolean exists(EntityWrap wrap); /** * 查询实体总数 * * @param entityClass * @param * @return */ int countAll(Class entityClass); /** * 根据条件查询实体数量 * * @param wrap * @param * @return */ int count(EntityWrap wrap); /** * 查询全部实体列表 * * @param entityClass * @param * @return */ List findAll(Class entityClass); /** * 根据条件查询实体列表 * * @param wrap * @param * @return */ List find(EntityWrap wrap); /** * 根据id列表查询实体列表 * * @param idList * @param entityClass * @param * @return */ List findByIds(Collection idList, Class entityClass); /** * 查询分页参数和条件查询实体分页 * * @param page * @param wrap * @param * @return */ IPage page(IPage page, EntityWrap wrap); } ``` 集成spring-boot,通过 DaoUtil.BASE 即可调用以上所有方法,因为是泛型实现,实体类只需继承于 Entity 即可 ### 例子2 Select接口单元测试 ```java @Test public void selectDistinct() { QueryLambdaWrap lambdaWrap = new QueryLambdaWrap(); lambdaWrap .selectDistinct(true) .select(SysUser::getEmail) .from(SysUser.class); List emailList = lambdaWrap.findList(String.class, DsUtil.getMysql()); System.out.println(JsonUtil.toPrettyJson(emailList)); } ``` ### 例子3 From接口单元测试 ```java @Test public void from() { QueryLambdaWrap lambdaWrap = new QueryLambdaWrap(); lambdaWrap .selectAll(SysUser.class) .from(SysUser.class); List userList = lambdaWrap.findList(SysUser.class, DsUtil.getMysql()); String sql = lambdaWrap.getExecSqlFormat(); log.info("exec sql format: " + sql); log.info(JsonUtil.toPrettyJson(userList)); } ``` ### 例子4 Where接口单元测试 ```java /** * 大于等于子查询结果 */ @Test public void geFunc() { QueryLambdaWrap lambdaWrap = new QueryLambdaWrap(); lambdaWrap .selectAll(SysUser.class) .from(SysUser.class) .ge(SysUser::getId, e -> e .select(SysUserRole::getUserId) .from(SysUserRole.class) .eq(SysUserRole::getRoleId, "1") ); List userList = lambdaWrap.findList(SysUser.class, DsUtil.getMysql()); log.info(JsonUtil.toPrettyJson(userList)); } ``` ### 例子5 GroupBy接口单元测试 ```java /** * 分组:GROUP BY 字段, ... *

例: groupBy("id", "name")

*/ @Test public void groupBys() { QueryLambdaWrap lambdaWrap = new QueryLambdaWrap(); lambdaWrap .selectAs(SysUser::getStatus, DictVO::getText) .selectCountAs(SysUser::getId, DictVO::getValue) .from(SysUser.class) .groupBy(SysUser::getStatus, SysUser::getSex); List userList = lambdaWrap.findList(DictVO.class, DsUtil.getMysql()); log.info(JsonUtil.toPrettyJson(userList)); } ``` ### 例子6 Having接口单元测试 ```java /** * 自定义having条件 *

* 例: having(i -> i.le("id", 10).gt("price", 1)) * 等价于 having id <= 10 and price > 1 *

*/ @Test public void having() { QueryLambdaWrap lambdaWrap = new QueryLambdaWrap(); lambdaWrap .selectAs(SysUser::getStatus, DictVO::getText) .selectCountAs(SysUser::getId, DictVO::getValue) .from(SysUser.class) .groupBy(SysUser::getStatus, SysUser::getSex) .having(e -> e .ge(SysUser::getStatus, 0) .ge(SysUser::getSex, 0) ); List userList = lambdaWrap.findList(DictVO.class, DsUtil.getMysql()); log.info(JsonUtil.toPrettyJson(userList)); } ``` ### 例子7 OrderBy接口单元测试 ```java /** * 排序 *

* 例 orderByCount(true, "id") * 等价于 order by count(id) asc *

* * @return */ @Test public void orderByCount() { QueryLambdaWrap lambdaWrap = new QueryLambdaWrap(); lambdaWrap .selectAs(SysUser::getStatus, DictVO::getText) .selectCountAs(SysUser::getId, DictVO::getValue) .from(SysUser.class) .groupBy(SysUser::getStatus, SysUser::getSex) .orderByCount(true, SysUser::getId); List userList = lambdaWrap.findList(DictVO.class, DsUtil.getMysql()); log.info(JsonUtil.toPrettyJson(userList)); } ``` ### 例子8 Union接口单元测试 ```java @Test public void union() { String username = "admin"; String menuId = "1"; QueryLambdaWrap lambdaWrap = new QueryLambdaWrap(); lambdaWrap .select(SysRoleMenu::getDataRuleIds) .from(SysRoleMenu.class) .innerJoin(SysMenu.class, SysMenu::getId, SysRoleMenu::getPermissionId) .innerJoin(SysRole.class, SysRole::getId, SysRoleMenu::getRoleId) .innerJoin(SysUserRole.class, SysUserRole::getRoleId, SysRole::getId) .innerJoin(SysUser.class, SysUser::getId, SysUserRole::getUserId) .eq(SysUser::getUsername, username) .eq(SysMenu::getId, menuId) .union(lb -> lb .select(SysDepartRoleMenu::getDataRuleIds) .from(SysDepartRoleMenu.class) .innerJoin(SysMenu.class, SysMenu::getId, SysDepartRoleMenu::getPermissionId) .innerJoin(SysDepartRole.class, SysDepartRole::getId, SysDepartRoleMenu::getRoleId) .innerJoin(SysDepartRoleUser.class, SysDepartRoleUser::getDroleId, SysDepartRole::getId) .innerJoin(SysUser.class, SysUser::getId, SysDepartRoleUser::getUserId) .eq(SysUser::getUsername, username) .eq(SysMenu::getId, menuId) ); String sql = lambdaWrap.getExecSqlFormat(); System.out.println(sql); List list = lambdaWrap.findList(String.class, DsUtil.getMysql()); log.info(JsonUtil.toPrettyJson(list)); } ``` ### 例子9 复杂多表连接查询 ```java public List queryDataRuleIds(String username, String permissionId) { // // QueryLambdaBootstrap bootstrap = new QueryLambdaBootstrap(); bootstrap .select(SysRoleMenu::getDataRuleIds) .from(SysRoleMenu.class) .innerJoin(SysMenu.class, SysMenu::getId, SysRoleMenu::getPermissionId) .innerJoin(SysRole.class, SysRole::getId, SysRoleMenu::getRoleId) .innerJoin(SysUserRole.class, SysUserRole::getRoleId, SysRole::getId) .innerJoin(SysUser.class, SysUser::getId, SysUserRole::getUserId) .eq(SysUser::getUsername, username) .eq(SysMenu::getId, permissionId) .union(lb -> lb .select(SysDepartRoleMenu::getDataRuleIds) .from(SysDepartRoleMenu.class) .innerJoin(SysMenu.class, SysMenu::getId, SysDepartRoleMenu::getPermissionId) .innerJoin(SysDepartRole.class, SysDepartRole::getId, SysDepartRoleMenu::getRoleId) .innerJoin(SysDepartRoleUser.class, SysDepartRoleUser::getDroleId, SysDepartRole::getId) .innerJoin(SysUser.class, SysUser::getId, SysDepartRoleUser::getUserId) .eq(SysUser::getUsername, username) .eq(SysMenu::getId, permissionId) ); return bootstrap.findList(String.class); } ``` ### 例子10 多个查询合并在同一个DAO方法中 这样好处是方便数据库DBA与程序员沟通 ```java /** * 根据用户查询用户权限 * * @param username */ @Override public List queryByUser(String username) { QueryLambdaBootstrap bootstrap = new QueryLambdaBootstrap(); bootstrap .selectAll(SysMenu.class) .from(SysMenu.class) .eq(SysMenu::getDelFlag, 0) .and(e -> e .exists(lb -> lb .select(SysRoleMenu::getId) .from(SysRoleMenu.class) .innerJoin(SysRole.class, SysRole::getId, SysRoleMenu::getRoleId) .innerJoin(SysUserRole.class, SysUserRole::getRoleId, SysRole::getId) .innerJoin(SysUser.class, SysUser::getId, SysUserRole::getUserId) .on(SysMenu::getId, SysRoleMenu::getPermissionId) .eq(SysUser::getUsername, username) ) .or(l -> l .likeRight(SysMenu::getUrl, ":code") .likeLeft(SysMenu::getUrl, "/online") .eq(SysMenu::isHidden, true) ) .or(l -> l.eq(SysMenu::getUrl, "/online")) ); QueryLambdaBootstrap bootstrap1 = new QueryLambdaBootstrap(); bootstrap1 .selectAll(SysMenu.class) .from(SysMenu.class) .eq(SysMenu::getDelFlag, 0) .and(e -> e .exists(lb -> lb .select(SysDepartRoleMenu::getId) .from(SysDepartRoleMenu.class) .innerJoin(SysDepartRole.class, SysDepartRole::getId, SysDepartRoleMenu::getRoleId) .innerJoin(SysDepartRoleUser.class, SysDepartRoleUser::getDroleId, SysDepartRole::getId) .innerJoin(SysUser.class, SysUser::getId, SysDepartRoleUser::getUserId) .on(SysMenu::getId, SysDepartRoleMenu::getPermissionId) .eq(SysUser::getUsername, username) ) ); //lambdaWrap.union(i -> lambdaWrap2); log.info("sql exec 1: " + bootstrap.getExecSql()); List list = bootstrap.findList(SysMenu.class); List list2 = bootstrap1.findList(SysMenu.class); List result = new ArrayList<>(); result.addAll(list); result.addAll(list2); result = result.stream().sorted(Comparator.comparing(l -> l.getSortNo(), Comparator.nullsLast(Double::compareTo))).collect(Collectors.toList()); return result; // SELECT * FROM ( // SELECT p.* // FROM sys_permission p // WHERE (exists( // select a.id from sys_role_permission a // join sys_role b on a.role_id = b.id // join sys_user_role c on c.role_id = b.id // join sys_user d on d.id = c.user_id // where p.id = a.permission_id AND d.username = #{username,jdbcType=VARCHAR} // ) // or (p.url like '%:code' and p.url like '/online%' and p.hidden = 1) // or p.url = '/online') // and p.del_flag = 0 // // UNION // SELECT p.* // FROM sys_permission p // WHERE exists( // select a.id from sys_depart_role_permission a // join sys_depart_role b on a.role_id = b.id // join sys_depart_role_user c on c.drole_id = b.id // join sys_user d on d.id = c.user_id // where p.id = a.permission_id AND d.username = #{username,jdbcType=VARCHAR} // ) // and p.del_flag = 0 // // ) h order by h.sort_no ASC } ``` ### 例子11 根据表结构生成实体 可以使用内置的代码生成框架生成代码何文档,也可以自定义的,用户可自行扩展SourceBuilder类 ```java public static void main(String[] args) { // 生成指定表名列表对应的实体 ConfigTableVo vo = new ConfigTableVo(); vo.setAuthor("william"); vo.setDataSource(getDs()); //实体所在包名前缀 vo.setPackageName("com.william.jdbcplus"); //模块名 vo.setModuleName("generator"); List tables = new ArrayList<>(); // 必须与数据库大小写一致 tables.add("excel_report"); tables.add("excel_report_data_source"); tables.add("excel_report_db"); tables.add("excel_report_db_field"); tables.add("excel_report_db_param"); vo.setTableNames(tables); vo.setTablePrefix(""); byte[] data = entityCode(vo); outputFile(data, "D:\\temp", "report.zip"); //生成数据库所有表对应的实体列表 ConfigVO configVO = new ConfigVO(); configVO.setAuthor("william"); configVO.setDataSource(getDs()); //实体所在包名前缀 configVO.setPackageName("com.william.jdbcplus"); //模块名 configVO.setModuleName("generator"); configVO.setTablePrefix(""); byte[] dataList = entityCodes(configVO); outputFile(dataList, "D:\\temp", "reportAll.zip"); } ``` ### 例子13 根据实体更新表结构 ```java public static void main(String[] args) { //指定数据源 DataSource ds = getDs(); // 根据模块更新数据库 DDLUtil.updateDatabase("com.william.jdbcplus.generator", ds); // 更新单表,表不存在则创建表,表存在则更新表结构 DDLUtil.updateTable(ExcelReport.class, ds); DDLUtil.updateTable(ExcelReportDataSource.class, ds); DDLUtil.updateTable(ExcelReportDb.class, ds); DDLUtil.updateTable(ExcelReportDbField.class, ds); DDLUtil.updateTable(ExcelReportDbParam.class, ds); } ```