# 基于 ORM 操作

下列介绍及举例的场景,都是基于实体来实现的增、删、改、查,如需直接执行SQL,可查看SQL

# 查询

# 创建查询对象

dao可创建基于实体模型的CriteriaQuery查询对象,传入的参数不同,返回的数据类型也不同

  1. 返回类型为实体本身

方法:CriteriaQuery<T> createQuery(Class<? extends T> entityClass)

CriteriaQuery<Entity> query = dao.createQuery(Entity.class);
  1. 指定返回类型,如指定返回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
);
  1. 返回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查询对象通用的方法可查看通用查询方法

# 查询字段

  • 默认:全部字段
  • 方法:
  1. CriteriaQuery<T> select(String... items) - 设置查询字段,若多次调用该方法,则只有最后一次设置生效
  2. CriteriaQuery<T> selectExtra(String... items) - 在原有的基础上,添加指定的查询字段
  3. CriteriaQuery<T> selectNot(String... items) - 不查询指定的字段

# 数据过滤

设置过滤条件的方法这里简单根据参数类型、功能性划分:

  • 直接传入SQL表达式的方法
  1. CriteriaQuery<T> where(String expr, Object... args)
  2. CriteriaQuery<T> andWhere(String expr, Object... args)
  3. CriteriaQuery<T> orWhere(String expr, Object... args)

方法参数中,参数名为expr表示应传入SQL表达式,args参数会按顺序填充入表达式中的占位符中,whereandWhere方法等同,添加一个表达式时,都是和已有的表达式构成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');

orWherewhereandWhere不同的是以OR为连接符。

  1. CriteriaQuery<T> where(String expr, Map<String, Object> params)

该方法参数用法与args有区别,详细可见通用添加参数

  • 以字段为主的方法
  1. CriteriaQuery<T> filters(String field, Object value)
  2. CriteriaQuery<T> filters(Map<String, ?> fields)

字段及对应的值都以=操作符连接。

  1. CriteriaQuery<T> filters(Filters filters)

Filters存储了已解析的filters表达式,示例:

Filters filters = new ScelFilters("name eq 'n'");
dao.createQuery(Entity.class).filters(filters).list();
  • 主键过滤
  1. CriteriaQuery<T> id(Object id)

最终生成的过滤条件,会根据实体中的主键字段生成,比如Entity实体中主键字段为code,生成的SQL为:

SELECT * FROM entity where code = 'id';
  • 字段IN过滤
  1. CriteriaQuery<T> in(String column, String param) - 需要另外调用paramForIn()设置参数值
  2. CriteriaQuery<T> in(String column, String param, Object... values)
  3. 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();

# 数据排序

  • 方法:
  1. CriteriaQuery<T> orderBy(Sort sort)

SortSpring Data的排序对象,详细使用说明可去Spring Data的官方文档查看,示例:

dao.createQuery(Entity.class)
    .orderBy(Sort.by(Sort.Order.desc("name")))
    .list();
  1. 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为展开查询表达式类,必须指定关系名,可选指定展开查询返回的字段等,该类中提供了静态方法快速生成:

  1. Expansion[] parse(String expression) - 解析字符串表达式,返回表达式数组
Expansion[] expansions = Expansion.parse("org");

字符串表达式中,首先是关系名{relation},若想指定返回的字段,则在关系名后添加括号{relation}({field1},{field2}...),多个字段用,分隔,由此组合成单个关系的展开查询表达式,多个关系展开查询表达式也用,分隔:

Expansion[] expansions = Expansion.parse("org(id,name),{relation}");
  1. 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注解的字段。假设实体Entitynamedesc配置了该注解,快速搜索时会对这些字段进行模糊查询,只需要其中一个字段满足条件:

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
  • 方法:
  1. String getAlias() - 返回主表别名
  2. 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
    
}

当前实体EntityAuthor实体建立多对一关系,属性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接口:

事件实现类中可注入beandao建议从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()组合使用。

顶部