I am trying to achieve some error handling in my DAO in my JavaEE application, using JPA.
I have the situation where someone (a user) might try to enter duplicates into my DB. My plan is to attempt to persist my entity, eg. user("test#test.com", "Test", "password"). If this fails with a PersistenceException I am thinking that I can check for duplicate entries on the unique columns like username ("Test") and email ("test#test.com). If I find a duplicate I'd like to check which column it failed for and notify the user accordingly.
My attempt is as follows:
getEntityManager().persist(entity);
try {
getEntityManager().flush();
} catch (PersistenceException ex) {
List<T> duplicates = findDuplicate(entity);
if (duplicates.size() > 0) {
// Notify user
} else {
// Probably pass exception forwards
}
}
The Entity manager is injected into the class with:
#PersistenceContext(unitName = "RecruitmentPU")
protected EntityManager mEM;
The getEntityManager() simply return this member. The class it self is annotated as #Stateless.
To find a duplicate I basically just do this:
String column = myEntity.getUniqueColumn(); // returns the name of the column
Object uniqueValue = myEntity.getUniqueValue(); // returns the value of the unique column
Query query = getEntityManager().createQuery(
"SELECT e FROM TestEntity e WHERE " + column + " = :identifier",
TestEntity.class
);
query.setParameter("identifier", uniqueValue);
List<T> entries = null;
try {
entries = (List<T>) query.getResultList(); // Here the exception is re-thrown
} catch(Exception ex) {
System.out.println("Caught something... \n" + ex.getMessage());
}
The entity also has an ID column which is annotated #GeneratedValue(strategy = GenerationType.IDENTITY). There is also a #ManyToOne attribute that was removed when I simplified the code. When I test this I get the following output:
Info: Caught something...
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.3.qualifier): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'Test' for key 'username_UNIQUE'
Error Code: 1062
Call: INSERT INTO test.test (email, username, role) VALUES (?, ?, ?)
bind => [3 parameters bound]
Query: InsertObjectQuery(ID: 0 | email: test#test.com | username: Test | password: ********)
At the moment I'm letting the container handle transactions, but I'm having a hunch that I'm getting these problems because I'm trying to query the database before the first transaction is finished (or something similar).
Is the flaw in the strategy or the implementation? And what steps could I take to start solving this?
You don't want to continue any transaction after an exception has occurred.
I suggest you switch the order of operations, like so:
query DB for records with unique keys that are equal to the unique key of the entity you would like to persist,
persist your entity.
Related
Using JPARepository, we are trying to persist department and student details if not already exists. It works fine in single threaded environment.
But, it's failing with multiple threads.
Caused by: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'DEP12' for key 'departmentId'
Code Snippet :
#Transactional
public void persistDetails(String departmentName, String studentName)
{
Department dep= departmentRepository.findByDepartmentName(departmentName);
if (dep== null) {
dep= createDepartmentObject(departmentName);
departmentRepository.save(dep);
}
...
}
How to achieve this in multi-threaded environment. We don't have to fail, instead use existing record and perform other operations.
Also, tried to catch exception and make select query inside it. But, in that case it fetches from cache object, not from DB.
Catching Exception : Code Snippet :
#Transactional
public void persistDetails(String departmentName, String studentName)
{
Department dep= departmentRepository.findByDepartmentName(departmentName);
try{
if (dep== null) {
dep= createDepartmentObject(departmentName);
departmentRepository.save(dep);
}
}
catch(Exception e)
{
dep= departmentRepository.findByDepartmentName(departmentName);
}
...
}
Implement your departmentRepository.save in such way that it uses saveOrUpdate (if you are using Hibernate directly) or merge (if you are using JPA API).
You are catching exception on a wrong place. The kind of catch you show here should be done outside of the transaction. Only then you can be sure you have consistent entities in the session.
I am developing a NamedQueryManager EJB that receives a JPQL string from a client and returns a query that only selects data that the user is authorized to read. For example, if the client program wants Companies, it would create the following query and send to the NamedQueryManager.
SELECT c FROM Company c
The NamedQueryManager would consider the query and the current user and if the user doesn't have full permissions to read Company entities would return a query similar to the following:
SELECT c FROM Company c where c.reader = :user
Instead of creating my own parser, I investigated EclipseLink 2.6.1 and found that I could perform the following to parse the query:
JPQLExpression jpql = new JPQLExpression(jpqlString, new JPQLGrammar2_1());
if (jpql.getQueryStatement() instanceof SelectStatement) {
logger.debug("JPQL Expression is a Select statement.");
} else if (jpql.getQueryStatement() instanceof UpdateStatement) {
logger.debug("JPQL Expression is a Update statement.");
} else if (jpql.getQueryStatement() instanceof DeleteStatement) {
logger.debug("JPQL Expression is a Delete statement.");
} else {
logger.debug("JPQL Expression is an unknown statement.");
}
I could then loop through the children of the QueryStatement and determine the pieces of the statement. For example, the following loops through the Select clause.
SelectStatement st = (SelectStatement) jpql.getQueryStatement();
logger.debug("JPQL Select Statement: {}", st);
logger.debug("********************");
SelectClause sc = (SelectClause) st.getSelectClause();
logger.debug("Select Clause: {}", sc);
for (Expression e : sc.children()) {
logger.debug("Child: {}", e);
}
for (Expression e : sc.orderedChildren()) {
logger.debug("Ordered Child: {}", e);
}
So my question is am I on the correct path for using EclipseLink to parse and modify my JPQL?
I am comfortable with the parsing but how do I modify? Should I build a new JPQL string using the parsed JPQL or can I add information to the parsed JPQL directly to create the new query?
I began investigating how I could determine if the passed jpql string is valid and found references to the HermesParser class in the org.eclipse.persistence.internal.jpa.jpql package.
Should I stay away from "internal" packages?
I don't think that the following code which I am using to parse the jpql string uses the HermesParser but the org.eclipse.persistence.jpa.jpql.parser package-info states that "This is the core of Hermes,..."
JPQLExpression jpql = new JPQLExpression(jpqlString, new JPQLGrammar2_1());
So this leads me to ask what is Hermes?
Any big picture help or references would be greatly appreciated.
Note: This is a continuation of the following question.
Should I use #NamedQuery annotation or addNamedQuery method?
I am trying to retrieve a USER from my database using the ID in the WHERE clause. However I am receiving an error and my program is failing.
This is the error I'm receiving when I run my program:
ERROR [org.jboss.as.ejb3.invocation] (default task-19)
JBAS014134: EJB Invocation failed on component CustomerServiceBeanImpl
for method public abstract package.name.entity.ICustomer
package.name.bean.CustomerServiceBean.getCustomerById(long):
javax.ejb.EJBException: java.lang.IllegalArgumentException:
Parameter value [19533] did not match expected type [package.name.entity.User (n/a)]
Note: [19533] is a test value I used.
This is the method that is having the error in the CustomerServiceBeanImpl.java:
#Override
public Customer getCustomerById (final long id)
{
return Customer.getById (this.em, id);
}
This is the method that's being called by the component CustomerServiceBeanImpl:
public static Customer getById (final EntityManager em, final long id)
{
for (final Customer c : em.createNamedQuery ("Customer.getById", Customer.class)
.setParameter ("id", id).setMaxResults (1).getResultList ())
{
return c;
}
return null;
}
The name query is this:
#NamedQuery (name = "Customer.getById",
query = "SELECT o FROM gnf.Customer o WHERE o.user = :id")
In the Customer.java class itself the relevant column is this one:
#ManyToOne (fetch = FetchType.LAZY)
#JoinColumn (name = "user_id")
private User user;
Doing a quick check of my ERD the "id" column in my "customer" table has a data type of BIGINT. However I'm not sure if that matters. (PostgreSQL database by the way.)
How can I fix this error?
The WHERE clause in your named query seems to be the problem. The attribute user in your Customer.class is of type User and your query expects it to be a type compatible to long.
...
Parameter value [19533] did not match expected type [package.name.entity.User ...
So if you need more help on this it would be great to see the complete entities User and Customer.
It is happening because in your database the parameter will be a #oneToOne object and you have to call the id inside the Object so you have to give the query as following :
"select user from User user where user.customer.id=:param"
I'm starting using JPA in Netbeans with a Glassfish server + Derby Database, and I have some doubts on its behavior. I did the following experiment: I defined an entity "User" with the property
#OneToMany(cascade=ALL, mappedBy="user")
public List<Thing> getThings() {
return things;
}
and the entity "Thing" with the property
#ManyToOne
public Cook4User getUser() {
return user;
}
Then I persisted one user and added one "Thing" to it. Everything all right, I can see the two tables "User" and "Thing" with one entry each, with the second table having a foreign key indicating the user id.
Then I removed the element from the "Thing" table, ran a select statement to recover the user, called the getThings() method on it and... I still found the element that I had removed from the Thing table! How is it possible? Where is it stored? I can't see it nowhere in the DB! Thanks for clearing things up to me.
EDIT: I tried to isolate the lines of code that produce the issue.
#PersistenceContext
private EntityManager em;
User user = new User();
em.persist(user);
Thing thing = new Thing();
em.persist(thing);
user.getThings().add(thing);
em.remove(thing);
user = em.find(User.class, userid);
logger.log(Level.INFO, "user still contains {0} things", user.getThings().size());
\\thing is still there!
In the form of a JUnit test with SpringRunner and Hibernate as the JPA implementation:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:spring-context.xml")
public class TestThings {
private final Logger log = LoggerFactory.getLogger(TestThings.class);
#PersistenceContext
private EntityManager em;
#Test
#Transactional
public void does_not_remove_thing() {
Cook4User user = new Cook4User();
em.persist(user);
Thing thing = new Thing();
em.persist(thing);
user.getThings().add(thing);
user = em.find(Cook4User.class, user.getId());
user.getThings().forEach((t) -> log.info("1 >> Users thing: {}", t.getId()));
em.remove(thing);
em.flush();
user = em.find(Cook4User.class, user.getId());
user.getThings().forEach((t) -> log.info("2 >> Users thing: {}", t.getId()));
assertThat(user.getThings()).isEmpty();
}
#Test
#Transactional
public void removes_thing_when_removed_from_owning_side() {
Cook4User user = new Cook4User();
em.persist(user);
Thing thing = new Thing();
em.persist(thing);
user.getThings().add(thing);
user = em.find(Cook4User.class, user.getId());
user.getThings().forEach((t) -> log.info("1 >> Users thing: {}", t.getId()));
user.getThings().remove(thing);
user = em.find(Cook4User.class, user.getId());
user.getThings().forEach((t) -> log.info("2 >> Users thing: {}", t.getId()));
assertThat(user.getThings()).isEmpty();
}
}
The first test does_not_remove_thing is per your question and fails as you have experienced, this is the output of that test with hibernate.show_sql logging set to true:
Hibernate:
insert
into
Cook4User
(id)
values
(null)
Hibernate:
insert
into
Thing
(id, user_id)
values
(null, ?)
[main] INFO TestThings - 1 >> Users thing: 1
[main] INFO TestThings - 2 >> Users thing: 1
java.lang.AssertionError: expecting empty but was:<[x.Thing#276]>
The second test removes_thing_when_removed_from_owning_side passes with output:
Hibernate:
insert
into
Cook4User
(id)
values
(null)
Hibernate:
insert
into
Thing
(id, user_id)
values
(null, ?)
[main] INFO TestThings - 1 >> Users thing: 1
So it looks like removing your Thing from the owning side of the relationship (he he) is the way to go.
Although, I must be honest, I'm not sure why exactly that works and your way does not. I would understand if you removed your Thing from a detached entity but that was not the case. Also, I was expecting a delete query after calling em.remove(thing) for Thing somewhere but nothing (I added em.flush() to try force that).
Maybe someone else can shed some light on the finer mechanics of what's going on here?
How can I remove multiple objects in batch call using their IDs ?
I tried this
EntityManager em = ...
em.getTransaction().begin();
try
{
for (Visitor obj : map.keySet())
{
Visitor fake = em.getReference(Visitor.class, obj.getId());
em.remove(fake);
}
em.getTransaction().commit();
}
catch (Exception ex)
{
ex.printStackTrace();
}
I see DELETE statements in log file but them it throws
<openjpa-2.1.1-r422266:1148538 fatal store error> org.apache.openjpa.persistence.RollbackException: Optimistic locking errors were detected when flushing to the data store. The following objects may have been concurrently modified in another transaction: [com.reporting.data.Visitor-53043]
at org.apache.openjpa.persistence.EntityManagerImpl.commit(EntityManagerImpl.java:593)
at com.reporting.ui.DBUtil.saveAnswers(DBUtil.java:311)
I have single thread.
Update:
I also tried
for (Visitor obj : map.keySet())
em.remove(obj);
But it's slow because on every iteration it sends SELECT to a server. I assume OpenJPA does it to reattach object to context.
After multiple experiments I ended up doing hacky JPQL query. Here is code-snippet:
List<Long> lstExistingVisitors = ...
Query qDeleteVisitors = em.createQuery("delete from Visitor obj where obj.id in (?1)");
qDeleteVisitors.setParameter(1, lstExistingVisitors);
qDeleteVisitors.executeUpdate();
I tried list as big as 5000 IDs. It works fine with mysql 5.1 and H2DB.
Try to use JPQL
em.createQuery("delete from Visitor v where v.id in (:param)")
.setParameter("param", idsList).executeUpdate();
OpenJPA docs: http://openjpa.apache.org/builds/1.2.0/apache-openjpa-1.2.0/docs/manual/jpa_langref.html#jpa_langref_bulk_ops