【问题标题】:Expose all IDs when using Spring Data Rest使用 Spring Data Rest 时公开所有 ID
【发布时间】:2015-09-03 22:37:00
【问题描述】:

我想使用 Spring Rest 接口公开所有 ID。

我知道默认情况下,这样的 ID 不会通过其余接口公开:

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(unique=true, nullable=false)
    private Long id;

我知道我可以使用它来公开User 的 ID:

@Configuration
public class RepositoryConfig extends RepositoryRestMvcConfiguration {
    @Override
    protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(User.class);
    }
}

但是有没有一种简单的方法来公开所有 ID 而无需在此 configureRepositoryRestConfiguration 方法中手动维护列表?

【问题讨论】:

  • 查看this,了解一些有用的示例,了解如何公开所有实体或仅扩展或实现特定超类的标识符或接口,或标有一些特定注释

标签: java spring rest spring-mvc spring-data-rest


【解决方案1】:

如果您想公开所有实体类的 id 字段:

import java.util.stream.Collectors;

import javax.persistence.EntityManager;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;

@Configuration
public class MyRepositoryRestConfigurerAdapter extends RepositoryRestConfigurerAdapter {

    @Autowired
    private EntityManager entityManager;

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(entityManager.getMetamodel().getEntities().stream().map(e -> e.getJavaType()).collect(Collectors.toList()).toArray(new Class[0]));
    }

}

【讨论】:

  • .map(EntityType::getJavaType)
  • 这应该是公认的答案,因为手动配置只能在最小的用例中进行。谢谢@mekazu,这是一件美丽的事情!它对我有用。
  • .toArray(Class[]::new));
【解决方案2】:

我发现,如果您将 @Id 字段命名为“Id”,并且您有 Id 的公共 getter,它将显示在 JSON 中。 Id 将显示为名为“id”的 JSON 键

例如:@Id @Column(name="PERSON_ROLE_ID") private Long Id;

这也适用于名为“Id”的 @EmbeddedId 字段,只要它具有公共 getter。在这种情况下,Id 的字段将显示为 JSON 对象。

例如:@EmbeddedId private PrimaryKey Id;

令人惊讶的是,这是区分大小写的,调用 id 'id' 不起作用,即使它是 Java 字段的更传统名称。

我应该说我完全是偶然发现了这一点,所以我不知道这是否是公认的约定,或者是否适用于 Spring Data 和 REST 的先前或未来版本。因此,我已经包含了我的 maven pom 的相关部分,以防它对版本敏感......

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.0.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-rest</artifactId>
    </dependency>
    <dependency>
        <groupId>com.oracle</groupId>
        <artifactId>ojdbc7</artifactId>
        <version>12.1.0.2</version>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
    </dependency>
</dependencies>

【讨论】:

  • 不遵循 java 命名约定的缺点是,像 SpEL 这样​​的 bean 评估语言不容易引用该字段。
  • 像魅力一样工作+10
  • 超级发现! ID 为我工作。注意:在 json 中,它仅显示为 id: 1 ,它也在所有其他字段的末尾。 “地址”:[ { “custid”:“1”,“address1”:“1,stree1”,“address2”:“stree1”,“city”:“city1”,“zip”:“1111”,“ccc ": "ccc1", "id": 1, "_links": { "self": { "href": "localhost:8080/addresses/1" }, "address": { "href": "localhost:8080/addresses/1" } } } ,
  • 这比设置 RepositoryRestConfigurer 为您想要的每个类公开 Id 容易得多。
【解决方案3】:

目前,SDR 无法提供此功能。 SDR Jira 跟踪器上的This issue 解释了为什么这不可能(也许不应该)。

这个论点基本上是因为 ID 已经包含在响应中的 self 链接中,您不需要将它们作为对象本身的属性公开。

也就是说,您可以使用反射来检索具有javax.persistence.Id 注释的所有类,然后调用RepositoryRestConfiguration#exposeIdsFor(Class&lt;?&gt;... domainTypes)

【讨论】:

  • thomas-letsch.de/2015/spring-data-rest-hateoas有一个使用反射(在本例中为 ClassPathScannning)的示例
  • 话虽如此,在无头服务和混合搭配用例上尤其需要 ID。必须有一个唯一键来指向对象,并在后端被解释为检索该唯一键的映射对象。还不如使用 ID 本身。
【解决方案4】:

@mekasu 的更新答案。 RepositoryRestConfigurer 接口在 2.4 中有所改变。

2.4 之前的版本:

@Configuration
public class Config implements RepositoryRestConfigurer {

    @Autowired
    private EntityManager entityManager;

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
        config.exposeIdsFor(entityManager.getMetamodel().getEntities().stream().map(Type::getJavaType).toArray(Class[]::new));
    }
}

发布 2.4

@Configuration
public class Config implements RepositoryRestConfigurer {

    @Autowired
    private EntityManager entityManager;

    @Override
    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
        config.exposeIdsFor(entityManager.getMetamodel().getEntities().stream().map(Type::getJavaType).toArray(Class[]::new));
    }
}

【讨论】:

    【解决方案5】:

    试试这个配置。它对我来说非常好用。

    @Configuration
    public class RestConfiguration extends RepositoryRestConfigurerAdapter{
    
          @PersistenceContext
          private EntityManager entityManager;
    
          @Override
          public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
               //TODO: Expose for specific entity!
               //config.exposeIdsFor(Officer.class);
               //config.exposeIdsFor(Position.class);
    
               //TODO: Expose id for all entities!
               entityManager.getMetamodel().getEntities().forEach(entity->{
                    try {
                         System.out.println("Model: " + entity.getName());
                         Class<? extends Object> clazz = Class.forName(String.format("yourpackage.%s", entity.getName()));
                         config.exposeIdsFor(clazz);
                    } catch (Exception e) {
                         System.out.println(e.getMessage());
                    }
                });
        }
    }
    

    【讨论】:

    • 完美运行。但是,不推荐使用 RepositoryRestConfigurerAdapter。我扩展了 RepositoryRestConfigurer 类并覆盖了 configureRepositoryRestConfiguration 方法并按原样编写代码。它对我很好
    【解决方案6】:

    您可以使用此方法查找EntityManagerFactory的所有@Entity类:

    private List<Class<?>> getAllManagedEntityTypes(EntityManagerFactory entityManagerFactory) {
        List<Class<?>> entityClasses = new ArrayList<>();
        Metamodel metamodel = entityManagerFactory.getMetamodel();
        for (ManagedType<?> managedType : metamodel.getManagedTypes()) {
            Class<?> javaType = managedType.getJavaType();
            if (javaType.isAnnotationPresent(Entity.class)) {
                entityClasses.add(managedType.getJavaType());
            }
        }
        return entityClasses;
    }
    

    然后,公开所有实体类的 ID:

    @Configuration
    public class RestConfig extends RepositoryRestMvcConfiguration {
    
        @Bean
        public RepositoryRestConfigurer repositoryRestConfigurer(EntityManagerFactory entityManagerFactory) {
            List<Class<?>> entityClasses = getAllManagedEntityTypes(entityManagerFactory);
    
            return new RepositoryRestConfigurerAdapter() {
    
                @Override
                public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
                    for (Class<?> entityClass : entityClasses) {
                        config.exposeIdsFor(entityClass);
                    }
                }
        }
    }
    

    【讨论】:

      【解决方案7】:

      以下代码看起来更漂亮:

      .exposeIdsFor(entityManager.getMetamodel().getEntities().stream().map(entityType -> entityType.getJavaType()).toArray(Class[]::new))
      

      【讨论】:

        【解决方案8】:

        基于Francois Gengler 答案的完整工作示例(请支持他的回答,但不要支持我的回答):

        @SpringBootApplication
        public class DataRestApplication {
            public static void main(String[] args) {
                SpringApplication.run(DataRestApplication.class, args);
            }
        
            @Bean
            public RepositoryRestConfigurer repositoryRestConfigurer(EntityManager entityManager) {
                return RepositoryRestConfigurer.withConfig(config -> {
                    config.exposeIdsFor(entityManager.getMetamodel().getEntities()
                            .stream().map(Type::getJavaType).toArray(Class[]::new));
                });
            }
        }
        

        【讨论】:

          【解决方案9】:

          也许您可以尝试将所有 id 字段包含在内。我还没有尝试过,但会继续发布。

           public class ExposeAllRepositoryRestConfiguration extends RepositoryRestConfiguration {
              @Override
              public boolean isIdExposedFor(Class<?> domainType) {
                  return true;
                  }
              }
          

          Excerpt from this link

          【讨论】:

            【解决方案10】:

            您可以通过 exposeIdsFor 添加所有实体类。将“db.entity”替换为您放置实体的 whick 包。

            @Configuration
            public class CustomRepositoryRestConfigurer extends RepositoryRestConfigurerAdapter {
                Logger logger = Logger.getLogger(this.getClass());
            
                @Override
                public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
                    Set<String> classNameSet = ClassTool.getClassName("db.entity", false);
                    for (String className : classNameSet) {
                        try {
                            config.exposeIdsFor(Class.forName(className));
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
            
                    logger.info("exposeIdsFor : " + classNameSet);
                }
            }
            

            ClassTool 是我的自定义函数,用于从给定包中获取类,您可以自己编写。

            【讨论】:

              【解决方案11】:

              这对我来说非常有效 (source here):

              @Configuration
              public class RepositoryRestConfig extends RepositoryRestConfigurerAdapter {
              
                @Override
                public void configureRepositoryRestConfiguration(final RepositoryRestConfiguration config) {
              
                  final ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
                      false);
                  provider.addIncludeFilter(new AnnotationTypeFilter(Entity.class));
              
                  final Set<BeanDefinition> beans = provider.findCandidateComponents("com.your.domain");
              
                  for (final BeanDefinition bean : beans) {
                    try {
                      config.exposeIdsFor(Class.forName(bean.getBeanClassName()));
                    } catch (final ClassNotFoundException e) {
                      // Can't throw ClassNotFoundException due to the method signature. Need to cast it
                      throw new IllegalStateException("Failed to expose `id` field due to", e);
                    }
                  }
                }
              }
              

              它会找到所有带有 @Entity 注释的 bean 并公开它们。

              【讨论】:

                【解决方案12】:

                请为此找到一个简单的解决方案,避免查找相关实体。

                @Component
                public class EntityExposingIdConfiguration extends RepositoryRestConfigurerAdapter {
                
                    @Override
                    public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
                        try {
                            Field exposeIdsFor = RepositoryRestConfiguration.class.getDeclaredField("exposeIdsFor");
                            exposeIdsFor.setAccessible(true);
                            ReflectionUtils.setField(exposeIdsFor, config, new ListAlwaysContains());
                        } catch (NoSuchFieldException e) {
                            e.printStackTrace();
                        }
                    }
                
                    class ListAlwaysContains extends ArrayList {
                
                        @Override
                        public boolean contains(Object o) {
                            return true;
                        }
                    }
                }
                

                【讨论】:

                • 产量:The type RepositoryRestConfigurerAdapter is deprecated
                【解决方案13】:

                您可以尝试以下解决方案: - 首先将reflections 库导入您的POM 文件:

                <dependency>
                    <groupId>org.reflections</groupId>
                    <artifactId>reflections</artifactId>
                    <version>0.9.11</version>
                </dependency>
                

                - 然后将您的 RepositoryConfig 类更改为:

                @Configuration
                public class RepositoryConfig extends RepositoryRestMvcConfiguration {
                    @Override
                    protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
                        Reflections reflections = new Reflections("com.example.entity");
                        Set<Class<?>> idExposedClasses = reflections.getTypesAnnotatedWith(Entity.class, false);
                        idExposedClasses.forEach(config::exposeIdsFor);
                        return config;
                    }
                }
                

                "com.example.entity" 更改为您的 Entity 包,一切顺利。祝你好运!

                【讨论】:

                  【解决方案14】:

                  我正在分享我基于 other answer 的解决方案。

                  在我配置多个数据库的情况下,我不知道为什么,但是我需要自动装配 EntityManagerFactory 的实例。

                  @Db1 @Autowire
                  EntityManagerFactory entityManagerFactoryDb1;
                  
                  @Db2 @Autowire
                  EntityManagerFactory entityManagerFactoryDb2;
                  

                  现在我只需要一个方法,流式传输从所有注入的持久性单元收集的所有实体类。

                  (也许,检查是否存在@Entity 注释或自定义注释,比如@EntityRestExposeId,可以应用。)

                      private void forEachEntityClass(final Consumer<? super Class<?>> consumer) {
                          Arrays.stream(DataRestConfiguration.class.getDeclaredFields())
                                  .filter(f -> {
                                      final int modifiers = f.getModifiers();
                                      return !Modifier.isStatic(modifiers);
                                  })
                                  .filter(f -> EntityManagerFactory.class.isAssignableFrom(f.getType()))
                                  .map(f -> {
                                      f.setAccessible(true);
                                      try {
                                          return (EntityManagerFactory) f.get(this);
                                      } catch (final ReflectiveOperationException roe) {
                                          throw new RuntimeException(roe);
                                      }
                                  })
                                  .flatMap(emf -> emf.getMetamodel().getEntities().stream().map(EntityType::getJavaType))
                                  .forEach(consumer);
                      }
                  

                  调用exposeIdFor 方法很简单。

                  @Configuration
                  class DataRestConfiguration {
                  
                      @Bean
                      public RepositoryRestConfigurer repositoryRestConfigurer() {
                          return RepositoryRestConfigurer.withConfig((configuration, registry) -> {
                              forEachEntityClass(configuration::exposeIdsFor);
                              // ...
                          });
                      }
                  
                      private void forEachEntityClass(final Consumer<? super Class<?>> consumer) {
                          // ...
                      }
                  
                      @Db1 @Autowired
                      EntityManagerFactory entityManagerFactoryDb1;
                  
                      @Db2 @Autowired
                      EntityManagerFactory entityManagerFactoryDb2;
                  
                      @Db3 @Autowired
                      EntityManagerFactory entityManagerFactoryDb3;
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 2016-04-30
                    • 2018-02-23
                    • 2016-12-07
                    • 2015-05-03
                    • 2016-06-23
                    • 1970-01-01
                    • 2023-03-09
                    • 2015-04-18
                    • 1970-01-01
                    相关资源
                    最近更新 更多