How to prevent automatic update in Spring Data JPA? - java

In my Spring boot batch application, I am calling a JPA repository class from Tasklet.
The JPA call retrieves a particular value (Entity object) from DB. The problem is, If I update some value in the entity object, once the control goes out of Tasklet, it automatically updates to DB even though I am not calling any save operation. How to prevent this? Default JPA implementation is Hibernate.
Tasklet class
Employee employee = employeeRepository.fetchEmployee(employeeName);
List<Address> addressList = employee.getAddress();
addressList.forEach(e -> e.setStatus(Status.INVALID.toString()));
Repository
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
#Query("select em from Employee em where em.employeeName = :employeeName")
public Employee fetchEmployee(#Param("employeeName") Long employeeName);
}
Entity class
#Entity
#Table(name = "Employee")
public class Employee implements java.io.Serializable {
private static final long serialVersionUID = -3769636546619492649L;
private Long id;
private List<Address> address;
private String employeeName;
// Getters and setters
// #OneToMany mapping to Address
}
Even though I am not calling a .save() operation, it automatically updates Address table Status to "INVALID"

This happen because the entity is not in detached state. In EJB we can do this in the following way.
EJB solution
#Query(value = "select * from Employee WHERE EmployeeName = ?1", nativeQuery = true)
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public List<Employee> fetchEmployee(String employeeName);
This will make the transaction closed. Changes you make to entity will not get saved in DB
Spring JPA
After a bit of research i found JPA doesn't provide the detach functionality out of the box.
Refer : https://github.com/spring-projects/spring-data-jpa/issues/641
To make it work we can have a custom JPA repository which overrides detach method. An example is given in this link.
https://www.javaer101.com/en/article/1428895.html

Use Deep cloning to solve your issue.
First override the clone method inside your Address class like below.
Note : Please customize the implementation of clone() method by adding your class attributes.Since you didn't mention the structure of the class Address , I have implemented the solution with my own defined class attributes.
Address class
public class Address {
private String country;
private String city;
private String district;
private String addressValue;
public Address() {
super();
}
public Address(String country, String city, String district, String addressValue) {
super();
this.country = country;
this.city = city;
this.district = district;
this.addressValue = addressValue;
}
//Getters and Setters
#Override
protected Object clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
return new Address(this.getCountry(), this.getCity(), this.getDistrict(),this.getAddressValue());
}
}
}
Then re construct your class Tasket like below.
Tasket Class
Employee employee = employeeRepository.fetchEmployee(employeeName);
List<Address> addressList = employee.getAddress();
List<Address> clonedAddressList = new ArrayList<>();
addressList.forEach(address -> clonedAddressList.add((Address)address.clone()) );
clonedAddressList.forEach(address -> address.setStatus(Status.INVALID.toString()));

Related

Java Spring Rest Api Custom Repository

So I have a student class
#Table(name = "student")
public class Student {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String firstname;
private String lastname;
private String idnumber;
private String adress;
private boolean active=Boolean.TRUE;
#OneToMany( mappedBy="student")
private Set<Contact> contacts = new HashSet<>();
#ManyToMany(mappedBy = "students")
private Set<Team> teams = new HashSet<>();
}
and I have repository
public interface StudentRepository extends JpaRepository<Student,Long> {
}
I want to create customrepository to find student idnumber, and while creating new student it will check all students if idnumber already exists throw exception.
You can use the jpa repository method
studentRepo.findById(studentId)
To fetch a record in the database with the studentId.
If you want it to find the record exists or not you can make the method return null and check whether the object is null or not and throw the appropriate exception. Like in the below example.
Student student = studentRepo.findById(studentId).orElse(null);
if(student == null)
throw new CustomException("student does not exist");
No need to create a new repo as customrepository to implement custom query. You can write a new method to retrieve data.
public interface StudentRepository extends JpaRepository<Student, Long> {
// using JPA method
Optional<Student> findByIdnumber(String idNumber);
// using JPA query | this is an optional method in case you need to implement any query in future
// ?1 represents the first parameter of the method, which is 'idNumber'
#Query(value = "SELECT s.id FROM Student s WHERE s.idnumber=?1", nativeQuery = true)
String findIdByIdnumber(String idNumber);
}
Through the service you can check whether a record is available or not and perform a respective action (throw an exception).

Access Few Columns of a table from CrudRepository in Spring Boot

I have an interface which is extending crud repository
public interface PersonRepo extends CrudRepository<Person, String> {
#Query(value="select name from PERSON where addr=?1", nativeQuery = true)
List<Person> getPeronUsingAddress(String addr);
}
Person entity looks like this:
class Person {
private String name;
private String phoneNumber;
private String address;
//along with getters setters and all basic hibernate annotation to persist and retrieve
}
the person object is saved into the databse and at the time of retrieving the native query is working fine as hibernate executes correct query. But I am not able to get the return type.
If the return type is List of Person then I am getting InvalidDataAccessResourceUsageException
If I create an interface and use the list of interface as return type like
interface response {
String getName();
}
List of Response interface getPeronUsingAddress(String addr);
then I am getting proxy object in the service. I am not able to get the datas from proxy object.
Another approach I did is to use List of object as return type. But it is not possible to downcast to my Person Object.
How to do that.? Or is there any other solution by which I can return selective columns from crud repository and get a Java object with those selected Columns.
In order to fetch selected columns from an entity, you can do like below :
class Person {
private Integer id;
private String name;
private String phoneNumber;
private String address;
//along with getters setters and all basic hibernate annotation to persist and retrieve
}
Create a DTO or Java Object like below :
public class PersonDTO {
private Integer id;
private String name;
private String phoneNumber;
private String address;
public PersonDTO(Integer id, String name, String phoneNumber, String address) {
// logic here
}
//If you want just want name and phone number.
public PersonDTO(String name, String phoneNumber) {
// logic here
}
// you can't create overridden constructors as all members are of same type and at runtime program won't be able to differentiate unless you provide some logic for it.
// getters, setters, any other methods here...
}
Now below will be you Query but it's not native, if you want to keep native query then you will need to use ResultTransformer like here
#Query("select new your.package.PersonDTO(p.name, p.phoneNumber) from Person p where p.id = :id")
public PersonDTO getPersonById(Integer id);

Saving entity using prorotype of objects in properties

I'm using JPA and have next entity
#Entity
public class Employee {
#Id
int id;
String name;
#ManyToOne
Employee manager;
#OneToMany(mappedBy="manager",fetch=FetchType.EAGER)
Set<Employee> subordinaries;
// constructors , getters, setters
}
Can I use prototype of property object for saving? It's works but I'm not shure that it's good practice. This is DAO class:
public class CompanyOrm {
#PersistenceContext(unitName="springHibernate")
EntityManager em;
...
#Transactional
public void addEmployee(int id, String name, int managerId){
//Using prototype of object manager - not
//object retrived from database with
//em.find(Employee.class, managerId)
//where filled only id
Employee prototype = new Employee();
prototype.setId(managerId);
Employee e = new Employee(id, name, prototype);
em.persist(e);
}
...
}
It may work but it's error-prone, since the prototype instance is not managed.
You should use em.getReference(Employee.class, managerId) to create the referenced entity. It returns an uninitialized proxy on the entity, without going to the database.

LdapRepository update spring-ldap

Spring LdapRepository save() method throws exception when I'm trying to update an existing object in LDAP database.
org.apache.directory.api.ldap.model.exception.LdapEntryAlreadyExistsException: ERR_250_ENTRY_ALREADY_EXISTS
What method should I use to update existing ldap objects?
Person class:
#Entry(objectClasses = { "inetOrgPerson", "organizationalPerson", "person", "top" })
public class Person implements Serializable {
public Person() {
}
#Id
private Name dn;
#Attribute(name = "cn")
#DnAttribute(value = "cn")
#JsonProperty("cn")
private String fullName;
#Attribute(name = "uid")
private String uid;
private String mail;
#Attribute(name = "sn")
private String surname;
//setters and getters
}
Person repo interface:
public interface PersonRepo extends LdapRepository<Person> {
}
That's how I'm updating person:
personRepo.save(person);
Default implementation for Spring LDAP repositories is SimpleLdapRepository, that checks the property annotated with #Id to determine if the objects is new - and perform create, or old - and perform update.
I'm guessing that Person.dn is null when you're trying to perform update.
You also can take the control over this by implementing org.springframework.data.domain.Persistable and place your logic in the isNew() method.
See the implementation details.

NucleusUserException: Cannot access field on entity extending mapped superclass

I am running into a NucleusUserException while querying my google datastore instance. I am querying for a field that exists on a MappedSuperclass on the class that extends it. Here is my abstract class that contains the field I am interested in:
#Entity
#MappedSuperclass
#JsonIgnoreProperties({ "password" })
public abstract class AbstractUser implements User {
#Persistent
protected String emailAddress;
public void setEmailAddress(String email) {
this.emailAddress = email;
}
public String getEmailAddress() {
return this.emailAddress;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long key;
//Other stuff.
}
The concrete instance looks like this:
#Entity
public class Client extends AbstractUser {
//Things that only clients have.
}
My query that is failing looks like this:
List existingUsersWithEmail = manager
.createQuery(
"SELECT c from Client AS c WHERE c.emailaddress = :mail")
.setParameter("mail", request.getEmailAddress())
.getResultList();
The exception is this:
Cannot access field emailaddress on type org.workouthound.user.Client
org.datanucleus.exceptions.NucleusUserException: Cannot access field emailaddress on type org.workouthound.user.Client
at org.datanucleus.query.compiler.JavaQueryCompiler.getType(JavaQueryCompiler.java:552)
at org.datanucleus.query.compiler.JavaQueryCompiler.getType(JavaQueryCompiler.java:529)
at org.datanucleus.query.symbol.SymbolTable.getType(SymbolTable.java:118)
at org.datanucleus.query.expression.PrimaryExpression.bind(PrimaryExpression.java:118)
at org.datanucleus.query.expression.DyadicExpression.bind(DyadicExpression.java:85)
at org.datanucleus.query.compiler.JavaQueryCompiler.compileFilter(JavaQueryCompiler.java:299)
at org.datanucleus.query.compiler.JPQLCompiler.compile(JPQLCompiler.java:75)
at org.datanucleus.store.query.AbstractJPQLQuery.compileInternal(AbstractJPQLQuery.java:246)
at org.datanucleus.store.query.Query.setImplicitParameter(Query.java:690)
at org.datanucleus.jpa.JPAQuery.setParameter(JPAQuery.java:428)
at org.workouthound.rest.client.UserResources.emailIsRegistered(UserResources.java:55)
at org.workouthound.rest.client.UserResources.createClient(UserResources.java:33)
I am new to DataNucleus and Google Data Store. I attempted to follow the tutorial as outlined here however I very well could have missed something. Please let me know as additional information is necessary.
UPDATE:
If I change the field name to email as well as the getters, setters and query, it works...why?

Categories