Can't Autowire #Repository interface in Spring Boot - spring-boot

The problem appeared when I tried to migrate on WebFlux.
I have one package university. It contains 4 files: Document, Controller, Service and Repository.
#Document
data class University(
#Id
#JsonSerialize(using = ToStringSerializer::class)
var id: ObjectId?,
var name: String,
var city: String,
var yearOfFoundation: Int,
#JsonSerialize(using = ToStringSerializer::class)
var students: MutableList<ObjectId> = mutableListOf(),
#Version
var version: Int?
)
#Service
class UniversityService(#Autowired private var universityRepository: UniversityRepository) {
fun getAllUniversities(): Flux<University> =
universityRepository.findAll()
fun getUniversityById(id: ObjectId): Mono<University> =
universityRepository.findById(id)
}
#RestController
#RequestMapping("api/universities", consumes = [MediaType.APPLICATION_NDJSON_VALUE])
class UniversityController(#Autowired val universityService: UniversityService) {
#GetMapping("/all")
fun getAll(): Flux<University> =
universityService.getAllUniversities().log()
#GetMapping("/getById")
fun getUniversityById(#RequestParam("id") id: ObjectId): Mono<University> =
universityService.getUniversityById(id)
}
#Repository
interface UniversityRepository: ReactiveMongoRepository<University, ObjectId>, CustomUniversityRepository {
fun existsByNameIgnoreCase(name: String): Mono<Boolean>
fun removeUniversityById(id: ObjectId): Mono<University?>
fun findUniversitiesByNameIgnoreCase(name: String): Flux<University>
}
All in separate files regarding their names.
I am getting a problem with my service, cause it cannot find repository. Consider defining a bean of type 'demo.university.UniversityRepository' in your configuration. But my repository file with exact name and interface is directly there.
I've tried to mark my repository with Bean annotation, but I can't do so with interfaces. Also, #EnableJpaRepositories does not help.
P.S. I know, it seems like a duplicate, but I really didn't find an answer in previous questions.

My problem was in a wrong project dependencies. As I mentioned, I migrated from simple Web to WebFlux. But I didn't change my MongoDB dependency. It should be marked as a reactive explicitly even if ReactiveMongoRepository interface is found correctly.
implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive:2.7.1")

Related

Neo4J (spring boot) with sealed classes in kotlin

I'm trying to create a somewhat generic implementation using kotlin and neo4j. My idea right now is that I want to have a GeoNode that can point to any kind of GeoJson feature (e.g. "Feature" or "FeatureCollection")
I tried to do this using kotlins sealed classes, e.g.
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
#JsonSubTypes(
JsonSubTypes.Type(value = GeoJsonFeature::class, name = "GeoJsonFeature")
)
#Node
sealed class FeatureContract(
#GeneratedValue #Id var id: Long? = null,
#Version var version: Long? = null
) {
companion object {
#JsonCreator
#JvmStatic
private fun creator(name: String): FeatureContract? {
return FeatureContract::class.sealedSubclasses.firstOrNull {
it.simpleName == name
}?.objectInstance
}
}
}
data class GeoJsonFeature(
val geometry: GeometryContract,
) : FeatureContract()
data class GeoJsonFeatureCollection(
val features: List<GeoJsonFeature>,
) : FeatureContract()
// and the "GeoNode" that holds this
#Node
data class GeoNode(
#Id val id: String,
#Version var version: Long? = null
#Relationship(type = "feature") var feature: FeatureContract?, // Should be either a featureCollection or a feature,
)
The idea is that I can have a point on a map that points to any kind of GeoJson.
It seems I am successful in serializing this and getting it into the Neo4J-db on write, however, on reading I get
Failed to instantiate [FeatureContract]: Is it an abstract class?; nested exception is java.lang.InstantiationException
The Jackson annotations are there cause I hoped they would help me (that Neo4J-OGM was using it under the hood) but it doesn't seem to have done the trick. I've read about Neo4JEntityConverters but I haven't understood how one can to this for a full object like this. Is there any good way to use sealed classes in kotlin with the neo4j-OGM for both serialization and deserialization?
Using spring boot 2.4.4 and spring-boot-starter-data-neo4j

How to tell Spring Data MongoDB to store nested fields of a document that are also documents in their own collection?

I have two collections called persons and addresses. The idea is to have person hold an address in the field address. I use Spring Data MongoDB to persist those mentioned documents.
My usual way of crafting the "relation" between Person > Address was to store the ID of the address and give it to the person object. Later when I find() a person I resolve the address object by it's id and voila I have my person + address.
However I find this somewhat every cumbersome since in my code I just want to add the Address object as whole and not only it's ID so I can work with it while also saving it to the repository at any point of time.
I therefore started a little unit test to see how Spring Data MongoDB saves the Address object if it's just a field of Person and is not saved by it's own Repository.
This is what I came up with:
import org.springframework.data.mongodb.core.mapping.Document
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.stereotype.Repository
#Document("person")
data class Person(
val id: String,
val name: String,
val age: Int,
var address: Address
)
#Document("addresses")
data class Address(
val id: String,
val street: String?,
val number: Int?
)
#Repository
interface PersonRepository : MongoRepository<Person, String>
#Repository
interface AddressRepository : MongoRepository<Address, String>
And this is the unit test - that fails with the last steps as I was expecting:
internal class FooTest #Autowired constructor(
private val personRepository: PersonRepository,
private val addressRepository: AddressRepository
) {
#Test
fun `some experiment`() {
val testPerson = Person("001", "Peter", 25, Address("011","Lumberbumber", 12))
personRepository.save(testPerson)
val person = personRepository.findAll()[0]
assertThat(person).isNotNull
assertThat(person.address).isNotNull
assertThat(person.address.street).isEqualTo("Lumberbumber")
assertThat(person.address.number).isEqualTo(12)
// works because address was just copied into the object structure
// of `person` and was not seen as a standalone document
val address = addressRepository.findAll()[0]
assertThat(address.street).isEqualTo("Lumberbumber") // fails
assertThat(address.number).isEqualTo(12) // fails
// As expected `address` was not persisted alongside the `person` document.
}
}
So I thought about using AbstractMongoEventListener<Person> to intercept the saving process and pick the Address object out from Person here and do a addressRepository.save(addressDocument) while putting a lightweight address object (only having the ID) back in the Person document.
The same I'd to in the reverse when doing a find for Person and assembling Person and Address together again.
#Component
class MongoSaveInterceptor(
val addressRepository: AddressRepository
) : AbstractMongoEventListener<Person>() {
override fun onBeforeConvert(event: BeforeConvertEvent<Person>) {
val personToSave = event.source
val extractedAddress = personToSave.address
val idOfAddress = addressRepository.save(extractedAddress).id
personToSave.address = Address(idOfAddress, null, null)
}
override fun onAfterConvert(event: AfterConvertEvent<Person>) {
val person = event.source
val idOfAddress = person.address.id
val foundAddress = addressRepository.findById(idOfAddress)
foundAddress.ifPresent {
person.address = it
}
}
}
It works that way and might be a workaround solution for my requirement.
BUT
I feel that there has to be something like that already working and I might just need to find the proper configuration for that.
That's where I am stuck atm and need some guidance.
Another research showed me it's about #DBRef (https://www.baeldung.com/cascading-with-dbref-and-lifecycle-events-in-spring-data-mongodb) I have to use. This way Spring Data MongoDB stores the embedded document class and resolves it when loading the parent document object from the database.

Class based projections using a DTO in Spring Data Jpa is not working

In my Spring Boot application, I am trying to implement a class based projection using DTOs as described at:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections.dtos
I have a domain class that looks like:
#Entity
#Table(name="metadatavalue")
public class MetadataValue {
#Id
#Column(name="metadata_value_id")
private Integer metadataValueId;
#Column(name="resource_id", insertable=false, updatable=false)
private Integer resourceId;
#Column(name="metadata_field_id", insertable=false, updatable=false)
private Integer metadataFieldId;
#Column(name="text_value")
private String textValue;
more class members, getters and setters, etc follow.
I also have a simple DTO class with one member:
public class MetadataDTO {
private String textValue;
public MetadataDTO(String textValue) {
this.textValue = textValue;
}
public String getTextValue() {
return textValue;
}
}
My repository class is:
public interface MetadataValueRepository extends CrudRepository<MetadataValue, Integer> {
#Query("SELECT m from MetadataValue m WHERE m.handle.handle = :handle AND m.resourceTypeId=m.handle.resourceTypeId")
List<MetadataValue> findAllByHandle(#Param("handle") String handle);
#Query("SELECT new path.to.my.package.domain.MetadataDTO(m.textValue AS textValue) FROM MetadataValue m "
+ "WHERE m.handle.handle = :handle AND m.resourceTypeId=m.handle.resourceTypeId")
List<MetadataDTO> findAllByHandleDTO(#Param("handle") String handle);
}
The first method, findAllByHandle works as expected, but when running the second method, findAllByHandleDTO, which I had hoped to return the projection, my application throws the error:
java.lang.IllegalArgumentException: Couldn't find PersistentEntity for type class path.to.my.package.domain.MetadataDTO!
I have also tried extending from JpaRepository with the same result. In another attempt, I tried using an Interface based projection which resulted in an almost identical stacktrace, with an internal class substituted for my class.
My project is spring-boot 2.3.0 with Spring Web, Spring Data JPA, Rest Repositories, and PostgreSQL Driver.
Can anybody help me understand what I am doing wrong and set me in the right direction?
Thanks so much!
Update on this question: (I have added the tag spring-data-rest). Experimenting, I have learned that I can successfully call my method, for instance, in the run() method of my application class. It's only when I call the method as a rest endpoint that I see the error. Is this simply an issue of incompatibility with Spring Data Rest?

Spring Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

I can't understand, what's wrong with my Service. I receive org.hibernate.StaleObjectStateException trying to run this method:
fun updateNameForPhone(phone: String, name: String): Client {
val res = clientRepository.findByPhone(phone) ?: throw ClientNotFoundException(phone)
res.name = name
return clientRepository.save(res)
}
ClientRepository:
#Repository
interface ClientRepository : JpaRepository<Client, UUID> {
fun findByPhone(phone: String): Client?
}
Client entity:
#Entity
data class Client(
var name: String = "",
var phone: String = "",
#Id #GeneratedValue(strategy = GenerationType.AUTO)
val uuid: UUID = defaultUuid()
)
Exception:
Object of class [com.app.modules.client.domain.Client] with identifier
[12647903-7773-4f07-87a8-e9f86e99aab3]: optimistic locking failed;
nested exception is org.hibernate.StaleObjectStateException: Row was
updated or deleted by another transaction (or unsaved-value mapping
was incorrect) :
[com.app.modules.client.domain.Client#12647903-7773-4f07-87a8-e9f86e99aab3]"
What is the reason?
I'm using Kotlin 1.3.11, Spring Boot 2.1.1, MySql. I don't run it in different threads, just trying with single request.
Well, finally I've found a solution. Better say workaround.
The problem is in the way spring uses UUID as entity identifier.
So there are two workarounds, solving this issue:
first, you can change your id field type to other one, such as Long, for example, if it's possible to you;
or you can add this annotation to your uuid field: #Column(columnDefinition = "BINARY(16)").
The last solution I've found from this question.

Kotlin & Spring (Data): custom setters

I currently am working on a project that uses Spring Boot, written in Kotlin. It has got to be mentioned that I am still relatively new to Kotlin, coming from Java. I have a small database consisting of a single table that is used to look up files. In this database, I store the path of the file (that for this testing purpose is simply stored in the the resources of the project).
The object in question looks as follows:
#Entity
#Table(name = "NOTE_FILE")
class NoteFile {
#Id
#GeneratedValue
#Column(name = "id")
var id: Int
#Column(name = "note")
#Enumerated(EnumType.STRING)
var note: Note
#Column(name = "instrument")
var instrument: String
#Column(name = "file_name")
var fileName: String
set(fileName) {
field = fileName
try {
file = ClassPathResource(fileName).file
} catch (ignored: Exception) {
}
}
#Transient
var file: File? = null
private set
constructor(id: Int, instrument: String, note: Note, fileName: String) {
this.id = id
this.instrument = instrument
this.note = note
this.fileName = fileName
}
}
I have created the following repository for retrieving this object from the database:
#Repository
interface NoteFileRepository : CrudRepository<NoteFile, Int>
And the following service:
#Service
class NoteFileService #Autowired constructor(private val noteFileRepository: NoteFileRepository) {
fun getNoteFile(id: Int): NoteFile? {
return noteFileRepository.findById(id).orElse(null)
}
}
The problem that I have is when I call the getNoteFile function, neither the constructor nor the setter of the constructed NoteFile object are called. As a result of this, the file field stays null, whereas I expect it to contain a value. A workaround this problem is to set the fileName field with the value of that field, but this looks weird and is bound to cause problems:
val noteFile: NoteFile? = noteFileService.getNoteFile(id)
noteFile.fileName = noteFile.fileName
This way, the setter is called and the file field gets the correct value. But this is not the way to go, as mentioned above. The cause here could be that with the Spring Data framework, a default constructor is necessary. I am using the necessary Maven plugins described here to get Kotlin and JPA to work together to begin with.
Is there some way that the constructor and/or the setter does get called when the object is constructed by the Spring (Data) / JPA framework? Or maybe should I explicitly call the setter of fileName in the service that retrieves the object? Or is the best course of action here to remove the file field as a whole and simply turn it into a function that fetches the file and returns it like that?

Resources