Getting bean creation errors when creating a shared interface for my ReactiveCrudRepositories, is it possible to create share interfaces like this? - spring-boot

so my Kotlin / spring-boot application makes use of several reactiveCrudRepositories, and I am trying to reduce the amount of code I need to write methods that are common to each table in my database.
I was trying to create a common interface that each table implements, but am getting the following error when i build:
Error creating bean with name 'sharedRepo' defined in com...sharedRepo defined in #EnableR2dbcRepositories declared on R2dbcRepositoriesAutoConfiguireRegistrar.EnableR2dbcRepositoriesConfiguration: Invocation of ini method failed; nested exception is in org.springframework.data.mapping.MappingException: Couldn't find persistentEntity for type class java.lang.Object!
I have tried reducing the complexity of the interface down to just an empty interface (so that i can still write generic methods) but this has not worked.. the following is what my code currently looks like:
itemA.kt
#Table(table_a)
data class itemA(
#Id
#Column("id")
val menuId: UUID
#Column("name")
val prodName: String
...
}
interface AClassRepository : sharedRepo<itemA>, Serializable
itemB.kt
#Table(table_b)
data class itemB(
#Id
#Column("id")
val menuId: UUID
#Column("name")
val prodName: String
...
}
interface BClassRepository : sharedRepo<itemB>, Serializable
SharedRepo.kt
interface sharedRepo<T> : ReactiveCrudRepository<T, UUID>
databaseAssistant.kt
fun <T> sharedRepo<T>.persist(item: T) = {
Try {
//this is calling the ReactiveCrudRepository.save method
save
} ...
}
fun <T> sharedRepo<T>.someOtherDbCall(item: T, newName: String) =
*some other method body*
serviceC.kt
#Service
class ServiceC(
private val repositoryA: AClassRepository,
private val repositoryB: BClassRepository
( {
fun useRepo() =
repositoryB.persist(someInstanceOfItemA)
fun useOtherRepo() =
repositoryA.someOtherDbCall(someInstanceOfItemB, "aNiceName")
}

Related

Spring can't find implementation

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.

Kotlin - Using data class with type from an interface

I have an overrides interface which I created in order to combine 2 data classes - like so:
interface Overrides
data class SoOverrides(
val soId: String,
val freeInterval: String?
) : Overrides
data class CoOverrides(
val coId: String,
val pubType: String,
) : Overrides
I'm then trying to set the type of an item in my main data class to Overrides like so:
#Document(collection = Campaign.COLLECTION)
data class Campaign(
#Id
val id: String,
val title: String,
val overrides: List<Overrides>? = null
) {
companion object {
const val COLLECTION: String = "campaigns"
}
}
However whenever I go to run this, I get the error:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [co.....models.Overrides]: Specified class is an interface
Could someone please explain what I need to do to use that type Overrides. I'm extending the interface that way so I can have multiple classes, under one name, but not sure why it doesnt work?
I guess I could just use val overrides: List<*>? = null
Any help appreciated.
Thanks.

Impossible to return a list / collection of projection

I made a spring-boot project to access to my datas.
My main class is Patient.java:
#Entity
public class Patient {
private Long id;
private String numeroSs;
private String profession;
// lot of stuff...., getters, setters, ...
}
In order to improve my requests, I've made a very simple projection of my class Patient. I've called this projection PatientCustom :
public interface PatientCustom {
String getNumeroSs();
Timestamp getDateProchainRdv();
}
And in my repository "PatientRepo.java", I created two custom methods :
#Transactional
#RepositoryRestResource(collectionResourceRel = "patient", path = "patient")
public interface PatientRepo extends JpaRepository<Patient, Long> {
PatientCustom findOneByNumeroSs(String numeroSs);
Collection<PatientCustom> findByNumeroSs(String numeroSs);
}
When I use the first method, no problem : it returns a PatientCustom :
{
numeroSs: "150505617017002",
dateProchainRdv: null
}
But When I want a list or a collection, it sends me an error :
{
cause: null,
message: "Couldn't find PersistentEntity for type class com.sun.proxy.$Proxy165!"
}
And the traces in my terminal :
2019-03-12 10:32:34.671 ERROR 2072 --- [nio-8090-exec-2] o.s.d.r.w.RepositoryRestExceptionHandler : Couldn't find PersistentEntity for type class com.sun.proxy.$Proxy165!
java.lang.IllegalArgumentException: Couldn't find PersistentEntity for type class com.sun.proxy.$Proxy165!
at org.springframework.data.mapping.context.PersistentEntities.lambda$getRequiredPersistentEntity$2(PersistentEntities.java:96) ~[spring-data-commons-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at java.util.Optional.orElseThrow(Optional.java:290) ~[na:1.8.0_181]
at org.springframework.data.mapping.context.PersistentEntities.getRequiredPersistentEntity(PersistentEntities.java:95) ~[spring-data-commons-2.1.5.RELEASE.jar:2.1.5.RELEASE]
I understand very well that spring wants to add a proxy class to my interface, but when it want to add this proxy to a list or a collection, it's unhappy because list and collection are not interfaces but classses.
What can I do to solve this ?

Jackson java.util.Optional serialization does not include type ID

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;
}
}

Spring Data JPA native query result binding

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.

Resources