Any benefit of adding a read Query in a service class in Spring - spring

There is a code where there are single read query in a service class , does this have any effect in terms of performance/internal way hibernate runs the query ? As it's easier to just write the query in a facade/handler layer than a call the service class in read only ops
Run with hibernate here and PersonRepo is the envers class for the Person class annotated with #Entity
For eg)
#Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, rollbackFor = [Exception::class])
#Service
class PersonService(
private val personRepo: PersonRepo
) {
fun getPersonById(personId: Long): PersonEntity {
return this.personRepo.findById(personId)?: throw ValidationException("person $personId does not exist")
}
}
when writing this query in handler/non service layer when should we
call it from a service class
#Component
class PersonHandler (
private val personRepo: PersonRepo,
private val personService: personService,
private val someOtherHandler: SomeOtherHandler
) {
fun displayPersonAge(personId: Long) {
val person = this.personService.getPersonById(person)
someOtherHandler(person)
}
}
get the object directly
#Component
class PersonHandler (
private val personRepo: PersonRepo,
private val personService: personService,
private val someOtherHandler: SomeOtherHandler
) {
fun displayPersonAge(personId: Long) {
val person = this.personRepo.findById(personId)?: throw ValidationException("person $personId does not exist")
someOtherHandler(person)
}
}
since both are one line here in kotlin , does adding it to a serviceClass have any benefit to it ? if it is run in a transaction/not in run in a transaction are there any effects on other db transactions and query performance under load

Related

Spring test #Transactional with #Nested junit classes

I am encountering a problem in spring transactions: even though I see in the output that a transaction is created and rolled back after each test method; when I check the database content, it still keeps the document that was saved during the transaction.
I ran mongodb instance in replica mode following instructions from here https://www.mongodb.com/docs/manual/tutorial/deploy-replica-set-for-testing/.
My test class is defined as follows:
#Transactional
#AutoConfigureMockMvc
#ActiveProfiles(profiles = ["test"])
#DisplayName("Login Functionality Test")
#SpringBootTest(classes = [TestConfiguration::class])
class LoginFunctionalityTest #Autowired constructor(
private val mockMvc: MockMvc,
private val userRepository: UserRepository,
private val openUserRepository: OpenUserRepository,
private val patientRepository: PatientRepository,
private val passwordEncoder: PasswordEncoder,
private val jwtMatcher: JWTMatcher,
#Value("\${secret:sD1fRUWtBdfA8BNcbf}") private val fastLoginSecret: String
) {
private val existingUsersPassword = "SOME_PASSWORD"
private val userFastLoginTokenSecret = "ANY_SECRET"
private lateinit var existingUser: User
private lateinit var existingOpenUser: OpenUser
private lateinit var existingPatient: Patient
#BeforeEach
fun prepareContext() {
println("Init db")
existingUser = userRepository.save(User(null, now(), "test", "test", "test#test.test", passwordEncoder.encode(existingUsersPassword), UserAuthority.Patient, true, true))
existingOpenUser = openUserRepository.save(OpenUser(null, "ANY", nextExpiryDate()))
existingPatient = patientRepository.save(Patient(null, existingUser))
}
#DisplayName("Login Controller")
#Nested
inner class LoginControllerTest{
#Test
fun `should return unauthorized 401 status code when user logins with not existing email`() {
val invalidEmail = "INVALID_EMAIL"
assertThrows<EmptyResultDataAccessException> { userRepository.findByEmail(invalidEmail) }
mockMvc.post("/auth/login") {
param("username", invalidEmail)
param("password", existingUsersPassword)
}.andExpect {
jsonPath("$.status", equalToIgnoringCase(HttpStatus.UNAUTHORIZED.name))
jsonPath("$.error",StringContains(true,"Invalid login credentials"))
jsonPath("$.data", nullValue())
status { isUnauthorized() }
}
}
...
}
#ActiveProfiles(profiles = ["open-user"])
#DisplayName("Open User Login Controller")
#Nested
inner class OpenUserControllerLoginTest(){
#Test
fun `should return bad request 400 status when any of the required login parameters are missing`(){
val validFastLoginToken = String(Base64.getEncoder().encode(passwordEncoder.encode("$userFastLoginTokenSecret${existingUser.passwordHash}$fastLoginSecret").toByteArray()))
mockMvc
.post("/auth/login/fast") {
param("userId", existingUser.id!!)
param("secret", userFastLoginTokenSecret)
param("fastLoginToken", validFastLoginToken)
}.andExpect {
jsonPath("$.data.isUserActivated", equalTo(existingUser.activated))
jsonPath("$.data.accessToken",jwtMatcher.matches(existingUser))
jsonPath("$.status", equalToIgnoringCase(HttpStatus.OK.name))
jsonPath("$.error", nullValue())
status { isOk() }
content { contentType(MediaType.APPLICATION_JSON) }
}
}
...
}
The output in console has the "Init db" printed between creation and roll back of transaction.
It works correctly only if I move #ActiveProfiles from #Nested class to the outer class. Seems like #ActiveProfiles causes context reload but I don't know why this causes problems with transactions.

Spring Boot auto commit manuell transaction without call save on repository

Spring Boot tries to auto commit changes in manuell transaction for entity personTransaction1 without calling save method in repository. Changes in personTransaction1 are commited. The method manuellTransaction throw org.springframework.orm.ObjectOptimisticLockingFailureException. The same code with annotation based transaction handling in method transactionByAnnotation work as expected and no changes for variable personTransaction1 were commited. What is the reason why spring try to commit?
#SpringBootApplication
#EnableJpaAuditing
class DbOptimiticLockingApplication : CommandLineRunner {
#Autowired
private lateinit var personRepository: PersonRepository
#Autowired
private lateinit var platformTransactionManager: PlatformTransactionManager
override fun run(vararg args: String?) {
executeInNewTransaction {
personRepository.deleteAll()
}
val person = createPerson()
transactionByAnnotation(person.id)
manuellTransaction(person.id)
}
#Transactional
fun createPerson(): Person {
val person = Person()
person.name = "Max"
return personRepository.save(person)
}
#Transactional(Transactional.TxType.REQUIRES_NEW)
fun transactionByAnnotation(id: Long) {
var personTransaction1 = personRepository.findById(id).get()
// don't trigger commit
personTransaction1.name = "Tom"
transaction11ByAnnotation(personTransaction1.id)
transaction12ByAnnotation(personTransaction1.id)
}
#Transactional(Transactional.TxType.REQUIRES_NEW)
fun transaction11ByAnnotation(id: Long) {
businessLogic1(id)
}
#Transactional(Transactional.TxType.REQUIRES_NEW)
fun transaction12ByAnnotation(id: Long) {
businessLogic2(id)
}
fun manuellTransaction(id: Long) {
executeInNewTransaction {
var personTransaction1 = personRepository.findById(id).get()
// trigger commit
personTransaction1.name = "Tom"
manuellTransaction11(personTransaction1.id)
manuellTransaction12(personTransaction1.id)
}
}
fun manuellTransaction11(id: Long) {
executeInNewTransaction {
businessLogic1(id)
}
}
fun manuellTransaction12(id: Long) {
executeInNewTransaction {
businessLogic2(id)
}
}
private fun businessLogic1(id: Long) {
val person = personRepository.findById(id).get()
person.name = "Martin"
personRepository.saveAndFlush(person)
}
private fun businessLogic2(id: Long) {
val person = personRepository.findById(id).get()
person.name = "Joe"
personRepository.saveAndFlush(person)
}
private fun <T> executeInNewTransaction(action: () -> T): T {
val transactionTemplate = TransactionTemplate(platformTransactionManager)
transactionTemplate.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRES_NEW
return try {
transactionTemplate.execute {
action()
}!!
} catch (e: Exception) {
throw e
}
}
}
#Entity
#EntityListeners
class Person {
#Id
#GeneratedValue
var id: Long = 0L
#Column
var name: String = ""
#Version
var version: Long = 0L
}
interface PersonRepository : JpaRepository<Person, Long>
fun main(args: Array<String>) {
runApplication<DbOptimiticLockingApplication>(*args)
}
What is the reason why spring try to commit?
When an entity is read from Database it becomes for the JPA layer a persistent or otherwise called managed entity.
Entities on persistent/managed state are observed by the ORM vendor and any changes being done on them are passed in the database layer automatically.
Perquisite for this to happen is that the method where the entity is considered as persistent/managed finishes without any errors and the method belongs to a JPA transaction!
For the following quote that you describe, no it does not work as expected and the behavior that you observe and what you expect to happen is just a coincidence.
transactionByAnnotation work as expected and no changes for variable
personTransaction1 were commited
#Transactional(Transactional.TxType.REQUIRES_NEW)
fun transactionByAnnotation(id: Long) {
var personTransaction1 = personRepository.findById(id).get()
#Transactional(Transactional.TxType.REQUIRES_NEW) -> is completely ignored in this case that you mention here since the invocation of that method is from the same instance from method run, so the annotation is not considered at all. More info here in an old SO question
So since this method finishes wihout any errors but does not belong to any JPA transaction, the changes being done on managed entity are not passed automatically in Database.
If it belonged into a JPA transaction and the #Transactinal was not ignored because of self invocation it would have persisted the changes in Database.

Spring TransactionManager behavior with Spring Data and JpaRepository

I have a controller which does the following
A submit end point which save an entry in db and then call some external service asynchronously
Track the update of asynchronous call (this call updates an associated table) by watching the db and update the status of the entry created in step one
I was using the #Query Annotation to verify if step one entry exist in db and it was always returning empty. I tried changing it to the default spring method and it starts returning the inserted value.
I read about proxies, #Transactional and how non CRUD methods in a JPARepository are non transactional and tried few things like transaction propagation and self injection and even explicitly marking the repo method #Transactional. But none of them fixed the issue. Using spring data method solved it but I still don't understand what happened. Can someone help with an explanation of this behavior.
Basic code snippet is below
MyController
#RestController
public class MyController {
private final MyService myService;
private final MyRepository myRepository;
#Autowired
public MyController(MyService myService,
MyRepository myRepository) {
this.myService = myService;
this.myRepository = myRepository;
}
#PostMapping(value = "/submit")
public ResponseEntity<MyResponse> submit(#Valid #RequestBody MyRequest myRequest) {
return ResponseEntity
.accepted()
.body(MyResponse.success(myService.submit(myRequest), "SUBMITTED"));
}
/**
* This method is to update the status of the entry created by /submit endpoint
* if the asynchoronous process triggered by submit endpoint update an associated table
*/
#PostConstruct
private void trackUpdates() {
..
someObserver.subscribe(trackedAssociatedEntity -> {
myService.trackAndUpdateBasedOnAssociatedEntity(trackedAssociatedEntity);
});
}
}
MyService
#Service
#Transactional
public class MyService {
private final MyRepository myRepository;
#Autowired
public MyService(MyRepository myRepository) {
this.myRepository = myRepository;
}
submit(MyRequest myRequest) {
myRepository.save(myEntity);
//makes that asynchronous call
}
public void trackAndUpdateBasedOnAssociatedEntity(#NotNull MyAssociatedEntity myassociatedEntity) {
// This commented call always return empty but the uncommented code works as expected
// List<MyEntity> existingEntity =
// myRepository.findEntityByField1AndField2(myassociatedEntity.getField1(),myassociatedEntity.getField2());
List<MyEntity> existingEntities =
myRepository.findByField1AndField2(myassociatedEntity.getField1(),myassociatedEntity.getField2());
if(existingEntities.isEmpty()){
//create new
}else{
//update
}
}
}
}
}
MyRepository
#Repository
public interface MyRepository extends JpaRepository<MyEntity, Long> {
#Query("SELECT e FROM MyEntity e WHERE e.field1 = ':field1' and e.field2 = ':field2' ")
List<MyEntity> findEntityByField1AndField2(String field1, String field2);
List<MyEntity> findByField1AndField2(String field1, String field2);
}
I believe that '' are not needed. Please try the following:
#Repository
public interface MyRepository extends JpaRepository<MyEntity, Long> {
#Query("SELECT e FROM MyEntity e WHERE e.field1 = :field1 and e.field2 = :field2")
List<MyEntity> findEntityByField1AndField2(String field1, String field2);
List<MyEntity> findByField1AndField2(String field1, String field2);
}

How to initialize variables in parent abstract class of spring bean using Kotlin?

I have the next structure of spring beans
abstract class GenericRepository<T> {
private val FIND_BY_ID_SQL = "SELECT * FROM ${this.getTableName()} WHERE id = ?"
abstract fun getTableName(): String
abstract fun jdbcTemplate(): JdbcTemplate
abstract fun getMapper(): RowMapper<T>
fun find(id: Long): T? {
return jdbcTemplate().queryForObject(FIND_BY_ID_SQL, arrayOf(id), getMapper())
}
}
User repository
#Repository
class UserRepository(
#Autowired
private val jdbcTemplate: JdbcTemplate
) : GenericRepository<User>() {
companion object {
private const val INSERT_SQL = "INSERT INTO \"user\"(name, age) VALUES (?,?)"
}
private class LogMapper : RowMapper<User> {
override fun mapRow(rs: ResultSet, rowNum: Int): User? {
return User(
id = rs.getLong("id"),
name = rs.getString("name"),
age = rs.getInt("operation")
)
}
}
override fun getTableName(): String {
return "user"
}
override fun jdbcTemplate(): JdbcTemplate {
return jdbcTemplate
}
override fun getMapper(): RowMapper<User> {
return LogMapper()
}
}
The problem when Spring creates proxy and creates bean of UserRepository it doesn't initialize FIND_BY_ID_SQL leaving it null.
The question: how usign abstract class make spring initialize FIND_BY_ID_SQL variable?
UPD
I used #Component instead of #Repository and the problem was solved. FIND_BY_ID_SQL is not null anymore.
You could work around the problem by making it lazy:
private val FIND_BY_ID_SQL by lazy { "SELECT * FROM ${this.getTableName()} WHERE id = ?" }
However, you should first be sure it's an actual problem (e.g. that when you call find you get an exception), because the proxy might simply delegate to a "real" UserRepository with non-null FIND_BY_ID_SQL (and jdbcTemplate etc.), depending on Spring's internal details.
In addition, you need to be careful when your superclass properties are initialized depending on subclass; I think your exact situation should work, but I'd prefer to write it as
abstract class GenericRepository<T>(val tableName: String) {
private val FIND_BY_ID_SQL = "SELECT * FROM ${tableName} WHERE id = ?"
abstract val jdbcTemplate: JdbcTemplate
abstract val mapper: RowMapper<T>
fun find(id: Long): T? {
return jdbcTemplate.queryForObject(FIND_BY_ID_SQL, arrayOf(id), mapper)
}
}
#Repository
class UserRepository(
#Autowired
override val jdbcTemplate: JdbcTemplate
) : GenericRepository<User>("user") { ... }

Spring-Data-JPA atomic insert with dependencies

I want to insert Entities to a database from a scalable microservice. I tried #Lock(LockModeType.PESSIMISTIC_WRITE) to prevent from doubled entries. The problem is, I have dependencies on my Entities.
A basic example is:
TestEntity.java
public class TestEntity {
#GeneratedValue()
#Id
private Long id;
private String string;
#ManyToOne
private TestEntityParent testEntityParent;
}
TestEntityParent.java
public class TestEntityParent {
#GeneratedValue()
#Id
private Long id;
private String stringTwo;
#OneToMany(mappedBy = "testEntityParent")
private List<TestEntity> testEntities;
}
TestEnityRepository.java
public interface TestEnityRepository extends JpaRepository<TestEntity,Long> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
TestEntity saveAndFlush(TestEntity testEntity);
Optional<TestEntity> findByStringAndTestEntityParentStringTwo(String string, String stringTwo);
}
TestEntityParentRepository.java
public interface TestEntityParentRepository extends JpaRepository<TestEntityParent, Long> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
TestEntityParent save(TestEntityParent testEntityParent);
Optional<TestEntityParent> findByStringTwo(String stringTwo);
}
AtomicDbService.java
#Service
public class AtomicDbService {
#Autowired
TestEnityRepository testEnityRepository;
#Autowired
TestEntityParentRepository testEntityParentRepository;
#Transactional
public TestEntity atomicInsert(TestEntity testEntity) {
TestEntityParent testEntityParent = testEntityParentRepository.findByStringTwo(testEntity.getTestEntityParent().getStringTwo())
.orElse(testEntityParentRepository.save(testEntity.getTestEntityParent()));
return testEnityRepository.findByStringAndTestEntityParentStringTwo(
testEntity.getString(), testEntity.getTestEntityParent().getStringTwo()
).orElse(testEnityRepository
.save(
TestEntity.builder()
.string(testEntity.getString())
.testEntityParent(testEntityParent)
.build()
)
);
}
}
My test case:
#Test
#Transactional
public void testAtomicInsert(){
TestEntityParent testEntityParent = TestEntityParent.builder().stringTwo("testTwo").build();
TestEntity testEntity = TestEntity.builder().string("test").testEntityParent(testEntityParent).build();
atomicDbService.atomicInsert(testEntity);
System.out.println(testEnityRepository.findAll());
atomicDbService.atomicInsert(testEntity);
System.out.println(testEnityRepository.findAll());
atomicDbService.atomicInsert(testEntity);
System.out.println(testEnityRepository.findAll());
System.out.println(testEnityRepository.findAll());
}
I get the following answer:
[TestEntity(id=2, string=test, testEntityParent=TestEntityParent(id=1, stringTwo=testTwo, testEntities=null))]
[TestEntity(id=2, string=test, testEntityParent=TestEntityParent(id=1, stringTwo=testTwo, testEntities=null)), TestEntity(id=3, string=test, testEntityParent=TestEntityParent(id=1, stringTwo=testTwo, testEntities=null))]
and an error:
query did not return a unique result: 2;
Without dependencies everything works fine.
UPDATE:
Adding #Lock(LockModeType.PESSIMISTIC_WRITE) to the find method leads to
Feature not supported: "MVCC=TRUE && FOR UPDATE && JOIN"; SQL statement:
... same applies to
#Lock(LockModeType.PESSIMISTIC_WRITE)
#Query("SELECT e from TestEntity e join e.testEntityParent p where e.string = :string and p.stringTwo = :stringTwo ")
Optional<TestEntity> findWhatever(#Param("string") String string, #Param("stringTwo") String stringTwo);
... since for update is always generated.
Apparently, it was a stupid mistake I needed to replace orElse with orElseGet and a lambda and everything worked, even without all those #Lock, etc - tricks.
Still I don't understand what exactly went wrong with the transactions and why.

Resources