performance optimization in spring with transaction - java

I am making one enterprise application. In which i have task to parse one csv file that contain suppose 50,000 k records. This csv file supplied by the end user at the time of registration. I made one program to parse the csv file into java object and then save all these object into database. This file contain mobile number and before save the csv file as a java object it firstly validated for mobile number. either it exist on database or not. It it is exist then it fails the validation and stop the exection.
Now Suppose two different user called A and B send a request for registration.Controller listen this request by the following code.
controller layer
#Transactional
#RequestMapping(value = "/saveCsvData")
public ModelAndView saveVMNDataFromCsv(#ModelAttribute("vmn") #Valid VMN vmn, BindingResult result, HttpSession session, HttpServletRequest request) {
String response = parseCsvService.parseAndPersistCsv(vmn.getVmnFile(),vmn.getNumberType(), request);
}
on the controller method i have user #Transactional annotation so that this method can complete its work completely.This controller call the helper call to read line by line from csv file and put them into java object.After getting the list of VMN Object with the help of loop i call service method which again call dao method for ecah line.
helper class
public String parseAndPersistCsv(MultipartFile csvFile,String numberType, HttpServletRequest request){
List<VMN> vmnList = new ArrayList<VMN>();
if(save){
for(VMN vmn : vmnList){
System.out.println("Remote Host :" + request.getRemoteHost());
System.out.println("Remote Add :" + request.getRemoteAddr());
vmnService.saveVmn(vmn, numberType);
}
response = constantService.getSuccess();
}
}
service Layer
public String saveVmn(final VMN vmn, String numberType) {
vmnService.saveVmn(vmn, numberType);
}
At Dao Layer method looks like this. This method insert record into multiple tables as it can be seen in method code.
Dao Layer
public String saveVmn(final VMN vmn, String numberType) {
String result = "error";
try {
final StringBuffer sql = new StringBuffer();
sql.append(constantService.getInsertInto());
sql.append(VMNTableSingleton.getInstance().getTableName());
sql.append(" (");
sql.append(VMNTableSingleton.getInstance().getVmnNo());
sql.append(constantService.getComma());
sql.append(VMNTableSingleton.getInstance().getNumberType());
sql.append(constantService.getComma());
sql.append(VMNTableSingleton.getInstance().getOperator());
sql.append(constantService.getComma());
sql.append(VMNTableSingleton.getInstance().getCircle());
sql.append(constantService.getComma());
sql.append(VMNTableSingleton.getInstance().getBuyingPrice());
sql.append(constantService.getComma());
sql.append(VMNTableSingleton.getInstance().getRecurringPrice());
sql.append(constantService.getComma());
sql.append(VMNTableSingleton.getInstance().getCreationDate());
sql.append(constantService.getComma());
sql.append(VMNTableSingleton.getInstance().getUpdationDate());
sql.append(constantService.getComma());
sql.append(VMNTableSingleton.getInstance().getActive());
sql.append(constantService.getComma());
sql.append(VMNTableSingleton.getInstance().getStatus());
sql.append(")");
sql.append(constantService.getValues());
sql.append(" (?,?,?,?,?,?,?,?,?,?)");
logger.info("Saving Vmn..." + sql);
KeyHolder keyHolder = new GeneratedKeyHolder();
int response = jdbcTemplate.update(new PreparedStatementCreator() {
#Override
public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
PreparedStatement ps = conn.prepareStatement(sql.toString(), Statement.RETURN_GENERATED_KEYS);
ps.setObject(1, vmn.getVmnNo());
ps.setObject(2, vmn.getNumberType());
ps.setObject(3, vmn.getOperator());
ps.setObject(4, vmn.getCircle());
ps.setObject(5, vmn.getBuyingPrice());
ps.setObject(6, vmn.getBuyingPrice());
ps.setObject(7, new Date());
ps.setObject(8, new Date());
ps.setObject(9, true);
ps.setObject(10, vmn.getStatus());
return ps;
}
}, keyHolder);
logger.info("Saved Successfully");
if (response == 1) {
if(vmn.getMappedVmn() != null){
Long vmnId = keyHolder.getKey().longValue();
if(vmnId > 0){
StringBuffer mappedsql = new StringBuffer();
mappedsql.append(constantService.getInsertInto());
mappedsql.append(MapDIDVMNTableSingleton.getInstance().getTableName());
mappedsql.append(" (");
mappedsql.append(MapDIDVMNTableSingleton.getInstance().getDidId());
mappedsql.append(constantService.getComma());
mappedsql.append(MapDIDVMNTableSingleton.getInstance().getMappedId());
mappedsql.append(constantService.getComma());
mappedsql.append(MapDIDVMNTableSingleton.getInstance().getType());
mappedsql.append(constantService.getComma());
mappedsql.append(MapDIDVMNTableSingleton.getInstance().getCreationDate());
mappedsql.append(constantService.getComma());
mappedsql.append(MapDIDVMNTableSingleton.getInstance().getModifiedDate());
mappedsql.append(")");
mappedsql.append(constantService.getValues());
mappedsql.append(" (?,?,?,?,?)");
logger.info("Mapping... DID with VMN");
int mappedresponse = jdbcTemplate.update(mappedsql.toString(),
new Object[] {vmn.getMappedVmn().getVmnId(),vmnId ,vmn.getNumberType(),new Date(),new Date()});
logger.info("Mapped Successfully");
if(mappedresponse == 1){
stringBuffer updatesql = new StringBuffer();
updatesql.append(constantService.getUpdate());
updatesql.append(VMNTableSingleton.getInstance().getTableName());
updatesql.append(constantService.getSet());
updatesql.append(VMNTableSingleton.getInstance().getStatus());
updatesql.append(constantService.getEqual());
updatesql.append(constantService.getQuestionMark());
updatesql.append(constantService.getComma());
updatesql.append(VMNTableSingleton.getInstance().getAllocationDateTime());
updatesql.append(constantService.getEqual());
updatesql.append(constantService.getQuestionMark());
updatesql.append(constantService.getWhere());
updatesql.append(VMNTableSingleton.getInstance().getVmnId());
updatesql.append(constantService.getEqual());
updatesql.append(constantService.getQuestionMark());
logger.info("Updating Vmn..." + updatesql);
jdbcTemplate.update(updatesql.toString(),
new Object[] { constantService.getMapped(),new Date(), vmn.getMappedVmn().getVmnId()});
logger.info("Saved Successfully");
}
}
}
result = "success";
} else {
result = "error";
}
} catch (Exception ex) {
}
return result;
}
Now when i have sent to request toward controller for registration. then i have seen on console both thread accessing method one by by one. By this code inside for loop.
System.out.println("Remote Host :" + request.getRemoteHost()); //10.0.0.0114
System.out.println("Remote Add :" + request.getRemoteAddr());//110.0.0.115
Not this can be dirty read problem because if one thread is reading data then other might be inserting. so to resolve this i have used sync block. Like this
synchronized (this) {
if(save){
for(VMN vmn : vmnList){
System.out.println("Remote Host :" + request.getRemoteHost());
System.out.println("Remote Add :" + request.getRemoteAddr());
vmnService.saveVmn(vmn, numberType);
}
response = constantService.getSuccess();
}
}
Now my question is, is this is the right way to do this or it can be done in some other way too.

this can be dirty read problem because if one thread is reading data
then other might be inserting
If your concern is visibility then I think this can be taken care of by transaction isolation configured ( usually READ COMMITTED by default) where a thread can only see the committed data and the data it is trying to update.
Also you should consider using batchUpdate method of JDBCTemplate which uses jdbc batching feature. Read and check the existence of number in batches and then update in batches.
Typically such features are handled well by Spring Batch framework. Your use case fits the description of a Spring job which can utilize inbuilt csv readers, processors and chunk based processing to achieve high volume processing of data. The job can be triggered from UI using Spring MVC controllers. For more details you can take a look here.

Related

Can we use cosmosContainer.queryItems() method to execute the delete query on cosmos container

I have a Java method in my code, in which I am using following line of code to fetch any data from azure cosmos DB
Iterable<FeedResponse<Object>> feedResponseIterator =
cosmosContainer
.queryItems(sqlQuery, queryOptions, Object.class)
.iterableByPage(continuationToken, pageSize);
Now the whole method looks like this
public List<LinkedHashMap> getDocumentsFromCollection(
String containerName, String partitionKey, String sqlQuery) {
List<LinkedHashMap> documents = new ArrayList<>();
String continuationToken = null;
do {
CosmosQueryRequestOptions queryOptions = new CosmosQueryRequestOptions();
CosmosContainer cosmosContainer = createContainerIfNotExists(containerName, partitionKey);
Iterable<FeedResponse<Object>> feedResponseIterator =
cosmosContainer
.queryItems(sqlQuery, queryOptions, Object.class)
.iterableByPage(continuationToken, pageSize);
int pageCount = 0;
for (FeedResponse<Object> page : feedResponseIterator) {
long startTime = System.currentTimeMillis();
// Access all the documents in this result page
page.getResults().forEach(document -> documents.add((LinkedHashMap) document));
// Along with page results, get a continuation token
// which enables the client to "pick up where it left off"
// in accessing query response pages.
continuationToken = page.getContinuationToken();
pageCount++;
log.info(
"Cosmos Collection {} deleted {} page with {} number of records in {} ms time",
containerName,
pageCount,
page.getResults().size(),
(System.currentTimeMillis() - startTime));
}
} while (continuationToken != null);
log.info(containerName + " Collection has been collected successfully");
return documents;
}
My question is that can we use same line of code to execute delete query like (DELETE * FROM c)? If yes, then what it would be returning us in Iterable<FeedResponse> feedResponseIterator object.
SQL statements can only be used for reads. Delete operations must be done using DeleteItem().
Here are Java SDK samples (sync and async) for all document operations in Cosmos DB.
Java v4 SDK Document Samples

How to test roll over feature in mongo db transactions

I am newbie to MongoDB i implemented transactional feature in one of my application, as per my requirements i need to persist data into different collections in the same database. Below is the code snippet for the same
In Tuple3 first element is database, second element is collection and third element is data i want to persist which is coming as json string which i am converting to bson document
ClientSession clientSession = mongoClient.startSession();
try {
clientSession.startTransaction(transactionOptions);
for (Tuple3<String, String, String> value: insertValues) {
MongoCollection<Document> collection = mongoClient
.getDatabase(insertValues.f0)
.getCollection(insertValues.f1);
Document data= Document.parse(insertValues.f2);
log.info(String.format("Inserting data into database %s and collection is %s", insertValues.f0, insertValues.f1));
collection.insertOne(clientSession, data);
clientSession.commitTransaction();
}
} catch (MongoCommandException | MongoWriteException exception) {
clientSession.abortTransaction();
log.error(String.format("Exception happened while inserting record into Mongo DB rolling back the transaction " +
"and cause of exception is: %s", exception));
} finally {
clientSession.close();
}
Below are transaction options i am using
TransactionOptions transactionOptions = TransactionOptions.builder().readConcern(ReadConcern.LOCAL).writeConcern(WriteConcern.W1).build();
Below is MongoClient method with MongoClientOptions i am taking Mongo DB Connection string as input to this method
public MongoClient getTransactionConnection(String connectionString) {
MongoClientOptions.Builder mongoClientOptions = new MongoClientOptions.Builder()
.readConcern(ReadConcern.LOCAL)
.writeConcern(WriteConcern.W1)
.readPreference(ReadPreference.primary())
.serverSelectionTimeout(120000)
.maxWaitTime(120000)
.connectionsPerHost(10)
.connectTimeout(120000);
MongoClientURI uri = new MongoClientURI(connectionString, mongoClientOptions);
return new MongoClient(uri);
}
Till here it is good and it is inserting data to three different collection under the specified database. But when i try to some negative scenario i am trying to throw exception in try block which ideally should rollback the data for that particular client session if any error happens.
I am trying to throw exception by using count variable which will increment and for if count value is equal to 1 i am throwing exception which should abort the transaction and rollback if any data is written to database but what i am seeing it is writing to one of the collection and throws exception after that stops the program but it is not rolling back the data written to collection actually. I am trying something like this below
ClientSession clientSession = mongoClient.startSession();
int count = 0;
try {
clientSession.startTransaction(transactionOptions);
for (Tuple3<String, String, String> value: insertValues) {
MongoCollection<Document> collection = mongoClient
.getDatabase(insertValues.f0)
.getCollection(insertValues.f1);
Document data= Document.parse(insertValues.f2);
log.info(String.format("Inserting data into database %s and collection is %s", insertValues.f0, insertValues.f1));
collection.insertOne(clientSession, data);
if(count == 1){
throw new MongoException("Aborting transaction.....");
}
count++;
clientSession.commitTransaction();
}
} catch (MongoCommandException | MongoWriteException exception) {
clientSession.abortTransaction();
log.error(String.format("Exception happened while inserting record into Mongo DB rolling back the transaction " +
"and cause of exception is: %s", exception));
} finally {
clientSession.close();
}
I am not sure where i am going wrong i am using Mongo DB version 4.0 deployed using Azure CosmosDB Api. Please help me in resolving this issue thanks in advance.
Cosmos DB does not have transaction support outside of a single partition (shard) of a single collection. This limitation exists regardless of API in use (in your case, MongoDB API). This is why you're not seeing the behavior you're expecting. Note: this is mentioned in the Cosmos DB MongoDB compatibility docs.
You'll need to come up with your own implementation for managing data consistency within your app.

How to count number of clicks on button and save in MySql database? The clicks in a new session should be added to the existing value

This is the DAO I have created:
public Poll updatePoll(int id){
Session s = factory.getCurrentSession();
Transaction t = s.beginTransaction();
Poll poll = (Poll) s.get(Poll.class, id);
Citizen citizen = (Citizen) s.get(Citizen.class, 1);
List<Poll> list = citizen.getPolledList();
boolean check = list.contains(poll);
if(!check){
Query q = s.createSQLQuery("update Poll set poll_count = poll_count + 1 where poll_id = id");
q.executeUpdate();
s.update(poll);
}else{
return poll;
}
s.close();
return poll;
}
This is the Action created:
public String submitVote(){
ServletContext ctx = ServletActionContext.getServletContext();
ProjectDAO dao = (ProjectDAO)ctx.getAttribute("DAO");
Poll poll = dao.updatePoll(poll_id);
String flag = "error";
if (poll != null){
ServletActionContext.getRequest().getSession(true).setAttribute("POLL", poll);
flag = "voted";
}
return flag;
}
I know I have been going horribly wrong and the code I'm posting might be utter rubbish. But I hope the intent is clear, thus if possible please lent me a helping hand. My project is mainly in JSP (Struts 2), jQuery and MySQL 5.1, so please do not suggest PHP codes as I've found earlier.
The framework is used to wrap the servlet stuff from user, you should use its features if you want doing something like
ServletActionContext.getRequest().getSession(true)
But
Map m = ActionContext.getContext().getSession();

What is other better way to send response and print data in table structure?

I am new to ajax, I am making ajax call, it will hit servlet, it fetches data and prints data to jsp using out.println(). It is working fine but I feel its not good way. Here is my coding ,
Ajax call,
xmlHttpReq.open('GET', "RTMonitor?rtype=groups&groupname="+temp, true);
In RTMonitor servlet I have,
sql ="SELECT a.vehicleno,a.lat,a.lng,a.status,a.rdate,a.rtime from latlng a,vehicle_details b where a.vehicleno=b.vehicleno and b.clientid="+clientid +" and b.groupid in(select groupid from group_details where groupname='"+gname+"' and clientid='"+clientid+"')";
resultSet = statement.executeQuery(sql);
while(resultSet.next())
{
response.setContentType("text/html");
out.println("<tr>"+
"<td>"+"&nbsp"+"&nbsp"+resultSet.getString("vehicleno")+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"&nbsp"+"<br>"+"<br>"+"</td>"+);
//other <td>s
}
I think this is not good way. So I think about returning response as JSON object. Tell me how to return object as JSON and set values in <td>. and tell me JSON is a good way , or is there any other way please suggest me.
Object to JSON -conversion is explained in question: Converting Java Object to Json using Marshaller
Other things:
Your SQL is unsafe! Please refer to the following question that explains prepared statements and has examples too: Difference between Statement and PreparedStatement
Generally you should not write your low-level AJAX-code by yourself unless you are aiming to learn things. There are many cross browser functioning Javascript libraries that provide these things in a robust manner, such as JQuery. JQuery's API has the getJSON which you will undoubtedly find very useful (API doc):
var params = {myObjectId: 1337}; // just some parameters
$.getJSON( "myUrl/myAjaxAction", params, function( data ) { /* this is the success handler*/
alert(data.myObject.name); // assuming that returned data (JSON) is: {myObject: {name: 'Hello World!'}}
});
You should try to avoid as much as possible mixing server-side code with client-side code.
Your client-side code should only offer a nice and rich user interface, by manipulating the data which is provided by the server. The server-side code should only process the data - coming from different calls, or taken from a storage, usually a database.
Usually the comunication( asynchrounous or not) from a client and a server goes like that :
client sends a request to the server
server process the request and it gives a response, usually some html or json/xml
client process the response from the server
Ok, now lets move our attention to your specific problem.
Your ajax call : xmlHttpReq.open('GET', "RTMonitor?rtype=groups&groupname="+temp, true); should send the request to the servlet and expect some data back to process and render in a nice way to the user. Your servlet should handle the request, by querying the database( you should definitely change your code so it uses prepared statements as they are preventing SQL injection). By doing so, you've separate your client-side code from server-side code.
private List<YourObject> loadObjectsBySomeLogic() throws SQLException{
String sql ="SELECT a.vehicleno,a.lat,a.lng,a.status,a.rdate,a.rtime FROM latlng a,vehicle_details b WHERE a.vehicleno=b.vehicleno AND b.clientid= ? AND b.groupid in(select groupid from group_details where groupname= ? and clientid= ?)";
List<YourObject> list = new ArrayList<YourObject>();//new ArrayList<>(); for Java versions > 1.7
PreparedStatement ps = null;
ResultSet rs = null;
try{
ps = connection.prepareStatement(sql);
ps.setLong(1, clientId);
ps.setString(2, gname);
ps.setLong(3, clientId);
rs = ps.executeQuery();
while(rs .next())
{
//load data from ResultSet into an object/list of objects
}
}finally{
closeResources(rs , ps);
}
return list;
}
private static final void closeResources(final ResultSet rs , final PreparedStatement ps){
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
//nasty rs. log the exception?
LOGGER.error("Could not close the ResultSet!" , e);
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
//nasty ps. log the exception?
LOGGER.error("Could not close the PreparedStatement!" , e);
}
}
}
You could delegate this method to a different object, which handles the business/aplication domain logic, but that's not our point in this case.
You can use Json for your data format, because it has a nice, and easy to understand way to format data, and it is more lightweight compared to XML. You can use any Java library to encode data as Json. I'll provide an example which uses Gson library.
List<YourObject> list = loadObjectsBySomeLogic();
String json = new Gson().toJson(list);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(json);
Now your Ajax request, should handle the Json data coming from server( I recommend you to use jQuery to make Ajax calls as it's been tested and it works great on all major browsers).
$.get('RTMonitor', function(responseJson) {
//handle your json response by rendering it using html + css.
});

Deleting from a Set in Google App Engine is not being persisted

I see a similar question in Problems while saving a pre-persisted object in Google App Engine (Java), and indeed I was not calling close() on my persistence manager. However, I am now calling close, but my object update is not being persisted. Specifically, I want to remove an element from a Set, and save that smaller set. Here is the persistence manager related code, that doesn't throw an exception, but doesn't save my data:
UserService userService = UserServiceFactory.getUserService();
User user = userService.getCurrentUser();
PersistenceManager pm = PMF.get().getPersistenceManager();
UserProfileInfo userProfile = pm.getObjectById(UserProfileInfo.class,user.getUserId());
int presize = userProfile.getAccounts().size();
AccountInfo ai = userProfile.removeAccount(id);
int postsize = userProfile.getAccounts().size();
UserProfileInfo committed = (UserProfileInfo)pm.makePersistent(userProfile);
int postcommitsize = committed.getAccounts().size();
pm.close();
And here is the relevant part of the UserProfileInfo class:
#PersistenceCapable(identityType = IdentityType.APPLICATION)
class UserProfileInfo {
#Persistent
private Set<AccountInfo> accounts;
public AccountInfo removeAccount(Long id) throws Exception {
Iterator<AccountInfo> it = accounts.iterator();
StringBuilder sb = new StringBuilder();
while(it.hasNext()) {
AccountInfo acctInfo = it.next();
Long acctInfoId = acctInfo.getId();
if(acctInfoId.equals(id)) {
it.remove();
return acctInfo;
}
sb.append(" ");
sb.append(acctInfoId);
}
throw new Exception("Cannot find id " + id + " Tried " + sb.toString());
}
}
So it looks like the answer is owned objects cannot use a Long primary key. The datanucleus enhancer told me this for another object type I added. I'm not sure why it skipped this warning for my AccountInfo object.
I switched my key over to a String, and changed the annotations to use the string properly, and now I'm able to delete from the collection.
I'd have thought that the first thing to do when debugging anything would be to look at the log (DEBUG level). It tells you what states the objects are in at the different points. So what state is it in when you call makePersistent() ? and after ? and what happens when you call pm.close() ...

Categories