How to update record in springboot JpaRepository? - spring

My model is
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name="name")
private String name;
#Column(name="email")
private String email;
//getter & setter
}
My controller is
#RequestMapping(value = "user/userProfile", method = RequestMethod.POST)
public String saveUserProfile(#Valid UserProfile userProfile, #RequestParam("file") MultipartFile file,
BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("userProfile", userProfile);
return "userprofileform";
}
userProfileService.saveUserProfile(userProfile);
} catch (IOException e) {
e.printStackTrace();
}
return "redirect:/user/home";
}
#RequestMapping("/user/userProfile/new")
public String newUserProfile(Model model) {
UserProfile profile = userProfileService.getUserProfileByUserId(userService.getLoggedInUser().getId());
model.addAttribute("userProfile", profile);
return "userprofile";
}
Repository is
#Transactional
public interface UserProfileRepository extends CrudRepository<UserProfile, Long>{
}
When I am creating first time userProfile it creates successfully but when I tried to update same it create new entry.
But in this controller when I use findone() and copy all information it works fine.
Same save method replaced below like :
public String saveUserProfile(#Valid UserProfile userProfile, #RequestParam("file") MultipartFile file,
BindingResult bindingResult, Model model) {
if (bindingResult.hasErrors()) {
model.addAttribute("userProfile", userProfile);
return "userprofileform";
}
UserProfile tempProfile = userProfileService.getUserProfileByUserId(userService.getLoggedInUser().getId());
try {
if (tempProfile != null) {
tempProfile.setName(userProfile.getName());
tempProfile.setEmail(userProfile.getEmail()); userProfileService.saveUserProfile(tempProfile);
} else {
userProfile.setUserId(userService.getLoggedInUser().getId());
userProfileService.saveUserProfile(userProfile);
}
} catch (IOException e) {
e.printStackTrace();
}
return "redirect:/user/home";
}
Is this write way or how can we update record?.
Or saving in session attribute (#SessionAttributes("yourAttributeName")) is only the write way?

Make sure the userProfile object has an id assigned. If your object doesn't have an id, hibernate assumes it's a new entity and therefore it creates a new record in the database.

You need to confirm that when you want to do an update the UserProfile has an id. Put a debug point at: profileRepo.save(userProfile) to confirm this, as a new Entity will be created if no id exists, and should be updated if the id already exists.

Related

Spring boot application not accepting ID of incoming POST request

I have an existing system that uses string based unique IDs for users and I want to transfer that System into a Spring boot application. I want to creat a user so I send a POST request with the following content:
As you can see, the id gets ignored.
This is my Spring code for the user class:
#PostMapping("/user")
ResponseEntity addUser(User receivedUser) {
Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
logger.info("Empfangener User: " + receivedUser.toString());
try {
User mailCheckUser = userService.getUserByMail(receivedUser.getEmail());
User nameCheckUser = userService.getUserByName(receivedUser.getUsername());
if (mailCheckUser != null){
return new ResponseEntity("Email already exists", HttpStatus.NOT_ACCEPTABLE);
}
if (nameCheckUser != null){
return new ResponseEntity("Username already exists", HttpStatus.NOT_ACCEPTABLE);
}
userService.addUser(receivedUser);
} catch (Exception userCreationError) {
return new ResponseEntity(receivedUser, HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity(receivedUser, HttpStatus.OK);
}
public void addUser(User user) {
userRepository.save(user);
}
And this is my user class:
#Entity
#Table
public class User {
#Id
#Column(unique =true)
private String id;
private #Column(unique =true)
String username;
private #Column(unique =true)
String email;
private #Column(unique =true)
String simpleAuthToken;
private
String password;
/*REDACTED*/
private
boolean isBlocked;
public User(String id, String name, String email, boolean isBlocked) {
this.id = id;
this.username = name;
this.email = email;
this.simpleAuthToken = simpleAuthToken;
this.isBlocked = false;
}
public User() {
}
/*GETTERS AND SETTERS ARE HERE, BUT I CUT THEM FOR SPACING REASONS*/
}
And this is the Spring Output:
My expected outcome would be that Spring would recognize the id and then create a user with the id I provided. Why is the id always null?
EDIT: If I put the ID in a Put or Get Mapping as Path variable, like so:
#PutMapping("/user/{id}")
ResponseEntity updateUser(#PathVariable String id, User receivedUser) {}
then it gets read and recognized, but it will still be null in the receivedUser
First add #RequestBody in the post request body. In the Post request (/test/user) your passing some params but in the method level not received.
If you want receive id from postman then add #RequestParam("id")String id in the method level.
How you generating unique Id by manually or some generators?
And double check user id at the database console level.

RestController: returning Resource<> makes FetchType.LAZY to behave as FetchType.EAGER

I am having below mappings as Company having 1:N relation with CompanyFunds
#Entity
public class Company{
#Id
private Integer companyId;
private String name;
#OneToMany(mappedBy = "company")
private List<CompanyFund> companyFunds;
}
#Entity
public class CompanyFunds{
#Id
private Integer fundId;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "company_id")
private Company company;
}
I am using Spring-data-Jpa for my persistence layer and below are the controller and service methods:
//controller
#GetMapping(value = "/{companyId}")
public Resource<Company> find(#PathVariable Integer companyId) {
Resource<Company> companyResource = companyService.find(companyId);
return companyResource;
}
//service
public Resource<CompanyTypeOther> find(Integer companyId) {
Company company = companyRepository.findById(companyId);
return restResourceAssembler.toResource(company);
}
#Component
public class RestResourceAssembler implements ResourceAssembler<T, Resource<T>> {
private EntityLinks entityLinks;
public RestResourceAssembler(EntityLinks entityLinks) {
this.entityLinks = entityLinks;
}
#Override
public Resource<T> toResource(T entity) {
Resource<T> resource = new Resource<>(entity);
resource.add(entityLinks.linkToSingleResource(entity.getClass(), entity.getId()).withSelfRel());
return resource;
}
}
Now the weird thing is, until the return companyResource;(in controller) doesn't get executed, the companyResource contains null for companyFunds i.e the LAZY loading is working fine till that point. But the moment the return companyResource; executes, something goes inside the Spring and the Select statement for CompanyFund gets fired. I debugged the steps and below is the code(try block) responsible for this:
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
......
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
........other code
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
.....
}
There are no toString() declared in entities, also no getCompanyFund() called. Can't understand what Spring is doing with returnValue above, so that some getters(or something) are getting called.
One more thing I noticed is, this problem only occurs when the Resource<Company> is returned. If I return Company from the controller, nothing unexpected happens. Lazy loading works fine.
Since I want to lazy load the entity, a little fix/hack solved the problem for me(as of now).
#JsonIgnore
#OneToMany(mappedBy = "company")
private List<CompanyFund> companyFunds;
#JsonIgnore prevents LAZY loaded entity from being serialized. So I guess the jackson is the culprit here.
This is not a fix is my view but just a hack to do the thing. Still waiting someone from Spring team to reply.

Updating entity with One to One relationship using Spring Data

I have an issue when updating entity with OneToOne relationship, it creates record instead of updating the existing one. Below are the sample entities.
#Entity
#Table(schema = "crm", name = "persons")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long getId() {
return id;
}
#OneToOne(mappedBy = "person", fetch = FetchType.LAZY)
public Employee getEmployee() {
return employee;
}
}
#Entity
#Table(schema = "crm", name = "employees")
public class Employee {
#Id
public Long getId() {
return id;
}
#OneToOne
#MapsId //Use the person PK id value as Employee PK id
#JoinColumn(name = "id")
public Person getPerson() {
return person;
}
}
I am using the PagingAndSortingRepository of Spring Data. Below is the service layer to update the entity.
#Override
#Transactional
public EmployeeResponse updateEmployee(Employee aEmployee) {
EmployeeResponse response = new EmployeeResponse();
try {
Optional<Employee> probableEmployee = employeeRepository.findById(aEmployee.getId());
if (!probableEmployee.isPresent()) {
throw new RecordNotFoundException(String.format(MessageConstants.EMPLOYEE_ID_NOT_FOUND, aEmployee.getId()));
}
Employee existingEmployeeToUpdate = probableEmployee.get();
EmployeeEntityHelper.updateExistingEntity(aEmployee, existingEmployeeToUpdate);
existingEmployeeToUpdate = employeeRepository.save(existingEmployeeToUpdate);
response.setSuccessfulResponse(existingEmployeeToUpdate);
} catch (Exception ex) {
LOGGER.error(ex.getLocalizedMessage(), ex);
response.setErrorAttributes(false, ReturnCode.FAILED.getCode(), ex.getLocalizedMessage());
}
return response;
}
The EmployeeEntityHelper.updateExistingEntity(source, target) will simply copy all properties of entities from source to target.
The save() method will generate an insert for Person even if I am explicitly passing the id existing in DB. But for employee it will generate an update which is expected.
Below is the updateExistingEntity() method:
public static void updateExistingEntity(Employee source, Employee target) {
copyProperties(source, target, Arrays,asList("person", "employeeNumber", "hiredDate", "birthDate"));
}
private static void copyProperties(Object aSource, Object aTarget, Iterable<String> aProperties) {
BeanWrapper sourceWrapper = PropertyAccessorFactory.forBeanPropertyAccess(aSource);
BeanWrapper targetWrapper = PropertyAccessorFactory.forBeanPropertyAccess(aTarget);
aProperties.forEach(p ->
targetWrapper.setPropertyValue(p, sourceWrapper.getPropertyValue(p))
);
}
Generally, a new INSERT instead of UPDATEs could be due to:
Either entity passed to save has no ID set, thus persist is called under the hood
Or entity with given ID is not present in the database thus merge fails (and maybe it is persisted as a fallback, dunno)
Check if entities have set ID field.
In your model , override equal/hash-code and just chek the id parameter
maybe it will help you :)
The issue is already fixed, somehow the copyproperties logic is not right in the sense that I am updating/setting the person also.

Upsert Mongo Document using spring data mongo

I have a Class
#Document
public class MyDocument {
#Id
private String id;
private String title;
private String description;
private String tagLine;
#CreatedDate
private Date createdDate;
#LastModifiedDate
private Date updatedDate;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getTagLine() {
return tagLine;
}
public void setTagLine(String tagLine) {
this.tagLine = tagLine;
}
}
i have added annotated application with #EnableMongoAuditing
i have created interface which implements mongorepository
public interface MyDocumentRepository extends MongoRepository<MyDocument, String> {
}
when i have created RestController with GET,POST,PATCH methods
in POST I'm sending
{'title':'first'}
Controller Class POST method is
#RequestMapping(value = "/", method = RequestMethod.POST)
public ResponseEntity<?> saveMyDocument(#RequestBody MyDocument myDocument) {
MyDocument doc = myDocumentRepo.save(myDocument);
return new ResponseEntity<MyDocument>(doc, HttpStatus.CREATED);
}
Its saving the data in mongo.
{
"_id" : ObjectId("56b3451f0364b03f3098f101"),
"_class" : "com.wiziq.service.course.model.MyDocument",
"title" : "test"
}
and PATCH request is like
#RequestMapping(value = "/{id}", method = RequestMethod.PATCH)
public ResponseEntity<MyDocument> updateCourse(#PathVariable(value = "id") String id,
#RequestBody MyDocument myDocument) {
myDocument.setId(id);
MyDocument doc = courseService.save(myDocument);
return ResponseEntity.ok(course);
}
when in make PATCH request with data {"description":"This is test"}
it update the docuent BUT it removes title field and createdDate form the document, its doing update which is ok. But i wanted to do an upsert, i can do its using mongoTemplate,
but there i have to set each property which i want to set.
Is there any generic way to that if i get a PATCH request i can update only not null properties.. properties which are coming in request
spring-data-rest seems to do it using #RepositoryRestResource. How can i achieve the same.
I don't want to code like this
Update update = new Update().set("title", myDocument.getTitle()).set("description", myDocument.getdescription());
Unfortunately its the behavior in MongoDB, you can verify the same using shell.
So to update create an Update Object and using
Query query = new Query(Criteria.where("id").is(ID));
Here ID is the document which you want to update.Based on your requirement set upsert after that using findAndModify update document.
mongoTemplate.findAndModify(query, update,
new FindAndModifyOptions().returnNew(true).upsert(false),
someclass.class);
If you have a model like MyModel.class and you need a smooth way to create an Update object from it there is no real clear way how to do this but you can use MongoConverter bean that is created in Spring Data Mongo auto configuration and then just use replaceOne method of MongoCollection.
#Autowired
private MongoTemplate template;
#Autowired
private MongoConverter mongoConverter;
...
#Override
public void upsertMyModel(MyModel model) {
Document documentToUpsert = new Document();
mongoConverter.write(model, documentToUpsert);
template.getCollection(collectionName).replaceOne(
Filters.eq("_id", model.getId()),
documentToUpsert,
new ReplaceOptions().upsert(true));
}
Upsert can be done in Spring data mongodb using BulkOperations.
Suppose there are two entities Entity1 and Entity2. Entity1 has foreginId which is primary id of Entity2. Both have a field title. Now, to upsert from entity2 to entity1, we can do it as follows:
Query query = new Query(Criteria.where("foreignId").is(entity2.getId()));
Update update = new Update();
update.set("title",entity2.getTitle());
List<Pair<Query, Update>> updates = new ArrayList<Pair<Query, Update>>();
updates.add(Pair.of(query, update););
BulkOperations bulkOps = this.mongoTemplate.bulkOps(BulkMode.UNORDERED, Entity1.class);
bulkOps.upsert(updates);
bulkOps.execute();

Cannot delete entity (JPA & Spring)

What ever I try, I cannot delete a user entity when I call delete() from my userService class. I get an exception java.lang.IllegalArgumentException: Entity must be managed to call remove: com.blackbox.genesis.entities.User#30168a, try merging the detached and try the remove again. I'm obviously doing something wrong - despite merging, but I can't see what. Everything else works fine - I can create and update user entities without any problem.
Regards
My entity class;
#Entity
#Table(uniqueConstraints = {#UniqueConstraint(columnNames = "EMAIL")})
public class User implements Serializable {
#Id
#Column(name="username", length=50)
private String username;
#OneToOne(cascade = {CascadeType.ALL})
private Password password;
private boolean enabled;
private int serial;
private String email;
#Version
private int version;
#ElementCollection(targetClass=Authority.class)
#CollectionTable(name="USER_AUTHORITY")
private List<Authority> authorities;
#OneToMany(mappedBy="user", fetch=FetchType.LAZY, cascade=CascadeType.ALL, ``orphanRemoval=true)
private Set<License> licenses;
private static final long serialVersionUID = 1L;
public User() {
super();
this.authorities = new ArrayList<Authority>();
}
.... getters/setters.
My DAO class;
#Repository
public class UserJpaController {
#PersistenceContext
EntityManager em;
protected static final Logger logger = Logger.getLogger("com.blackbox.genesisng.entities.UsersJpaController");
public void create(User user) throws PreexistingEntityException, Exception {
if (findUser(user.getUsername()) != null) {
throw new PreexistingEntityException("Users " + user + " already exists.");
}
em.persist(user);
em.flush();
}
public void edit(User user) throws NonexistentEntityException, Exception {
user = em.merge(user);
em.flush();
}
public void destroy(String id) throws NonexistentEntityException {
User user = em.find(User.class, id);
user = em.merge(user);
em.remove(user);
}
public List<User> findUserEntities() {
return findUserEntities(true, -1, -1);
}
public List<User> findUserEntities(int maxResults, int firstResult) {
return findUserEntities(false, maxResults, firstResult);
}
private List<User> findUserEntities(boolean all, int maxResults, int firstResult) {
Query q = em.createQuery("select object(o) from User as o");
if (!all) {
q.setMaxResults(maxResults);
q.setFirstResult(firstResult);
}
return q.getResultList();
}
public User findUser(String id) {
return em.find(User.class, id);
}
public int getUserCount() {
Query q = em.createQuery("select count(o) from User as o");
return ((Long) q.getSingleResult()).intValue();
}
public User findUserByEmail(String email) {
Query q = em.createQuery("select Object(o) from User as o where o.email = :email");
q.setParameter("email", email);
List list = q.getResultList();
if (list.size() == 0) {
return null;
}
return (User) list.get(0);
}
public boolean exists(String id) {
try {
em.getReference(User.class,id);
return true;
}
catch (EntityNotFoundException e) {
return false;
}
}
}
and finally, the relevant portion of my service class
#Service
public class UserService {
#Autowired
UserJpaController dao;
#Autowired
LicenseJpaController licenseDao;
#Transactional
public void delete(UserDTO userDTO) {
if (exists(userDTO.getUserName())){
try {
dao.destroy(userDTO.getUserName());
} catch (NonexistentEntityException e) {
// ignore as the previous test should prevent this.
}
}
}
So sorry, but I'm an idiot! I was not calling the service class that I thought I was. Fixed that and everything works as expected. Once again, sorry folks.
Regards
Remove the
user = em.merge(user);
statement in your DAO destroy method. I am not sure if it causes the probem, but it is not needed because the user is loaded in the statement before.

Resources