Simple example
#Data
#Document("product")
#Accessors(chain = true)
public class Product {
#Id
private String id;
private BigDecimal cost;
}
ArangoDB configuration:
#Override
public Builder arango() {
final Builder builder = new Builder();
arangoDbConfig.getHosts().forEach(host -> builder.host(host.getHost(), host.getPort()));
return builder
.user(arangoDbConfig.getUser())
.password(arangoDbConfig.getPassword())
.registerModule(new VPackJdk8Module());
}
Reading document generate exception:
Invocation of init method failed; nested exception is org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate java.math.BigDecimal using constructor NO_CONSTRUCTOR with arguments
Dependency:
SpingBoot 1.5.9
Arangodb-spring-data 1.0.1
Velocypack-module-jdk8 1.0.2
Ok, just forget BigDecimal is mapped to HashMap. Creating queries (biggerThan/lessThan) will be bizarre. I change the data type to Double.
You need to define JsonSerializer and JsonDeserializer for class which contains BigDecimal, because ArangoDB do not have support for that type of data.
Check this arango serializer documentation: link
Related
Here is my folder structure:
In my IAppUserMapper I have a method to convert every AppUser entity instance to Data Transfer Object Model. Here is the code in IAppUserMapper interface:
import com.server.ecommerceapp.dto.AppUserDTO;
import com.server.ecommerceapp.model.AppUser;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
#Mapper
public interface IAppUserMapper {
IAppUserMapper appUserMapper = Mappers.getMapper(IAppUserMapper.class);
#Mapping(target = "username")
#Mapping(target = "email")
#Mapping(target = "password")
#Mapping(target = "roles", expression = "java(appUser.getRoles().stream().map(this::getRoleName).collect(Collectors.toList()))")
AppUserDTO toAppUserDTO(AppUser appUser);
default String getRoleName(Role role) {
return role.getRoleName();
}
}
And here is the MapperConfiguration class code where I configure both Product and user mappers:
import com.server.ecommerceapp.mapper.IAppUserMapper;
import com.server.ecommerceapp.mapper.IProductMapper;
import org.mapstruct.factory.Mappers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class MapperConfiguration {
#Bean
public IAppUserMapper appUserMapper() {
return Mappers.getMapper(IAppUserMapper.class);
}
#Bean
public IProductMapper productMapper() {
return Mappers.getMapper(IProductMapper.class);
}
}
The error I get:
Error creating bean with name 'appUserMapper' defined in class path
resource
[com/server/ecommerceapp/configuration/MapperConfiguration.class]:
Bean instantiation via factory method failed; nested exception is
org.springframework.beans.BeanInstantiationException: Failed to
instantiate [com.server.ecommerceapp.mapper.IAppUserMapper]: Factory
method 'appUserMapper' threw exception; nested exception is
java.lang.RuntimeException: java.lang.ClassNotFoundException: Cannot
find implementation for com.server.ecommerceapp.mapper.IAppUserMapper
I was told I should make META-INF package in resources, with service package and the com.server.ecommerceapp.mapper.AppUserMapper txt with the content same as the name of the file, so that Spring can scan and find the package following the path:
src/main/resources/META-INF/service/com.server.ecommerceapp.mapper.AppUserMapper
but it didnt work. Any ideas how to solve this, and by the way, is it bad practise to start interface names with capital I cause Im coming from ASP?
Edit:
I added #Mapper(componentModel = "spring") to my interfaces and implemented them as DI with Autowired. I dont know if its related to that problem that I had but now I get error that it cant find collectors. Im trying to map a collection of Roles from AppUser to AppUserDTO. Here are both AppUser and AppUserDTO classes:
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Data
public class AppUser {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#Column(name = "username", nullable = false, unique = true)
private String username;
#Column(name = "email", nullable = false, unique = true)
private String email;
#Column(name = "password", nullable = false)
private String password;
#ManyToMany(fetch = EAGER)
private Collection<Role> roles;
}
And DTO:
#NoArgsConstructor
#AllArgsConstructor
#Data
public class AppUserDTO {
private String username;
private String email;
private String password;
private Collection<String> roles;
}
So you're using Spring, but you are trying to not use Spring.
You should make your mappers use Spring component model:
#Mapper(componentModel = "spring")
public interface MyMapper {
Target map(Source source);
}
Check docs for dependency injection: https://mapstruct.org/documentation/stable/reference/html/#using-dependency-injection
Or do it with shared configuration: https://mapstruct.org/documentation/stable/reference/html/#shared-configurations
After that you can just #Autowired MyMapper myMapper; as any other Spring bean. No need to create instance in interface (the "Mappers.getMapper" thing) and no need to create mappers in java configuration, bean creation will be handled by framework.
#Mapping(target = "roles", expression = "java(appUser.getRoles().stream().map(this::getRoleName).collect(Collectors.toList()))")
now I get error that it cant find collectors
You are using an expression with Collectors class. As stated in the documentation https://mapstruct.org/documentation/stable/reference/html/#expressions:
Please note that the fully qualified package name is specified because MapStruct does not take care of the import of the TimeAndFormat class (unless it’s used otherwise explicitly in the SourceTargetMapper). This can be resolved by defining imports on the #Mapper annotation.
So you either need to fully qualify java.util.stream.Collectors in your expression or set "imports" parameter in #Mapper annotation: #Mapper(imports = Collectors.class).
I would also say, you could just write a normal Java method for roles mapping and not be dealing with expressions. But that's up to your taste.
The file name of the service should be the interface and its content the implementation. You have named it by the implementation.
I got the following classes:
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "oid"
)
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "clazz")
#JsonSubTypes({
#JsonSubTypes.Type(value = MySubEntity.class, name = "MySubEntity"),
})
public abstract class Entity {
...
}
public class MySubEntity extends Entity {
...
}
Now when I serialize that MySubEntity wrapped in an Optional then JSON does not contain the clazz attribute containing the type ID. Bug? When I serialize to List<MySubEntity> or just to MySubEntity it works fine.
Setup: jackson-databind 2.9.4, jackson-datatype-jdk8 2.9.4, serialization is done in Spring Boot application providing a RESTful web service.
EDIT: Here is the Spring REST method that returns the Optional:
#RequestMapping(method = RequestMethod.GET, value = "/{uuid}", produces = "application/json")
public Optional<MySubEntity> findByUuid(#PathVariable("uuid") String uuid) {
...
}
EDIT:
I made a SSCCE with a simple Spring REST controller and two tests. The first test is using ObjectMapper directly which is successful in deserialization although the clazz is missing. The second test calls the REST controller and fails with an error because clazz is missing:
Error while extracting response for type [class com.example.demo.MySubEntity] and content type [application/json;charset=UTF-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Missing type id when trying to resolve subtype of [simple type, class com.example.demo.MySubEntity]: missing type id property 'clazz'; nested exception is com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Missing type id when trying to resolve subtype of [simple type, class com.example.demo.MySubEntity]: missing type id property 'clazz'
This, indeed, looks like a bug. There is one workaround that I can suggest for this case, is to use JsonTypeInfo.As.EXISTING_PROPERTY and add field clazz to your Entity. There only one case with this approach is that the clazz must be set in java code manually. However this is easy to overcome.
Here is the full code for suggested workaround:
#JsonIdentityInfo(
generator = ObjectIdGenerators.IntSequenceGenerator.class,
property = "oid"
)
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY, //field must be present in the POJO
property = "clazz")
#JsonSubTypes({
#JsonSubTypes.Type(value = MySubEntity.class, name = "MySubEntity"),
})
public abstract class Entity {
#JsonProperty
private String uuid;
//Here we have to initialize this field manually.
//Here is the simple workaround to initialize in automatically
#JsonProperty
private String clazz = this.getClass().getSimpleName();
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getClazz() {
return clazz;
}
public void setClazz(String clazz) {
this.clazz = clazz;
}
}
Here is my simple bean
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Foo {
private String firstName;
private String lastName;
public String getFullName(){
return firstName + lastName;
}
}
when this object gets serialized in Spring-boot controller with Jackson,
I get the following error
j.l.NullPointerException: null
com.example.foobar.foo.getFullName(Foo.java:28)
s.r.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java)
s.r.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
s.r.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
j.l.reflect.Method.invoke(Method.java:498)
c.f.j.d.s.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:653)
c.f.j.d.s.s.BeanSerializerBase.serializeFields(BeanSerializerBase.java:690)..
28 common frames omitted\nWrapped by: c.f.j.d.JsonMappingException: (was java.lang.NullPointerException)
(through reference chain: com.example.foobar.foo[\"fullName\"])
c.f.j.d.JsonMappingException.wrapWithPath(JsonMappingException.java:379)
c.f.j.d.JsonMappingException.wrapWithPath(JsonMappingException.java:339)
c.f.j.d.s.s.StdSerializer.wrapAndThrow(StdSerializer.java:343)
c.f.j.d.s.s.BeanSerializerBase.serializeFields(BeanSerializerBase.java:698)
c.f.j.d.s.BeanSerializer.serialize(BeanSerializer.java:155)
c.f.j.d.s.DefaultSerializerProvider.serializeValue(DefaultSerializerProvider.java:292)
c.f.j.d.ObjectWriter$Prefetch.serialize(ObjectWriter.java:1419)
c.f.j.d.ObjectWriter.writeValue(ObjectWriter.java:940)
o.s.h.c.j.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:267)...
23 common frames omitted\nWrapped by: o.s.h.c.HttpMessageNotWritableException: Could not write content: (was java.lang.NullPointerException) (through reference chain: com.example.foobar.foo[\"fullName\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.example.foobar.foo[\"fullName\"])
o.s.h.c.j.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:274 ..."
Here is the requestBody that I sent
{"firstName": "foo",
"lastName: null
}
Is Jackson trying to serialize fullName property ? but I have no such field declared. is this expected behavior? I am unable to find documentation that supports this behavior.
Thanks
Your guess is right, Jackson is trying to find the field name by its getter method, which it cannot find. And hence NPE.
Different solutions
use #JsonIgnore on the getFullName method.
you can disable this feature by setting this property in spring boot application.properties file
spring.jackson.mapper.use-getters-as-setters=false
If you wish to do it with the java code (If not using spring boot), you can do it with a bean declaration like this
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.featuresToDisable(MapperFeature.USE_GETTERS_AS_SETTERS);
return builder;
}
Entity class:
#Entity
#SqlResultSetMapping(
name="hourMapping",
classes=#ConstructorResult(
targetClass=Representation.class,
columns={
#ColumnResult(name="hour", type=BigDecimal.class),
#ColumnResult(name="transactions", type=BigDecimal.class)
}
)
)
#NamedNativeQuery(name="MyEntity.reportByHour", query="SELECT hour,SUM(tran_per_hour) AS transactions FROM MY_ENTITY GROUP BY hour ORDER BY hour"
,resultSetMapping="hourMapping")
#Table(name="MY_ENTITY")
public class MyEntity implements Serializable {
Pojo class:
#Data //Lombok
#JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class Representation {
public Representation(BigDecimal hour, BigDecimal transactions) {
this.hour = hour;
this.transactions = transactions;
}
private BigDecimal hour;
private BigDecimal transactions;
Repository interface:
public interface MyEntityRepository extends JpaRepository<MyEntity, MyEntityPK> {
List<Representation> reportByHour();
}
When I run the endpoint which invokes the native query, I get exception:
Failed to convert from type [java.lang.Object[]] to type [com.representation.Representation] for value '{0, 198}'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.math.BigDecimal] to type [com.representation.Representation]
Now if I just have "hour" field returned from my native query (and relevant changes to POJO constructor etc) it works fine.
Any help appreciated.
Ok, false alarm. My hibernate dependencies were all messed up and causing conflicts so resulting in the above exception.
After fixing these dependency issues, works great!!
Long story short: let spring-boot-* handle most hibernate dependencies instead of overriding or managing your own.
I have a Spring Batch Job that defines a JdbcPagingItemReader with a BeanPropertyRowMapper :
JdbcPagingItemReader<RawNotice> reader = new JdbcPagingItemReader<>();
final SqlPagingQueryProviderFactoryBean sqlPagingQueryProviderFactoryBean = new SqlPagingQueryProviderFactoryBean();
sqlPagingQueryProviderFactoryBean.setDataSource(dataSource);
sqlPagingQueryProviderFactoryBean.setSelectClause("select *");
sqlPagingQueryProviderFactoryBean.setFromClause("from a_table");
sqlPagingQueryProviderFactoryBean.setWhereClause("state = :state");
sqlPagingQueryProviderFactoryBean.setSortKey("id");
Map<String, Object> parameters = new HashMap<>();
parameters.put("state", "interesting_state");
reader.setQueryProvider(sqlPagingQueryProviderFactoryBean.getObject());
reader.setDataSource(dataSource);
reader.setPageSize(10);
// The line below is the interesting one
reader.setRowMapper(new BeanPropertyRowMapper<>(MyEntity.class));
reader.setParameterValues(parameters);
return reader;
This used to work fine, but since we upgraded to spring boot 1.4 and spring cloud Camden, it throws an exception :
org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type [java.sql.Timestamp] to required type [org.joda.time.LocalDateTime] for property 'ADateColumn'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.sql.Timestamp] to required type [org.joda.time.LocalDateTime] for property 'ADateColumn': no matching editors or conversion strategy found
The column ADateColumn is declared as a Joda LocalDateTime and stored as a java.sql.Timestamp in the database.
I'm quite aware I could add my own joda converters to the BeanPropertyRawMapper conversionService for example, or create a PropertyEditor that understands Java LocalDateTime, but that looks rather like a configuration problem, like something isn't being registered right.
Anybody with a solution/suggestion to fix this problem ?
Thanks !
This is the part of the entity that poses problem :
#Entity
#EqualsAndHashCode(of = { "..." })
#ToString(of = { .... })
public class MyEntity {
#Getter
#Setter
#Id
#GeneratedValue
private Long id;
#Getter
#Version
#Column(nullable = false)
private int version;
//<--- snip --->
#Getter
#Setter
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentLocalDateTime")
private LocalDateTime aDateColumn;
}
Hibernate is version 4.3.11.Final
JPA is version 2.1 with Hibernate Entity Manager 4.3.11.Final
So I finally ended up creating my own BeanPropertyRowMapper with custom Joda converters :/