'Expose all IDs when using Spring Data Rest
I'd like to expose all IDs using a Spring Rest interface.
I know that per default an ID like this will not be exposed via the rest interface:
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(unique=true, nullable=false)
private Long id;
I'm aware that I can use this to expose the ID for User:
@Configuration
public class RepositoryConfig extends RepositoryRestMvcConfiguration {
@Override
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(User.class);
}
}
But is there an easy way to expose all IDs without manually maintaining a list in this configureRepositoryRestConfiguration method?
Solution 1:[1]
If you want to expose the id field for all your entity classes:
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]));
}
}
Solution 2:[2]
I discovered that if you name the @Id field 'Id' it will display in the JSON if you have a public getter for the Id. The Id will show up as a JSON key called 'id'
For example: @Id @Column(name="PERSON_ROLE_ID") private Long Id;
This also works for @EmbeddedId fields called 'Id' as well as long as it has a public getter. In this case the fields of the Id will show up as a JSON object.
For example: @EmbeddedId private PrimaryKey Id;
Surprisingly this is case sensitive, calling id 'id' doesn't work even though it would be a more conventional name for a Java field.
I should say that I discovered this completely by accident so I don't know if this is an accepted convention or will work with previous or future versions of Spring Data and REST. Therefore I have included the relevant parts of my maven pom just incase it's sensittive to versions...
<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>
Solution 3:[3]
An updated answer to @mekasu. The RepositoryRestConfigurer interface was changed a bit in 2.4.
Pre 2.4:
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import javax.persistence.EntityManager;
import javax.persistence.metamodel.Type;
@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));
}
}
Post 2.4
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import javax.persistence.EntityManager;
import javax.persistence.metamodel.Type;
@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));
}
}
Solution 4:[4]
Try this configuration. It works perfectly fine for me.
@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());
}
});
}
}
Solution 5:[5]
You can use this method to find all @Entity classes of the EntityManagerFactory:
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;
}
then, to expose the IDs for all your entity classes:
@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);
}
}
}
}
Solution 6:[6]
Following piece of code looks prettier:
.exposeIdsFor(entityManager.getMetamodel().getEntities().stream().map(entityType -> entityType.getJavaType()).toArray(Class[]::new))
Solution 7:[7]
Full working example based on @FrancoisGengler's answer:
@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));
});
}
}
Solution 8:[8]
Proabably you can try this to include all id fields. I havent tried it yet, but will keep posted.
public class ExposeAllRepositoryRestConfiguration extends RepositoryRestConfiguration {
@Override
public boolean isIdExposedFor(Class<?> domainType) {
return true;
}
}
Solution 9:[9]
You can add all your entity classes by exposeIdsFor. Replace "db.entity" to whick package you put your entities.
@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);
}
}
The ClassTool is my custom function to get class from given package, you can write by yourself.
Solution 10:[10]
Here is what worked perfectly for me (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);
}
}
}
}
It finds all beans with the @Entity annotation and exposes them.
Solution 11:[11]
Please find a simple solution for this, avoiding to find entities related.
@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;
}
}
}
Solution 12:[12]
You can try with this solution: - First import reflections library to your POM file:
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>
- Then change your RepositoryConfig class to:
@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;
}
}
Change "com.example.entity" to your Entity package and you are good to go. Good luck!
Solution 13:[13]
I'm sharing my solution which is based on other answer.
In my case which configures multiple databases, I don't why but, I need to autowire instances of EntityManagerFactory.
@Db1 @Autowire
EntityManagerFactory entityManagerFactoryDb1;
@Db2 @Autowire
EntityManagerFactory entityManagerFactoryDb2;
Now all I need is a method streaming all entity classes gathered from all injected persistence units.
(Maybe, checking the existence of @Entity annotation or a custom annotation , say @EntityRestExposeId, can be applied.)
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);
}
Calling the exposeIdFor method is straightforward.
@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;
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
