How to unwrap custom serialized objects from "content" key with Jackon & Spring Boot - spring

I have customly serialized my object which is represented by this class:
public class Product {
private Long id;
private String name;
private Unit defaultUnit;
private Section section;
}
Serialization:
#Override
public void serialize(Product product, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeNumberField("id", product.getId());
jsonGenerator.writeStringField("name", product.getName());
jsonGenerator.writeStringField("defaultUnit", product.getDefaultUnit().toString());
jsonGenerator.writeObjectField("section", product.getSection());
}
However this produces an error which as I understand means that the default serializer has created a key and I have to provide it with a value:
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Can not write a field name, expecting a value; nested exception is com.fasterxml.jackson.core.JsonGenerationException: Can not write a field name, expecting a value]
Now it is obvious that the solution is to wrap the fields adding
jsonGenerator.writeStartObject();
jsonGenerator.writeEndObject();
This however results in generating entity wrapped in "content" object:
"content": {
"id": 1,
"name": "Product",
"defaultUnit": "Unit",
"section": {
"name": "Section"
}
}
My question is whether it is possible to write the entity unwrapped that is without the "content" key.
The following is all the code associated with Product:
Product.java
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#RequiredArgsConstructor
#JsonDeserialize(using = ProductDeserializer.class)
#JsonSerialize(using = ProductSerializer.class)
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#ManyToOne
#NonNull
private Unit defaultUnit;
#ManyToOne
#NonNull
private Section section;
}
ProductExcerpt.java
#Projection(name = "productExcerpt", types = {Product.class})
public interface ProductExcerpt {
String getName();
#Value("#{target.defaultUnit.toString()}")
String getDefaultUnit();
SectionExcerpt getSection();
}
ProductRepository.java
#RepositoryRestResource(excerptProjection = ProductExcerpt.class)
#CrossOrigin
public interface ProductRepository extends JpaRepository<Product, Long> {
}
ProductSerializer.java
public class ProductSerializer extends StdSerializer<Product> {
ProductSerializer(){
super(Product.class);
}
#Override
public void serialize(Product product, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeNumberField("id", product.getId());
jsonGenerator.writeStringField("name", product.getName());
jsonGenerator.writeStringField("defaultUnit", product.getDefaultUnit().toString());
jsonGenerator.writeObjectField("section", product.getSection());
jsonGenerator.writeEndObject();
}
}
There is also ProductDeserializer class however I think that it is irrevelant in this case. I do not have a Controller configured beacause as far as I am concerned there is no need while using spring-data-rest.

Related

How to remove empty object in which all the fields are null and those fields are removed by using " #JsonInclude(Include.NON_NULL) "?

I am using this #JsonInclude(Include.NON_NULL) to remove null fields from the Objects. It is working as expected. Let's see with an example .
If I have an object like this -
User{id=null,name=null}
(user object which has all of its fields as null value).
In the response it is coming like ----
{
user:{}
}
This is the thing I need to remove. Either I should assign null or remove the entire property.
Thanks for the help !.
To meet your requirement we need to use #JsonSerialize annotation with User field. Here is the working code.
#Getter
#Setter
public class Demo implements Serializable{
#JsonSerialize(using = CustomJsonSerializer.class)
private User user;
}
#Getter
#JsonInclude(JsonInclude.Include.NON_NULL)
public class User implements Serializable{
private Integer id;
private String name;
public User() {
}
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
}
public class CustomJsonSerializer extends JsonSerializer<User> {
#Override
public void serialize(User user, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
if (user.getId() == null && user.getName() == null) {
jsonGenerator.writeNull();
} else {
jsonGenerator.writeStartObject();
jsonGenerator.writeNumberField("id", user.getId());
jsonGenerator.writeStringField("name", user.getName());
jsonGenerator.writeEndObject();
}
}
}
if User{id=null,name=null} then response will be
{
"user":null
}
Reference - https://www.sghill.net/2012/how-do-i-write-a-jackson-json-serializer-deserializer/

Redis - Why details are saving both as HASH and SET using Spring Data Redis?

I am new to Redis and developing Spring Boot + Spring Data Redis example. I am using CrudRepository, Example and ExampleMatchers API to do the searching from the Redis Key value store DB.
Now when I simply run my code, I saw that persons data saved as SET and HASH as well. Is this correct ? What's the use of saving the Person details both as SET and HASH
Showing all my code
public enum Gender {
MALE, FEMALE {
#Override
public String toString() {
return "Superwoman";
}
}
}
Species.java
#Builder
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Species {
#Indexed
private String name;
}
Person.java
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#RedisHash("persons")
public class Person {
#Id
private String id;
#Indexed
private String firstname;
private String lastname;
#Indexed
private Gender gender;
private List<String> nicknames;
#Indexed
private Integer age;
private Map<String, String> physicalAttributes;
#Reference
private Person relative;
private Species species;
}
PersonRepository.java
public interface PersonRepository extends CrudRepository<Person, String>, QueryByExampleExecutor<Person> {
}
RedisExampleDemoApplication.java
#SpringBootApplication
public class RedisExampleDemoApplication implements CommandLineRunner{
RedisMappingContext mappingContext = new RedisMappingContext();
ExampleQueryMapper mapper = new ExampleQueryMapper(mappingContext, new PathIndexResolver(mappingContext));
#Autowired
private PersonRepository personRepository;
public static void main(String[] args) {
SpringApplication.run(RedisExampleDemoApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
Person person = Person.builder().firstname("Walter").gender(Gender.MALE).age(50).build();
Person person1 = Person.builder().firstname("Savani").gender(Gender.FEMALE).age(35).build();
personRepository.save(person);
personRepository.save(person1);
// [firstname:Walter, gender:MALE, age:50]
RedisOperationChain operationChain = mapper.getMappedExample(Example.of(person, ExampleMatcher.matchingAny()));
System.out.println(operationChain.getOrSismember());
System.out.println("----------------------------------------------");
Person p = Person.builder().lastname("Foo").build();
RedisOperationChain roc = mapper.getMappedExample(Example.of(p));
System.out.println(" == "+roc.getOrSismember());
System.out.println("-- "+roc.getSismember());
}
}
May be it is late to answer now , the reason that SET is visible is because of the secondary Index. I.e in your example First name is annotated as Indexed. Redis consider this as secondary index which is default a SET.

Spring Data JPA Redis : Cannot write custom method based query

I have configured Spring Data JPA with Redis and using RedisRepositories with provides methods like find(), findAll() etc. All these methods seem to be working just fine, but I am not able to write my custom method like.
RedisEntity findByGenderAndGrade(String gender, String grade);
RedisEntity is a simple POJO Entity class. If you want any more info, please let me know in messages.
Following is my entity:
#Data
#RedisHash("test1")
public class RedisEntity implements Serializable {
#Id
#GeneratedValue
private String id;
private String name;
private String gender;
private Integer grade;
}
Repository:
#Repository
public interface TestRepository extends JpaRepository<RedisEntity, String> {
List<RedisEntity> findAllByGender(String gender);
List<RedisEntity> findAllByGrade(Integer grade);
}
Service/Controller:
#Override
public List<RedisEntity> getById(String id) {
return testRepository.findById(id); //returns data perfectly.
}
#Override
public List<RedisEntity> getAllByGender(String gender) {
return testRepository.findAllByGender(gender); //returns []
}
#Override
public void saveEntity(RedisEntity redisEntity) {
testRepository.save(redisEntity); // saves it in redis perfectly.
}
Also,
findByGender and findAllByGender both give [], although I can see data in my redis database and save it as well.
As requested by FrançoisDupire,
#Configuration
public class RedisConfig {
#Autowired
private DeploymentProperties deploymentProperties;
private static Logger logger = LoggerFactory.getLogger(RedisConfig.class);
#Bean
JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("localhost", 6379);
redisStandaloneConfiguration.setPassword(RedisPassword.of("root"));
return new JedisConnectionFactory(redisStandaloneConfiguration);
}
#Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
return template;
}
}
Also, I had referred this article: Baeldung article on Spring data redis
As mentioned by #JoshJ and verified by myself and others,
The solution to the problem is:
Adding #Indexed annotation
to all those columns/fields which need to be used with all finds.
#Data
#RedisHash("EmployeeDetails")
public class RedisEntity {
#Id
private String employeeId;
private String firstName;
private String lastName;
#Indexed
private String gender;
#Indexed
private String grade;
}
We have the Spring Data Redis Library which provides the scope to write the custom method.Attaching Sample code.
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.0.8.RELEASE</version>
</dependency>
Entity Definition
#Data
#RedisHash("EmployeeDetails")
public class RedisEntity {
#Id
private String employeeId;
private String firstName;
private String lastName;
private String gender;
private String grade;
}
Repository Definition
#Repository
public interface RedisEntityRepository extends CrudRepository<RedisEntity, String>{
List<RedisEntity> findAllByGenderAndGrade(String gender, String grade);
}
Implementation
#Component
public class RedisEntityImpl implements RedisEntityService {
#Autowired
private RedisEntityRepository redisEntityRepository;
#Override
public List<RedisEntity> getAllByGenderAndGrade(String gender, String grade) {
return redisEntityRepository.findAllByGenderAndGrade(gender,grade);
}
}
Properties
spring.cache.type = redis
spring.redis.host = localhost
spring.redis.port = 6379

Spring Data REST and custom entity lookup (Provided id of the wrong type)

I have a model that looks something like this:
#Entity
public class MyModel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(unique = true, nullable = false)
#RestResource(exported = false)
private int pk;
#Column(unique = true, nullable = false)
private String uuid = UUID.randomUUID().toString();
#Column(nullable = false)
private String title;
public int getPk() {
return pk;
}
public void setPk(int pk) {
this.pk = pk;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
As you can see I have an auto-incrementing PK as my ID for the model, but also a random UUID. I want to use the PK in the database as the primary key, but want to use the UUID as a public facing ID. (To be used in URLs etc.)
My repository looks like this:
#RepositoryRestResource(collectionResourceRel = "my-model", path = "my-model")
public interface MyModelRepository extends CrudRepository<MyModel, String> {
#RestResource(exported = false)
MyModel findByUuid(#Param("uuid") String id);
}
As you can see I've set the repository to use a String as the ID.
Finally I set the entity lookup in a config file like this:
#Component
public class RepositoryEntityLookupConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.withEntityLookup().forRepository(MyModelRepository.class, MyModel::getUuid, MyModelRepository::findByUuid);
}
}
This works perfectly well for GET and POST requests, but for some reason I get an error returned on PUT and DELETE methods.
o.s.d.r.w.RepositoryRestExceptionHandler : Provided id of the wrong type for class MyModel. Expected: class java.lang.Integer, got class java.lang.String
Anyone know what might be causing this? I don't understand why it's expecting an Integer.
I may be doing something stupid as I'm quite new to the framework.
Thanks for any help.
The identifier of your domain object is obviously of type int. That means, your repository needs to be declared as extends CrudRepository<MyModel, Integer>.

Resolving entities with Spring Data Neo4j returns wrong entity types

I'm experiencing some strange behavior when I'm looking up node entities with Spring Data Neo4j (SDN). If I use GraphRepository.findOne(long) it will return an entity with that identifier even though the entity is not of the same type.
This is what my (very) simplified entity structure looks like:
#NodeEntity
protected abstract class BaseEntity {
#GraphId
private Long id;
#JsonIgnore
#RelatedTo(type = RelationType.ENTITY_AUDIT)
private Audit audit;
}
#NodeEntity
public final class Person extends BaseEntity {
#Indexed(indexType = IndexType.FULLTEXT)
private String firstName;
#Indexed(indexType = IndexType.FULLTEXT)
private String lastName;
}
#NodeEntity
public class Audit extends BaseEntity {
#RelatedTo(type = RelationType.ENTITY_AUDIT, direction = Direction.INCOMING)
private BaseEntity parent;
private Long date;
private String user;
}
For every entity type, I've created repositories like this:
#Repository
public interface PersonRepository extends GraphRepository<Person> {}
#Repository
public interface AuditRepository extends GraphRepository<Audit> {}
I've got an abstract base class for my service layer classes. That is what they roughly look like:
public abstract class MyServiceImpl<T extends BaseEntity> implements MyService<T> {
private GraphRepository<T> repository;
public MyServiceImpl(final GraphRepository<T> repository) {
this.repository = repository;
}
#Override
public T read(final Long identifier) throws EntityNotFoundException {
return repository.findOne(identifier);
}
#Override
public T create(final T entity) {
return repository.save(entity);
}
}
#Service
public class PersonServiceImpl extends MyServiceImpl<Person> implements PersonService {
private PersonRepository personRepository;
#Autowired
public PersonServiceImpl(final PersonRepository personRepository) {
super(personRepository);
this.personRepository = personRepository;
}
}
When I execute the following code, the result is not as expected:
Person person = new Person();
person.setFirstName("Test");
person.setLastName("Person");
personService.create(person);
// suppose the person identifier is 1L
final Audit audit = auditRepository.findOne(1L);
You'd expect that the AuditRepository would return null, but this in not the case. Instead, it returns an Audit with identifier 1L and null in all of its properties. It seems that as long as there's a node that corresponds to a given identifier, it will be returned, no mather what its type is. If Person and Audit would have had matching property names, they would contain their values too... Is all this expected behavior, or am I missing something?
For now, I've solved this problem with the code below, where I do the type check myself.
public abstract class MyServiceImpl<T extends BaseEntity> implements MyService<T> {
private GraphRepository<T> repository;
public MyServiceImpl(final GraphRepository<T> repository) {
this.repository = repository;
}
#Override
public T read(final Long identifier) throws EntityNotFoundException {
return get(identifier);
}
protected T get(final Long identifier) throws EntityNotFoundException {
final T entity = repository.findOne(identifier);
final Class<T> type = getServiceType();
if (entity == null || !(type.equals(repository.getStoredJavaType(entity)))) {
throw new EntityNotFoundException(type, identifier);
}
return entity;
}
#SuppressWarnings("unchecked")
private Class<T> getServiceType() {
return (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
}
}
If you need more configuration, please let me know.
My framework versions are:
<spring.version>3.2.0.RC1</spring.version>
<neo4j.version>1.8</neo4j.version>
<spring.data.neo4j.version>2.1.0.RELEASE</spring.data.neo4j.version>
we had that behavior before that it failed on the wrong entity type being returned, we changed that behavior so that the type you provide is used to automatically project the node to.
public <S extends PropertyContainer, T> T createEntityFromStoredType(S state, MappingPolicy mappingPolicy) {..}
template. createEntityFromStoredType(node, null) will get you the object with the stored state.
public Class getStoredJavaType(Object entity) {}
gives you the stored class for a node or relationship (or entity)
We had a discussion of changing the behavior back and failing esp. in Repositories.
The question is, what should happen then? An Exception? A Null result? ...
In general if you provide a raw node-id that is valid, returning an error or Null doesn't seem to be like a correct answer either?

Resources