# 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;
}
```