Duplicate id using JPA2 and testing - oracle

I'm a bit confused right now :-S
I'm working on a project that uses JPA2, Spring 3.0.5, Hibernate 3.6.0 Final. We have the following code (only relevant classes)
#Entity
public class User extends AbstractEntity implements Serializable {
#Id
#Column(name = "ID", nullable = false, insertable = true, updatable = true, length = 36)
protected String id;
#NotNull
#Size(min = 1, max = 30)
#Column(name = "NAME", length = 30, nullable = false)
private String name;
protected User() {
id = java.util.UUID.randomUUID().toString();
}
#Override
public boolean equals(Object object) {
if (!(object instanceof User)) {
return false;
}
User other = (User) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
}
#Repository("userDao")
public class UserDaoImpl implements UserDao {
#PersistenceContext
private EntityManager em;
public void create(User user) throws PreexistingEntityException, Exception {
try {
em.persist(user);
} catch (EntityExistsException ex) {
logger.error("User " + user + " already exists.", ex);
throw new PreexistingEntityException("User " + user + " already exists.", ex);
} catch (Exception ex) {
logger.error("Exception occurred:", ex);
throw ex;
}
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "/testDaoContext.xml" })
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
#Transactional
public class UserDaoTest {
private UserDao userDao;
#Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
#Test
public void testInsertUserExistingID() {
User user = User.valueOf("1");
user.setFirstname("DUMMY");
user.setName("CRASH");
logger.debug(user);
try {
userDao.create(user);
sessionFactory.getCurrentSession().flush();
} catch (PreexistingEntityException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
logger.debug("id = " + user.getId());
User retrieved = userDao.find(user.getId());
Assert.assertEquals(user.getId(), retrieved.getId());
Assert.assertEquals("DUMMY", retrieved.getFirstname());
Assert.assertEquals("CRASH", retrieved.getName());
}
}
Now, when I run the test (I know, it's not a real unit test) with rollback set to false, I get the following stacktrace:
org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [insert into PV_UMDB.USERS (CREATION_DT, CREATION_USR, MODIFICATION_USR, MODIFICATION_DT, VERSION, BIRTHDAY, EMAIL, FAX, FIRSTNAME, INTERNAL, MOBILE, NAME, PHONE, PICTURE, STAFF_NO, STAFF_NO_KBC, ID) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)]; constraint [PV_UMDB.USERS_PK]; nested exception is org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:637)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:102)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:471)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
at org.springframework.test.context.transaction.TransactionalTestExecutionListener$TransactionContext.endTransaction(TransactionalTestExecutionListener.java:515)
at org.springframework.test.context.transaction.TransactionalTestExecutionListener.endTransaction(TransactionalTestExecutionListener.java:290)
at org.springframework.test.context.transaction.TransactionalTestExecutionListener.afterTestMethod(TransactionalTestExecutionListener.java:183)
at org.springframework.test.context.TestContextManager.afterTestMethod(TestContextManager.java:406)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:90)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:46)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:268)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:184)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:76)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:467)
... 25 more
Caused by: java.sql.BatchUpdateException: ORA-00001: unique constraint (PV_UMDB.USERS_PK) violated
at oracle.jdbc.driver.DatabaseError.throwBatchUpdateException(DatabaseError.java:343)
at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:10768)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
... 34 more
If I use rollback, then the test passes, which of course is incorrect.
Now, is there a good solution?
Thanks for your help
BB
Peter

You cannot rely on EntityExistsException thrown by persist().
From the javadoc:
EntityExistsException - if the entity
already exists. (If the entity already
exists, the EntityExistsException may
be thrown when the persist operation
is invoked, or the
EntityExistsException or another
PersistenceException may be thrown at
flush or commit time.)
In your case, you get another exception thrown at the commit time. If you replace
sessionFactory.getCurrentSession().flush();
with
em.flush();
you can catch a PersistenceException thrown at the flush time (I'm not sure why it doesn't work the same way with SessionFactory).

The test is to see if a User with an
existing ID is stored, the
PreexistingEntityException is thrown.
The general pattern for checking a exception is:
Junit4: #Test(excpect=ExcpectedException.class), or
JUnit3 or when the easy pattern is not working:
psydocode for JUnit3
try {
invokeExceptionThrowingMethod();
fail("ExceptionX expected");
} catch(ExcpectedException e) {
//expected - do nothing
}
I strongly belive, that if you write your test case more clear, than you will find the bug.
edited
In your case, you need the second variant, because the test must not accept an exception from the first user creation.
#Test
public void testInsertUserExistingID()
//(See my comment to your question about throwing Exception)
throws Exception{
User user = User.valueOf("1");
user.setFirstname("DUMMY");
user.setName("CRASH");
try {
userDao.create(user);
sessionFactory.getCurrentSession().flush();
fail("PreexistingEntityException expected");
} catch (PreexistingEntityException e) {
//Thats what is execpected
}
}
Anyway: axtavt is right

Related

Spring Jpa Repository crash on insert

I'm trying to implement a repository for refresh JWT tokens and I can't save my token in postgresql.
My entity :
#Entity(name = "refreshtoken")
public class RefreshToken {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private long id;
#Column(nullable = false, unique = true)
private String user;
#Column(nullable = false, unique = true)
private String token;
#Column(nullable = false)
private Instant expiryDate;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Instant getExpiryDate() {
return expiryDate;
}
public void setExpiryDate(Instant expiryDate) {
this.expiryDate = expiryDate;
}
The refresh token is initialised with the right values.
The logs are
Hibernate: select nextval ('hibernate_sequence') Hibernate: insert
into refreshtoken (expiry_date, token, user, id) values (?, ?, ?, ?)
2022-04-08 11:17:22.475 ERROR 24272 --- [nio-8080-exec-1]
o.h.engine.jdbc.spi.SqlExceptionHelper : ERREUR: erreur de syntaxe
sur ou prΦs de ½ user ╗ Positioná: 47 2022-04-08 11:17:22.483 DEBUG
24272 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet :
Failed to complete request:
org.springframework.dao.InvalidDataAccessResourceUsageException: could
not execute statement; SQL [n/a]; nested exception is
org.hibernate.exception.SQLGrammarException: could not execute
statement 2022-04-08 11:17:22.497 ERROR 24272 --- [nio-8080-exec-1]
o.a.c.c.C.[.[.[.[dispatcherServlet] : Servlet.service() for
servlet [dispatcherServlet] in context with path [/xatis-web] threw
exception [Request processing failed; nested exception is
org.springframework.dao.InvalidDataAccessResourceUsageException: could
not execute statement; SQL [n/a]; nested exception is
org.hibernate.exception.SQLGrammarException: could not execute
statement] with root cause
org.postgresql.util.PSQLException: ERREUR: erreur de syntaxe sur ou
prΦs de ½ user ╗ Positioná: 47
I have spring.jpa.hibernate.ddl-auto= update
I created refreshtoken table manualy but I also noticed that after it crashed, all field are created except user.
Any help would be welcome, thanks
Like Manuel pointed out in the comment, user is a reserved word in postgresql
"user" is a reserved keyword for Postgresql

Save entities using Spring Data JPA saveAll method even in exception scenario

Is there any way to save the true entities in the DB even after any exception occurred during saveAll execution?
For example, I have 100 entities and 2 entities result in data violation exception but I still want to save the other 98 entities in DB. As of now if a data violation exception occurred it save none in the DB.
#Autowired
BillingJpaRepository billingJpaRepository;
#Transactional
private void saveBillingRecords(List<BillingInfo> billingInfos) {
try {
billingJpaRepository.saveAll(billingInfos);
} catch (DataIntegrityViolationException dataIntegrityViolationException) {
this.saveBillingRecordsIndividually(billingInfos);
} catch (Exception ex) {
throw ex;
}
}
#Transactional
private void saveBillingRecordsIndividually(List<BillingInfo> billingInfos) {
for(BillingInfo billingInfo : billingInfos) {
try {
billingJpaRepository.save(billingInfo);
} catch (DataIntegrityViolationException dataIntegrityViolationException) {
logger.error("data voilation exception occured");
} catch (Exception ex) {
throw ex;
}
}
}
Because of this, I have to save all true entities individually which is a performance hit. Since all the entities will be in detached stated after saveAll it will execute merge operation instead of persist and because of this, an extra SELECT statement will be triggered for each save operation. Below is my entity structure
#AllArgsConstructor
#NoArgsConstructor
#Data
#Entity
#Table(name= "BLI_INFO")
public class BillingInfo implements Persistable<long> {
#Id
#Column(name = "BLI_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "id_gen")
#SequenceGenerator(sequenceName = "BLI_ID_SEQ", name = "id_gen", allocationSize = 1)
private long billingId;
...
// rest of the fields
...
#Transient
private boolean isNew = true;
#PrePersist
#PostLoad
void markNotNew() {
this.isNew = false;
}
#Override
public boolean isNew(){
return true;
}
}

Unit test failing for custom processor's 'optional' properties

To create a custom processor, I followed the documentation.
I made the necessary code changes in the MyProcessor.java and the MyProcessorTest runs fine except when I try to use some 'optional' properties. Note : I tried all the builder methods like required(false), addValidator() etc. for the optional properties, in vain. Actually, a validator doesn't make sense for an optional property ...
MyProcessor.java
#Tags({ "example" })
#CapabilityDescription("Provide a description")
#SeeAlso({})
#ReadsAttributes({ #ReadsAttribute(attribute = "", description = "") })
#WritesAttributes({ #WritesAttribute(attribute = "", description = "") })
#Stateful(description = "After a db-level LSN is processed, the same should be persisted as the last processed LSN", scopes = { Scope.CLUSTER })
public class MyProcessor extends AbstractProcessor {
public static final Relationship REL_SUCCESS = new Relationship.Builder()
.name("success")
.description(
"Successfully created FlowFile from SQL query result set.")
.build();
public static final Relationship REL_FAILURE = new Relationship.Builder()
.name("failure").description("SQL query execution failed. ???")
.build();
/* Start : Mandatory properties */
public static final PropertyDescriptor DBCP_SERVICE = new PropertyDescriptor.Builder()
.name("Database Connection Pooling Service")
.description(
"The Controller Service that is used to obtain connection to database")
.required(true).identifiesControllerService(DBCPService.class)
.build();
public static final PropertyDescriptor CONTAINER_DB = new PropertyDescriptor.Builder()
.name("containerDB").displayName("Container Database")
.description("The name of the container database").required(true)
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
...
...more mandatory properties
...
/* End : Mandatory properties */
/*Start : Optional properties */
public static final PropertyDescriptor CDC_TS_FROM = new PropertyDescriptor.Builder()
.name("cdcTSFrom").displayName("Load CDC on or after")
.description("The CDC on or after this datetime will be fetched.")
.required(false).defaultValue(null).build();
public static final PropertyDescriptor SCHEMA = new PropertyDescriptor.Builder()
.name("schema").displayName("DB Schema")
.description("The schema which contains the xxxxxx")
.defaultValue(null).required(false).build();
/*End : Optional properties */
private List<PropertyDescriptor> descriptors;
private Set<Relationship> relationships;
#Override
protected void init(final ProcessorInitializationContext context) {
final List<PropertyDescriptor> descriptors = new ArrayList<PropertyDescriptor>();
descriptors.add(CONTAINER_DB);
descriptors.add(DBCP_SERVICE);
...
...
...
descriptors.add(CDC_TS_FROM);
descriptors.add(SCHEMA);
...
...
...
this.descriptors = Collections.unmodifiableList(descriptors);
final Set<Relationship> relationships = new HashSet<Relationship>();
relationships.add(REL_FAILURE);
relationships.add(REL_SUCCESS);
this.relationships = Collections.unmodifiableSet(relationships);
}
#Override
public Set<Relationship> getRelationships() {
return this.relationships;
}
#Override
public final List<PropertyDescriptor> getSupportedPropertyDescriptors() {
return descriptors;
}
// TODO : Check if the component lifecycle methods esp. onScheduled() and
// onShutDown() are required
#Override
public void onTrigger(final ProcessContext context,
final ProcessSession session) throws ProcessException {
...
...
...
}
}
MyProcessorTest.java
public class MyProcessorTest {
private TestRunner testRunner;
private final String CONTAINER_DB = "test";
private final String DBCP_SERVICE = "test_dbcp";
...
...
...
private final String SCHEMA = "dbo";
private final String CDC_TS_FROM = "";
...
...
...
#Before
public void init() throws InitializationException {
testRunner = TestRunners.newTestRunner(MyProcessor.class);
final DBCPService dbcp = new DBCPServiceSQLServerImpl(...);
final Map<String, String> dbcpProperties = new HashMap<>();
testRunner = TestRunners.newTestRunner(MyProcessor.class);
testRunner.addControllerService(DBCP_SERVICE, dbcp, dbcpProperties);
testRunner.enableControllerService(dbcp);
testRunner.assertValid(dbcp);
testRunner.setProperty(MyProcessor.DBCP_SERVICE, DBCP_SERVICE);
testRunner.setProperty(MyProcessor.CONTAINER_DB, CONTAINER_DB);
...
...
...
testRunner.setProperty(MyProcessor.CDC_TS_FROM, CDC_TS_FROM);
testRunner.setProperty(MyProcessor.SCHEMA, SCHEMA);
...
...
...
}
#Test
public void testProcessor() {
testRunner.run();
}
/**
* Simple implementation only for MyProcessor processor testing.
*/
private class DBCPServiceSQLServerImpl extends AbstractControllerService
implements DBCPService {
private static final String SQL_SERVER_CONNECT_URL = "jdbc:sqlserver://%s;database=%s";
private String containerDB;
private String password;
private String userName;
private String dbHost;
public DBCPServiceSQLServerImpl(String containerDB, String password,
String userName, String dbHost) {
super();
this.containerDB = containerDB;
this.password = password;
this.userName = userName;
this.dbHost = dbHost;
}
#Override
public String getIdentifier() {
return DBCP_SERVICE;
}
#Override
public Connection getConnection() throws ProcessException {
try {
Connection connection = DriverManager.getConnection(String
.format(SQL_SERVER_CONNECT_URL, dbHost, containerDB),
userName, password);
return connection;
} catch (final Exception e) {
throw new ProcessException("getConnection failed: " + e);
}
}
}
}
Now if I comment the optional properties in the test class :
//testRunner.setProperty(MyProcessor.CDC_TS_FROM, CDC_TS_FROM);
//testRunner.setProperty(MyProcessor.SCHEMA, SCHEMA);
, the test completes normally but if I enable any or all of the optional properties, say, CDC_TS_FROM, then I the test case assertion fails, no matter what value I put for CDC_TS_FROM :
java.lang.AssertionError: Processor has 1 validation failures:
'cdcTSFrom' validated against '' is invalid because 'cdcTSFrom' is not a supported property
at org.junit.Assert.fail(Assert.java:88)
at org.apache.nifi.util.MockProcessContext.assertValid(MockProcessContext.java:251)
at org.apache.nifi.util.StandardProcessorTestRunner.run(StandardProcessorTestRunner.java:161)
at org.apache.nifi.util.StandardProcessorTestRunner.run(StandardProcessorTestRunner.java:152)
at org.apache.nifi.util.StandardProcessorTestRunner.run(StandardProcessorTestRunner.java:147)
at org.apache.nifi.util.StandardProcessorTestRunner.run(StandardProcessorTestRunner.java:142)
at org.apache.nifi.util.StandardProcessorTestRunner.run(StandardProcessorTestRunner.java:137)
at processors.NiFiCDCPoC.sqlserver.MyProcessorTest.testProcessor(MyProcessorTest.java:74)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Edit-1 :
I added two(?) validators :
public static final PropertyDescriptor CDC_TS_FROM = new PropertyDescriptor.Builder()
.name("cdcTSFrom").displayName("Load CDC on or after")
.description("The CDC on or after this datetime will be fetched.")
.required(false).defaultValue(null).addValidator(Validator.VALID)
.addValidator(StandardValidators.TIME_PERIOD_VALIDATOR).build();
Error :
java.lang.AssertionError: Processor has 1 validation failures:
'cdcTSFrom' validated against '2017-03-06 10:00:00' is invalid because Must be of format <duration> <TimeUnit> where <duration> is a non-negative integer and TimeUnit is a supported Time Unit, such as: nanos, millis, secs, mins, hrs, days
All Property Descriptors (required or optional) must have a Validator set explicitly, otherwise it will return the error you are seeing. It appears you are not looking to perform validation, but you still must set a validator, so on your optional properties add the following to the builder:
.addValidator(Validator.VALID)
EDIT (see comments below): Marking the PropertyDescriptor as required(false) allows it to be an optional property and thus can have no value specified. If the user enters a value, and you want to validate that against certain rules, you can add that particular Validator (or write your own and add that). For a Time Period (2 seconds, e.g.), and for other cases, there are a set of built-in validators, for example allowing only values between 2 and 20 seconds:
.addValidator(StandardValidators.createTimePeriodValidator(
2, TimeUnit.SECONDS, 20, TimeUnit.SECONDS
))

JSF bean validation and exceptions thrown by validators

Bean validation is suppressed when an exception is thrown by a validator. I wonder what is the correct way of handling this. The form:
<h:form id="registration_form">
<h:panelGrid columns="3">
<h:outputLabel for="username">Username</h:outputLabel>
<h:inputText id="username" value="#{userController.user.username}">
<f:validator binding="#{userController$UniqueUsernameValidator}"
redisplay="true"/> <!-- Not sure about this -->
<f:ajax event="blur" render="usernameMessage" />
</h:inputText>
<h:message id="usernameMessage" for="username" />
<!-- etc-->
</h:panelGrid>
</h:form>
The UserController:
#ManagedBean
#ViewScoped
public class UserController {
// http://stackoverflow.com/a/10691832/281545
private User user;
#EJB
// do not inject stateful beans !
private UserService service;
public User getUser() {
return user;
}
#PostConstruct
void init() {
// http://stackoverflow.com/questions/3406555/why-use-postconstruct
user = new User();
}
#ManagedBean
#RequestScoped
public static class UniqueUsernameValidator implements Validator {
// Can't use a Validator (no injection) - see:
// http://stackoverflow.com/a/7572413/281545
#EJB
private UserService service;
#Override
public void validate(FacesContext context, UIComponent component,
Object value) throws ValidatorException {
if (value == null) return; // Let required="true" handle, if any.
try {
if (!service.isUsernameUnique((String) value)) {
throw new ValidatorException(new FacesMessage(
FacesMessage.SEVERITY_ERROR,
"Username is already in use.", null));
}
} catch (Exception e) {
System.out.println(cause(e));
Throwable cause = e.getCause();
if (cause instanceof PersistenceException) {
Throwable cause2 = cause.getCause();
// ((PersistenceException)cause) - only superclass methods
if (cause2 instanceof DatabaseException) {
// now this I call ugly
int errorCode = ((DatabaseException) cause2)
.getDatabaseErrorCode(); // no java doc in eclipse
if (errorCode == 1406)
throw new ValidatorException(new FacesMessage(
FacesMessage.SEVERITY_ERROR, "Max 45 chars",
null));
}
}
// TODO: DEGUG, btw the EJBException has null msg
throw new ValidatorException(new FacesMessage(
FacesMessage.SEVERITY_ERROR, cause.getMessage(), null));
}
}
private static String cause(Exception e) {
StringBuilder sb = new StringBuilder("--->\nEXCEPTION:::::MSG\n"
+ "=================\n");
for (Throwable t = e; t != null; t = t.getCause())
sb.append(t.getClass().getSimpleName()).append(":::::")
.append(t.getMessage()).append("\n");
sb.append("FIN\n\n");
return sb.toString();
}
}
}
The entity:
/** The persistent class for the users database table. */
#Entity
#Table(name = "users")
#NamedQuery(name = "User.findAll", query = "SELECT u FROM User u")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private int iduser;
#NotNull(message = "Please enter a username")
#Pattern(regexp = "[A-Za-z0-9_]{6}[A-Za-z0-9_]*",
message = "Usernames can have latin characters, the underscore and "
+ "digits and are at least 6 characters")
#Size(max = 45)
private String username;
//etc
}
and the service:
#Stateless
public class UserService {
#PersistenceContext
private EntityManager em;
public boolean isUsernameUnique(String username) {
Query query = em
.createNativeQuery("SELECT r1_check_unique_username(?)");
short i = 0;
query.setParameter(++i, username);
return (boolean) query.getSingleResult();
}
}
What happens is that if I put a username longer than 45 chars MySql throws an exception - the output of cause() is:
INFO: --->
EXCEPTION:::::MSG
=================
EJBException:::::null
PersistenceException:::::Exception[EclipseLink-4002](Eclipse Persistence Services\
- 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data\
too long for column 'username_given' at row 198
Error Code: 1406
Call: SELECT r1_check_unique_username(?)
bind => [1 parameter bound]
Query: DataReadQuery(sql="SELECT r1_check_unique_username(?)")
DatabaseException:::::
Internal Exception: com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data\
too long for column 'username_given' at row 198
Error Code: 1406
Call: SELECT r1_check_unique_username(?)
bind => [1 parameter bound]
Query: DataReadQuery(sql="SELECT r1_check_unique_username(?)")
MysqlDataTruncation:::::Data truncation:Data too long for column 'username_given'\
at row 198
FIN
The way I handle it the "Max 45 chars" message is shown.
But this handling (with explicit check on the error code) is a bit smelly. On the other hand, if I do not throw a ValidatorException (and just catch (Exception e) {} which is also smelly) the bean validation kicks in ( the one induced by #Size(max = 45) ) but I still see the exception trace in the glassfish logs.
Questions
Is this way of handling it correct (should I catch (Exception ignore) {} in the validator or check the exception I get manually - ideally I would be able to require the validator be run after the (all) bean validation(s) in User - and only if these validations pass)
How do I suppress the Exception being printed in the server logs ?

Hibernate: ConstraintViolationException with parallel inserts

I have a simple Hibernate entity:
#Entity
#Table(name = "keyword",
uniqueConstraints = #UniqueConstraint(columnNames = { "keyword" }))
public class KeywordEntity implements Serializable {
private Long id;
private String keyword;
public KeywordEntity() {
}
#Id
#GeneratedValue
#Column(unique = true, updatable=false, nullable = false)
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name="keyword")
public String getKeyword() {
return this.keyword;
}
public void setKeyword(String keyword) {
this.keyword = keyword;
}
}
DAO for it:
#Component
#Scope("prototype")
public class KeywordDao {
protected SessionFactory sessionFactory;
#Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public KeywordEntity findByKeyword(String keyword) throws NotFoundException {
Criteria criteria = sessionFactory.getCurrentSession()
.createCriteria(KeywordEntity.class)
.add(Restrictions.eq("keyword", keyword));
KeywordEntity entity = (KeywordEntity) criteria.uniqueResult();
if (entity == null) {
throw new NotFoundException("Not found");
}
return entity;
}
public KeywordEntity createKeyword(String keyword) {
KeywordEntity entity = new KeywordEntity(keyword);
save(entity);
return entity;
}
}
and a service, which puts everything under #Transactional:
#Repository
#Scope("prototype")
public class KeywordService {
#Autowired
private KeywordDao dao;
#Transactional(readOnly = true)
public KeywordEntity getKeyword(String keyword) throws NotFoundException {
return dao.findByKeyword(keyword);
}
#Transactional(readOnly = false)
public KeywordEntity createKeyword(String keyword) {
return dao.createKeyword(keyword);
}
#Transactional(readOnly = false)
public KeywordEntity getOrCreateKeyword(String keyword) {
try {
return getKeyword(keyword);
} catch (NotFoundException e) {
return createKeyword(keyword);
}
}
}
In a single-threaded environment this code runs just fine. The problems, when I use it in multi-threaded environment. When there are many parallel threads, working the same keywords, some of them are calling the getOrCreateKeyword with the same keyword at the same time and following scenario occurs:
2 threads at the same time call keyword service with the same keyword, both first tries to fetch the existing keyword, both are not finding, and both try to create new one. The first one succeeds, the second - causes ConstraintViolationException to be thrown.
So I did try to improve the getOrCreateKeyword method a little:
#Transactional(readOnly = false)
public KeywordEntity getOrCreateKeyword(String keyword) {
try {
return getKeyword(keyword);
} catch (NotFoundException e) {
try {
return createKeyword(keyword);
} catch (ConstraintViolationException ce) {
return getKeyword(keyword);
}
}
}
So theoretically it should solve the issues, but in practice, once ConstraintViolationException is thrown, calling the getKeyword(keyword) results in another Hibernate exception:
AssertionFailure - an assertion failure occured (this may indicate a bug in Hibernate,
but is more likely due to unsafe use of the session)org.hibernate.AssertionFailure:
null id in KeywordEntity entry (don't flush the Session after an exception occurs)
How to solve this problem?
You could use some sort of Pessimistic locking mechanism using the database/hibernate or you could make the service method getOrCreateKeyword() synchronized if you run on a single machine.
Here are some references.
Hibernates documentation http://docs.jboss.org/hibernate/core/3.3/reference/en/html/transactions.html#transactions-locking
This article shows how to put a lock on a specific entity and all entities from a result of a query which may help you.
http://www.objectdb.com/java/jpa/persistence/lock#Locking_during_Retrieval_
The solution was to discard the current session once ConstraintViolationException occurs and retrieve the keyword one more time within the new session. Hibernate Documentation also point to this:
If the Session throws an exception, the transaction must be rolled back and the session discarded. The internal state of the Session might not be consistent with the database after the exception occurs.

Resources