Spring Hibernate - #Transactional between different transactions - spring

I'm creating a test and basically doing different transactions inside a #Transactional method.
I add a Project, then add a Task to it, and last will fetch the project again from DB to test it has the task saved.
Please note the case I'm showing is a unit test but I'm more interesting in fixing the transactional methods and not the test itself as I already had this in the past in "production code".
Model Classes:
#Entity
#Table(name = "Task")
data class Task(
#Id
#SequenceGenerator(name = "TaskSeq", sequenceName = "TaskSeq", initialValue = 100)
#GeneratedValue(generator = "TaskSeq")
val id: Long = 0,
#Column(nullable = false)
val name: String,
val description: String,
val inZ: LocalDateTime = LocalDateTime.now(),
var outZ: LocalDateTime = JpaConstants.MAX_DATETIME,
var completed: Boolean = false,
#ManyToOne(cascade = [CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH])
#JoinColumn(name = "projectId")
var project: Project? = null
) {
}
#Entity
#Table(name = "Project")
data class Project(
#Id
#SequenceGenerator(name = "ProjectSeq", sequenceName = "ProjectSeq", initialValue = 100)
#GeneratedValue(generator = "ProjectSeq")
val id: Long = 0,
#Column(nullable = false)
var name: String,
#OneToMany(mappedBy = "project", cascade = [CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH])
val tasks: MutableList<Task> = Lists.mutable.empty()
) {
}
Service Classes:
#Service
class ProjectServiceImpl(private val projectRepository: ProjectRepository) : ProjectService {
override fun save(project: Project): Project {
return projectRepository.save(project)
}
}
#Service
class TaskServiceImpl(private val taskRepository: TaskRepository, private val projectRepository: ProjectRepository) : TaskService {
override fun save(task: Task): Task {
return taskRepository.save(task)
}
override fun addTaskToProject(projectId: Long, task: Task): Task {
val project = projectRepository.findById(projectId).orElseThrow { RecordNotFoundException("Couldn't find project with id {$projectId}") }
task.project = project
return save(task)
}
}
The class I'm trying to use the transactional method:
class TaskServiceImplTest : TaskApplicationTests() {
#Autowired
private lateinit var taskService: TaskService
#Autowired
private lateinit var taskRepository: TaskRepository
#Autowired
private lateinit var projectService: ProjectService
#Test
#Transactional
fun canInsertTaskToProject() {
val project = projectService.save(Project(name = "Conquer Paris"))
var task = Task(name = "Check how many people we need to hire", description = "")
task = taskService.addTaskToProject(project.id, task)
assertTrue(task.id > 0)
val projects = projectService.findAll()
assertEquals(1, projects.size())
assertEquals(1, projects[0].tasks.size)
assertEquals(task.id, projects[0].tasks[0].id)
}
If I add a #Transactional(REQUIRES_NEW) to the methods in the service it will work, but I don't want it as if this method is called inside a real transaction I want it to be rolled back accordingly. Also I'd like to avoid using too many REQUIRES_NEW to avoid future problems
If I remove the #Transactional from the test method, it won't work when I test the size of the task list on last two lines as they are lazy.
What is the best way to make it work ? I thought that inside a #Transactional when I used another command from db it would get the latest updates that were not committed yet..
If needed, code in Java is fine too :)
Thanks in advance!

Based on your scenarios, you can use #TestEntityManagerso that each test can be managed in transaction context.
This example can help you,
https://grokonez.com/testing/datajpatest-with-spring-boot

Related

Why is EntityGraph not working with DataJpaTest?

Hey today i found out that my Repository-Test runs perfectly fine when i use the #SpringBootTest-Annotation. But when i switch it to #DataJpaTest-Annotation, my #OneToMany-Annotated Collection of child elements is null.
Here an example:
ParentEntity.class
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#NamedEntityGraph(name="parent.childs", attributeNodes = #NamedAttributeNode("childEntities"))
#Table(name = "parent")
public class ParentEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
Integer id;
#OneToMany(mappedBy = "parentId")
Collection<ChildEntity> childEntities;
}
ParentRepository.class
#Repository
public interface ParentRepository extends JpaRepository<ParentEntity, Integer> {
#EntityGraph(value = "parent.childs")
Optional<ParentEntity> findById(Integer id);
}
ChildEntity.class
#Data
#Entity
#Table(name = "child")
public class ChildEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)#Column(name = "id", nullable = false)
private Integer id;
#Column(name = "parentId", nullable = false, insertable = false, updatable = false)
private Integer parentId;
#ManyToOne#JoinColumn(name = "parentId", referencedColumnName = "id", nullable = false)
private ParentEntity parentEntity;
}
ChildRepository.class
#Repository
public interface ChildRepository extends JpaRepository<ChildEntity, Integer> {
}
And this is the Test:
#SpringBootTest
#AutoConfigureTestDatabase
public class RepoTest {
#Autowired
ParentRepository parentRepository;
#Autowired
ChildRepository childRepository;
#Commit
#Rollback(false)
#Test
void test(){
/* arrange */
ParentEntity parent = new ParentEntity();
var parentId = parentRepository.save(parent).id;
ChildEntity child = new ChildEntity();
child.setParentEntity(parent);
childRepository.save(child);
/* act */
/* Yes, i know there should be an exists()-check but lets keep it simple */
ParentEntity returnedParent = parentRepository.findById(parentId).get();
/* assert */
assertThat(returnedParent.getChildEntities()).hasSize(1);
}
}
This test works as expected.
But when i change the #SpringBootTest-Annotation to #DataJpaTest, the childEntities-Field of the ParentEntity.class stays null
I tried to delombok the code and find the cause by debugging each step of the query but i couldnt make it out right now. The resulting hibernate query contains the left outer join that i would expect. So my guess is that the error has to do with Data-Binding. Maby some type of (auto-)configuration is not loaded when i run the test with the other annotation.
I am very interested in the cause, so I would appreciate an explanation
After a lot of further research, i found the following helpful link:
Spring Data Jpa Test returns null list even after child is saved
There is explained what the cause of the problem is:
The parent
gets not loaded for the database but from the internal cache.
And to solve this problem:
You need to write a FlushAndClear method
#PersistenceContext
private EntityManager em;
private void flushAndClear() {
em.flush();
em.clear();
}

How to force variable to be public kotlin

i have the following code:
#Entity
#Table(name = "`users`")
class User(
var name: String,
var avatar: ByteArray
) {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
var id: Long = -1
fun getAvatarAsImage(): BufferedImage? {
val img: InputStream = ByteArrayInputStream(avatar)
return ImageIO.read(img)
}
fun setAvatarAsImage(img: BufferedImage) {
val out = ByteArrayOutputStream()
ImageIO.write(img, "PNG", out)
avatar = out.toByteArray()
}
}
but hibernate says that id is private (thats because kotlin auto-generating getters and setters),
so compiled to java it seems like:
#Id
#GeneratedValue(
strategy = GenerationType.AUTO
)
private long id;
public long getId() {
return this.id;
}
public void setId(long var1) {
this.id = var1;
}
QUESTION: how to make it compiled like:
#Id
#GeneratedValue(
strategy = GenerationType.AUTO
)
public long id;
???
idk what is this....
I'm not sure you have interpreted the error message from Hibernate properly, as the #Id-annotation seems correct. But I'm wondering if you perhaps forgot to use the kotlin-jpa compiler plugin that helps creating Hibernate-friendly classes?
Read more here: https://kotlinlang.org/docs/no-arg-plugin.html#jpa-support
Maybe you can try putting the annotations on the getter instead to force Hibernate to use property-based access... Try modifying the annotations to:
#get:Id
#get:GeneratedValue(strategy = GenerationType.AUTO)
var id: Long = -1
... or even worst case expose the field as public (as you are asking for) with...
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JvmField
var id: Long = -1

Unit test for Service PUT method doesn't work Spring Boot application

I made my Unit Test for my service, testing PUT method using JUnit5 (Note that given static import is from DBBMockito and verify static import is from Mockito. Also I used #Builder to made the objects.). Please take a look at the current test:
#ExtendWith(MockitoExtension.class)
class BillServiceTest {
#Mock
private BillRepository billRepository;
#InjectMocks
private BillService billService;
#Test
void whenGivenId_shouldUpdateBill_ifFound() {
Bill bill =
Bill.builder()
.billId(70L)
.hospitalServicesDescription("Cardiology")
.price(100)
.build();
Bill newBill =
Bill.builder()
.price(400)
.build();
given(billRepository.findById(bill.getBillId())).willReturn(Optional.of(bill));
billService.updateBill(bill.getBillId(), newBill);
verify(billRepository).save(newBill);
verify(billRepository).findById(bill.getBillId());
}
}
When I run this I get a report that the arguments are different:
Argument(s) are different! Wanted:
billRepository.save(
Bill(billId=null, dateOfBill=null, hospitalServicesDescription=null, price=400, patient=null)
);
-> at com.app.hospitalmanagementsystem.service.BillServiceTest.whenGivenId_shouldUpdateBill_ifFound(BillServiceTest.java:96)
Actual invocations have different arguments:
billRepository.findById(
70L
);
-> at com.app.hospitalmanagementsystem.service.BillService.updateBill(BillService.java:44)
billRepository.save(
Bill(billId=70, dateOfBill=null, hospitalServicesDescription=null, price=400, patient=null)
);
-> at com.app.hospitalmanagementsystem.service.BillService.updateBill(BillService.java:49)
Comparison Failure:
<Click to see difference>
When I click on <Click to see difference> , this is what I get:
Expected:
billRepository.save(
Bill(billId=null, dateOfBill=null, hospitalServicesDescription=null, price=400, patient=null)
);
Actual:
billRepository.findById(
70L
);
billRepository.save(
Bill(billId=70, dateOfBill=null, hospitalServicesDescription=null, price=400, patient=null)
);
Also, the code which I am trying to test is here:
#Service
public class BillService {
private final BillRepository billRepository;
#Autowired
public BillService(BillRepository billRepository) {
this.billRepository = billRepository;
}
public Bill updateBill(Long billId, Bill billUpdatedDetails) {
Bill updatedBill = billRepository.findById(billId)
.orElseThrow(()-> new ResourceNotFoundException("Bill with id " + billId + " doesn't exist."));
updatedBill.setDateOfBill(billUpdatedDetails.getDateOfBill());
updatedBill.setHospitalServicesDescription(billUpdatedDetails.getHospitalServicesDescription());
updatedBill.setPrice(billUpdatedDetails.getPrice());
return billRepository.save(updatedBill);
}
This is the Entity:
#Entity
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Builder
#Table(
name = "bill"
)
public class Bill {
#Id
#SequenceGenerator(
name = "bill_sequence",
sequenceName = "bill_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "bill_sequence"
)
private Long billId;
#Column(
name = "date_of_bill",
nullable = false
)
private LocalDate dateOfBill;
#Column(
name = "hospital_services_description",
nullable = false
)
private String hospitalServicesDescription;
#Column(
name = "price",
nullable = false
)
private Integer price;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(
name = "patient_id",
referencedColumnName = "patientId"
)
private Patient patient;
#JsonIgnore
#OneToMany(mappedBy = "bill")
#ToString.Exclude
private List<PatientMedicalHistory> patientMedicalHistories;
public void connectPatient(Patient patient) {
this.patient = patient;
}
}
Does anyone have an idea how I can solve this?
In your code, the argument passed to billRepository.save() is updatedBill. updatedBill comes from billRepository.findById():
public Bill updateBill(Long billId, Bill billUpdatedDetails) {
// updatedBill comes from billRepository.findById()
Bill updatedBill = billRepository
.findById(billId)
.orElseThrow(()-> new ResourceNotFoundException("..."));
// ...
// updatedBill is passed to billRepository.save()
return billRepository.save(updatedBill);
}
In your test, you make sure that billRepository.findById() returns bill:
given(billRepository.findById(bill.getBillId())).willReturn(Optional.of(bill));
// ...
So that means that the argument passed to billRepository.save() should also be bill. However, in your test you passed newBill in stead of bill, which is why the test fails:
verify(billRepository).save(newBill); // Incorrect
verify(billRepository).save(bill); // Correct

Spring Data CrudRepository's save throws InvocationTargetException

I have spent the whole weekend trying to debug this piece of code. I have a Spring RestController :
import com.tsakirogf.schedu.model.ContactMean;
import com.tsakirogf.schedu.model.DefaultContactMean;
import com.tsakirogf.schedu.model.human.Business;
import com.tsakirogf.schedu.services.BusinessService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
import java.util.Set;
#RestController
#RequestMapping("api/v1/business/")
public class BusinessController
{
#Autowired
BusinessService businessService;
#GetMapping(value = "businesss")
Iterable<Business> list()
{
Iterable<Business> retVal = businessService.findAll();
return retVal;
}
#RequestMapping(value = "business", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
Business create(#RequestBody Business business)
{
CollectionOfContactMethods collectionOfContact = business.getContact();
collectionOfContact.setBusiness(business);
Set<ContactMean> contactMeanSet = collectionOfContact.getContactMeans();
DefaultContactMean defaultContactMeanSet = collectionOfContact.getDefaultContactMean();
defaultContactMeanSet.getCollectionOfContactMethodsDefault().setId(collectionOfContact.getId());
for (ContactMean element : contactMeanSet)
{
element.setCollectionOfContactMethods(collectionOfContact);
}
collectionOfContact.setDefaultContactMean(defaultContactMeanSet);
business.setContact(collectionOfContact);
Business retval = businessService.save(business);
return retval;
}
#RequestMapping(value = "business/{id}", method = RequestMethod.GET )
Optional<Business> get(#PathVariable Long id)
{
return businessService.findById(id);
}
}
And the service :
public interface BusinessService extends CrudRepository<Business, Long>
{
}
This is the model :
#Table(name = "business")
public class Business
{
#Id
#Column(name = "business_id", nullable = false)
private Long id;
#JsonProperty("name")
private String name;
#Embedded
#JsonProperty("address")
private Address address;
#OneToMany(mappedBy = "business",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
#JsonProperty("operatives")
#JsonIgnore
Set<Professional> operatives;
#OneToOne(mappedBy = "business",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
optional = false)
#JsonBackReference
#JsonProperty("contact_numbers")
private CollectionOfContactMethods contact;
public Business()
{
}
// Getters and Setters
}
When I send a POST request like this :
Where I got the following
{
"timestamp": "2021-11-01T08:59:06.343+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/api/v1/business/business"
}
I debug and I am getting InvocationTargetException as seen below
This is the controller, right before save() which seems to throw :
And here is the catch :
I found this article posted in a similar event in StackOverflow but I don't think that's what is happening in this case since I have only H2 database for now.
This is application.properties file :
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.hibernate.hbm2ddl.auto=create
I would appreciate any ideas. Thanks for your time.
If you look at your last screenshot you see a message indicating that there is an id field that has no value.
In your entity you have the following declaration of your id field:
#Id
#Column(name = "business_id", nullable = false)
private Long id;
Which indicates to hibernate that it shouldn't generate a key or that there is no database assigned one. Which means you will manually need to set the value for id. If you don't you will run into this exception.
Now I assume that this was a mistake and that you actually wanted to have a sequence or auto-incremented id field. For this add the #GeneratedValue annotation to add this behavior.
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE))
#Column(name = "business_id", nullable = false)
private Long id;
This will instruct hibernate to use a sequence to generate the id upon inserting the entity. If your database supports identity columns you might want to use GenerationType.IDENTITY instead of GenerationType.SEQUENCE.

SpringBoot #Scheduled and #Transactional is required to be in separate class?

I read that It is not a good practice to put #Scheduled and #Transactional annotation together because of the Spring default AOP implementation. (proxy classes etc.)
I have decided to try it by myself and I created a very short app for testing but surprisingly my code works when I put these things together and also when I created a separated Schedule service for the annotation.
#Entity
class AEntity(
#OneToMany(mappedBy = "aEntity", fetch = FetchType.LAZY, cascade = [CascadeType.REMOVE])
var bEntities: MutableSet<BEntity>? = null,
#Id #GeneratedValue var id: Long? = null
)
#Entity
class BEntity(
#ManyToOne
#JoinColumn(name = "a_entity_id", referencedColumnName = "id")
var aEntity: AEntity,
#Id #GeneratedValue var id: Long? = null
)
Service:
#Service
class AService(val aRepository: ARepository) {
#Scheduled(fixedRate = 5000)
#Transactional
fun test() {
val aEntity = aRepository.findOneById(1)
aEntity?.bEntities?.forEach {
println(it.id)
}
}
}
You can reach my full project from here

Resources