跳到主要内容

Draft

之前的文档我们发现

  • 用户定义了Book类型,例子代码就可以使用BookDraft类型

  • 用户定义了TreeNode类型,例子代码就可以使用TreeNodeDraft类型

这些名称以Draft结尾且和用户定义的类型一一对应的类型,叫Draft类型

使用预编译器

定义TreeNode

用户定先义不可变数据接口,这里无需使用ORM实体注解@Entity,使用非ORM的注解@Immutable即可达到演示目的。

TreeNode.java
package yourpackage;

import javax.validation.constraints.Null;
import java.util.List;

import org.babyfish.jimmer.Immutable;

@Immutable
public interface TreeNode {

String name();

@Null
TreeNode parent();

List<TreeNode> childNodes();
}

生成TreeNodeDraft

要自动生成TreeNodeDraft需要启用预编译器

  • Java: 使用AnnotationProcessor jimmer-apt

  • Kotlin: 使用KSP jimmer-ksp

信息

如何使用jimmer-apt/jimmer-ksp、以及遇到可能的问题后该如何处理, 在生成代码中有非常详细的介绍,本文不再赘述。

TreeNodeDraft.java
package org.babyfish.jimmer.example.core.model;

import java.util.List;
import org.babyfish.jimmer.DraftConsumer;
import org.babyfish.jimmer.lang.OldChain;

public interface TreeNodeDraft extends TreeNode, Draft {

TreeNodeDraft.Producer $ = Producer.INSTANCE;


@OldChain
TreeNodeDraft setName(String name);


TreeNodeDraft parent();

TreeNodeDraft parent(boolean autoCreate);


@OldChain
TreeNodeDraft setParent(TreeNode parent);

@OldChain
TreeNodeDraft applyParent(DraftConsumer<TreeNodeDraft> block);

@OldChain
TreeNodeDraft applyParent(TreeNode base, DraftConsumer<TreeNodeDraft> block);



List<TreeNodeDraft> childNodes(boolean autoCreate);

@OldChain
TreeNodeDraft setChildNodes(List<TreeNode> childNodes);

@OldChain
TreeNodeDraft addIntoChildNodes(DraftConsumer<TreeNodeDraft> block);

@OldChain
TreeNodeDraft addIntoChildNodes(TreeNode base, DraftConsumer<TreeNodeDraft> block);



class Producer {

private Producer() {}

public TreeNode produce(
DraftConsumer<TreeNodeDraft> block
) {
return produce(null, block);
}

public TreeNode produce(
TreeNode base,
DraftConsumer<TreeNodeDraft> block
) {
...omit...
}

...省略其他代码...
}

...省略其他代码...
}

你可以这样使用

  1. 从头创建全新的对象

    TreeNode oldTreeNode = Objects.createTreeNode(treeNodeDraft -> {
    ...省略...
    });
  2. 根据已有的对象,进行某些“变更”后,创建新的对象

    TreeNode newTreeNode = Objects.createTreeNode(oldTreeNode, treeNodeDraft -> {
    ...省略...
    });

标量属性

TreeNode.name是一个标量属性。TreeNodeDraft会定义如下一个setter方法/可写属性

TreeNodeDraft.java
public interface TreeNodeDraft extends TreeNode, Draft {

@OldChain
TreeNodeDraft setName(String name);

...省略其他代码...
}

开发人员可以通过此方法设置draft代理的name属性

TreeNode treeNode = Objects.createTreeNode(draft -> {
draft.setName("Root Node");
});

引用关联

TreeNode.parent是一个关联属性。其类型是对象,而非集合。如果用ORM的话来讲,是一对一或多对一关联。

TreeNodeDraft为其定义了多个方法

覆盖getter parent()

TreeNodeDraft.java
public interface TreeNodeDraft extends TreeNode, Draft {

TreeNodeDraft parent();

...省略其他代码...
}
信息

注意,此方法的返回类型是TreeNodeDraft,而非TreeNode

即,如果draft对象的引用关联被设置过且被设置为非null,那么该方法一定返回draft对象。这样,用户就可以直接修改更深的关联对象。

TreeNode newTreeNode = Objects.createTreeNode(treeNode, draft -> {
draft.parent().setName("Daddy");
draft.parent().parent().setName("Grandpa");
});

新增getter parent(boolean)

TreeNodeDraft.java
public interface TreeNodeDraft extends TreeNode, Draft {

TreeNode parent(boolean autoCreate);

...省略其他代码...
}

Java的parent(false)和Kotlin的parent,具备以下两个问题:

  • 如果draft对象的属性parent未被设置,访问它会导致异常

  • 如果draft对象的属性parent被设置为null,访问它会返回null,null无法支持进一步修改。

parent(true)可以解决以上的问题,如果上述任何一种情况满足,就自动创建并设置一个关联对象,然后允许用户修改。这是一个非常实用的功能,尤其是从头创建对象时。

TreeNode treeNode = Objects.createTreeNode(/* No `base` here */ draft -> {
draft.parent(true).setName("Daddy");
draft.parent(true).parent(true).setName("Grandpa");
});

新增setParent

TreeNodeDraft.java
public interface TreeNodeDraft extends TreeNode, Draft {

@OldChain
TreeNodeDraft setParent(TreeNode parent);

...省略其他代码...
}

该setter允许用户替换整个关联对象。

TreeNode treeNode = Objects.createTreeNode(draft -> {
draft.setParent(
Objects.createTreeNode(daddyDraft -> {
daddyDraft.setName("Daddy")
})
)
});

新增基于lambda的applyParent

信息

此功能仅适用于Java

kotlin的代码已经足够简洁了,不需要这种方法来简化代码。

TreeNodeDraft.java
public interface TreeNodeDraft extends TreeNode, Draft {

@OldChain
TreeNodeDraft applyParent(
DraftConsumer<TreeNodeDraft> block
);

@OldChain
TreeNodeDraft applyParent(
TreeNode base,
DraftConsumer<TreeNodeDraft> block
);

...省略其它代码...
}

这两个setter用于简化的代码。由于两个方法的用法高度相似,仅以第一个举例。

  1. 比较啰嗦的写法

    TreeNode oldTreeNode = Objects.createTreeNode(draft -> {
    draft.setParent(
    Objects.createTreeNode(daddyDraft -> {
    daddyDraft.setName("Daddy")
    })
    )
    });
  2. 比较简洁的写法

    TreeNode oldTreeNode = Objects.createTreeNode(draft -> {
    draft.applyParent(daddyDraft -> {
    daddyDraft.setName("Daddy")
    })
    });

二者完全等价。

集合关联

TreeNode.childNodes是一个关联属性。其类型是集合,而非对象。如果用ORM的话来讲,是一对多或多对多关联。

TreeNodeDraft为其定义了多个方法

继承getter childNodes()

无论对Java还是kotlin,TreeNodeDraft无法覆盖childNodes()方法的返回类型,从语法层面上讲,只能继承TreeNodechildNodes()方法。

List<TreeNode> childNodes();
警告

虽然这个的方法在TreeNode接口中定义的返回类型是List<TreeNode>,但是,被TreeNodeDraft接口继承后,应该将其返回类型理解成List<TreeNodeDraft>

信息

如果draft对象的集合关联被设置过,返回的集合中的所有元素都是draft。这样,用户就可以直接修改集合中更深的关联对象。

TreeNode newTreeNode = Objects.createTreeNode(treeNode, draft -> {
((TreeNodeDraft)
draft
.childNodes().get(0)
).setName("Son");
((TreeNodeDraft)
draft
.childNodes().get(0)
.childNodes().get(0)
).setName("Grandson");
});
危险

上述代码中,两个强制类型转换对开发体验产生了显著的破坏,所以,不推荐在实际项目中如此使用。

为实现相同目的,更推荐接下来要介绍的childNodes(boolean)方法。

新增getter childNodes(boolean)

TreeNodeDraft.java
public interface TreeNodeDraft extends TreeNode, Draft {

List<TreeNodeDraft> childNodes(boolean autoCreate);

...省略其他代码...
}

childNodes(false)childNodes()等价,如果draft对象的属性childNodes未被设置,访问它会导致异常。 childNodes(true)会解决这个问题,如果集合关联属性还未被设置,则自动创建并设置一个集合,然后允许用户修改该集合。

TreeNode newTreeNode = Objects.createTreeNode(treeNode, draft -> {
draft
.childNodes(false)
.get(0)
.setName("Son");
draft
.childNodes(false)
.get(0)
.childNodes(false)
.get(0)
.setName("Grandson");
});
提示

诚然,当参数为 true 时,childNodes(boolean) 能够在未指定 childNodes 属性时自动创建集合。

然而,当参数为 false 时,同样也很有用。由于此方法的返回类型是List<TreeNodeDraft>而不是List<TreeNode>,因此上一个示例中对开发人员不友好的代码将不再存在。

新增setChildNodes

TreeNodeDraft.java
public interface TreeNodeDraft extends TreeNode, Draft {

@OldChain
TreeNodeDraft setChildNodes(List<TreeNode> childNodes);

...省略其他代码...
}

该setter允许用户替换整个关联集合。

TreeNode treeNode = Objects.createTreeNode(draft -> {
draft.setChildNodes(
Arrays.asList(
Objects.createTreeNode(childDraft -> {
childDraft.setName("Eldest son")
}),
Objects.createTreeNode(childDraft -> {
childDraft.setName("Second son")
})
)
)
});
信息

略显繁琐,更推荐下面即将介绍的addIntoChildNodes

新增addIntoChildNodes

上面的例子中,我们使用setChildNodes替换了整个集合,但是还可以选择以逐个添加集合元素,而非一次性替换整个集合。

生成代码如下

TreeNodeDraft.java
public interface TreeNodeDraft extends TreeNode, Draft {

@OldChain
TreeNodeDraft addIntoChildNodes(
DraftConsumer<TreeNodeDraft> block
);

@OldChain
TreeNodeDraft addIntoChildNodes(
TreeNode base,
DraftConsumer<TreeNodeDraft> block
);

...省略其他代码...
}

你应该如此使用。

TreeNode treeNode = Objects.createTreeNode(draft -> {
draft
.addIntoChildNodes(childDraft ->
childDraft.setName("Eldest son")
)
.addIntoChildNodes(childDraft ->
childDraft.setName("Second son")
)
});
信息

这种写法隐含了一个功能,draft对象的属性childNodes未被设置,自动创建集合。即,内置了一个childNodes(true)

很明显,这种写法比使用setter替换整个集合更简单,所以,更推荐这种写法。