How to update only few fields of entity using JPA and Hibernate? - mysql

I'm using MySQL DB.
My entity for the table is Account with the following fields:
id(long), balance (double), created_on(Date), currency(Enum).
When I'm doing a PUT request to update the account, I pass in the request body JSON.
I want to update, for example, only the balance, but the other columns' values to be saved.
In that case (I'm not passing the currency type) the balance is updated, but the currency has value NULL. Is that because it's enum?
I've tried using #DynamicUpdate annotation, but still, it doesn't have any change.
#RestController
public class AccountController {
#PutMapping("/accounts/{id}")
public void updateAccount(\#PathVariable long id, #RequestBody AccountDto accountDto) {
accountService.updateAccount(id, accountDto);
}
}
I'm using AccountDto (which I pass in the request body) and I'm calling the accountService
public void updateAccount(long id, AccountDto accountDto) {
Account account = accountRepository.getOne(id);
account.fromDto(accountDto);
this.accountRepository.save(account); }),
which calls the AccountRepository
public void fromDto(AccountDto accountDto) {
this.balance = accountDto.getBalance();
this.currency = accountDto.getCurrency();
}
Here is the AccountDto class:
public class AccountDto {
private long id;
#NotNull #PositiveOrZero
private double balance;
#NotNull #Enumerated(EnumType.STRING)
private Currency currency;
}

You need to perform a select query on Account entity and then update only the desired fields.
(Eg - making assumptions of my own of underlying method being used for accessing DB)
public updateAccount(AccountModel jsonBody) {
Account entity = accountRepository.findById(jsonBody.getAccountId());
entity.setBalance(jsonBody.getBalance());
accountRepository.save(entity);
}

If you get null as currency in the JSON you shouldn't update it:
So fromDto must look like:
public void fromDto(AccountDto accountDto) {
this.balance = accountDto.getBalance();
if (accountDto.getCurrency() != null) {
this.currency = accountDto.getCurrency();
}
}

Related

How to run update sql natively and not calling JPA PreUpdate

In my model class Account, in the jpa #PreUpdate I am setting the value of the field lastUpdateDate to the current date and time.
The Class model looks like this:
class Account {
private String m_id;
private String m_fullName;
...
private Date m_indexDate;
private Date m_lastUpdateDate;
#Id
public String getId() {
return m_id;
}
#Audited
public String getFullName() {
return m_fullName;
}
public String getIndexDate() {
return m_indexDate;
}
public String getLastUpdateDate() {
return m_indexDate;
}
...
#PreUpdate
void preUpdate() {
setLastUpdateDate(new Date());
}
}
I also have indexDate which is updated after every successfull sync/update with Elasticsearch.
Now, because of #PreUpdate ... lastUpdateDate always gets updated whenever I update the indexDate via JPA save or update... or even via #Query (native = true).
Is there a way to update indexDate (via native sql update) or other way but I will not hit #PreUpdate so that lastUpdateDate will not get updated?
I want to update indexDate without getting lastUpdateDate updated. #PreUpdate is working fine.. I jsut dont want to call this on indexDate update which is triggered only after successful update/sync with elasticsearch.

How handle currents updates in spring-boot hibernate problem? Also need to make app scalable

Project type :- Spring-boot JPA project
Hi,
I have below Rest service which increments a number in database.
#RestController
public class IncrementController {
#Autowired
MyNumberRepository mynumberRepository;
#GetMapping(path="/incrementnumber")
public String incrementNumber(){
Optional<MyNumber> mynumber = mynumberRepository.findById(1);
int i = mynumber.get().getNumber();
System.out.println("value of no is "+i);
i = i+1;
System.out.println("value of no post increment is "+i);
mynumber.get().setNumber(i);
MyNumber entity = new MyNumber();
entity.setId(1);
entity.setNumber(i);
mynumberRepository.save(entity);
return "done";
}
}
Entity is as below :-
#Entity
#Table(name = "my_number")
public class MyNumber {
#Id
private Integer id;
private Integer number;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
}
Below is the Repository :-
public interface MyNumberRepository extends JpaRepository<MyNumber, Integer>{
}
The service works well when I call increment number sequentially , but when concurrent threads call the incrementservice then i get non consistent results. How can I handle this situation ?
Also have to deploy the app on multiple places and connecting to same DB. i.e Scalability concern.
Thanks,
Rahul
You must use a pessimistic lock. This will issue a SELECT FOR UPDATE and lock the row for the transaction and it's not possible for another transaction to overwrite the row.
public interface MyNumberRepository extends JpaRepository<MyNumber, Integer> {
#Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<MyNumber> findById(Integer id);
}
And then you have to make your REST method transactional by adding #Transactional
#RestController
public class IncrementController {
#Autowired
MyNumberRepository mynumberRepository;
#Transactional
#GetMapping(path="/incrementnumber")
public String incrementNumber(){
Optional<MyNumber> mynumber = mynumberRepository.findById(1);
int i = mynumber.get().getNumber();
System.out.println("value of no is "+i);
i = i+1;
System.out.println("value of no post increment is "+i);
mynumber.get().setNumber(i);
MyNumber entity = new MyNumber();
entity.setId(1);
entity.setNumber(i);
mynumberRepository.save(entity);
return "done";
}
}
Above solution will work , but i feel you are doing over-engineering for very simple problem.
My recommendation would be to use database sequence.I feel your requirement is quite straight forward.In your service u can simply call getnextvalue on the sequence and then set the value in the Id field.This way u don't have to manage locks also as Database will do that for you.
In oracle particularly sequences are managed in a different transactions . So if ur calling code fails with exception , still the value of sequence will be incremented . This will ensure that multi-threads will not see the same value of the sequence in case of exceptions.
Instead of locking transaction, you could also use an Oracle sequence or MySQL "AUTO_INCREMENT" feature which will prevent any ID being returned twice.
https://community.oracle.com/thread/4156674
Thread safety of MySql's Select Last_Insert_ID

Getting AutoIncrement value after saving in Spring

I'm working with Spring Boot 2 (Hibernate, JPA) and MySQL. I need a unique, auto increment custom reference number, for example 18/10/5 (here, 18 is year, 10 is month, 5 is auto increment field). The strategy I used is:
Create an auto increment field. Get the value of auto increment after saving and joining with yy/MM/.
I removed unwanted annotations for easiness.
Model class
#Entity
#Table(name="account")
public class Account{
#Id
private String id;
#GeneratedValue(strategy=GenerationType.AUTO)
private Long autoIncrement;
String refNo;
//Other fields, Constructors, Getters And Setters
}
Then in the controller, first I save, then I get the id of the saved object and trying to update
Controller class
#RestController
#RequestMapping("/account")
public class AccountController {
#PostMapping("/save")
public ModelAndView saveAccount(#ModelAttribute("account") Account account){
//few codes
accountService.save(account) //Saving
accountService.updateRefNo(account.getId()) //try to update
}
}
Service class
#Service
#Transactional
public class AccountServiceImpl implements AccountService{
#Autowired
AccountRepository accountRepository;
//Few methods
public void updateRefNo(String id) {
Account account=findById(id); // return the object
String year = // getting year
String month = //getting month
account.setRefNo(year+"/"+month+"/"+account.getAutoIncrement());
}
}
Data is saved. When I try to update, account.getAutoIncrement() returns null, but its saved in the database. I tried saveAndFlush() also. Why does this happen? Not committed?
in your updateRefNo I dont see save or update call on Account entity
public void updateRefNo(String id) {
Account account=findById(id); // return the object
String year = // getting year
String month = //getting month
account.setRefNo(year+"/"+month+"/"+account.getAutoIncrement());
// update in DB
accountRepository.save(account); or accountRepository.update(account);
}

How to read csv data one by one and pass it in multiple testNG tests

I need to insert a data multiple times in an web application. I am using selenium with testNG along with data driven framework.
I am using CSV file for reading the the input values.
Please find the sample code below.
public class TestData
{
private static String firstName;
public static String lastName;
#BeforeClass
public void beforeClass() throws IOException
{
reader = new CSVReader(new FileReader(fileName));
while((record = reader.readNext()) != null)
{
firstName = record[0];
lastName = record[1];
}
}
#Test
public void test1()
{
driver.findElement(By.id(id)).sendKeys(firstName);
driver.findElement(By.id(id)).click();
and so on....
}
#Test
public void test2()
{
driver.findElement(By.id(id)).sendKeys(lastName);
driver.findElement(By.id(id)).click();
and so on....
}
}
Here, I need to insert 3 records, but when I use the above code, only the 3rd record gets inserted.
Kindly help me to fix this issue.
Sample Input File
What you need here is a Factory powered by a DataProvider. The Factory would produce test class instances (A test class here is basically a regular class that contains one or more #Test methods housed in it). The data provider would basically feed the factory method with the data required to instantiate the test class.
Now your #Test methods would basically work with the data members in the instances to run its logic.
Here's a simple sample that shows this in action.
import org.assertj.core.api.Assertions;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;
public class TestClassSample {
private String firstName;
private String lastName;
#Factory(dataProvider = "dp")
public TestClassSample(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
#DataProvider(name = "dp")
public static Object[][] getData() {
//feel free to replace this with the logic that reads up a csv file (using CSVReader)
// and then translates it to a 2D array.
return new Object[][]{
{"Mohan", "Kumar"},
{"Kane", "Williams"},
{"Mark", "Henry"}
};
}
#Test
public void test1() {
Assertions.assertThat(this.firstName).isNotEmpty();
}
#Test
public void test2() {
Assertions.assertThat(this.lastName).isNotEmpty();
}
}
As per the data given by you , the while loop ends at the third record of CSV file. In each iteration your variables "firstName" and "lastName" are overwritten.
When the loop breaks , the variables store the lastly written values. So , use a better data structure for storing all values. I recommend map.
You can further club all the test cases in a single method , use invocationcount attribute in #Test annotation to repeat the execution for each entry from map. Add one more method with #BeforeTest for increment to next keyset in map.

Mapping BIGDECIMAL from Mongo, lack CONSTRUCTOR

I have a problem when I want get a object from mongo with a BigDecimal field.
I have the next structure in mongo:
{
"_id":ObjectId("546b07420c74bf96c7c3cd5f"),
"accountId":"1",
"modelVersion":"seasonal_optimized",
"yearMonth":"20143",
"income":{
"unscaled":{
"$numberLong":"68500"
},
"scale":2
},
"expense":{
"unscaled":{
"$numberLong":"125900"
},
"scale":2
}
}
And the entity is :
#Data
#NoArgsConstructor
#AllArgsConstructor
#Document(collection = "forecasts")
public class Forecast {
private String accountId;
private LocalDate monthYear;
private String modelVersion;
private BigDecimal income;
private BigDecimal expense;
}
and I'm trying retrieving a object from mongo, but I got the next error:
org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate java.math.BigDecimal using constructor NO_CONSTRUCTOR with arguments.
Anybody can help me?
Thank you!!!!
It's an old question but I had the same issue using Spring data mongo and the aggregation framework.
Specifically when I want a BigDecimal as return type.
A possible workaround is to wrap your BigDecimal in a class and provide it as your return OutPutType, ie:
#Data
public class AverageTemperature {
private BigDecimal averageTemperature;
}
#Override
public AverageTemperature averageTemperatureByDeviceIdAndDateRange(String deviceId, Date from, Date to) {
final Aggregation aggregation = newAggregation(
match(
Criteria.where("deviceId")
.is(deviceId)
.andOperator(
Criteria.where("time").gte(from),
Criteria.where("time").lte(to)
)
),
group("deviceId")
.avg("temperature").as("averageTemperature"),
project("averageTemperature")
);
AggregationResults<AverageTemperature> result = mongoTemplate.aggregate(aggregation, mongoTemplate.getCollectionName(YourDocumentClass.class), AverageTemperature.class);
List<AverageTemperature> mappedResults = result.getMappedResults();
return (mappedResults != null ? mappedResults.get(0) : null);
}
In the example above the aggregation calculates the average temperature as BigDecimal.
Keep in mind that the default BigDecimal converter map it to a string in mongodb when saving. mapping-conversion
Retrieving the document as is should not be a problem anymore with Spring data mongo, versions I used:
org.springframework.boot:spring-boot-starter-data-mongodb:jar:2.0.1.RELEASE
spring-data-mongodb:jar:2.0.6.RELEASE
org.mongodb:mongodb-driver:jar:3.6.3