# 基于 ORM 操作
下列介绍及举例的场景,都是基于实体来实现的增、删、改、查,如需直接执行SQL,可查看SQL。
# 查询
# 创建查询对象
dao可创建基于实体模型的CriteriaQuery查询对象,传入的参数不同,返回的数据类型也不同。
- 返回类型为实体本身:
方法:CriteriaQuery<T> createQuery(Class<? extends T> entityClass)
CriteriaQuery<Entity> query = dao.createQuery(Entity.class);
- 指定返回类型,如指定返回
EntityResult类:
方法:CriteriaQuery<T> createQuery(Class<?> entityClass, Class<? extends T> resultClass)
或:CriteriaQuery<T> createQuery(String entityName, Class<? extends T> resultClass)
CriteriaQuery<EntityResult> query = dao.createQuery(
Entity.class,
EntityResult.class
);
- 返回
fly.core.data.model.Record类型:
方法:CriteriaQuery<Record> createQuery(String entityName)
CriteriaQuery<Record> query = createQuery("entity");
下列将介绍该查询对象自身的查询方法,当方法返回类型为对象本身时,可以多次组合叠加调用,最后再调用list()、scalar()等执行查询,现存在以下方法:
CriteriaQuery<T> alias(String alias)
CriteriaQuery<T> select(String... items)
组合调用示例:
dao.createQuery(Entity.class)
.alias("e")
.select("id", "name")
.list();
除接下来罗列的方法外,其余与SQL查询对象通用的方法可查看通用查询方法。
# 查询字段
- 默认:全部字段
- 方法:
CriteriaQuery<T> select(String... items)- 设置查询字段,若多次调用该方法,则只有最后一次设置生效CriteriaQuery<T> selectExtra(String... items)- 在原有的基础上,添加指定的查询字段CriteriaQuery<T> selectNot(String... items)- 不查询指定的字段
# 数据过滤
设置过滤条件的方法这里简单根据参数类型、功能性划分:
- 直接传入
SQL表达式的方法
CriteriaQuery<T> where(String expr, Object... args)CriteriaQuery<T> andWhere(String expr, Object... args)CriteriaQuery<T> orWhere(String expr, Object... args)
方法参数中,参数名为expr表示应传入SQL表达式,args参数会按顺序填充入表达式中的占位符中,where与andWhere方法等同,添加一个表达式时,都是和已有的表达式构成AND关系,如果已有表达式为空,则不会再补充AND:
dao.createQuery(Entity.class)
.select("id")
.where("name = ? OR name = ?", "n", "m")
.list();
生成的SQL:
SELECT id FROM entity where `name` = 'n' OR `name` = 'm';
若已有表达式时再调用where:
dao.createQuery(Entity.class)
.select("id")
.where("desc LIKE ?", "$n")
.where("name = ? OR name = ?", "n", "m")
.list();
生成的SQL:
SELECT id FROM entity where (`desc` LIKE 'n') and (`name` = 'n' OR `name` = 'm');
orWhere与where、andWhere不同的是以OR为连接符。
CriteriaQuery<T> where(String expr, Map<String, Object> params)
该方法参数用法与args有区别,详细可见通用添加参数。
- 以字段为主的方法
CriteriaQuery<T> filters(String field, Object value)CriteriaQuery<T> filters(Map<String, ?> fields)
字段及对应的值都以=操作符连接。
CriteriaQuery<T> filters(Filters filters)
Filters存储了已解析的filters表达式,示例:
Filters filters = new ScelFilters("name eq 'n'");
dao.createQuery(Entity.class).filters(filters).list();
- 主键过滤
CriteriaQuery<T> id(Object id)
最终生成的过滤条件,会根据实体中的主键字段生成,比如Entity实体中主键字段为code,生成的SQL为:
SELECT * FROM entity where code = 'id';
- 字段
IN过滤
CriteriaQuery<T> in(String column, String param)- 需要另外调用paramForIn()设置参数值CriteriaQuery<T> in(String column, String param, Object... values)CriteriaQuery<T> in(String column, String param, Collection values)
in方法主要为了简化表达式的写法,column为列名,param为参数名称,values为该参数名称对应的参数值,示例:
CriteriaQuery<Entity> query = dao.createQuery(Entity.class).in("name", "names");
query.paramForIn("names", New.list("n", "m"));
query.list();
dao.createQuery(Entity.class).in("name", "names", "n", "m").list();
# 数据排序
- 方法:
CriteriaQuery<T> orderBy(Sort sort)
Sort为Spring Data的排序对象,详细使用说明可去Spring Data的官方文档查看,示例:
dao.createQuery(Entity.class)
.orderBy(Sort.by(Sort.Order.desc("name")))
.list();
CriteriaQuery<T> orderBy(OrderBy orderBy)
OrderBy为框架内部定义的排序对象,提供了静态方法解析表达式:
OrderBy orderBy = OrderBy.of("name desc");
或:
OrderBy orderBy = OrderByParser.parse("name desc");
其他排序方法可查看通用数据排序。
# 关联查询
- 方法:
CriteriaQuery<T> join(Class<?> entityClass, String alias)- 关联指定实体的表,并设置别名
当前实体与关联实体之间需要先定义关系,如用户实体User与组织实体Org之间存在多对一关系,需要在查询用户信息时同时返回用户所在组织的名称:
dao.createQuery(User.class)
.selectExtra("o.name as org_name")
.join(Org.class, "o")
.list();
示例中,关联后的别名可用于查询字段设置,也可以在其他如条件过滤中使用。
# 展开查询
若实体查询实体中定义了关系,如在查询用户User与组织Organization实体为多对一关系,关系名为org,想要在查询用户的同时返回用户所属组织的数据,可使用展开查询,查询结果会作为新属性添加到用户信息中,属性名为关系名。
- 方法:
CriteriaQuery<T> expand(Expansion... expansions)
Expansion为展开查询表达式类,必须指定关系名,可选指定展开查询返回的字段等,该类中提供了静态方法快速生成:
Expansion[] parse(String expression)- 解析字符串表达式,返回表达式数组
Expansion[] expansions = Expansion.parse("org");
字符串表达式中,首先是关系名{relation},若想指定返回的字段,则在关系名后添加括号{relation}({field1},{field2}...),多个字段用,分隔,由此组合成单个关系的展开查询表达式,多个关系展开查询表达式也用,分隔:
Expansion[] expansions = Expansion.parse("org(id,name),{relation}");
ExpansionBuilder builder(String name)- 构造类时就需指定关系名,返回单个表达式
ExpansionBuilder中,可使用select(String... properties)方法指定该关系展开查询返回的字段,最后调用build()方法返回表达式类:
Expansion expansion = Expansion.builder("org")
.select("id", "name")
.build();
了解如何获得参数后,就可以使用展开查询方法了:
dao.createQuery(Entity.class)
.expand(Expansion.parse("org"))
.list();
# 快速搜索
- 方法:
CriteriaQuery<T> search(String search)
快速搜索只对实体中配置了fly.core.data.annotation.Searchable注解的字段。假设实体Entity中name、desc配置了该注解,快速搜索时会对这些字段进行模糊查询,只需要其中一个字段满足条件:
dao.createQuery(Entity.class)
.select("id")
.filters("field", "f1"),
.search("%keyword%")
.list();
生成的SQL:
SELECT id FROM entity
WHERE field = 'f1' AND (
`name` LIKE '%keyword%'
OR `desc` LIKE '%keyword%'
)
# 主表别名
- 默认:
t - 方法:
String getAlias()- 返回主表别名CriteriaQuery<T> alias(String alias)- 设置主表别名
# 表达式属性
在Dao查询指定实体类的情况下,非字段属性需要通过如特殊且固定的SQL表达式来获取数据,如下示例中,displayable属性需要通过IF数据库函数进行判断返回内容,此时可以添加@SelectBySQL注解:
@Entity
public class MemberEntity {
@Column
protected String id;
@Column
protected String name;
@SelectBySQL("IF(t.enabled = 1, 'yes', 'no')")
protected String displayable;
// get/set
}
dao.createQuery(MemberEntity.class).list();
执行查询后,生成的SQL为:
SELECT t.id, t.name, t.enabled, (IF(t.enabled = 1, 'yes', 'no')) AS displayable FROM member t limit 10
# 关联查询属性
在Dao查询指定实体类的情况下,需要属性自动对应关系实体中的某个字段,可以在属性上添加@SelectByJoin注解,需要先定义实体关系,在当前实体查询时自动进行关联查询,并赋值关联字段,示例如下:
设已有Author实体:
@Entity
public class Author {
@UUID
private String id;
@Column
private String name;
// get/set
}
当前实体Entity与Author实体建立多对一关系,属性authorName对应authorId所关联Author实体记录的name字段:
@Column
@ManyToOne(Author.class)
private String authorId;
@SelectByJoin(joinField = "authorId", targetField = "name")
private String authorName;
执行实体查询,即使不调用join方法,也自动进行关联查询:
dao.createQuery(Entity.class)
.id("id")
.single();
生成的SQL为:
SELECT t.id, t.`name`, t.enabled, t.author_id, a1.`name` AS author_name FROM entity t LEFT JOIN author a1 ON t.author_id = a1.id limit 10
# 新增
# 普通新增
已有实体对象,直接作为参数传入,自动解析实体:
T insert(T record)
dao.insert(entity);
非实体对象则需指定实体:
T insert(Class<?> entityClass, T record)
dao.insert(Entity.class, map);
自动将传入的字段和新建时自动计算的字段设置到create参数对象:
T insert(Object record, T created)
dao.insert(map, entity);
# 批量新增
如果为实体对象列表,可直接作为参数传入,返回影响行数:
int[] batchInsert(List<?> records)
List<Entity> entities = new ArrayList<>();
entities.add(...);
int affectedRows = dao.batchInsert(entities);
否则需要指定实体:
int[] batchInsert(Class<?> entityClass, List<?> records)
List<Map> entities = new ArrayList<>();
entities.add(...);
int affectedRows = dao.batchInsert(Entity.class, entities);
# 更新
# 普通更新
已有实体对象,直接作为参数传入,自动解析实体及id:
int update(Object record)
int affectedRow = dao.update(entity);
非实体对象则需指定实体,id已知时:
int update(Class<?> entityClass, Object id, Object fields)
int affectedRow = dao.update(Entity.class, id, map);
也可不传入id,会从对象中自动解析:
int update(Class<?> entityClass, Object record)
int affectedRow = dao.update(Entity.class, map);
# 强制更新
在普通更新方法命名前添加must前缀,表示为强制更新方法,若更新不成功返回的影响行数小于等于0,则抛出NoSuchRecordException异常:
int mustUpdate(Object record)
int affectedRow = dao.mustUpdate(Entity.class, id, map);
其他拓展方法:ignoreNulls() - 忽略 Null 值字段、ignoreNotWritable() - 忽略不能写入字段
# 删除
# 普通删除
已有实体对象,直接作为参数传入,自动解析实体及id:
int delete(Object record)
int affectedRow = dao.delete(entity);
非实体对象则需指定实体和id:
int delete(Class<?> entityClass, Object id)
int affectedRow = dao.delete(Entity.class, id);
# 强制删除
在普通删除方法命名前添加must前缀,表示为强制删除方法,若删除不成功返回的影响行数小于等于0,则抛出NoSuchRecordException异常:
int mustDelete(Class<?> entityClass, Object id)
int affectedRow = dao.mustDelete(Entity.class, id);
其他非单条数据的操作方法:deleteAll() - 删除实体所有记录
#
# 操作所有记录
通过dao.careful()获取CarefulOperations对象,对象中定义了针对所有记录增删改查及统计的操作方法,大量的数据操作可能会影响性能,需谨慎使用。
# 查询实体所有记录
List<T> findAll(Class<T> entityClass)
List<Entity> records = dao.careful().findAll(Entity.class);
# 更新实体所有记录的指定字段和值
执行后返回影响行数。
- 单个字段
int updateAll(Class<?> entityClass, String field, Object value)
int affectedRows = dao.careful().updateAll(Entity.class,"字段名称", "更新内容");
- 多个字段
int updateAll(Class<?> entityClass, Map<String, Object> fields)
Map<String, Object> map = new HashMap<>();
map.set("字段名称", "更新内容");
affectedRows = dao.careful().updateAll(Entity.class,map);
# 删除实体所有记录
执行后返回影响行数
int deleteAll(Class<?> entityClass)
int affectedRows = dao.careful().deleteAll(Entity.class);
# 统计实体记录总数
long countAll(Class<?> entityClass)
long count = dao.careful().countAll(Entity.class);
# 执行事务
事务执行过程中,任何的异常将会导致事务进行回滚。
常用方法:void doInTransaction(ExceptionalRunnable action)
dao.doInTransaction(() -> {
// 事务内部执行代码
});
还提供的方法有:
void doInTransaction(ExceptionalRunnable action)T getInTransaction(ExceptionalSupplier<T> action)T getInTransaction(ExceptionalFunction<TransactionStatus, T> action)
如果需要用到 TransactionDefinition 来定义事务的隔离级别等,可使用dao.transaction(),以上方法也是由该方法实现:
dao.transaction().run(() -> {
// 事务内部执行代码
}, TransactionDefinition.withDefaults());
# 事件监听器
在@Entity注解的listeners属性或直接在实体上使用@Entity.Listener注解添加事件监听器,用于在实体进行dao command级别的新增、修改、删除操作前后能完成额外的业务需求,支持开启事务。
- 自定义监听器,实现
EntityEventListener接口:
事件实现类中可注入
bean,dao建议从EntityEventContext中获取,而不是通过注入的方式。
import fly.orm.dao.listener.EntityEventListener;
public class NewsEntityEventListener implements EntityEventListener {
@Autowired
protected TestBean testBean;
@Override
public void preCreate(EntityEventContext context, PreCreateEvent event) {
// 当前创建操作操作的数据
EntityObject entity = event.getEntity();
String titleValue = entity.getString("title");
// 在事件上下文中添加额外属性
context.setAttribute("preCreate", "test");
// 额外执行数据库操作
context.dao().insert(OtherEntity.class, entity);
}
@Override
public void postCreate(EntityEventContext context, PostCreateEvent event) {
// 在事件上下文中取额外属性
Object value = context.getAttribute("preCreate");
}
@Override
public PostUpdateHandler getTransactionalPostUpdateHandler(EntityEventContext context, EntityType entityType) {
return new PostUpdateHandler() {
@Override
public void postUpdate(PostUpdateEvent event) {
// 开启事务中额外执行数据库操作
context.dao().insert(OtherEntity.class, entity);
}
};
}
}
- 设置监听器,使其生效,两种配置方式示例:
@Entity(listeners = NewsEntityEventListener.class)
@Entity.Listener(NewsEntityEventListener.class)
public class NewsEntity {
@Id
protected String id;
@Column
protected String title;
// get/set
}
# 其他
除基本的增删改查及事务外,还提供了额外的方法来便捷开发,其中部分方法需要结合实体定义或注解来使用,也可以组合叠加使用。
# 忽略Null值字段
- 场景:新增、更新
- 方法:
IgnoreNullsOperations ignoreNulls()
默认传入对象中若字段为null,新增(没有设置默认值)或更新时会将该字段设为null,若想忽略所有值为null的字段,可使用该方法。
dao.ignoreNulls().update(entity);
可以和
ignoreNotWritable()组合使用。
# 忽略不能写入字段
- 场景:新增、更新
- 方法:
IgnoreNotWritableOperations ignoreNotWritable()
需要在实体字段上添加@Writable来定义该字段是否可写,当不可写时,即使传入对象中有该字段,都不会算入最后的新增或更新里。
dao.ignoreNotWritable().update(entity);
可以和
ignoreNulls()组合使用。