放弃老旧的 Mybatis,试试这款国产现代化 ORM!

360影视 动漫周边 2025-03-14 08:54 3

摘要:在 Java 开发中,数据库操作是绕不开的话题。无论是传统的 JDBC,还是更现代化的 MyBatis、MyBatis-Plus 等 ORM 框架,它们都为开发者提供了不同程度的便捷性。MyBatis 以灵活著称,允许开发者手写 SQL 实现复杂的业务逻辑,而

在 Java 开发中,数据库操作是绕不开的话题。无论是传统的 JDBC,还是更现代化的 MyBatis、MyBatis-Plus 等 ORM 框架,它们都为开发者提供了不同程度的便捷性。MyBatis 以灵活著称,允许开发者手写 SQL 实现复杂的业务逻辑,而 MyBatis-Plus 则在此基础上进一步封装,提供了诸如 CRUD 接口、单表操作等便捷功能,让开发效率有了显著提升。然而,在实际使用中,这些框架也存在一些局限性,比如在强类型支持、复杂查询构建和扩展性上的不足,往往需要开发者在灵活性和易用性之间做出权衡。

正是在这样的背景下,easy-query应运而生。

easy-query是一款无任何依赖的 JAVA/Kotlin ORM 框架,十分轻量,拥有非常高的性能,支持单表查询、多表查询、union、子查询、分页、动态表名、VO 对象查询返回、逻辑删、全局拦截、数据库列加密( 支持高性能 like 查询)、数据追踪差异更新、乐观锁、多租户、自动分库、自动分表、读写分离,支持框架全功能外部扩展定制,拥有强类型表达式。

作者开发这款 ORM 框架的原因如下:

Github 地址:Gitee 地址:官网:

Spring Boot 项目引入依赖:

last-version

com.easy-query sql-springboot-starter ${easy-query.version}

数据库脚本(MySQL 为例):

create table t_topic( id varchar(32) not null comment '主键ID'primary key, stars int not null comment '点赞数', title varchar(50) null comment '标题', create_time datetime not null comment '创建时间')comment '主题表';create table t_blog( id varchar(32) not null comment '主键ID'primary key, deleted tinyint(1) default 0 not null comment '是否删除', create_by varchar(32) not null comment '创建人', create_time datetime not null comment '创建时间', update_by varchar(32) not null comment '更新人', update_time datetime not null comment '更新时间', title varchar(50) not null comment '标题', content varchar(256) null comment '内容', url varchar(128) null comment '博客链接', star int not null comment '点赞数', publish_time datetime null comment '发布时间', score decimal(18, 2) not null comment '评分', status int not null comment '状态', `order` decimal(18, 2) not null comment '排序', is_top tinyint(1) not null comment '是否置顶', top tinyint(1) not null comment '是否置顶')comment '博客表';

查询对象:

@Datapublic class BaseEntity implements Serializable { private static final long serialVersionUID = -4834048418175625051L; @Column(primaryKey = true) private String id; /** * 创建时间;创建时间 */ private LocalDateTime createTime; /** * Update时间;Update时间 */ private LocalDateTime updateTime; /** * 创建人;创建人 */ private String createBy; /** * Update人;Update人 */ private String updateBy; /** * 是否Delete;是否Delete */ @LogicDelete(strategy = LogicDeleteStrategyEnum.Boolean) private Boolean deleted;}@Data@Table("t_topic")@EntityProxy //or @EntityFileProxy@ToStringpublic class Topic implements ProxyEntityAvailable { @Column(primaryKey = true) private String id; private Integer stars; private String title; private LocalDateTime createTime;}//The ProxyEntityAvailable interface can be quickly generated using the IDEA plugin EasyQueryAssistant.@Data@Table("t_blog")@EntityProxy //or @EntityFileProxypublic class BlogEntity extends BaseEntity implements ProxyEntityAvailable{ /** * 标题 */ private String title; /** * 内容 */ private String content; /** * 博客链接 */ private String url; /** * 点赞数 */ private Integer star; /** * 发布时间 */ private LocalDateTime publishTime; /** * 评分 */ private BigDecimal score; /** * 状态 */ private Integer status; /** * 排序 */ private BigDecimal order; /** * 是否置顶 */ private Boolean isTop; /** * 是否置顶 */ private Boolean top;}

代码:

Topic topic = easyEntityQuery .queryable(Topic.class) .where(o -> o.id.eq("3")) .firstOrNull;

执行:

==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic` t WHERE t.`id` = ? LIMIT 1==> Parameters: 3(String)

利用 join :

Topic topic = entityQuery .queryable(Topic.class) .leftJoin(BlogEntity.class, (t, t1) -> t.id.eq(t1.id)) .where(o -> { o.id.eq("3"); o.title.eq("4"); }) .firstOrNull;

执行:

==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic` t LEFT JOIN `t_blog` t1 ON t1.`deleted` = ? AND t.`id` = t1.`id` WHERE t.`id` = ? LIMIT 1==> Parameters: false(Boolean),3(String)

利用 join + group +分页:

EasyPageResult page = easyEntityQuery .queryable(Topic.class) .innerJoin(BlogEntity.class,(t1,t2)->t1.id.eq(t2.id)) .where((t1,t2)->t2.title.isNotNull) .groupBy((t1,t2)->GroupKeys.TABLE2.of(t2.id)) .select(g->{ BlogEntityProxy r = new BlogEntityProxy; r.id.set(g.key1); r.score.set(g.sum(g.group.t2.score)); return r; }) .toPageResult(1, 20);

执行:

==> Preparing: SELECT COUNT(*) FROM (SELECT t1.`id` AS `id`,SUM(t1.`score`) AS `score` FROM `t_topic` t INNER JOIN `t_blog` t1 ON t1.`deleted` = ? AND t.`id` = t1.`id` WHERE t1.`title` IS NOT NULL GROUP BY t1.`id`) t2 ==> Parameters: false(Boolean) Preparing: SELECT t1.`id` AS `id`,SUM(t1.`score`) AS `score` FROM `t_topic` t INNER JOIN `t_blog` t1 ON t1.`deleted` = ? AND t.`id` = t1.`id` WHERE t1.`title` IS NOT NULL GROUP BY t1.`id` LIMIT 20==> Parameters: false(Boolean)增删改查

新增一条数据:

Topic topic = new Topic;topic.setId(String.valueOf(0));topic.setStars(100);topic.setTitle("标题0");topic.setCreateTime(LocalDateTime.now.plusDays(i));long rows = easyEntityQuery.insertable(topic).executeRows;

执行:

//返回结果rows为1==> Preparing: INSERT INTO `t_topic` (`id`,`stars`,`title`,`create_time`) VALUES (?,?,?,?)==> Parameters: 0(String),100(Integer),标题0(String),2023-03-16T21:34:13.287(LocalDateTime)

修改指定的数据:

//实体更新 Topic topic = easyEntityQuery.queryable(Topic.class) .where(o -> o.id.eq("7")).firstNotNull("未找到对应的数据"); String newTitle = "test123" + new Random.nextInt(100); topic.setTitle(newTitle);long rows=easyQuery.updatable(topic).executeRows;

执行:

==> Preparing: UPDATE t_topic SET `stars` = ?,`title` = ?,`create_time` = ? WHERE `id` = ?==> Parameters: 107(Integer),test12364(String),2023-03-27T22:05:23(LocalDateTime),7(String)

删除指定的数据:

long l = easyQuery.deletable(Topic.class) .where(o->o.title.eq("title998")) .executeRows;

执行:

==> Preparing: DELETE FROM t_topic WHERE `title` = ?==> Parameters: title998(String)联合查询

利用 union :

Queryable q1 = easyQuery .queryable(Topic.class);Queryable q2 = easyQuery .queryable(Topic.class);Queryable q3 = easyQuery .queryable(Topic.class);List list = q1.union(q2, q3).where(o -> o.eq(Topic::getId, "123321")).toList;

执行:

==> Preparing: SELECT t1.`id`,t1.`stars`,t1.`title`,t1.`create_time` FROM (SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic` t UNION SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic` t UNION SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic` t) t1 WHERE t1.`id` = ?==> Parameters: 123321(String)子查询

in 子查询:

EntityQueryable idQuery = easyEntityQuery.queryable(BlogEntity.class) .where(o -> o.id.eq("1" )) .select(o -> new StringProxy(o.id)); List list1 = easyEntityQuery.queryable(Topic.class) .where(o -> o.id.in(idQuery)) .toList;

执行:

==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic` t WHERE t.`id` IN (SELECT t1.`id` FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ?) ==> Parameters: false(Boolean),1(String)

exists 子查询:

EntityQueryable where = easyEntityQuery.queryable(BlogEntity.class) .where(o -> o.id.eq("1" ));List list2 = easyEntityQuery.queryable(Topic.class) .where(o -> { o.exists( -> where.where(q -> q.id.eq(o.id))); }).toList;

执行:

==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic` t WHERE EXISTS (SELECT 1 FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ? AND t1.`id` = t.`id`)==> Parameters: false(Boolean),1(String)分片//创建分片对象@Data@Table(value = "t_topic_Sharding_time",shardingInitializer = TopicShardingTimeShardingInitializer.class)@ToStringpublic class TopicShardingTime { @Column(primaryKey = true) private String id; private Integer stars; private String title; @ShardingTableKey private LocalDateTime createTime;}//分片初始化器很简单 假设我们是2020年1月到2023年5月也就是当前时间进行分片那么要生成对应的分片表每月一张public class TopicShardingTimeShardingInitializer extends AbstractShardingMonthInitializer { @Override protected LocalDateTime getBeginTime { return LocalDateTime.of(2020, 1, 1, 1, 1); } @Override protected LocalDateTime getEndTime { return LocalDateTime.of(2023, 5, 1, 0, 0); } @Override public void configure0(ShardingEntityBuilder builder) {////以下条件可以选择配置也可以不配置用于优化分片性能// builder.paginationReverse(0.5,100)// .ascSequenceConfigure(new TableNameStringComparator)// .addPropertyDefaultUseDesc(TopicShardingTime::getCreateTime)// .defaultAffectedMethod(false, ExecuteMethodEnum.LIST,ExecuteMethodEnum.ANY,ExecuteMethodEnum.COUNT,ExecuteMethodEnum.FIRST)// .useMaxShardingQueryLimit(2,ExecuteMethodEnum.LIST,ExecuteMethodEnum.ANY,ExecuteMethodEnum.FIRST); }}//分片时间路由规则按月然后bean分片属性就是LocalDateTime也可以自定义实现public class TopicShardingTimeTableRoute extends AbstractMonthTableRoute { @Override protected LocalDateTime convertLocalDateTime(Object shardingValue) { return (LocalDateTime)shardingValue; }}

数据库脚本参考源码:

其中shardingInitializer为分片初始化器用来初始化告诉框架有多少分片的表名(支持动态添加)

ShardingTableKey表示哪个字段作为分片键(分片键不等于主键)

代码:

LocalDateTime beginTime = LocalDateTime.of(2021, 1, 1, 1, 1);LocalDateTime endTime = LocalDateTime.of(2021, 5, 2, 1, 1);Duration between = Duration.between(beginTime, endTime);long days = between.toDays;List list = easyQuery.queryable(TopicShardingTime.class) .where(o->o.rangeClosed(TopicShardingTime::getCreateTime,beginTime,endTime)) .orderByAsc(o -> o.column(TopicShardingTime::getCreateTime)) .toList;

执行:

==> SHARDING_EXECUTOR_2, name:ds2020, Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic_sharding_time_202101` t WHERE t.`create_time` >= ? AND t.`create_time` SHARDING_EXECUTOR_3, name:ds2020, Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic_sharding_time_202102` t WHERE t.`create_time` >= ? AND t.`create_time` SHARDING_EXECUTOR_2, name:ds2020, Parameters: 2021-01-01T01:01(LocalDateTime),2021-05-02T01:01(LocalDateTime)==> SHARDING_EXECUTOR_3, name:ds2020, Parameters: 2021-01-01T01:01(LocalDateTime),2021-05-02T01:01(LocalDateTime) SHARDING_EXECUTOR_2, name:ds2020, Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic_sharding_time_202103` t WHERE t.`create_time` >= ? AND t.`create_time` SHARDING_EXECUTOR_3, name:ds2020, Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic_sharding_time_202104` t WHERE t.`create_time` >= ? AND t.`create_time` SHARDING_EXECUTOR_2, name:ds2020, Parameters: 2021-01-01T01:01(LocalDateTime),2021-05-02T01:01(LocalDateTime)==> SHARDING_EXECUTOR_3, name:ds2020, Parameters: 2021-01-01T01:01(LocalDateTime),2021-05-02T01:01(LocalDateTime) main, name:ds2020, Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic_sharding_time_202105` t WHERE t.`create_time` >= ? AND t.`create_time` main, name:ds2020, Parameters: 2021-01-01T01:01(LocalDateTime),2021-05-02T01:01(LocalDateTime)@Data@Table(value = "t_topic_sharding_ds",shardingInitializer = DataSourceAndTableShardingInitializer.class)@ToStringpublic class TopicShardingDataSource { @Column(primaryKey = true) private String id; private Integer stars; private String title; @ShardingDataSourceKey private LocalDateTime createTime;}public class DataSourceShardingInitializer implements EntityShardingInitializer { @Override public void configure(ShardingEntityBuilder builder) { EntityMetadata entityMetadata = builder.getEntityMetadata; String tableName = entityMetadata.getTableName; List tables = Collections.singletonList(tableName); LinkedHashMap> initTables = new LinkedHashMap> {{ put("ds2020", tables); put("ds2021", tables); put("ds2022", tables); put("ds2023", tables); }}; builder.actualTableNameInit(initTables); }}//分库数据源路由规则public class TopicShardingDataSourceRoute extends AbstractDataSourceRoute { @Override protected RouteFunction getRouteFilter(TableAvailable table, Object shardingValue, ShardingOperatorEnum shardingOperator, boolean withEntity) { LocalDateTime createTime = (LocalDateTime) shardingValue; String dataSource = "ds" + createTime.getYear; switch (shardingOperator){ case GREATER_THAN: case GREATER_THAN_OR_EQUAL: return ds-> dataSource.compareToIgnoreCase(ds)dataSource.compareToIgnoreCase(ds)>0; } return ds->dataSource.compareToIgnoreCase(ds)>=0; } case LESS_THAN_OR_EQUAL: return ds->dataSource.compareToIgnoreCase(ds)>=0; case EQUAL: return ds->dataSource.compareToIgnoreCase(ds)==0; default:return t->true; } }}

代码:

LocalDateTime beginTime = LocalDateTime.of(2020, 1, 1, 1, 1);LocalDateTime endTime = LocalDateTime.of(2023, 5, 1, 1, 1);Duration between = Duration.between(beginTime, endTime);long days = between.toDays;EasyPageResult pageResult = easyQuery.queryable(TopicShardingDataSource.class) .orderByAsc(o -> o.column(TopicShardingDataSource::getCreateTime)) .toPageResult(1, 33);

执行:

==> SHARDING_EXECUTOR_23, name:ds2022, Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic_sharding_ds` t ORDER BY t.`create_time` ASC LIMIT 33==> SHARDING_EXECUTOR_11, name:ds2021, Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic_sharding_ds` t ORDER BY t.`create_time` ASC LIMIT 33==> SHARDING_EXECUTOR_2, name:ds2020, Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic_sharding_ds` t ORDER BY t.`create_time` ASC LIMIT 33==> SHARDING_EXECUTOR_4, name:ds2023, Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic_sharding_ds` t ORDER BY t.`create_time` ASC LIMIT 33

来源:散文随风想

相关推荐