How to mock jpa repository save(without return object) and modify id of input object - spring-boot

I use Spring Data JPA in my project and my model code is here:
#Getter
#Setter
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Activity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String number;
//...
}
My service code is here:
activityRepository.save(activity);//activity has no data in field id
activity.setNumber("D"+activity.getId()); //A
activityRepository.save(activity);
And my mock code is here:
when(activityRepository.save(activity)).thenReturn(tempActivity);
//invoke service method
verify(activityRepository).save(activity);
The question is that I have always been met with the Null Pointer Exception in code A.So how can I mock this repository save method?

Two points:
Use the returned object from activityRepository.save(activity)
activity = activityRepository.save(activity);//activity has no data in field id
activity.setNumber("D"+activity.getId()); //A
activity = activityRepository.save(activity);
Return a modified version, that is returned by the mock
tempActivity = activity.toBuilder().id(5).build();
when(activityRepository.save(activity)).thenReturn(tempActivity);
//invoke service method
verify(activityRepository).save(activity);

Related

JPA Entity class which is not mapped to any table

I am using a entity class for mixing two/three table columns in one entity to hold an outcome of SYS_REFCURSOR in oracle
This allows me to have single class which is not mapped to any table but it still is an Entity
#Data
#Entity
#NoArgsConstructor
class EmployeeDetails {
#Id
#Column("emp_id")
String empId;
#Column("job_name")
String jobName;
#Column("dept_name")
String deptName;
//Future requirement
//String updatedBy
}
Now I have an additional requirement, to add who last modified the employee table, I don't want modify the procedure now, the procedure is being re-used in another background procedure and batch jobs.
My question is, is it possible to use #ManyToOne on this class which is obviously not mapped to any table
If not how do avoid manually looping a child array list, is there a ready made option in JPA or spring boot to achieve that.
Or what will be the smartest/recommended way to bring the below Entity into this class
#Data
#Entity
#NoArgsConstructor
#Table(name="app_users")
class AppUsers {
#Id
#Column(name="user_id")
String userId;
#Column
String userName;
}
#Transient, check how this annotation works it will resolve the issue, you need to understand working of #Transient
My spring boot 2.6.2 EntityManager code is as follows
q = em.createStoredProcedureQuery("MY_PROC",EmployeeDetails.class);
q.registerStoredProcedureParameter("OUT_REFC", void.class, ParameterMode.REF_CURSOR);
q.execute();
q.getResultList()
I have modified my class EmployeeDetails as below
#Data
#Entity
#NoArgsConstructor
class EmployeeDetails {
#Id
#Column("emp_id")
String empId;
#Column("job_name")
String jobName;
#Column("dept_name")
String deptName;
#OneToOne
#JoinColumn(
name="user_id",
referencedColumnName="emp_id",
insertable=false,
updatable=false,
nullable=true
)
AppUsers updatedBy;
}
The log prints Hibernate two times one after one as below, first it calls the proc and then it calls the select query, so, I did not wrote that SQL myself, the JPA layer is taking care of it
Hibernate:
{call MY_PROC(?)}
Hibernate:
select
...
...
from app_users
where user_id=?
so, my expectation achieved and I am getting the values

How to bind #PathVariable value to #Where clause in Spring boot rest API

I am reading #PathVaraible in Controller and using #Where annotation in Entity. If I pass hardcoded value to #Where clause in Entity it is working fine. But I want to pass run time value which is passed in #PathVariable to #Where in Entity.
#GetMapping("/api/v1/course/details/{courseId}")
public List<CourseDto> getcourseDetails(#Valid #PathVariable final String courseId) {
// lines of code
}
#Entity
#Where(clause="course_id=:courseId")
public class CourseEntity{
#NotBlank(message = "Please provide courseId)
#Getter
#Setter
#Column(columnDefinition = "nvarchar(50)",length = 50)
private String courseId;
}
#Repository
public interface MyRepo extends JpaRepository<Course, Integer> {
}
With this code I am getting error as
Incorrect syntax near ':'.
How to fix this error. Is there any other solution ?
You cannot change a #Where clause at runtime. If you want to set a parameter you can use #Filter:
#Entity
#FilterDef(
name="courseFilter",
parameters=#ParamDef(
name="courseId",
type="int"
)
)
#Filter(
name="firstAccounts",
condition="course_id=:courseId"
)
public class CourseEntity{
...
}
Now with the Hibernate ORM session or entity manager you can set a parameter the following way:
entityManager
.unwrap( Session.class )
.enableFilter( "courseFilter" )
.setParameter( "courseId", ...);
List<CourseEntity> courses = entityManager
.createQuery("from CourseEntity c", CourseEntity.class)
.getResultList();
The Hibernate ORM documentation has more details about filtering entities using #Filter or #Where.

JaversException ENTITY_INSTANCE_WITH_NULL_ID for ignored id

Using javers 5.11.2 I get the following exception although the id is set to be ignored. Why is that?
JaversException ENTITY_INSTANCE_WITH_NULL_ID: Found Entity instance 'my.package.javers.Leaf' with null Id-property 'id'
Update: I learned that
JaVers matches only objects with the same GlobalId
The id is specified using javax.persistence.Id. However, with each ORM it is possible to have an entity with a collection, then add a new element without id to that entity and then save it (CascadeType.Persist).
Is there any way to compare objects with javers in such a case?
Example (used lombok for boiler plate code).
The leaf:
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Data
#Entity
public class Leaf {
#DiffIgnore <============ id is ignored
#Id
private Long id;
private String color;
}
The tree:
#Builder
#Data
#Entity
public class Tree {
#Id
private Long id;
private String name;
#OneToMany
private Set<Leaf> leafs;
}
Test adds a leaf to the oakSecond without an id set. The diff cannot be made. An Exception is thrown.
#Test
public void testCompare_AddLeafToTree() {
Leaf leaf = Leaf.builder().id(1L).color("11").build();
Set<Leaf> leafsOfOakFirst = new HashSet<>();
leafsOfOakFirst.add(leaf);
Tree oakFirst = Tree.builder().id(1L).name("oakFirst").build();
oakFirst.setLeafs(leafsOfOakFirst);
Set<Leaf> leafsOfOakSecond = new HashSet<>();
leafsOfOakSecond.add(leaf);
leafsOfOakSecond.add(Leaf.builder().color("12").build());
Tree oakSecond = Tree.builder().id(1L).name("oakFirst").build();
oakSecond.setLeafs(leafsOfOakSecond);
Javers javers = JaversBuilder.javers().build();
Changes changes = javers.compare(oakFirst, oakSecond).getChanges();
assertThat(changes).isNotEmpty();
}
Same with the following definition of the Javers instance:
EntityDefinition leafEntityDefinition = EntityDefinitionBuilder.entityDefinition(Leaf.class).withIgnoredProperties("id").build();
Javers javers = JaversBuilder.javers().registerEntity(leafEntityDefinition).build();
You can't pass an Entity to Javers with null Id because it would be non-identifiable. If you use Hibernate to generate your Ids, make sure that you pass your object to javers.commit() after hibernate are done with its job. That's how the #JaversSpringDataAuditable aspect works.
Alternatively, you can model those objects with unstable IDs as Value Object in Javers.

Spring Data Rest Does Not Update Default Value in DB

I have a Spring Boot application using Spring Data REST. I have a domain entity called User with a boolean field isTeacher. This field has been already setup by our DBA in the User table with type bit and a default value of 1:
#Data
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // This Id has been setup as auto generated in DB
#Column(name = "IS_TEACHER")
private boolean isTeacher;
}
And the User repository:
public interface UserRepository extends CrudRepository<User, Long>{
}
I was able to add a new user by giving the below request and POST to http://localhost:8080/users, a new user was created in the DB having isTeacher value 1:
{
"isTeacher" : true
}
However, when I tried to change IS_TEACHER by giving PATCH (or PUT) and this request:
{
"isTeacher" : false
}
The response showed that "isTeacher" is still true and the value didn't get changed in the table either. Can someone please let me know why this is happening?
The issue is due to #Data annotation of lombok is ignoring if you have a field that start with isXx it generates getters and setters to boolean with isTeacher for getters and setTeacher for setters then you are not able to update correctly your property, if you put "teacher" when updating should work but you should solve this by overriding that setter.
#Setter(AccessLevel.NONE) private boolean isTeacher;
public void setIsTeacher(boolean isTeacher) {
this.isTeacher = isTeacher;
}

ID field is null in controller

I am using spring mvc with hibernate and JPA. I have a Person class which is inherited by another class called Agent. The mapping is implemented as follows:
#Entity
#Table(name = "Person")
#Inheritance(strategy = InheritanceType.JOINED)
public class Person extends Auditable implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "PersonId")
protected Long id;
//other variables
...
}
#Entity
#PrimaryKeyJoinColumn(name = "PersonId")
public class Agent extends Person implements Serializable {
//additional agent specific variables go here
...
}
Saving new data is smooth and I have no problem there. however, when I edit data, everything except the id value is bound to the controller method's model attribute. I have verified that the id has been sent along with other items from the browser using chrome's developer tools. but the id field at the controller is always null and as a result the data is not updated. This is what my controller method looks like:
#RequestMapping(value = "register", method = RequestMethod.POST)
public #ResponseBody CustomAjaxResponse saveAgent(ModelMap model, #ModelAttribute("agent") #Valid Agent agent, BindingResult result) {
...
}
I suspect the problem is probably with my inheritance mapping because I have other classes inheriting from the Person class and I face a similar problem there as well.
Please help!
you need a public setter for id.
In cases like this I commonly use a specific dto for the form, and/or implement a conversion service that retrieves the entity via hibernate based on id and then performs a merge.

Resources