# basedao **Repository Path**: yyjxpfz/basedao ## Basic Information - **Project Name**: basedao - **Description**: 一款轻量、高效、便捷、无侵入的ORM辅助工具,基于MyBatis或JDBC Template快速实现数据库增删改查。 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 9 - **Forks**: 0 - **Created**: 2024-01-29 - **Last Updated**: 2025-11-09 ## Categories & Tags **Categories**: database-dev **Tags**: MyBatis, Java, MySQL, JdbcTemplate ## README # BaseDao ### 介绍 这是一款轻量、高效、便捷、无侵入的ORM辅助工具,帮助开发者在基于MyBatis或JDBC Template的系统上快速实现数据库的增删改查操作。 核心特性: 1. **标准CRUD方法**:提供了一套标准的、通用的数据库操作方法,如`insert`、`updateByUniqueKey`、`selectByExample`、`deleteById`等,极大地减少了手动编写SQL的工作量。 2. **支持复杂查询**:利用注解处理器(JSR269),框架可以在编译时自动生成Query类。这些Query类提供流式调用的方式,帮助开发者更直观方便地构建查询条件,以应对复杂查询SQL需求。 3. **逻辑删除功能**:支持逻辑删除。在逻辑删除模式下,删除操作自动切换为更新删除标记操作,新增操作自动初始化为未删除状态,更新和查询操作自动忽略已删除数据。此外,采用了不破坏唯一索引约束的逻辑删除方式,相同唯一键下允许存在多条已删除的数据,仅允许存在一条未删除的数据。 4. **支持分库分表**:内置分库分表功能,支持指定键分键和自定义分库分表算法,框架自动根据算法选择库表。同时也兼容第三方分库分表组件。 5. **代码生成**:提供工具通过数据库表结构自动生成Entity类,提高开发效率。 6. **字节码增强**:采用字节码增强技术提高框架性能和效率,相比传统的反射调用方式性能表现显著提升。 ### 安装及使用 #### 菜鸟级——开箱即用 1. 根据需要,引入basedao-mybatis或者basedao-jdbctemplate的jar包。 ```xml priv.pfz basedao-mybatis 1.0-SNAPSHOT priv.pfz basedao-jdbctemplate 1.0-SNAPSHOT ``` 2. 将需要增强的dao继承basedao提供的基类 MyBatis:Mapper继承SimpleBaseMapper JDBC Template:Dao继承SimpleBaseDao(非必须),DaoImpl继承SimpleBaseDaoImpl ```java //MyBatis public interface UserMapper extends SimpleBaseMapper { //可根据需求在基础CRUD之外扩展方法 ... } //JDBC Template public interface UserDao extends SimpleBaseDao { //可根据需求在基础CRUD之外扩展方法 ... } @Repository public class UserDaoImpl extends SimpleBaseDaoImpl implements UserDao { //可根据需求在基础CRUD之外扩展方法 ... } ``` 3. 继承后,无需任何其它配置,即可自动具备一组基础方法,开箱即用。 ```java import java.util.ArrayList; @Resource private UserMapper userMapper; @Test public void test() { userMapper.insert(new UserEntity()); userMapper.batchInsert(new ArrayList<>()); userMapper.updateById(new UserEntity()); userMapper.updateByExample(new UserEntity(), new UserEntity()); userMapper.selectById(1); userMapper.listByIds(new ArrayList<>()); userMapper.mapByIds(new ArrayList<>()); userMapper.selectByExample(new UserEntity()); userMapper.listByExample(new UserEntity()); userMapper.countAll(); userMapper.listAll(); userMapper.deleteById(1); userMapper.deleteByIds(new ArrayList<>()); userMapper.deleteByExample(new UserEntity()); userMapper.saveById(new UserEntity()); userMapper.batchSaveById(new UserEntity()); } ``` #### 进阶级——注解增强 在Entity类的字段上进行注解标注,例如@UniqueKey、@NoColumn、@NoSelect、@Column等,即可满足更多的定制需求。 ```java public class UserEntity { /** * 数据库自增id */ @NoSelect //不希望查询此字段 private Integer id; /** * 身份证号 */ @UniqueKey //身份证号是唯一键,标注后支持updateByUniqueKey方法 private String idCardNo; /** * 姓名 */ private String name; /** * 年龄 */ @Column(columnName = "years_old") //数据库表列名和Entity字段名不一致 private Integer age; /** * 住址 */ @NoColumn //User表中并没有此字段,不希望读写此字段 private String address; } ``` #### 专家级——复杂查询 1. 类似Lombok,在Entity类上标注@Entity注解,即可在编译时自动生成对应的Query类,实现便捷地构造复杂查询条件。 ```java @Entity public class UserEntity { ... } ``` 此类为框架自动生成,用于构造复杂查询的查询条件 ```java /** * @author BaseDao * 2024/1/29 23:26 */ public class UserQuery extends BaseQuery { public final IntColumn id = new IntColumn<>(this, "id", "id"); public final StringColumn idCardNo = new StringColumn<>(this, "idCardNo", "id_card_no"); public final StringColumn name = new StringColumn<>(this, "name", "name"); public final IntColumn age = new IntColumn<>(this, "age", "years_old"); } ``` 2. 将继承的basedao基类更换为DefaultBaseMapper/DefaultBaseDaoImpl,即可支持复杂查询。 ```java //MyBatis public interface UserMapper extends DefaultBaseMapper { ... } //JDBC Template public interface UserDao extends DefaultBaseDao { ... } @Repository public class UserDaoImpl extends DefaultBaseDaoImpl implements UserDao { ... } ``` ```java @Resource private UserMapper userMapper; @Test public void test() { //default userMapper.update(new UserEntity(), new UserQuery()); userMapper.count(new UserQuery()); userMapper.list(new UserQuery()); userMapper.delete(new UserQuery()); userMapper.pageQuery(new UserQuery()); //query example UserQuery query = new UserQuery() .idCardNo.eq("123456") .name.like("诸葛") .age.between(20, 40) .id.desc() .page(2, 10); userMapper.list(query); } ``` ### BaseDao方法汇总 #### INSERT | 方法名 | 方法签名 | 功能 | 说明 | SQL伪代码 | |--------------|--------------------------------------------------------|---------------|-------------------------------|-------------------------------------------------------------------------------------------------------------| | insert | int insert(@Nonnull Entity entity); | insert一条记录 | entity中为空的字段不会保存 | INSERT INTO table (a,b,c) VALUES (entity.a, entity.b, entity.c) | | batchInsert | int batchInsert(@Nonnull Iterable\ entities); | 批量insert多条记录 | 以第一个entity中非空的字段为模板保存所有entity | INSERT INTO table (a,b,c) VALUES (entity1.a, entity1.b, entity1.c), (entity2.a, entity2.b, entity2.c), ... | #### UPDATE | 方法名 | 方法签名 | 功能 | 说明 | SQL伪代码 | |--------------------|-------------------------------------------------------------------------------------|--------------------------------|----------------------------------------|-------------------------------------------------------------------------------------------------------------------------| | updateById | int updateById(@Nonnull Entity entity); | 按id更新一条记录 | entity中为空的字段不会更新 | UPDATE table SET a=entity.a, b=entity.b WHERE id=entity.id | | updateByUniqueKey | int updateByUniqueKey(@Nonnull Entity entity); | 按Entity中标注了@UniqueKey的字段更新一条记录 | entity中为空的字段不会更新 | UPDATE table SET a=entity.a, b=entity.b WHERE uk=entity.uk | | updateByExample | int updateByExample(@Nonnull Entity entity, @Nonnull Entity example); | 将example匹配的记录更新为entity | example中为空的字段不会作为查询条件,entity中为空的字段不会更新 | UPDATE table SET a=entity.a, b=entity.b WHERE b=example.b AND c=example.c | | updateByExamples | int updateByExamples(@Nonnull Entity entity, @Nonnull Iterable\ examples); | 将examples匹配的记录更新为entity | example中为空的字段不会作为查询条件,entity中为空的字段不会更新 | UPDATE table SET a=entity.a, b=entity.b WHERE (b=example1.b AND c=example1.c) OR (c=example2.c AND d=example2.d) OR ... | | update | int update(@Nonnull Entity entity, @Nonnull Query query); | 按query条件批量更新多条记录 | entity中为空的字段不会更新 | UPDATE table SET a=entity.a, b=entity.b WHERE query | #### SELECT | 方法名 | 方法签名 | 功能 | 说明 | SQL伪代码 | |-----------------|----------------------------------------------------------------------|----------------------------|-----------------------|-----------------------------------------------------------------------------------------------------| | selectById | Entity selectById(long id); | 按id查询一条记录 | | SELECT a,b,c FROM table WHERE id=#{id} | | listByIds | List\ listByIds(@Nonnull Iterable\ ids); | 按ids批量查询多条记录 | | SELECT a,b,c FROM table WHERE id in ids | | selectByExample | Entity selectByExample(@Nonnull Entity example); | 按example中的条件查询第一条记录 | example中为空的字段不会作为查询条件 | SELECT a,b,c FROM table WHERE a=entity.a AND b=entity.b LIMIT 1 | | listByExample | List\ listByExample(@Nonnull Entity example); | 按example中的条件查询多条记录 | example中为空的字段不会作为查询条件 | SELECT a,b,c FROM table WHERE a=entity.a AND b=entity.b | | listByExamples | List\ listByExamples(@Nonnull Iterable\ examples); | 按examples中的条件查询多条记录 | example中为空的字段不会作为查询条件 | SELECT a,b,c FROM table WHERE (a=entity1.a AND b=entity1.b) OR (b=entity2.b AND c=entity2.c) OR ... | | count | int count(@Nonnull Query query); | 按query条件查询数量 | query中为空的字段不会作为查询条件 | SELECT count(*) FROM table WHERE query | | countAll | int countAll(); | 查询全表数量 | | SELECT count(*) FROM table | | list | List\ list(@Nonnull Query query); | 按query条件查询多条记录 | query中为空的字段不会作为查询条件 | SELECT a,b,c FROM table WHERE query | | listAll | List\ listAll(); | 查询全表所有记录 | | SELECT a,b,c FROM table | #### DELETE | 方法名 | 方法签名 | 功能 | 说明 | SQL伪代码 | |------------------|--------------------------------------------------------------|---------------------|-----------------------|-----------------------------------------------------------------------------------------------| | deleteById | int deleteById(long id); | 按id删除一条记录 | | DELETE FROM table WHERE id=#{id} | | deleteByIds | int deleteByIds(@Nonnull Iterable\ ids); | 按ids批量删除多条记录 | | DELETE FROM table WHERE id in ids | | deleteByExample | int deleteByExample(@Nonnull Entity example); | 按example中的条件删除多条记录 | example中为空的字段不会作为删除条件 | DELETE FROM table WHERE a=entity.a AND b=entity.b | | deleteByExamples | int deleteByExamples(@Nonnull Iterable\ examples); | 按examples中的条件删除多条记录 | example中为空的字段不会作为删除条件 | DELETE FROM table WHERE (a=entity1.a AND b=entity1.b) OR (b=entity2.b AND c=entity2.c) OR ... | | delete | int delete(@Nonnull Query query); | 按query中的条件删除多条记录 | query中为空的字段不会作为删除条件 | DELETE FROM table WHERE query | #### 组合SQL | 方法名 | 方法签名 | 功能 | 说明 | SQL伪代码 | |----------------------|-----------------------------------------------------------------|----------|-------------------|----------------------------| | loopInsert | int loopInsert(@Nonnull Iterable\ entities); | 循环insert | | 参考insert | | pageQuery | Page\ pageQuery(@Nonnull Query query); | 分页查询 | | 参考count和list | | saveById | int saveById(@Nonnull Entity entity); | 按id保存 | 尝试保存,唯一键冲突时按id更新 | 参考insert和updateById | | saveByUniqueKey | int saveByUniqueKey(@Nonnull Entity entity); | 按唯一键保存 | 尝试保存,唯一键冲突时按唯一键更新 | 参考insert和updateByUniqueKey | | batchSaveById | int batchSaveById(@Nonnull Iterable\ entities); | 批量按id保存 | 尝试保存,唯一键冲突时按id更新 | 参考insert和updateById | | batchSaveByUniqueKey | int batchSaveByUniqueKey(@Nonnull Iterable\ entities); | 批量按唯一键保存 | 尝试保存,唯一键冲突时按唯一键更新 | 参考insert和updateByUniqueKey | ### 注解汇总 | 注解名 | 标注位置 | 功能 | |---------------|----------|----------------------------------------------------------| | @Table | Dao类 | 指定Dao对应的数据库名、表名、是否开启逻辑删除、分库分表模式等 | | @Entity | Entity类 | 使此Entity类开启自动生成相应的Query类 | | @NoColumn | Entity字段 | 使框架全局忽略此字段 | | @NoSelect | Entity字段 | select/list系列方法不返回此字段,适用于text大字段,以提升性能(对于大字段建议单独手写SQL查询) | | @UniqueKey | Entity字段 | updateByUniqueKey方法以标注了此注解的一个或多个字段作为查询条件 | | @Column | Entity字段 | 手工指定数据库列名,推荐Entity字段名与数据库列名保持一致,尽量避免使用此注解 | | @ShardingKey | Entity字段 | 在分库分表模式下用于指定切分键和切分策略 | ### Query查询能力汇总 1. WHERE(字段级) ```java UserQuery query = new UserQuery() .idCardNo.eq("123456"); ``` 除between和notBetween外,当value为空时,不会创建条件。非String类型“空”的含义为null,String类型“空”的含义为blank。 | 数据类型 | 操作 | 方法签名 | SQL伪代码 | |--------|------------|-------------------------------------------------------------------------------------|--------------------------------------| | 所有 | eq | Query eq(@Nullable T value); | xxx = 'abc' | | 所有 | ne | Query ne(@Nullable T value); | xxx != 'abc' | | 所有 | gt | Query gt(@Nullable T value); | xxx > 'abc' | | 所有 | lt | Query lt(@Nullable T value); | xxx < 'abc' | | 所有 | gte | Query gte(@Nullable T value); | xxx >= 'abc' | | 所有 | lte | Query lte(@Nullable T value); | xxx <= 'abc' | | 所有 | isNull | Query isNull(); | xxx IS NULL | | 所有 | notNull | Query notNull(); | xxx IS NOT NULL | | 所有 | in | Query in(@Nullable Iterable\ values)
Query in(@Nullable T... values) | xxx IN ('a', 'b', 'c') | | 所有 | notIn | Query notIn(@Nullable Iterable\ values)
Query notIn(@Nullable T... values) | xxx NOT IN ('a', 'b', 'c') | | 所有 | between | Query between(@Nonnull T from, @Nonnull T to); | xxx BETWEEN 'a' AND 'b' | | 所有 | notBetween | Query notBetween(@Nonnull T from, @Nonnull T to); | xxx NOT BETWEEN 'a' AND 'b' | | String | isEmpty | Query isEmpty(); | xxx = '' | | String | notEmpty | Query notEmpty(); | xxx != '' | | String | like | Query like(@Nullable String value); | xxx LIKE CONCAT('%', 'abc', '%') | | String | notLike | Query notLike(@Nullable String value); | xxx NOT LIKE CONCAT('%', 'abc', '%') | | String | startWith | Query startWith(@Nullable String value); | xxx LIKE CONCAT('abc', '%') | | String | endWith | Query endWith(@Nullable String value); | xxx LIKE CONCAT('%', 'abc') | 2. ORDER BY(字段级) ```java UserQuery query = new UserQuery() .id.desc(); ``` | 操作 | 方法签名 | SQL伪代码 | |--------|-----------------|-------------------| | asc | Query asc(); | ORDER BY xxx ASC | | desc | Query desc(); | ORDER BY xxx DESC | 3. LIMIT(查询级) ```java UserQuery query = new UserQuery() .page(2, 10); ``` | 操作 | 方法签名 | SQL伪代码 | |-------|---------------------------------------|-------------------------------------| | page | Query page(int pageNo, int pageSize); | LIMIT (pageNo-1)*pageSize, pageSize | | limit | Query limit(int offset, int rows); | LIMIT offset, rows | | limit | Query limit(int rows) | LIMIT rows | ### 逻辑删除功能 1. 开启条件:数据库表中已存在delete_flag字段,类型为int,唯一索引需包含delete_flag字段。 2. 开启方式:在@Table注解中设置enableLogicDelete=true ```java //MyBatis @Table(enableLogicDelete = true) public interface UserMapper extends DefaultBaseMapper { ... } //JDBC Template public interface UserDao extends DefaultShardingBaseDao { ... } @Repository @Table(enableLogicDelete = true) public class UserDaoImpl extends DefaultShardingBaseDaoImpl implements UserDao { ... } ``` 3. 开启效果:所有字段由物理删除改为逻辑删除,delete_flag=0表示未删除,delete_flag>0表示已删除,已删除的记录无法查询或更新。 4. SQL转换原理: SELECT默认添加"delete_flag=0"条件 INSERT默认设置delete_flag为0 UPDATE默认添加"delete_flag=0"条件 DELETE转换为"UPDATE table SET delete_flag = id WHERE query AND delete_flag=0" ### 分库分表功能 1. 开启方式: 1)在@Table中设置shardingMode为SHARDING_DB(仅分库)、SHARDING_TABLE(仅分表)、SHARDING_DB_TABLE(分库分表)之一。开启分库时,@Table的dbName为必填。 2)可选:将basedao基类替换为分库分表版本,即xxxShardingBaseMapper/xxxShardingBaseDaoImpl,即可通过参数中的shardingInfo手工指定分库分表信息和获取分库分表结果。 ```java //MyBatis @Table(dbName = "db", shardingMode = ShardingMode.SHARDING_DB_TABLE) public interface UserMapper extends DefaultShardingBaseMapper { ... } //JDBC Template public interface UserDao extends DefaultShardingBaseDao { ... } @Repository @Table(dbName = "db", shardingMode = ShardingMode.SHARDING_DB_TABLE) public class UserDaoImpl extends DefaultShardingBaseDaoImpl implements UserDao { ... } ``` 3)可选:在Entity的切分键字段上标注@ShardingKey,并设置切分策略类。设置@ShardingKey后,若SQL中有此字段的精确条件,则会根据切分策略自动生成分库分表信息。 ```java /** * 身份证号 */ @UniqueKey @ShardingKey(shardingStrategy = UserShardingStrategy.class) private String idCardNo; ``` ```java /** * user表根据idCardNo分库分表的切分策略 */ public class UserShardingStrategy implements ShardingStrategy { @Override public ShardingInfo getShardingInfo(String idCardNo) { String dbSuffix = ...; String tableSuffix = ...; return ShardingInfo.dbTable(dbSuffix, tableSuffix); } } ``` 2. 使用方式: 手工指定分库分表:通过方法的shardingInfo参数指定。 ```java UserEntity userEntity = new UserEntity(); userEntity.setName("yyjx"); userEntity.setAge(99); //手工指定02库05表 ShardingInfo shardingInfo = ShardingInfo.dbTable("02", "05"); userMapper.insert(userEntity, shardingInfo); //INSERT INTO db_02.`user_05` (`name`, `age`) VALUES (?, ?); ``` 自动选择分库分表:在Entity或Query中填写切分键字段,若SQL中存在切分键的精确条件,则会自动生成分库分表信息。 ```java //包含切分键idCardNo,根据123456自动计算出分库分表 UserEntity userEntity = new UserEntity(); userEntity.setName("yyjx"); userEntity.setAge(99); userEntity.setIdCardNo("123456"); //shardingInfo可不设置值 ShardingInfo shardingInfo = ShardingInfo.empty(); userMapper.insert(userEntity, shardingInfo); //INSERT INTO db_02.`user_05` (`name`, `age`, `id_card_no`) VALUES (?, ?, ?); ``` 获取分库分表结果:调用方法后,框架实际选择的分库分表结果会回填到shardingInfo参数中。 ```java UserEntity userEntity = new UserEntity(); userEntity.setName("yyjx"); userEntity.setAge(99); userEntity.setIdCardNo("123456"); ShardingInfo shardingInfo = ShardingInfo.empty(); userMapper.insert(userEntity, shardingInfo); //INSERT INTO db_02.`user_05` (`name`, `age`, `id_card_no`) VALUES (?, ?, ?); //调用方法后从shardingInfo中获取实际的分库分表信息 System.out.println(shardingInfo); //ShardingInfo(dbSuffix=02, tableSuffix=05) ``` 3. 支持第三方分库分表组件 在@Table中设置shardingMode为THIRD_SHARDING_COMPONENT,然后通过方法的shardingInfo参数指定三方分库分表信息。 ```java UserEntity userEntity = new UserEntity(); userEntity.setName("yyjx"); userEntity.setAge(99); userEntity.setIdCardNo("123456"); //shardingInfo中传入第三方所需的分库分表信息,此信息段追加到SQL末尾 ShardingInfo shardingInfo = ShardingInfo.third("/@sdb=02;ts=05@/"); userMapper.insert(userEntity, shardingInfo); //INSERT INTO `user` (`name`, `age`, `id_card_no`) VALUES (?, ?, ?) /@sdb=02;ts=05@/ ``` ### Entity生成器 框架内置了一个Entity生成器,开发阶段可手工调用生成器生成Entity类,生成器会连接数据库查询DDL,根据DDL生成对应的Entity类。 调用示例如下: ```java public static void main(String[] args) { Generator.newInstance() .entitySuffix("Entity") .entityPath("basedao-lab/src/main/java/priv/pfz/basedao/lab/dao/entity/") .entityPackage("priv.pfz.basedao.lab.dao.entity") .dbName("world") .tableNames("tb_goods_00", "tb_trade_info_00") .tableNamePrefix("tb_") .tableNameSuffix("_00") .dbUrl("jdbc:mysql://localhost:3306/") .dbUser("root") .dbPassword("123456") .generate(); } ``` 数据库DDL如下: ```mysql CREATE TABLE `tb_goods_00` ( `id` int NOT NULL COMMENT '自增id', `goods_code` varchar(30) NOT NULL COMMENT '商品编码', `name` varchar(30) DEFAULT NULL COMMENT '商品名称', `price` int DEFAULT NULL COMMENT '价格', `delete_flag` int NOT NULL COMMENT '删除标识', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ``` 自动生成的Entity类如下: ```java package priv.pfz.basedao.lab.dao.entity; import lombok.Data; import priv.pfz.basedao.annotations.Entity; import java.util.Date; /** * @author BaseDao * 2024/2/6 0:27 */ @Data @Entity public class GoodsEntity { /** * 自增id */ private Long id; /** * 商品编码 */ private String goodsCode; /** * 商品名称 */ private String name; /** * 价格 */ private Long price; /** * 删除标识 */ private Long deleteFlag; /** * 创建时间 */ private Date createTime; /** * 更新时间 */ private Date updateTime; } ```