Lateinit property not intialized "FindByIndexNameSessionRepository" - spring

I am particularly new to Spring Boot and Hibernate, I have some trouble with lateinit in the code below.
To give you a simple overview, I am implementing a simple Hibernate interceptor for onSave. So every time the Admin modifies a certain User in db and saves it. The onSave event will be triggered. Then basically validates if the entity is of type user then log the entity, and find the entity.username in the SessionRepository if found log the id.
Everytime I process this.sessions.findByPrincipalName(user).values in searchByPrincipalName I have an exception that sessions has not been initialized.
My understanding of lateinit is that, at object usage it will be initialized.
Maybe I am missing something? or I misunderstood the concept?
Code:
#Component
class UserChangeInterceptor: EmptyInterceptor() {
#Autowired
private lateinit var sessions: FindByIndexNameSessionRepository<out Session>
val logger = loggerFor<UserChangeInterceptor>()
override fun onSave(
entity: Any?,
id: Serializable?,
state: Array<out Any>?,
propertyNames: Array<out String>?,
types: Array<out Type>?
): Boolean {
if (entity is User) {
logger.info("UserChangeInterceptor::onSave $entity")
searchByPrincipalName(entity.username).forEach {
logger.info("UserChangeInterceptor::onSave -> ${it.id}")
}
}
return super.onSave(entity, id, state, propertyNames, types)
}
private fun searchByPrincipalName(user: String): MutableCollection<out Session> {
return this.sessions.findByPrincipalName(user).values
}
}

Related

Testing Kotlin Extension Functions with Spring

I have a controller that gets a specific service based on a customer name:
#RestController
class BasicController(){
#Autowired
private lateinit var services: List<BasicService<*>>
private var service: BasicService<*>? = null
#GetMapping("/{customer}")
fun getAll(#PathVariable customer: String): ResponseEntity<String>{
service = services.getServiceByCustomer(customer)
/... code w/return value .../
}
}
I have a file Extensions.kt with the following:
fun <T: BasicService> List<T>.getServiceByCustomer(customer: String): T?{
return this.find{
it::class.simpleName?.contains(customer, ignoreCase = true) == true
}
}
Is it possible to return a mock of service when services.getServiceByCustomer is called similar to `when`(mock.function(anyString())).thenReturn(value)?
I've tried using mockK with the following:
mockkStatic("path.to.ExtensionsKt")
every {listOf(service).getServiceByCustomer)} returns service
But I don't think I'm using that properly... I'm currently using com.nhaarman.mockitokotlin2 but have tried io.mockk
You just need to use a customer that actually matches the simple name of the mocked service. You don't need or even should mock the extension function. Try the following:
class BasicControllerTest {
#MockK
private lateinit var basicService: BasicService
private lateinit var basicController: BasicController
#BeforeEach
fun setUp() {
clearAllMocks()
basicController = BasicController(listOf(basicService))
}
}
Additionally, consider using constructor injection instead of field injection:
#RestController
class BasicController(private val services: List<BasicService<*>>){
private var service: BasicService<*>? = null
#GetMapping("/{customer}")
fun getAll(#PathVariable customer: String): ResponseEntity<String>{
service = services.getServiceByCustomer(customer)
/... code w/return value .../
}
}
Finally, consider testing the Controllers with #WebMvcTest instead of regular unit tests. Check more info here https://www.baeldung.com/spring-boot-testing#unit-testing-with-webmvctest.

How to hook all successfull inserts, updates and deletions in a spring data repository

I am currently trying to build hooks that get a list of all successfully commited entity changes of a repository:
#Entity data class User(#Id val id: Long, val name: String)
interface extends JpaRepository<User, Long>
#Service
class UserService(val userRepository:UserRepository){
#Transactional
fun someProcess(){
val newUser = User()
newUser.name = "newUser"
userRepository.save(newUser)
val userToUpdate = userRepository.findById(1).get()
userToUpdate.name = "updatedUser"
val userToDelete = userRepository.findById(2).get()
userRepository.delete(userToDelete)
}
// TODO implement hooks
fun hookAllInserts(inserts :List<User>){
// list is expected to contain newUser
}
fun hookAllUpdates(updates: List<User>) {
// list is expected to contain the latest state of updatedUser
}
fun hookAllDeletions(deletions: List<User>){
// list is expected to contain the deletedUser
}
}
The hooks should only be triggered after a successful commit, so changes that are rolled back are not propagated.
How can I achieve this with spring data?
I have not found a general solution for all spring data repositories,
however there is a solution for when one uses spring data in conjunction with hibernate:
#Component
class ChangeListener(
private val entityManagerFactory: EntityManagerFactory,
) : PostUpdateEventListener, PostInsertEventListener, PostDeleteEventListener {
#PostConstruct
private fun init() {
val sessionFactory = entityManagerFactory.unwrap(SessionFactoryImpl::class.java)
val registry = sessionFactory.serviceRegistry.getService(EventListenerRegistry::class.java)
registry.getEventListenerGroup(EventType.POST_COMMIT_UPDATE).appendListener(this)
registry.getEventListenerGroup(EventType.POST_COMMIT_INSERT).appendListener(this)
registry.getEventListenerGroup(EventType.POST_COMMIT_DELETE).appendListener(this)
}
override fun requiresPostCommitHanding(persister: EntityPersister): Boolean {
return true
}
override fun onPostUpdate(event: PostUpdateEvent) {
// event.entity contains the latest state of the entity
}
override fun onPostDelete(event: PostDeleteEvent) {
// event.entity contains the latest state of the entity
}
override fun onPostInsert(event: PostInsertEvent) {
// event.entity contains the last state of the entity
}
}
It is based on Hibernate EventListeners
See also here.
There is also a nice post on Vlad Mihalcea's blog

Why does Spring Data MongoDB not expose events for update…(…) methods?

It appears that the update for mongoOperations do not trigger the events in AbstractMongoEventListener.
This post indicates that was at least the case in Nov 2014
Is there currently any way to listen to update events like below? This seems to be quite a big omission if it is the case.
MongoTemplate.updateMulti()
Thanks!
This is no oversight. Events are designed around the lifecycle of a domain object or a document at least, which means they usually contain an instance of the domain object you're interested in.
Updates on the other hand are completely handled in the database. So there are no documents or even domain objects handled in MongoTemplate. Consider this basically the same way JPA #EntityListeners are only triggered for entities that are loaded into the persistence context in the first place, but not triggered when a query is executed as the execution of the query is happening in the database.
I know it's too late to answer this Question, I have the same situation with MongoTemplate.findAndModify method and the reason I needed events is for Auditing purpose. here is what i did.
1.EventPublisher (which is ofc MongoTemplate's methods)
public class CustomMongoTemplate extends MongoTemplate {
private ApplicationEventPublisher applicationEventPublisher;
#Autowired
public void setApplicationEventPublisher(ApplicationEventPublisher
applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
//Default Constructor here
#Override
public <T> T findAndModify(Query query, Update update, Class<T> entityClass) {
T result = super.findAndModify(query, update, entityClass);
//Publishing Custom Event on findAndModify
if(result!=null && result instanceof Parent)//All of my Domain class extends Parent
this.applicationEventPublisher.publishEvent(new AfterFindAndModify
(this,((Parent)result).getId(),
result.getClass().toString())
);
return result;
} }
2.Application Event
public class AfterFindAndModify extends ApplicationEvent {
private DocumentAuditLog documentAuditLog;
public AfterFindAndModify(Object source, String documentId,
String documentObject) {
super(source);
this.documentAuditLog = new DocumentAuditLog(documentId,
documentObject,new Date(),"UPDATE");
}
public DocumentAuditLog getDocumentAuditLog() {
return documentAuditLog;
}
}
3.Application Listener
public class FindandUpdateMongoEventListner implements ApplicationListener<AfterFindAndModify> {
#Autowired
MongoOperations mongoOperations;
#Override
public void onApplicationEvent(AfterFindAndModify event) {
mongoOperations.save(event.getDocumentAuditLog());
}
}
and then
#Configuration
#EnableMongoRepositories(basePackages = "my.pkg")
#ComponentScan(basePackages = {"my.pkg"})
public class MongoConfig extends AbstractMongoConfiguration {
//.....
#Bean
public FindandUpdateMongoEventListner findandUpdateMongoEventListner(){
return new FindandUpdateMongoEventListner();
}
}
You can listen to database changes, even the changes completely outside your program (MongoDB 4.2 and newer).
(code is in kotlin language. same for java)
#Autowired private lateinit var op: MongoTemplate
#PostConstruct
fun listenOnExternalChanges() {
Thread {
op.getCollection("Item").watch().onEach {
if(it.updateDescription.updatedFields.containsKey("name")) {
println("name changed on a document: ${it.updateDescription.updatedFields["name"]}")
}
}
}.start()
}
This code only works when replication is enabled. You can enable it even when you have a single node:
Add the following replica set details to mongodb.conf (/etc/mongodb.conf or /usr/local/etc/mongod.conf or C:\Program Files\MongoDB\Server\4.0\bin\mongod.cfg) file
replication:
replSetName: "local"
Restart mongo service, Then open mongo console and run this command:
rs.initiate()

How to define Spring Data Repository scope to Prototype?

I'm using Spring data jpa & hibernate for data access along with Spring boot. All the repository beans are singleton by default. I want to define the scope of all my repositories to Prototype. How can I do that?
#Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}
Edit 1
The problem is related to domain object being shared in 2 different transactions which is causing my code to fail. I thought it is happening because repository beans are singleton. That's the reason I asked the question. Here is the detailed explanation of the scenario.
I have 2 entities User and UserSkill. User has 1-* relationship with UserSkills with lazy loading enabled on UserSkill relation.
In a UserAggregationService, I first make a call to fetch an individual user skill by id 123 which belongs to user with id 1.
public class UserAggregationService {
public List<Object> getAggregatedResults() {
resultList.add(userSkillService.getUserSkill(123));
//Throws Null Pointer Exception. See below for more details.
resultList.add(userService.get(1));
}
}
Implementation of UserSkillService method looks like
#Override
public UserSkillDTO getUserSkill(String id) {
UserSkill userSkill = userSkillService.get(id);
//Skills set to null avoid recursive DTO mapping. Dozer mapper is used
//for mapping.
userSkill.getUser().setSkills(null);
UserSkillDTO result = mapper.map(userSkill, UserSkillDTO.class);
return result;
}
In the call of user aggregation service, I call UserService to fetch userDetails. UserService code looks like
#Override
public UserDTO getById(String id) {
User user = userService.getByGuid(id);
List<UserSkillDTO> userSkillList = Lists.newArrayList();
//user.getSkills throws null pointer exception.
for (UserSkill uSkill : user.getSkills()) {
//Code emitted
}
....
//code removed for conciseness
return userDTO;
}
UserSkillService method implementation
public class UserSkillService {
#Override
#Transactional(propagation = Propagation.SUPPORTS)
public UserSkill get(String guid) throws PostNotFoundException {
UserSkill skill = userSkillRepository.findByGuid(guid);
if (skill == null) {
throw new SkillNotFoundException(guid);
}
return skill;
}
}
UserService method implementation:
public class UserService {
#Override
#Transactional(readOnly = true)
public User getByGuid(String guid) throws UserNotFoundException {
User user = userRepo.findByGuid(guid);
if (user == null) {
throw new UserNotFoundException(guid);
}
return user;
}
}
Spring boot auto configuration is used to instantiate entity manager factory and transaction manager. In the configuration file spring.jpa.* keys are used to connect to the database.
If I comment the below line of code, then I do not get the exception. I am unable to understand why change in the domain object is being affecting the object fetch in a different transaction.
userSkill.getUser().setSkills(null);
Please suggest If I have missed something.

Retrieving data context changes with Spring Data JPA

In my application, I need to retrieve the lists of new, updated and removed entities per each transaction. Like this:
// useful functionality
#Transactional
public void createNewBlogPost(int userId, String title, String text) {
Post post = new Post();
post.title = title; // "hello"
post.text = text; // "there"
postRepository.save(post);
// more work with JPA repositories here
}
...
// gets called right after createNewBlogPost()
public void onTransaction(UnitOfWork uow) {
List<?> newEntities = uow.getNewEntities();
assertEquals(1, newEntities.size()); // 1 new entity
Object firstNewEntity = newEntities.get(0);
assertTrue(firstNewEntity instanceof Post); // this new entity
// is a Post
Post newPost = (Post)firstNewEntity;
assertEquals("hello", newPost.title);
assertEquals("there", newPost.text);
}
The most relevant thing I managed to find was an audit functionality that Spring provides with annotations like #CreatedBy, #CreatedDate, #LastModifiedBy, #LastModifiedDate. Though it's technically very close, yet it's not exactly what I want to achieve.
Does Spring Data JPA provide a mechanism to retrieve data changes per every single transaction?
Since your use case is Hibernate and JPA specific, you should take a look at Hibernate Envers and Spring Data Envers. They might give you some ideas, but be careful re: the projects themselves, I'm not sure if they're active.
I've spent some time for the research and managed to find a relatively straightforward Hibernate-specific solution. There are basically 2 problems to resolve:
Intercept data change events.
Do it on a per-request basis.
To address p.1, one can use EventListenerRegistry. Here's an example:
#Component
public class HibernateListenersConfigurer {
#Autowired
private EntityManagerFactory entityManagerFactory;
#Autowired
private HibernateListeners hibernateListeners;
#PostConstruct
public void init() {
HibernateEntityManagerFactory hibernateEntityManagerFactory =
(HibernateEntityManagerFactory)entityManagerFactory;
SessionFactoryImpl sessionFactoryImpl =
(SessionFactoryImpl)hibernateEntityManagerFactory.getSessionFactory();
EventListenerRegistry eventListenerRegistry = sessionFactoryImpl.
getServiceRegistry().
getService(EventListenerRegistry.class);
eventListenerRegistry.appendListeners(EventType.PRE_INSERT, hibernateListeners);
eventListenerRegistry.appendListeners(EventType.PRE_UPDATE, hibernateListeners);
eventListenerRegistry.appendListeners(EventType.PRE_DELETE, hibernateListeners);
}
}
hibernateListeners object gets all these events and can do whatever required to audit them. Here's an example:
#Component
public class HibernateListeners implements
PreInsertEventListener,
PreUpdateEventListener,
PreDeleteEventListener {
#Autowired
private ChangeTracker changeTracker;
#Override
public boolean onPreInsert(PreInsertEvent event) {
// event has a bunch of relevant details
changeTracker.trackChange(event);
return false;
}
...other listeners here...
Then, to address p.2, changeTracker seen above is a request-scoped bean:
#Component
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ChangeTracker {
// a sort of "Unit of Work"
private List<Change> changes = new ArrayList<Change>();
public void trackChange(PreInsertEvent event) {
changes.add(makeChangeFromEvent(event));
}
public void handleChanges() {
// Do whatever needed :-)
}
}
Then, there are few options available to finally call handleChanges() once request processing is complete: call it manually, use HandlerInterceptor, use filter, use AOP. HandlerInterceptors and filters, are not as great, because in my case they were getting called after response has already been sent to the client, this caused inconsistency between "business data" and "changes data". I eventually switched to AOP and it seems to work just fine.
Here's a playground: https://github.com/loki2302/spring-change-tracking-experiment

Resources