OptimisticLockException Ebean even with #Version - java

I tried to update a row in my DB using Ebean in my Play! Framework program.
Here is the class of the entity I would like to update.
Transaction.java
#Entity
#Table(name = "transactions")
public class Transaction extends Model{
#Id
#GeneratedValue
public int id;
#OneToOne
#JoinColumn(name = "car_fk")
public Car car;
#OneToOne
#JoinColumn(name = "user_lender_fk")
public User user;
#Version
public Timestamp from_date;
#Version
public Timestamp to_date;
public boolean availability; // true -> Available.
public String status;
}
And here is the metho I use to update it:
Transaction transaction = new Transaction();
transaction.car = concernedCars.get(i);
transaction.user = currentUser;
transaction.from_date = Tools.StringAndroidToTimestamp(dateFrom);
transaction.to_date = Tools.StringAndroidToTimestamp(dateTo);
transaction.status = Constants.WAITING_FOR_ANSWER;
try{
Ebean.update(transaction);
}catch(OptimisticLockException e){
Logger.info(e.toString());
}
And if necessary, my method to convert a String to Timestamp:
public static Timestamp StringAndroidToTimestamp(String s){
String toConvert = s.substring(0, s.length()-2);
Logger.info("ToConvert = "+toConvert);
Timestamp timestamp = null;
try{
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date parsedDate = dateFormat.parse(toConvert);
timestamp = new Timestamp(parsedDate.getTime());
}catch(Exception e){
Logger.info("Exception date = " +e.toString());
}
return timestamp;
}
Of course, I get the fabulous error:
javax.persistence.OptimisticLockException: Data has changed. updated
[0] rows sql
What did I do wrong?

There are few ways you can handle this.
1) Use #EntityConcurrencyMode(ConcurrencyMode.NONE) before class name
2) Use raw update query.(preferred)
I was facing too much problem because of the same ebean.update throwing optimistic lock exception, finally I did raw update query and it worked for me.

I can see some problems from here:
You are using two Version fields. I don't know if that is alright to Ebean but usually one is enough. Also I think Ebean will manage those fields by itself, so it may be better for you to specify a version field that you don't want to use (an int to serve as a counter or a timestamp of last change);
You are calling update but it seems you are really creating a new transaction, so you should be using Ebean.save
Regarding the error, that exception is thrown when you try to update a record that has changed between the time you loaded it and the update. To find out that the record has changed, Ebean uses the values from the Version columns.
So the update in your code would generate a SQL similar to this:
UPDATE transactions
SET car_fk=<SOME_VAL>, ...
WHERE id=null AND from_date=<OTHER_VAL> AND to_date=<ANOTHER_VAL>
which wouldn't update any record and throw that exception.
I'm posting this information because I don't know a easy way to disable optimistic locking in the Ebean version that comes with Play <2.4.x and you probably will find that error again. In general you can minimize it using a version field and using transactions or reload/retry the operation.

I have an alternative solution to this problem
you can use:
#EntityConcurrencyMode(ConcurrencyMode.NONE)
in your Entity Class
This will disable the optimistic locking concurrent modification check
the new sql query will be:
update TABLE-NAME SET PARAM1=? WHERE ID = ?
the EntityConcurrencyMode comes in package
package com.avaje.ebean.annotation;

Related

Codec not found for requested operation: [TEXT <-> java.time.LocalDate]

This is the code which I am using to fill the column in the db.
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS");
JSONObject publishedObj = jsonObject.optJSONObject("created");
if(publishedObj != null){
String dateStr = publishedObj.getString("value");
book.setPublishedDate(LocalDate.parse(dateStr,dateFormat));
}
Below is the instance variable of the column where the data needs to go:
#Column("published_date")
#CassandraType(type = CassandraType.Name.DATE)
private LocalDate publishedDate;
Error Message which i am getting:
com.datastax.oss.driver.api.core.type.codec.CodecNotFoundException: Codec not found for requested operation: [TEXT <-> java.time.LocalDate]
Can please someone help.
Thankyou!!
I can reproduce that error with your code above. To remedy it, I have ALTERed the book_by_id table with two new columns:
ALTER TABLE book_by_id ADD pubdate2 TEXT;
ALTER TABLE book_by_id ADD pubdate3 DATE;
My BookEntity class for those columns looks like this:
#Column("pubdate2")
#CassandraType(type = CassandraType.Name.TEXT)
private String publishedDate2;
#Column("pubdate3")
#CassandraType(type = CassandraType.Name.DATE)
private LocalDate publishedDate3;
The code to parse and set the date looks like this:
DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS");
String dateStr = "2022-03-03T09:52:33.235555";
musicBook.setPublishedDate2(LocalDate.parse(dateStr,dateFormat).toString());
musicBook.setPublishedDate3(LocalDate.parse(dateStr,dateFormat));
template.insert(musicBook);
tl;dr;
Redefine published_date as a DATE type, and it will work. Besides, dates/times should be stored in date/time types in databases.
Note that Cassandra won't allow you to modify a column's data type. Also, the process of dropping and adding a column with the same name in quick succession has proven to be problematic with Cassandra in the past. I'd advise adding a newly named column of a DATE type, and reloading its data. Or recreate the table (with the correct data types) and reload the data.

ERROR: duplicate key value violates unique constraint error JPA spring boot

I have two tables notification and message.
Message.java
...
...
#Table(name = "message",
uniqueConstraints = {#UniqueConstraint(name = "UniqueMessage",
columnNames = { "message_id" })})
public class Message implements Serializable {
#Id
#Column(name = “message_id")
private int messageId;
#Column(name = "description")
private String description;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "start_time")
private Date startTime;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "end_time")
private Date endTime;
#PrePersist
#PreUpdate
public void generateMessageId(){
this.messageId = Math.abs(Objects.hash(this.getDescription(),
this.getStartTime().getTime() / 1000));
}
My use case is -- I will be creating a message with certain description and start time. If I get same message again, I should be able to update the endTime. MessageId is calculated separately under method generateMessageId as this will act as an identifier to find if message has already been saved. If yes, I will update the message. It works well for first message but when I try to save again with updated endTime, I get
ERROR: duplicate key value violates unique constraint “message_pkey”
Steps:
I tried:
Message m1 = new Message();
m1.setDescription(“fake”);
m1.setStartTime(“2022-01-03T12:05:00”)
messageRepository.save(m1);
This works well. I can see a row in database with
Message_id description start_time end_time
46536723 fake 2022-01-03T12:05:00 null
Message m2 = new Message();
m2.setDescription(“fake”);
m2.setStartTime(“2022-01-03T12:05:00”);
m2.setEndTime(“2022-01-05T12:00:00)
messageRepository.save(m2);
I get ERROR: duplicate key value violates unique constraint error.
Isn’t jpa should find the existing id and do an update to the row instead of inserting?
Please suggest if this method looks like a work-around.
You are trying to update the message endTime, First time, it works because there is no entry of that message now when you are trying to update but you are not setting the id by default id is zero and if Id is zero it will try to save the message instead of updating.
For resolving this issue you can check (by finding the message by message because the message is unique) if that message is present then update the endTime.

Call transactional method Play Java JPA Hibernate

I have 2 database one is mysql and other is postgree.
I tried to get postgree data from mysql transactional method.
#Transactional(value = "pg")
public List<String> getSubordinate(){
Query q1 = JPA.em().createNativeQuery("select vrs.subordinate_number, vrs.superior_number\n" +
"from view_reporting_structure vrs\n" +
"where vrs.superior_number = :personel_number");
q1.setParameter("personel_number","524261");
List<String> me = q1.getResultList();
return me;
}
}
from another method
#Transactional
public Result getOpenRequestList(){
Subordinate subordinate = new Subordinate();
List<String> subordinateData = subordinate.getSubordinate();
....
}
i got error
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'db_hcm.view_reporting_structure' doesn't exist
so my Postgre method recognized as mySQL transaction which is the view not exist in mySQL database. how do I get data from different presistence unit with 1 method?
I never did it (different databases), but I guess the following may work.
For example, you have the following data source definition in application.conf:
# MySql
db.mysql.driver=com.mysql.jdbc.Driver
... the rest of setting for db.mysql
# H2
db.postgre.driver=org.postgresql.Driver
... the rest of setting for db.postgre
Instead of using #Transactional annotation, manage a transaction explicitly and use JPA withTransaction API:
private static final String MYSQL_DB = "mysql";
private static final String POSTGRE_DB = "postgre";
public List<String> getSubordinate() {
JPA.withTransaction(MYSQL_DB, true/* this is read-only flag*/,
() -> {
Query q1 = JPA.em().createNativeQuery("select vrs.subordinate_number, vrs.superior_number\n" +
"from view_reporting_structure vrs\n" +
"where vrs.superior_number = :personel_number");
q1.setParameter("personel_number","524261");
List<String> me = q1.getResultList();
return me;
}
}
public Result getOpenRequestList(){
JPA.withTransaction(POSTGRE_DB, true/* this is read-only flag*/,
() -> {
Subordinate subordinate = new Subordinate();
List<String> subordinateData = subordinate.getSubordinate();
....
}
}
Note: I prefer always use withTransaction, since it allows better control of unhappy flow. You should wrap the call with try-catch. If JPA throws a run-time exception on commit, you can do proper error handling. In case of using #Transactional annotation, commit takes place after controller have finished and you cannot handle the error.

Hibernate not deleting/updating one to many

I am trying to learn how to work with hibernate, and until now i thought i was doing ok...
The problem is, i have a one to many relationship that i can't update/delete.
My DB is pretty basic, i have a ClientsBasic that has a one to many relationship with IndirectClients (which simply has a ClientsBasic ID and a URL, both keys because you can have for the same ID lots of URLs)
ClientBasic:
#OneToMany(fetch = FetchType.EAGER, mappedBy = "clientsBasic", cascade=CascadeType.ALL)
public List<IndirectClients> getIndirectClients() {
return this.indirectClients;
}
public void setIndirectClients(List<IndirectClients> indirectClients) {
// this.indirectClients = indirectClients;
this.indirectClients.clear();
this.indirectClients.addAll(indirectClients);
}
ClientDao:
public ClientsBasic save(ClientsBasic client) throws HibernateException {
Transaction tx = null;
tx = session.beginTransaction();
session.saveOrUpdate(client);
tx.commit();
log.info("Client saved with id: " + client.getClientId());
return client;
}
Now if i try to delete ClientsBasic, it will delete both ClientsBasic and all related indirectClients, so its working as expected, but if i simply try to update/delete and entry in indirectClients it doesn't work.
Example:
I create a new Client
ClientsBasic cb = new ClientsBasic("company_1", 1234, "company_1#email.com");
cbDao.save(cb);
And then a new Indirect Client
List<IndirectClients> indirectClientsSet= new ArrayList<IndirectClients>();
indirectClientsSet.add(new IndirectClients(new IndirectClientsId(cb.getClientId(), "www.url.test_1.com"), cb));
cb.setIndirectClients(indirectClientsSet);
cbDao.save(cb);
Now if i try to change the url like this
ClientsBasic cb = cbDao.findClientById(1);
List<IndirectClients> indC = cb.getIndirectClients();
indC.get(0).getId().setUrl("TEST");
cb.setIndirectClients(indC);
cbDao.save(cb);
no changes are made in the DB.
Can someone please help me?
Thank you.
If your IndirectClients is defined as an Entity it has its own life cycle, meaning you have to persist/delete instances separately from their ClientBasic parent.
If you want a scenario where all children are managed through their parent relation, consider using ElementCollection.
See also JPA: When to choose Multivalued Association vs. Element Collection Mapping

Spring Data MongoDB: BigInteger to ObjectId conversion

I have a problem with update query using Spring Data MongoDB. I retrieve some object's _id as BigInteger value. Then I want to make following query:
Query query = new Query(Criteria.where("_id").is(id));
Update update = new Update();
update.set("version",version);
mongoOperations.updateFirst(query, update, Audit.class);
Query part fails to match any documents since id value passed to is() somehow must be converted to ObjectId. I can't find any documentation on this kind of conversion. Will appreciate any help.
p.s.: SpringData Mongodb version 1.2
You can convert it also manually:
ObjectId convertedId = new ObjectId(bigInteger.toString(16));
Query query = new Query(Criteria.where("_id").is(convertedId));
You probably want to write a custom Spring converter BigInteger => ObjectId and ObjectId => BigInteger.
See the doc part here:
http://static.springsource.org/spring-data/data-document/docs/current/reference/html/#d0e2670
------UPDATE------
It seems that this kind of converter already exists in the Spring-Data-MongoDB library:
http://static.springsource.org/spring-data/data-document/docs/1.0.0.M1/api/org/springframework/data/document/mongodb/SimpleMongoConverter.ObjectIdToBigIntegerConverter.html
So you just have to specify it in your Spring configuration.
Alternatively you can add an 'id' field to your collection classes or potentially a base class and annotate it with org.springframework.data.annotation.Id, as below:
import org.springframework.data.annotation.Id;
public abstract class BaseDocument {
#Id
protected long id;
This will allow you to perform the queries of the form:
public boolean doesDocumentExist(Class clazz, long documentId) {
Query queryCriteria = new Query(Criteria.where("id").is(documentId));
return mongoTemplate.count(queryCriteria, clazz) == 1;
}
Annotating your own id field with '#Id' will store your id as the mongo objectId, therefore saving you from doing the conversion yourself.
//get the converter from the mongoTemplate
MappingMongoConverter converter = (MappingMongoConverter)mongoTemplate.getConverter();
//get the conversion service from the mongo converter
ConversionService conversionService = converter.getConversionService();
//iterate the status list and get the each id to add the arraylist
for(Status status: statusList){
ObjectId objectIdVal = conversionService.convert(status.getId(), ObjectId.class);
**//here status.getId() returns the BigInteger**
statusArrayList.add(objectIdVal);
}
//get the users list whose status is active and cancel
query.addCriteria(new Criteria().where("status.$id").in(statusArrayList));
List<User> usersList = mongoTemplate.find(query, User.class);
You can convert a BigIngeter to ObjectId using the hex representation of the BigInteger. However, an ObjectId is supposed to be exactly 24 characters long, and parsing a shorter string will fail in Java. Thus it's better to ensure that the hex representation is 0-padded appropriately:
String hexString24 = StringUtils.leftPad(bigInteger.toString(16), 24, "0");
ObjectId convertedId = new ObjectId(hexString24);
Query query = new Query(Criteria.where("_id").is(convertedId));

Categories

Resources