### Complete Example: Non-Spring Crane4j Usage Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/user_guide/getting_started/getting_started_without_spring.md A full example demonstrating the setup and execution of Crane4j in a standalone Java application. This includes creating the template, registering a data source, defining a data class with annotations, and executing the population. ```java public class QuickStart { public static void main(String[] args) { // 创建操作门面 Crane4jTemplate crane4jTemplate = Crane4jTemplate.withDefaultConfiguration(); // 创建并注册数据源 Map map = MapBuilder.create() .put(1, "a").put(2, "b").put(3, "c") .build(); crane4jTemplate.opsForContainer() .registerMapContainer("test", map); // 执行填充 List foos = Arrays.asList(new Foo(1), new Foo(2), new Foo(3)); crane4jTemplate.execute(foos); System.out.println(foos); } @Data // 使用 lombok 生成构造器、getter/setter 方法 @RequiredArgsConstructor public static class Foo { // 根据 id 填充 name @Assemble(container = "test", props = @Mapping(ref = "name")) private final Integer id; private String name; } } ``` -------------------------------- ### Complete Spring Integration Example Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/user_guide/getting_started/getting_started_with_spring.md A full example demonstrating manual and automatic data filling within a Spring test environment. ```java @Configuration @Import(DefaultCrane4jSpringConfiguration.class) @RunWith(SpringJUnit4ClassRunner.class) public class QuickStartWithSpringTest { @Autowired private Crane4jTemplate crane4jTemplate; @Autowired private Service service; @Test public void run() { // 创建并注册数据源 Map map = MapBuilder.create() .put(1, "a").put(2, "b").put(3, "c") .build(); crane4jTemplate.opsForContainer() .registerMapContainer("test", map); // 手动执行填充 List foos = Arrays.asList(new Foo(1), new Foo(2), new Foo(3)); crane4jTemplate.execute(foos); System.out.println(foos); // 自动执行填充 foos = service.getFoos(); System.out.println(foos); } @Component public static class Service { @AutoOperate(type = Foo.class) public List getFoos() { return Arrays.asList(new Foo(1), new Foo(2), new Foo(3)); } } @Data // 使用 lombok 生成构造器、getter/setter 方法 @RequiredArgsConstructor public static class Foo { // 根据 id 填充 name @Assemble(container = "test", props = @Mapping(ref = "name")) private final Integer id; private String name; } } ``` -------------------------------- ### MappingType Examples Source: https://github.com/opengoofy/crane4j/blob/dev/docs/basic/container/method_container.html Examples demonstrating different MappingType configurations for @ContainerMethod. ```APIDOC ## MappingType.ONE_TO_ONE ### Description Configures the method to group results one-to-one based on the key. ### Method `listUserByIds` ### Endpoint N/A (SDK Method) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```java @ContainerMethod( namespace = "userName", type = MappingType.ONE_TO_ONE, resultType = User.class, resultKey = "deptId" ) public List listUserByIds(List ids); ``` ### Response #### Success Response (200) `List` - A list of users, grouped one-to-one by department ID. #### Response Example N/A ## MappingType.ONE_TO_MANY ### Description Configures the method to group results one-to-many based on the key. ### Method `listUserByDeptId` ### Endpoint N/A (SDK Method) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```java @ContainerMethod( namespace = "userName", type = MappingType.ONE_TO_MANY, resultType = User.class, resultKey = "deptId" ) public List listUserByDeptId(List deptIds); ``` ### Response #### Success Response (200) `List` - A list of users, grouped one-to-many by department ID. #### Response Example N/A ## MappingType.NO_MAPPING ### Description Indicates that the return value is already a grouped Map and no further grouping is needed. ### Method `listUserMapByIds` ### Endpoint N/A (SDK Method) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```java @ContainerMethod(namespace = "userName", type = MappingType.NO_MAPPING) public Map listUserMapByIds(List ids); ``` ### Response #### Success Response (200) `Map` - A map where keys are IDs and values are User objects. #### Response Example N/A ## MappingType.NO_MAPPING (with List) ### Description Indicates that the return value is already a grouped Map of Lists and no further grouping is needed. ### Method `listUserByDeptIds` ### Endpoint N/A (SDK Method) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```java @ContainerMethod(namespace = "userName", type = MappingType.NO_MAPPING) public Map> listUserByDeptIds(List deptIds); ``` ### Response #### Success Response (200) `Map>` - A map where keys are department IDs and values are lists of users. #### Response Example N/A ## MappingType.ORDER_OF_KEYS ### Description Merges the input keys with the results in order. Suitable for String or primitive return types. ### Method `getUserNameById` ### Endpoint N/A (SDK Method) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```java @ContainerMethod(namespace = "userName", type = MappingType.ORDER_OF_KEYS) public String getUserNameById(Integer id); ``` ### Response #### Success Response (200) `String` - The user name. #### Response Example N/A ## MappingType.ORDER_OF_KEYS (with List) ### Description Merges the input keys with the results in order. Suitable for List return types. ### Method `listUserAgeNameByIds` ### Endpoint N/A (SDK Method) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```java @ContainerMethod(namespace = "userName", type = MappingType.ORDER_OF_KEYS) public List listUserAgeNameByIds(List ids); ``` ### Response #### Success Response (200) `List` - A list of user ages. #### Response Example N/A ``` -------------------------------- ### Complete Spring Boot Example with Manual and Auto-Population Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/user_guide/getting_started/getting_started_with_springboot.md This comprehensive example demonstrates setting up crane4j, registering a data source, performing manual population, and utilizing auto-population via @AutoOperate within a Spring Boot test context. ```java @Configuration @RunWith(SpringRunner.class) @SpringBootTest(classes = {QuickStartWithSpringBootTest.Service.class}) public class QuickStartWithSpringBootTest { @Autowired private Crane4jTemplate crane4jTemplate; @Autowired private Service service; @Test public void run() { // 创建并注册数据源 Map map = MapBuilder.create() .put(1, "a").put(2, "b").put(3, "c") .build(); crane4jTemplate.opsForContainer() .registerMapContainer("test", map); // 执行填充 List foos = Arrays.asList(new Foo(1), new Foo(2), new Foo(3)); crane4jTemplate.execute(foos); System.out.println(foos); foos = service.getFoos(); System.out.println(foos); } @Component public static class Service { @AutoOperate(type = Foo.class) public List getFoos() { return Arrays.asList(new Foo(1), new Foo(2), new Foo(3)); } } @Data // 使用 lombok 生成构造器、getter/setter 方法 @RequiredArgsConstructor public static class Foo { // 根据 id 填充 name @Assemble(container = "test", props = @Mapping(ref = "name")) private final Integer id; private String name; } } ``` -------------------------------- ### SQL Schema for Foo Table Source: https://github.com/opengoofy/crane4j/blob/dev/crane4j-example/README.md Defines the structure of the 'foo' table used in MyBatis-Plus extension examples. Ensure this table exists and is populated for the examples to run correctly. ```sql -- ---------------------------- -- Table structure for foo -- ---------------------------- DROP TABLE IF EXISTS `foo`; CREATE TABLE `foo` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '', `age` int(3) NULL DEFAULT NULL, `sex` int(1) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; ``` -------------------------------- ### Create a CacheableContainer in Java Source: https://github.com/opengoofy/crane4j/wiki/使用/缓存 Demonstrates how to wrap an existing container with caching capabilities using CacheableContainer and a ConcurrentMapCache. This is useful for manual caching setup. ```java Container original = LambdaContainer.forLambda("original", keys -> { Map map = new HashMap<>(); keys.forEach(key -> map.put(key, new Object())); return map; }); Cache cache = new ConcurrentMapCache<>(new ConcurrentHashMap<>(2)); CacheableContainer container = new CacheableContainer<>(container, cache); ``` -------------------------------- ### SQL Records for Foo Table Source: https://github.com/opengoofy/crane4j/blob/dev/crane4j-example/README.md Provides sample data for the 'foo' table, which is necessary for testing the MyBatis-Plus extension examples. Ensure these records are inserted into your database. ```sql -- ---------------------------- -- Records of foo -- ---------------------------- INSERT INTO `foo` VALUES (1, '小明', 18, 1); INSERT INTO `foo` VALUES (2, '小红', 18, 0); INSERT INTO `foo` VALUES (3, '小刚', 17, 1); INSERT INTO `foo` VALUES (4, '小李', 19, 0); ``` -------------------------------- ### Install Jackson Extension Dependency Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/extension/jackson_extension.md Add the crane4j-extension-jackson dependency to your project. Include Jackson databind and core if not already present. ```xml cn.crane4j crane4j-extension-jackson ${last-version} com.fasterxml.jackson.core jackson-databind ${last-version} com.fasterxml.jackson.core jackson-core ${last-version} ``` -------------------------------- ### Enable Crane4j Auto-Configuration Source: https://github.com/opengoofy/crane4j/wiki/入门/快速开始 Annotate your Spring Boot application class with @EnableCrane4j to activate automatic configuration and setup. ```java @EnableCrane4j // 启用 crane4j @SpringBootApplication public class Application { @Autowrite private OperateTemplate operateTemplate; @PostConstruct public void doSomething() { // 下文示例执行的代码 } public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` -------------------------------- ### Initial JSON Data Structure Source: https://github.com/opengoofy/crane4j/blob/dev/docs/use_case/example_multi_datasource.html Shows the initial JSON structure containing minimal information for an order, with placeholders for items. This data serves as the starting point before Crane4j populates the remaining fields. ```json { "id": 123, "orderType": "new_order", "customerId": 123, "items": [ { "id": 1 }, { "id": 2 } ] } ``` -------------------------------- ### Full Spring Boot Example with Manual and Auto Population Source: https://github.com/opengoofy/crane4j/blob/dev/docs/user_guide/getting_started/getting_started_with_springboot.html A complete Spring Boot test class demonstrating both manual data population using Crane4jTemplate.execute() and automatic population via the @AutoOperate annotation on a service method. ```java @Configuration @RunWith(SpringRunner.class) @SpringBootTest(classes = {QuickStartWithSpringBootTest.Service.class}) public class QuickStartWithSpringBootTest { @Autowired private Crane4jTemplate crane4jTemplate; @Autowired private Service service; @Test public void run() { // 创建并注册数据源 Map map = MapBuilder.create() .put(1, "a").put(2, "b").put(3, "c") .build(); crane4jTemplate.opsForContainer() .registerMapContainer("test", map); // 执行填充 List foos = Arrays.asList(new Foo(1), new Foo(2), new Foo(3)); crane4jTemplate.execute(foos); System.out.println(foos); foos = service.getFoos(); System.out.println(foos); } @Component public static class Service { @AutoOperate(type = Foo.class) public List getFoos() { return Arrays.asList(new Foo(1), new Foo(2), new Foo(3)); } } @Data // 使用 lombok 生成构造器、getter/setter 方法 @RequiredArgsConstructor public static class Foo { // 根据 id 填充 name @Assemble(container = "test", props = @Mapping(ref = "name")) private final Integer id; private String name; } } ``` -------------------------------- ### Processing Cascading Relationships at Data Source Level Source: https://github.com/opengoofy/crane4j/blob/dev/docs/use_case/example_fill_relations.html This example demonstrates how to handle cascading relationships directly at the data source level by providing a method data source container. This approach can be more performant than sequential operations. ```java @Component @RequiredArgsConstructor public class DeptService { private final DeptMapper deptMapper; @ContainerMethod( container = "thirdDept", resultType = Map.class, resultKey = "thirdDeptId" ) public List> queryDeptWithParentByThirdDept(List thirdDeptIds) { // 查询一级部门,并按 ID 分组 Map thirdDepts = deptMapper.listByIds(thirdDeptIds).stream() .collect(Collectors.toMap(Dept::getId, dept -> dept)); // 查询二级部门,并按 ID 分组 Set secondDeptIds = thirdDepts.values().stream() .map(Dept::getParentId) .collect(Collector.toSet()); Map secondDepts = deptMapper.listByIds(secondDeptIds).stream() .collect(Collectors.toMap(Dept::getId, dept -> dept)); // 查询一级部门,并按 ID 分组 Set firstDeptIds = secondDeptIds.values().stream() .map(Dept::getParentId) .collect(Collector.toSet()); Map firstDepts = deptMapper.listByIds(firstDeptIds).stream() .collect(Collectors.toMap(Dept::getId, dept -> dept)); // 组装数据 List> results = new ArrayLsit<>(thirdDepts.size()); thirdDepts.values().forEach(td -> { Dept sd = secondDepts.get(td.getParentId()); Dept fd = firstDepts.get(sd.getParentIds()); Map r = new HashMap<>(6); r.put("thirdDeptId", r.getId()); r.put("thirdDeptName", r.getName()); r.put("secondDeptId", sd.getId()); r.put("secondDeptName", sd.getName()); r.put("firstIdDeptId", fd.getId()); r.put("firstIdDeptName", fd.getName()); results.put(r); }); return results; } } ``` -------------------------------- ### Manual Field Filling Example Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/user_guide/what_is_crane4j.md This Java code demonstrates the manual process of filling associated fields, specifically converting gender codes to gender names using a map derived from an enum. This is the pattern Crane4j aims to simplify. ```java public List listPerson(List ids) { // 1、根据 id 查询 person 数据 List persions = personMapper.listByIds(ids); // 2、将性别枚举按编码分组 // {key = 0, value = GenderEnum.FEMALE}, {key = 1, value = GenderEnum.MALE} Map genderMap = Stream.of(GenderEnum.values()) .collect(Collectors.toMap(GenderEnum::getCode, e -> e)); // 3、根据 person 对象中的性别编码,为其设置对应的性别值 persions.forEach(person -> { Integer genderCode = person.getGenderCode(); GenderEnum gender = genderMap.get(genderCode); person.setGenderName(gender.getName()); }); } ``` -------------------------------- ### 引入 Crane4j Spring Boot Starter 依赖 Source: https://github.com/opengoofy/crane4j/blob/dev/docs/user_guide/getting_started/getting_started_with_springboot.html 在 SpringBoot 项目的 pom.xml 文件中添加此依赖,以启用 Crane4j 的自动配置和集成。 ```xml cn.crane4j crane4j-spring-boot-starter ${last-version} ``` -------------------------------- ### Adding Decorators to PropertyOperator Source: https://github.com/opengoofy/crane4j/blob/dev/docs/advanced/reflection_factory.html Extend an existing PropertyOperator by adding decorators. This example shows how to retrieve the current PropertyOperator, get its delegate, and then wrap it with a new custom decorator. ```java DecoratedPropertyOperator decoratedPropertyOperator = (DecoratedPropertyOperator) configuration.getPropertyOperator(); PropertyOperator delegate = decoratedPropertyOperator.getPropertyOperator(); delegate = new CustomPropertyOperator(delegate); propertyOperatorHolder.setPropertyOperator(delegate); ``` -------------------------------- ### Create Various Container Types Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/user_guide/basic_concept.md Demonstrates creating different types of data source containers using the Containers factory class: forMap, forEnum, and forLambda. ```java // 基于 Map 集合创建一个容器 Container mapContainer = Containers.forMap("map_container", new HashMap()); // 基于枚举类创建一个容器 Container enumContainer = Containers.forEnum("enum_container", GenderEnum.class, GenderEnum::getCode, GenderEnum::getName); // 基于函数式接口创建一个容器 Container enumContainer = Containers.forLambda( "lambda_container", ids -> ids.stream().collect(Collections.toMap(id -> id, id -> id)) ); ``` -------------------------------- ### Manual Filling with OperateTemplate Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/advanced/operator_interface.md Demonstrates an equivalent manual filling process using OperateTemplate, where each object is executed individually. This approach is functionally similar to using the Operator Interface but offers more explicit control. ```java @Component public class Example { @Autowired private OperateTemplate operateTemplate; public Tuple doSomething() { Foo1 foo1 = new Foo1(1); Foo2 foo2 = new Foo2(2); operateTemplate.execute(foo1); // 填充 foo1 operateTemplate.execute(foo2); // 填充 foo2 return Tuple.of(new); } } ``` -------------------------------- ### Configure Student Class Data Source Container Source: https://github.com/opengoofy/crane4j/wiki/入门/快速开始 Sets up a data source container named 'student-class' that maps integer IDs to StudentClass objects. ```java @Configuration public class DataContainerConfig { // 声明应该 namespace 为 student-class ,能够根据 classId 返回 StudentClass 的数据源容器 @Bean public Container studentClassContainer() { // 输入 [1, 2] // 返回 {1={ id = 1, name = class1 }, 2={ id = 2, name = "class2"} return LambdaContainer( "student-class", ids -> ids.stream().collect( Collectors.toMap(Fucntion.identity, id -> new StudentClass(id, "一年" + id + "班") ) ) } ``` -------------------------------- ### Specify OneToOneAssembleOperationHandler Source: https://github.com/opengoofy/crane4j/blob/dev/docs/basic/assemble_operation_handler.html You can specify the handler to be used in the 'handler' or 'handlerType' attribute of the @Assemble annotation. This example shows how to explicitly set the OneToOneAssembleOperationHandler. ```java public class Foo { @Assemble(container = "foo", handlerType = OneToOneAssembleOperationHandler.class) private String name; private String alias; } ``` -------------------------------- ### Execute Basic Data Population Source: https://github.com/opengoofy/crane4j/wiki/入门/快速开始 Demonstrates executing the data population process using OperateTemplate on a list of Student objects. ```java public void doSomething { List students = Arrays.asList( new Student("小红", 1, 0), new Student("小明", 2, 1) ); operateTemplate.execute(students); } ``` -------------------------------- ### OperationAwareBean Callback Example Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/advanced/callback_of_component.md Implement OperationAwareBean to hook into the assembly operation process. The beforeAssembleOperation and afterOperationsCompletion methods allow custom logic before and after assembly. ```java @Data private static class Bean implements OperationAwareBean { @Assemble(container = "user", props = @Mapping("name")) private Integer id; private String name; @Assemble(container = "entry", props = @Mapping("value")) private Integer key; private String value; @Disassemble(type = NestedBean.class) private NestedBean nestedBean; private String remark; @Override public boolean supportOperation(String key) { // 若 nestedBean 为空,则不进行针对 nestedBean 属性的递归填充 return !Objects.equals("nestedBean", key) || Objects.nonNull(this.nestedBean); } @Override public void beforeAssembleOperation() { // 在填充开始前,若 id 为空,则为其设置一个默认值 if (Objects.isNull(this.id)) { this.id = 0; } } @Override public void afterOperationsCompletion() { // 在完成填充后,对属性值做一些处理 this.name = "尊敬的:" + this.name; } } ``` -------------------------------- ### MappingType.NO_MAPPING Example Source: https://github.com/opengoofy/crane4j/blob/dev/docs/basic/container/method_container.html Use NO_MAPPING when the method's return value is already a Map, meaning it's already grouped and no further mapping is needed by Crane4j. ```java @ContainerMethod(namespace = "userName", type = MappingType.NO_MAPPING) public Map listUserMapByIds(List ids); // 查询结果集已经分好组了 ``` ```java @ContainerMethod(namespace = "userName", type = MappingType.NO_MAPPING) public Map> listUserByDeptIds(List deptIds); ``` -------------------------------- ### MappingType.ONE_TO_ONE Example Source: https://github.com/opengoofy/crane4j/blob/dev/docs/basic/container/method_container.html Use ONE_TO_ONE when the query result is a collection of objects that need to be mapped one-to-one to a key. The resultKey specifies the property to use for mapping. ```java @ContainerMethod( namespace = "userName", type = MappingType.ONE_TO_ONE, resultType = User.class, resultKey = "deptId" ) public List listUserByIds(List ids); // 查询用户,并按用户 ID 一对一分组 ``` -------------------------------- ### Create Crane4jTemplate Instance Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/user_guide/getting_started/getting_started_without_spring.md Instantiate the Crane4jTemplate with the default configuration to begin using its features. ```java // 创建操作门面 Crane4jTemplate crane4jTemplate = Crane4jTemplate.withDefaultConfiguration(); ``` -------------------------------- ### Configure Container Caching Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/other/configuration_properties.md Add caching functionality to specified data source containers. This example adds a cache named 'shared-cache' to the container with the namespace 'testContainer'. ```yaml crane4j: cache-containers: shared-cache: testContainer ``` -------------------------------- ### Multi-Key Object Mapping with Handler Source: https://github.com/opengoofy/crane4j/wiki/使用/字段映射 Demonstrates object mapping when using `MultiKeyAssembleOperationHandler`. This allows mapping a collection of objects based on multiple keys derived from a single source field. ```java public class StudentVO { @Assemble( namespace = "teacher", props = @Mapping(ref = "teachers") handler = MultiKeyAssembleOperationHandler.class ) private String teacherIds; // 格式默认支持 1, 2, 3 格式 private List teachers; } ``` -------------------------------- ### Option-Style Configuration with @AssembleMethod Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/use_case/example_fill_method.md Configure data filling using the option-style approach by consolidating all configurations within the @AssembleMethod annotation on the UserInfo class. This includes referencing the target object, method details, and property mappings. ```java @Data public UserInfo { // 其他属性...... @AssembleMethod( target = "userDAO", // 引用目标对象,可以是 beanName 或者类的全限定名 method = @ContainerMethod( bindMethod = "listUsers", // 绑定 listUsers 方法 resultType = User.class, // 该方法的返回值为 User resultKey = "id", // 将 User 列表按 id 分组 type = MappingType.ONE_TO_ONE // 映射类型为一对一,即一个 UserInfo 对象对应一个 User 对象 ), props = { // 配置属性映射 @Mapping(src = "name", ref = "userName"), @Mapping(src = "address", ref = "userAddress"), @Mapping(src = "age", ref = "userAge") } ) private Integer userId; private String userName; private String userAddress; private Integer userAge; } ``` -------------------------------- ### Using @AssembleKey for Key-Value Mapping Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/basic/container/introspection_container.md Apply the @AssembleKey annotation to a field to utilize a registered key-value mapper. This example uses the 'phone_number_desensitization' mapper for a 'phone' field. ```java private static class Foo { @AssembleKey(mapper = "phone_number_desensitization", sort = 1) private String phone; } ``` -------------------------------- ### Manual Exclusion with OperateTemplate Source: https://github.com/opengoofy/crane4j/blob/dev/docs/use_case/example_exclude_operation.html When using `OperateTemplate` for manual filling, you can specify a filter to include or exclude specific operations. This example shows how to execute only assembly operations. ```java OperateTeamplte template = SpringUtil.getBean(OperateTemplate.class); template.execute(fooList, op -> op instanceof AssembleOperation); // 只执行装配操作 ``` -------------------------------- ### MappingType.ONE_TO_MANY Example Source: https://github.com/opengoofy/crane4j/blob/dev/docs/basic/container/method_container.html Use ONE_TO_MANY when a single key can map to multiple values in the query result. This is useful for scenarios like grouping users by department ID. ```java @ContainerMethod( namespace = "userName", type = MappingType.ONE_TO_MANY, resultType = User.class, resultKey = "deptId" ) public List listUserByDeptId(List deptIds); // 查询用户,并按用户的所属部门 ID 一对多分组 ``` -------------------------------- ### 引入 Crane4j 和 Lombok 依赖 Source: https://github.com/opengoofy/crane4j/blob/dev/docs/user_guide/getting_started/getting_started_without_spring.html 在非 Spring 环境下,需要引入 crane4j-core 模块。同时,为了方便地生成构造方法、getter/setter 方法,建议引入 Lombok 依赖。 ```xml cn.crane4j crane4j-core ${last-version} org.projectlombok lombok ${last-version} ``` -------------------------------- ### Define Custom Condition Annotation Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/basic/operation_condition.md Define a custom annotation to mark conditions for operation filling. This example creates `@ConditionOnTargetSerializable` to check if the target object implements `Serializable`. ```java @Documented @Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface ConditionOnTargetSerializable { String[] id() default {}; ConditionType type() default ConditionType.AND; boolean negate() default false; int sort() default Integer.MAX_VALUE; } ``` -------------------------------- ### 创建 Crane4j 全局配置 Source: https://github.com/opengoofy/crane4j/blob/dev/docs/user_guide/getting_started/getting_started_without_spring.html 在非 Spring 环境中,使用默认配置创建一个 Crane4j 的操作门面实例。 ```java // 创建操作门面 Crane4jTemplate crane4jTemplate = Crane4jTemplate.withDefaultConfiguration(); ``` -------------------------------- ### Configure Gender Data Source Container Source: https://github.com/opengoofy/crane4j/wiki/入门/快速开始 Creates a 'gender' data source container mapping integer codes to Chinese gender strings ('女' for 0, '男' for 1). ```java // 声明一个 namespace 为 gender,能够根据字典值返回性别名称的数据源容器 @Bean public Container genderContainer() { Map sources = new HashMap<>(); sources.put(0, "女"); sources.put(1, "男"); return ConstantContainer.forMap("gender", sources); } } ``` -------------------------------- ### MappingType.ORDER_OF_KEYS Example Source: https://github.com/opengoofy/crane4j/blob/dev/docs/basic/container/method_container.html Use ORDER_OF_KEYS when the method returns primitive types or Strings and the results should be merged in the order of the input keys. This is suitable when a key cannot be directly extracted from the result. ```java @ContainerMethod(namespace = "userName", type = MappingType.ORDER_OF_KEYS) public String getUserNameById(Integer id); // 查询结果集是 String 类型,无法获取 key 值,因此直接按顺序合并即可 ``` ```java @ContainerMethod(namespace = "userName", type = MappingType.ORDER_OF_KEYS) public List listUserAgeNameByIds(List ids); ``` -------------------------------- ### Configure Operation Groups in @Disassemble Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/basic/operation_group.md Define operation groups for disassembly using the `groups` attribute in the `@Disassemble` annotation. This example shows how to specify a group for disassembling a list of objects. ```java public class Foo { @Assemble(container = "user", props = @Mapping(src = "name", ref = "name"), groups = "admin") private Integer id; private String name; @Disassemble(type = Foo.class, groups = "nested") private List fooList; } ``` -------------------------------- ### Create and Register a Map Container Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/basic/container/container_abstract.md Demonstrates how to create a container from a Map and register it with the Crane4j template. This container can then be referenced by its namespace in assembly operations. ```java Crane4jTemplate crane4jTemplate = Crane4jTemplate.withDefaultConfiguration(); Container mapContainer = Containers.forMap("map_container", new HashMap()); crane4jTemplate.opsForContainer().registerContainer(mapContainer); ``` -------------------------------- ### ManyToMany with Custom Delimiter Source: https://github.com/opengoofy/crane4j/blob/dev/docs/basic/assemble_operation_handler.html Customize the delimiter used by `ManyToManyAssembleOperationHandler` for splitting key strings by specifying it in the `keyDesc` attribute. This example uses '|' as the delimiter and casts keys to `Integer`. ```java @Data public class Foo { @Assemble( container = "foo", keyDesc = "|", // 指定使用 “|” 作为分隔符 keyType = Integer.class, // 指定将分割出的每个 key 都转为 Integer 类型 props = @Mapping(ref = "teachers") ) private String teacherIds; private List teachers; } ``` -------------------------------- ### Recursive Disassemble Operation Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/basic/declare_disassemble_operation.md Disassemble operations are recursive by default. This example shows how to recursively disassemble nested Department objects. Be mindful of potential stack overflows due to circular references. ```java public class Department { private Integer id; private String name; @Disassemble(type = Department.class) // 递归填充下级部门 private List departments; } ``` -------------------------------- ### Create and Use a Map-Based Container Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/user_guide/basic_concept.md Create a Container based on a Map and retrieve data using a list of keys. The container is identified by a unique namespace. ```java // 创建一个基于 Map 集合的容器 Container mapContainer = Containers.forMap("map_container", new HashMap()); // 根据 key 值获得相应的数据 Map datas = mapContainer.get(Arrays.asList(1, 2, 3)); ``` -------------------------------- ### Applying a Field Mapping Template Source: https://github.com/opengoofy/crane4j/wiki/使用/字段映射 Introduce a defined mapping template into an `@Assemble` annotation using `propTemplates`. This simplifies complex configurations by referencing the pre-defined mappings. ```java public class StudentVO { @Assemble( namespace = "student", propTemplates = StudentMappingTemplate.class ) private Integer id; private String name; private String className; private String teacherName; } ``` -------------------------------- ### Exclusion via OperationAwareBean Callback Source: https://github.com/opengoofy/crane4j/blob/dev/docs/use_case/example_exclude_operation.html Implement the `OperationAwareBean` interface to provide a callback that determines whether an operation should be supported for a given bean. This example excludes the operation for the 'id3' property if its value is null. ```java public class Foo implements OperationAwareBean { @Assemble(container = "test1", prop = ":name1") private Integer id1; private String name1; @Assemble(container = "test2", prop = ":name2") private Integer id2; private String name2; @Assemble(container = "test3", prop = ":name3") private Integer id3; private String name3; @Override public boolean supportOperation(String key) { // 若 id3 为空,则不进行针对 id3 属性的填充 return !Objects.equals("id3", key) || Objects.nonNull(this.id3); } } ``` -------------------------------- ### Many-to-Many Assemble Operation Handler Source: https://github.com/opengoofy/crane4j/blob/dev/docs/basic/assemble_operation_handler.html Utilize `ManyToManyAssembleOperationHandler` for scenarios where multiple keys map to multiple data source objects. This example maps a string of teacher IDs to a list of teacher names. ```java public class StudentVO { @Assemble( container = "teacher", handlerType = ManyToManyAssembleOperationHandler, props = @Mapping(src = "name", ref = "teacherNames") ) private String teacherIds; // 默认支持 "1, 2, 3" 格式 private List teacherNames; // 填充的格式默认为 src1, src2, src3 } ``` ```java public class StudentVO { @Assemble( container = "teacher", props = @Mapping(ref = "teachers"), handlerType = ManyToManyAssembleOperationHandler ) private String teacherIds; // 默认支持 "1, 2, 3" 格式 private List teachers; } ``` -------------------------------- ### Initial Data Population with Auto-filling Source: https://github.com/opengoofy/crane4j/blob/dev/docs/basic/operation_sort.html Pre-process data sources during retrieval using auto-filling by specifying the `Emp.class` type in the `@AutoOperate` annotation. This is useful for preparing data before nested operations. ```java @AutoOperate(type = Emp.class) @ContainerMethd(resultType = Emp.class) public List listByIds(Collection ids) { return empMapper.listByIds(ids); } ``` -------------------------------- ### Accepting Parameter Objects Source: https://github.com/opengoofy/crane4j/blob/dev/docs/basic/container/method_container.html Configuring @ContainerMethod to accept parameter objects for queries (version 2.7.0+). ```APIDOC ## Accepting Parameter Objects ### Description This section describes how to configure a method annotated with @ContainerMethod to accept complex objects as query parameters, allowing for more structured data retrieval. This feature is available from version 2.7.0 onwards. ### Method `listItemByIdsAndTypes` ### Endpoint N/A (SDK Method) ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example ```java @Assemble( container = "dict", props = @Mapping(src = "name", ref = "dictName"), keyType = DictItemQueryDTO.class, // Specify the parameter object type keyDesc = "dictId:id, dictType:type" // Map properties to parameter object fields ) @Data public class Foo { private Integer dictId; private String dictType; private String dictName; } // Corresponding parameter object @Data public class CustomerQueryDTO { private String id; private String type; } // Corresponding query method accepting parameter object @ContainerMethod( namespace = "onoToOneMethod", resultType = DictItemQueryVO.class, type = MappingType.ORDER_OF_KEYS ) public List listItemByIdsAndTypes(List dtos) { // do something } ``` ### Response #### Success Response (200) `List` - A list of DictItemQueryDTO objects. #### Response Example N/A ``` -------------------------------- ### Get Global Configuration via Dependency Injection Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/user_guide/basic_concept.md Obtain the default Crane4jGlobalConfiguration object from the Spring container using dependency injection. Crane4j automatically registers a configuration object in a Spring environment. ```java @Autowired private Crane4jGlobalConfiguration configuration; // 从 Spring 获的默认配置类 ``` -------------------------------- ### Configure Map Container with Containers.forMap Source: https://github.com/opengoofy/crane4j/blob/dev/docs/basic/container/map_container.html Use the `Containers.forMap` factory method to quickly configure a Map collection as a data source container. This container retrieves values from the map based on a provided key. ```java Map map = new HashMap<>(); map.put(key, value); Container container = Containers.forMap("example", map); ``` -------------------------------- ### Parameter Object Configuration Source: https://github.com/opengoofy/crane4j/blob/dev/docs/basic/container/method_container.html Configure how parameter objects are generated for data source container methods using keyType and keyDesc. This requires a public no-arg constructor for the parameter object class. ```java @Assemble( container = "dict", props = @Mapping(src = "name", ref = "dictName"), keyType = DictItemQueryDTO.class, // 指定参数对象类型,该类必须有一个公开的无参构造方法 keyDesc = "dictId:id, dictType:type", // 指定如何将属性值映射到参数对象 ) @Data public class Foo { private Integer dictId; private String dictType; private String dictName; } // 对应的参数对象 @Data public class CustomerQueryDTO { private String id; private String type; } // 对应的接受参数对象的查询方法 @ContainerMethod( namespace = "onoToOneMethod", resultType = DictItemQueryVO.class, type = MappingType.ORDER_OF_KEYS ) public List listItemByIdsAndTypes(List dtos) { // do something } ``` -------------------------------- ### Trigger Fill Operation via AOP Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/use_case/example_fill_enum.md Configure a service method with @AutoOperate to automatically trigger the fill operation on its return value. The example simulates fetching student data and setting gender codes. ```java @Component public class ServiceImpl { // 你的 service 接口,确保 Spring 可以代理它 @AutoOperate(type = Student.class) // 声明自动填充该方法的返回值 public List getStudents(Collection ids) { return ids.stream() // 模拟返回数据 .map(id -> { int genderCode = id % 2; // 随机设置一个性别编码 return new Student().setGenderCode(genderCode); }) .collect(Collectors.toList()); } } // 测试 ServiceImpl service = SrpingUtil.getBean(ServiceImpl.class); service.getStudents(Arrays.asList(1, 2)); // 执行结果为:[{genderCode=1, genderName="男"}, {genderCode=0, genderName="女"}] ``` -------------------------------- ### Create Container from Map Source: https://github.com/opengoofy/crane4j/wiki/使用/数据源容器 Use `ConstantContainer.forMap` to quickly build a container from a Map. This is useful for direct key-value mappings. ```java Map map = new HashMap<>(); map.put(key, value); Container container = ConstantContainer.forMap(map); ``` -------------------------------- ### Handling Nested Operations with Default Executor Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/basic/operation_sort.md When using the default executor, disassemble operations always execute before assemble operations. This example shows a scenario where this order prevents nested filling from working as expected. ```java public class Dept { // 根据部门管理查询员工,然后填充到 emps 属性 @Assemble(container = "emp", prop = ":emps", order = 1) private Integer id; // 对员工对象进行填充 @Disassemble(type = Emp.class, order = 2) private List emps; } ``` -------------------------------- ### Annotate Model Class for Data Population Source: https://github.com/opengoofy/crane4j/blob/dev/docs/user_guide/getting_started/getting_started_with_springboot.html Annotate a class property with @Assemble to specify how data should be populated from a registered data source container. This example maps the 'id' to the 'name' property using the 'test' container. ```java @Data // 使用 lombok 生成构造器、getter/setter 方法 @RequiredArgsConstructor public static class Foo { // 根据 id 填充 name @Assemble(container = "test", props = @Mapping(ref = "name")) private final Integer id; private String name; } ``` -------------------------------- ### Implement DataSource Switcher Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/extension/mybatis_plus_extension.md Implement the `AssembleMpAnnotationHandler.DataSourceSwitcher` interface to define custom logic for switching data sources before and after query execution. ```java public class ExampleDataSourceSwitcher implements AssembleMpAnnotationHandler.DataSourceSwitcher { @Override public void beforeInvoke(String dataSource) { // 在这里定义切换数据源的逻辑 } @Override public void afterInvoke(String dataSource) { // 在这里定义完成查询后清理上下文的逻辑 } } ``` -------------------------------- ### 手动填充时指定操作执行器 Source: https://github.com/opengoofy/crane4j/wiki/使用/操作执行器 在手动填充时,可以通过 `OperateTemplate` 的指定重载方法设置本次填充操作使用的操作执行器。需要从 Spring 上下文中获取 `OperateTemplate` 和所需的操作执行器。 ```java OperateTemplate operateTemplate = SpringUtil.get(OperateTemplate.class); OperateTemplate executor = SpringUtil.get(DisorderedBeanOperationExecutor.class); operateTemplate.execute(fooList, executor, op -> true); ``` -------------------------------- ### Referencing Container Provider in @Assemble Annotation Source: https://github.com/opengoofy/crane4j/blob/dev/website/docs/basic/container/container_provider.md Specify the container provider to use for fetching data sources within the @Assemble annotation. This example shows how to reference a provider named 'fooContainerProvider' for a container with namespace 'user'. ```java public class UserVO { @Assemble( container = "user", containerProvider = "fooContainerProvider", props = @Mapping(src = "name", ref = "name") ) private Integer id; private String name; } ``` -------------------------------- ### Triggering Enum Filling via AOP Source: https://github.com/opengoofy/crane4j/blob/dev/docs/use_case/example_fill_enum.html Demonstrates how to automatically trigger enum filling for a method's return value using the @AutoOperate annotation. This example simulates data retrieval and populates enum-based fields. ```java @Component public class ServiceImpl { // 你的 service 接口,确保 Spring 可以代理它 @AutoOperate(type = Student.class) // 声明自动填充该方法的返回值 public List getStudents(Collection ids) { return ids.stream() // 模拟返回数据 .map(id -> { int genderCode = id % 2; // 随机设置一个性别编码 return new Student().setGenderCode(genderCode); }) .collect(Collectors.toList()); } } // 测试 ServiceImpl service = SrpingUtil.getBean(ServiceImpl.class); service.getStudents(Arrays.asList(1, 2)); // 执行结果为:[{genderCode=1, genderName="男"}, {genderCode=0, genderName="女"}] ```