I've a JPA Entity which has a unique constraint based on multiple columns, I'm trying to handle and generate a human friendly error message but somehow spring/hibernate is stealing the exception from me
#Entity
#Table(name = "columns", uniqueConstraints = {#UniqueConstraint(columnNames = {"position", "board_id"}, name = UNIQUE_COLUMN_BOARD_POSITION)})
public class Column {
//some stuff
}
Then i have the service class
#Transactional
#NonNull
public Column update(long columnId, #NonNull Column transientColumn) {
Column persistentColumn = get(columnId);
persistentColumn.setTitle(transientColumn.getTitle());
persistentColumn.setPosition(transientColumn.getPosition());
try {
return columnRepository.save(persistentColumn);
} catch (Exception e) {
System.out.println(e); //NEVER REACHES HERE
}
return transientColumn;
}
I'm writing my test clases so I autowired the service class, I intentionaly try to update a column to break the unique contraint, hibernate stops the update throw a giant exception... BUT WONT LET ME CAPTURE THE EXCEPTION
I've debugged line by line, the code reaches the save line, then spring/hibernate code starts to run, after that the exception is thrown but my catch clause never gets it and the test finishes
here is the test class
#Test
void testFailUpdateDueRepeatedPosition() {
Column column = new Column(persistentBoard, "column", 1);
Column column2 = new Column(persistentBoard, "column", 2);
column = columnRepository.save(column);
column2 = columnRepository.save(column2);
column2.setPosition(1);
columnService.update(column2.getId(), column2);
columnRepository.delete(column);
}
I don't care if I can't catch this exception all i want is to have a custom message which i can set in case it happens
Related
I have a class that tests adding a group to a database:
class GroupDAOTest extends TestCase {
private IDatabaseTester databaseTester;
private GroupDao groupDao;
#BeforeEach
protected void setUp() throws Exception
{
databaseTester = new JdbcDatabaseTester("org.postgresql.Driver",
"jdbc:postgresql://localhost:5432/database_school", "principal", "school");
String file = getClass().getClassLoader().getResource("preparedDataset.xml").getFile();
IDataSet dataSet = new FlatXmlDataSetBuilder().build(new File(file));
databaseTester.setDataSet(dataSet);
databaseTester.setSetUpOperation(DatabaseOperation.CLEAN_INSERT);
databaseTester.onSetup();
groupDao = new GroupDao();
}
#Test
void add() throws Exception {
groupDao.save(new Group("NEW_GROUP"));
IDataSet databaseDataSet = databaseTester.getConnection().createDataSet();
ITable actualTable = databaseDataSet.getTable("groups");
String file = getClass().getClassLoader().getResource("GroupDao/add.xml").getFile();
IDataSet expectedDataSet = new FlatXmlDataSetBuilder().build(new File(file));
ITable expectedTable = expectedDataSet.getTable("groups");
Assertion.assertEquals(expectedTable, actualTable);
}
And here is the method "groupDao.save (new Group (" NEW_GROUP "));" must add a group with id = 4, name = "NEW_GROUP". Once the test passed, but when I ran it again and again, the group was added, but for some reason the id grew by one. And for some launch it was already like this:
[![enter image description here][1]][1]
Checked groupDao.save () - everything is fine, tried changing databaseTester.setSetUpOperation (DatabaseOperation ***), but it didn't help.
Can you tell me where the problem is, maybe I'm just not clearing something?
And just in case my dao method:
#Override
public void save(Group group) {
try (Connection connection = connectionProvider.getConnection();
PreparedStatement statement = connection.prepareStatement(SAVE_NEW_RECORD)) {
statement.setString(1, group.getName());
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
And table schema:
CREATE TABLE groups
(
group_id serial PRIMARY KEY,
group_name VARCHAR(10) UNIQUE NOT NULL
);
Once the test passed, but when I ran it again and again, the group was added, but for some reason the id grew by one.
The issue is not with your code or configuration. PostgreSQL serial field type is auto-increment. It adds 1 to the field value each time saving row to the table.
Use the dbUnit ValueComparer assertion instead to compare with greater than or equal to instead of the assertion method you currently using which compares only on equality.
http://dbunit.sourceforge.net/datacomparisons/valuecomparer.html
I have a method in CDI bean which is transactional, on error it creates an entry in database with the exception message. This method can be called by RESTendpoint and in multithread way.
We have a SQL constraint to avoid duplicity in database
#Transactional
public RegistrationRuleStatus performCheck(RegistrationRule rule, User user) {
try {
//check if rule is dependent of other rules and if all proved, perform check
List<RegistrationRule> rules = rule.getRuleParentDependencies();
boolean parentDependenciesAreProved = true;
if (!CollectionUtils.isEmpty(rules)) {
parentDependenciesAreProved = ruleDao.areParentDependenciesProved(rule,user.getId());
}
if (parentDependenciesAreProved) {
Object service = CDI.current().select(Object.class, new NamedAnnotation(rule.getProvider().name())).get();
Method method = service.getClass().getMethod(rule.getProviderType().getMethod(), Long.class, RegistrationRule.class);
return (RegistrationRuleStatus) method.invoke(service, user.getId(), rule);
} else {
RegistrationRuleStatus status = statusDao.getStatusByUserAndRule(user, rule);
if (status == null) {
status = new RegistrationRuleStatus(user, rule, RegistrationActionStatus.START, new Date());
statusDao.create(status);
}
return status;
}
} catch (Exception e) {
LOGGER.error("could not perform check {} for provider {}", rule.getProviderType().name(), rule.getProvider().name(), e.getCause()!=null?e.getCause():e);
return statusDao.createErrorStatus(user,rule,e.getCause()!=null?e.getCause().getMessage():e.getMessage());
}
}
create Error method:
#Transactional
public RegistrationRuleStatus createErrorStatus(User user, RegistrationRule rule, String message) {
RegistrationRuleStatus status = getStatusByUserAndRule(user, rule);
if (status == null) {
status = new RegistrationRuleStatus(user, rule, RegistrationActionStatus.ERROR, new Date());
status.setErrorCode(CommonPropertyResolver.getMicroServiceErrorCode());
status.setErrorMessage(message);
create(status);
}else {
status.setStatus(RegistrationActionStatus.ERROR);
status.setStatusDate(new Date());
status.setErrorCode(CommonPropertyResolver.getMicroServiceErrorCode());
status.setErrorMessage(message);
update(status);
}
return status;
}
the problem is method is called twice at same time and the error recorded is DuplicateException but we don't want it. We verify at the beginning if object already exists, but I think it is called at exactly same time.
JAVA8/wildfly/CDI/JPA/eclipselink
Any idea?
I'd suggest you to consider following approaches:
1) Implement retry logic. Catch exception, analyze it. If it indicates an unexpected duplicate (like you described), then don't consider it as an error and just repeat the method call. Now your code will work differently: It will notice that a record already exists and will not create a duplicate.
2) Use isolation level SERIALIZABLE. Then within a single transaction your will "see" a consistent behaviour: If select operation hasn't found a particular record, then till the end of this transaction no other transaction will insert such record and there will be no exception related to duplicates. But the price is that the whole table will be locked for each such transaction. This can degrade the application performance essentially.
Below is my class which is under threaded spring executors. Based on the type of source/service TAXP, TAXS, TAXT methods are getting called.
Logic is if the 'taxInfo.getGroupingId()' is already present in primary tax table do not insert, else insert primary table.
All secondary and tertiary tables records are inserted. TAXP, TAXS, TAXT are the topics and they receive data anytime. there might be milllisecond gap or at the same time the data would be sent so the blocks are synchroized.
All the 3 methods are called from 3 different thread executors.
executor1.insertPrimaryTaxInfo(taxInfo);
executor2.insertSecTaxInfo(taxInfo);
executor3.insertTerTaxInfo(taxInfo);
#Service
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class TaxServiceImpl implements TaxService {
private static final Logger LOG = LogManager.getLogger(ScanServiceImpl.class);
// TAXP
#Override
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = TaxServiceException.class)
public void insertPrimaryTaxInfo(TaxInfo taxInfo) throws TaxServiceException {
String taxId = null;
try {
synchronized (this) {
taxId = taxMapper.checkExists(taxInfo.getGroupingId());
if (taxId == null) {
taxMapper.insertTaxInfo(taxInfo); // primary tax table
}
}
LOG.info("tax id -- " + taxId);
} catch (Exception ex) {
LOG.error("Error inserting txId for " + taxInfo.getGroupingId()
+ ex);
throw new TaxServiceException(ex);
}
}
// TAXS
#Override
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = TaxServiceException.class)
public void insertSecTaxInfo(TaxInfo taxInfo) throws TaxServiceException {
String taxId = null;
try {
synchronized (this) {
taxId = taxMapper.checkExists(taxInfo.getGroupingId());
if (taxId == null) {
taxMapper.insertTaxInfo(taxInfo); // primary tax table
}
}
taxMapper.insertIntoSecTable(taxInfo); // secondary tax table
LOG.info("tax id -- " + taxId);
} catch (Exception ex) {
LOG.error("Error inserting txId for " + taxInfo.getGroupingId()
+ ex);
throw new TaxServiceException(ex);
}
}
// TAXT
#Override
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = TaxServiceException.class)
public void insertTerTaxInfo(TaxInfo taxInfo) throws TaxServiceException {
String taxId = null;
try {
synchronized (this) {
taxId = taxMapper.checkExists(taxInfo.getGroupingId());
if (taxId == null) {
taxMapper.insertTaxInfo(taxInfo); // primary tax table
}
}
taxMapper.insertIntoSecTable(taxInfo); // secondary tax table
taxMapper.insertIntoTerTable(taxInfo); // Tertiary tax table
LOG.info("tax id -- " + taxId);
} catch (Exception ex) {
LOG.error("Error inserting txId for " + taxInfo.getGroupingId()
+ ex);
throw new TaxServiceException(ex);
}
}
}
The issue is when TAXP, TAXS, TAXT are getting data at the same time, and the 3 above methods are called simultaneously. At a millisecond difference one of the thread inserts into primary table and the other thread trying to do the same but finds a record already exisitng in the table and throws duplicate key excepiton.
Im getting the below exception:
"com.data.exception.TaxServiceException: org.springframework.dao.DuplicateKeyException:
### Error updating database. Cause: java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (TAXDB2.TAX_PK) violated
The reason for synchrnozing the block is to overcome this exception. What is wrong with the above code?
It looks like you may be attempting to generate non guid based primary keys in your application instead of letting the database generate them and running into conflicts.
Its a losing battle to attempt to synchronize database access in your application. You should be letting the database manage the concurrency through its existing mechanisms. For more information refer to ACID. Additionaly, it is very instructive to lookup the current Isolation level on your database implementation and what it does in comparison to the others. For example SQL Server docs Understanding Isolation Levels
I have a database table which has both Foreign key and Unique key constraints. To make changes in the database table, i am using java application which uses hibernate. To be more precise i am doing Session.save() to make changes in database table. My code looks like this.
try{
transaction.begin();
------logic-------
session.save();
transaction.commit();
}catch(ConstarintViolationException exception){
---here is problem------
}
In case of any type constraint violation hibernate throws the same exception named ConstraintViolationException.
My question is what should i do to identify that what constraint has caused the exception. I tried doing
exception.getConstraintName()
But unfortunately i am getting null as output.
I want to differentiate between the two cases because i want to translate the hibernate exception to my own exception before sending it to client.
I would say let the database detect the violations of the constraint.....
Or
You can have something like the below class which might help in your case to catch the constraint violation exceptions...
public class DataIntegrityViolationExceptionsAdvice {
public void afterThrowing(DataIntegrityViolationException ex) throws DataIntegrityViolationException {
// extract the affected database constraint name:
String constraintName = null;
if ((ex.getCause() != null) && (ex.getCause() instanceof ConstraintViolationException)) {
constraintName = ((ConstraintViolationException) ex.getCause()).getConstraintName();
}
// create a detailed message from the constraint name if possible
String message = ConstraintMsgKeyMappingResolver.map(constraintName);
if (message != null) {
throw new DetailedConstraintViolationException(message, ex);
}
throw ex;
}
}
Is there a difference between:
Object o = new Object();
session.save(o)
and
session.save(new Object());
I ask because I am finding sometimes the objects are mixed up in my implementation. The discrepancy is found in production database. So it is hard to test.
Here is the edited code:
pubic class Product {
Logger logger = Logger.getRootLogger();
private String globalId;
public void service() {
this.gobalId = getVariable("GLOBALID"); //from Asterisk
Transaction tran = null;
try {
Session session = HibernateUtil.getCurrentSession();
int charge=0;
//set charge
Transaction tran = session.beginTransaction();
session.save(new Packet(globalId, charge));
tran.commit();
} catch (Exception e) {
if (tran != null) tran.rollback();
logger.error("Error", e);
} finally {
HibernateUtil.closeCurrentSession();
}
}
}
At run time, I see in the Packet table duplicate globalId rows with different values for the charge column. The globalId is supposed to be unique, even though it is not the primary key in the table. There could be a couple of explanations:
Asterisk is sending a wrong value for globalId in the getVariable() method
The service method needs to be synchrnozied (the Asterisk Java API says the Product class is implemented like a Servlet)
I don't see any errors in the logs. So no exceptions are thrown in the code.
Any help is appreciated.
Thanks