I am using JPA 2.1, Oracle DB and have a list of ids for entities to be removed (about 430000 ids). At first, it was implemented as splitting that id list into each smaller one with 1000 ids, pass them as parameters for a JPQL and executing.
delete from SOPFilter f where f.id in (?1)
Then, I want to change to use JPA CriteriaDelete.
CriteriaDelete<SOPFilter> criteriaDelete = cb.createCriteriaDelete(SOPFilter.class);
Root<SOPFilter> from = criteriaDelete.from(SOPFilter.class);
criteriaDelete.where(from.get(SOPFilter_.id).in(sopFilterIds));
It runs fine until it reach the 90000th one and there is a runtime exception cause it to stop here
org.hibernate.SessionException: Session is closed
and make entity manager factory to close.
INFO : bernate.impl.StmpContainerEntityManagerFactoryBean: Closing JPA EntityManagerFactory for persistence unit 'IMOFFERINGMANAGEMENT'
For whom was mislead by my first post with this exception
java.lang.IllegalStateException: EntityManagerFactory is closed
There was a catch clause to handle runtime exception by adding a record to database before throwing it. And to add a event record, it attempts to create another entity manger from the factory which is closed now.
public static void logEvent(EntityManager em) {
EntityManager em2 = null;
EntityManagerFactory emFactory = em.getEntityManagerFactory();
em2 = emFactory.createEntityManager();
// ...
}
Could anyone shed some light on it?
Im not clear on your code but you are likely hitting a transaction timeout. You can set a hint -
query.setHint("javax.persistence.query.timeout", 8000);
There may also be timeouts on the database side
Related
An example from Pro JPA:
#Stateless
public class AuditServiceBean implements AuditService {
#PersistenceContext(unitName = "EmployeeService")
EntityManager em;
public void logTransaction(int empId, String action) {
// verify employee number is valid
if (em.find(Employee.class, empId) == null) {
throw new IllegalArgumentException("Unknown employee id");
}
LogRecord lr = new LogRecord(empId, action);
em.persist(lr);
}
}
#Stateless
public class EmployeeServiceBean implements EmployeeService {
#PersistenceContext(unitName = "EmployeeService")
EntityManager em;
#EJB
AuditService audit;
public void createEmployee(Employee emp) {
em.persist(emp);
audit.logTransaction(emp.getId(), "created employee");
}
// ...
}
And the text:
Even though the newly created Employee is not yet in the database, the
audit bean can find the entity and verify that it exists. This works
because the two beans are actually sharing the same persistence
context.
As far as I understand Id is generated by the database. So how can emp.getId() be passed into audit.logTransaction() if the transaction has not been committed yet and id has not been not generated yet?
it depends on the strategy of GeneratedValue. if you use something like Sequence or Table strategy. usually, persistence provider assign the id to the entities( it has some reserved id based on allocation size) immediately after calling persist method.
but if you use IDENTITY strategy id different provider may act different. for example in hibernate, if you use Identity strategy, it performs the insert statement immediately and fill the id field of entity.
https://thoughts-on-java.org/jpa-generate-primary-keys/ says:
Hibernate requires a primary key value for each managed entity and
therefore has to perform the insert statement immediately.
but in eclipselink, if you use IDENTITY strategy, id will be assigned after flushing. so if you set flush mode to auto(or call flush method) you will have id after persist.
https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Entities/Ids/GeneratedValue says:
There is a difference between using IDENTITY and other id generation
strategies: the identifier will not be accessible until after the
insert has occurred – it is the action of inserting that caused the
identifier generation. Due to the fact that insertion of entities is
most often deferred until the commit time, the identifier would not be
available until after the transaction has been flushed or committed.
in implementation UnitOfWorkChangeSet has a collection for new entities which will have no real identity until inserted.
// This collection holds the new objects which will have no real identity until inserted.
protected Map<Class, Map<ObjectChangeSet, ObjectChangeSet>> newObjectChangeSets;
JPA - Returning an auto generated id after persist() is a question that is related to eclipselink.
there are good points at https://forum.hibernate.org/viewtopic.php?p=2384011#p2384011
I am basically referring to some remarks in Java Persistence with
Hibernate. Hibernate's API guarantees that after a call to save() the
entity has an assigned database identifier. Depending on the id
generator type this means that Hibernate might have to issue an INSERT
statement before flush() or commit() is called. This can cause
problems at rollback time. There is a discussion about this on page
490 of Java Persistence with Hibernate.
In JPA persist() does not return a database identifier. For that
reason one could imagine that an implementation holds back the
generation of the identifier until flush or commit time.
Your approach might work fine for now, but you could run into troubles
when changing the id generator or JPA implementation (switching from
Hibernate to something else).
Maybe this is no issue for you, but I just thought I bring it up.
I have a main thread where I get an object from database, then close the EntityManager, then put the object into a queue. A worker thread makes some business with the object, then puts it into a finished queue. Then the main thread gets the object from the finished queue and merges the object:
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
// Print the primary key ID of the object,
// it is NOT empty yes it exists in the db.
em.merge(myObject);
em.getTransaction().commit();
em.close();
I confirm the primary key ID of the object by printing it before merging, and yes it exists in the database. Unfortunately it throws a duplicate key exception from MySQL. How? Shouldn't JPA know that the object has an ID and update it, instead of inserting?
The SQL statement that is mentioned in the exception is an INSERT statement, not UPDATE.
The primary key of the entity is below:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "COMPANYID")
private long companyId;
SOLVED
I was setting a List field of the object to the List field of another newly created object. Then, for some reason that I don't understand, EclipseLink thinks that the object is a new object. God knows why. This JPA stuff is extremely counter-intuitive. You have to be truly an expert to use it. Otherwise it is totally useless because anytime you can mess it up. In practice the total headache and time loss it creates is much more than its advantages!
Anyway, I solved the problem by adding the elements of the other object to the old object as follows:
oldObject.getHistories().clear();
for (History history : newObject.getHistories()) {
history.setCompany(oldObject);
oldObject.getHistories().add(history);
}
Now EclipseLink correctly UPDATES instead of inserting new. I will be happy if someone can explain why this behavior?
This is happening because your EntityManager does not know about the object myObject.
For your code to work you should do something like this:
EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
Object myObject = em.findById(someId);
em.getTransaction().commit();
object.setName("yourNewName");
.....................
//And now run
em.getTransaction().begin();
em.merge(myObject);
em.getTransaction().commit();
em.close();
Please note that I close the EntityManager only after I have done all my transactions needed to the database.
And myObject is exactly the one that I recieved from my em.findById, I do not simply create a new object, because that will not work.
This is a tutorial that maybe can help you understand a bit more.(you have in the right a playlist with more videos about JPA ).
I have an issue with a manytomany bi-directional JPA mapping that is causing me a stack overflow error
While I have a total of 5 entities in my application I believe the issue is only related to 2 of them which are described below for simplicity.
Entities:
Application
User
An Application can have many developers
A User can develop many applications
Here is how they are mapped:
//Application Entity:
#ManyToMany
private List<Users> users
//User Entity
#ManyToMany(mappedBy = "users", fetch = FetchType.LAZY)
private List <Applications> applications;
This results in 3 tables being created in the database:
APPLICATIONS < ID APPNAME
APPLICATION_USERS < Join Table contains USERS_ID APPLICATIONS_ID
USERS < ID USER NAME
It is a circular reference that keeps running until there is a stack overflow.
The application works fine when it is first deployed with empty tables.
A user registers for an application and this creates a row in the APPLICATIONS table (if the Application does not exist)
A row is also created in the USERS table (if the user does not exist) and the join table APPLICATION_USERS
is populated with the ID from the APPLICATION Table called APPLICATIONS_ID and
the ID from the USERS table called USERS_ID.
You can add as many applications or users as you wish and the application works perfectly.
I have verified that data is being loaded and persisted into the 3 tables exactly as expected
Here is an example of the data in the tables after a user registers an Application:
APPLICATIONS
ID
51
APPLICATION_USERS
USERS_ID APPLICATIONS_ID
1 51
USERS
ID
1
Now when the server is stopped and restarted or when the application is re-deployed using create-tables
(vs drop-and-create-tables) (and data is present in the tables) then I get a stack overflow at each entities toString() function.
I have run this in debug with breakpoints on the Applications toString() function and on the Users toString() function and I can click resume and watch each toString() function get called over and over until
the stack overflow results.
Here is the console log:
(Entity query being executed)
[EL Fine]: 2014-01-21
14:48:44.383--ServerSession(1615948530)--Connection(49767657)--Thread(Thread[http-bio-8080-exec-9,5,main])--SELECT
t1.ID, t1.APPIDENTIFIER, t1.DATECREATED, t1.DATEMODIFIED,
t1.DEVICETYPE FROM APPLICATIONS_Users t0, APPLICATIONS t1 WHERE
((t0.users_ID = ?) AND (t1.ID = t0.applications_ID))
(second entity query is invoked)
[EL Fine]: 2014-01-21
14:50:02.444--ServerSession(1615948530)--Connection(1871047709)--Thread(Thread[http-bio-8080-exec-9,5,main])--SELECT
t1.ID, t1.DATECREATED, t1.DATEMODIFIED, t1.EMAIL, t1.FIRSTNAME,
t1.FULLNAME, t1.LASTLOGINDATE, t1.LASTNAME, t1.USERNAME FROM
APPLICATIONS_Users t0, Users t1 WHERE ((t0.applications_ID = ?) AND
(t1.ID = t0.users_ID))
[EL Finest]: 2014-01-21
14:50:02.471--ServerSession(1615948530)--Connection(1601422824)--Thread(Thread[http-bio-8080-exec-9,5,main])--Connection
released to connection pool [read].
java.lang.StackOverflowError
at java.util.Vector.get(Vector.java:693)
at java.util.AbstractList$Itr.next(AbstractList.java:345)
at java.util.AbstractCollection.toString(AbstractCollection.java:421)
at java.util.Vector.toString(Vector.java:940)
at org.eclipse.persistence.indirection.IndirectList.toString(IndirectList.java:797)
at java.lang.String.valueOf(String.java:2826)
at java.lang.StringBuilder.append(StringBuilder.java:115)
at com.sap.crashlogserver.dao.entities.Applications.toString(Applications.java:150)
at java.lang.String.valueOf(String.java:2826)
at java.lang.StringBuilder.append(StringBuilder.java:115)
at java.util.AbstractCollection.toString(AbstractCollection.java:422)
at java.util.Vector.toString(Vector.java:940)
at org.eclipse.persistence.indirection.IndirectList.toString(IndirectList.java:797)
at java.lang.String.valueOf(String.java:2826)
at java.lang.StringBuilder.append(StringBuilder.java:115)
at com.sap.crashlogserver.dao.entities.Users.toString(Users.java:168)
at java.lang.String.valueOf(String.java:2826)
at java.lang.StringBuilder.append(StringBuilder.java:115)
at java.util.AbstractCollection.toString(AbstractCollection.java:422)
at java.util.Vector.toString(Vector.java:940)
at org.eclipse.persistence.indirection.IndirectList.toString(IndirectList.java:797)
at java.lang.String.valueOf(String.java:2826)
at java.lang.StringBuilder.append(StringBuilder.java:115)
at com.sap.crashlogserver.dao.entities.Applications.toString(Applications.java:150)
at java.lang.String.valueOf(String.java:2826)
at java.lang.StringBuilder.append(StringBuilder.java:115)
at java.util.AbstractCollection.toString(AbstractCollection.java:422)
at java.util.Vector.toString(Vector.java:940)
Based on a number of threads I have read I tried:
1. Reversing the mappings,
2. Adding #JsonIgnore to some of the entity fields
3. Using fetch = FetchType.LAZY
and many other config tweaks but none of them resolved this issue.
Some of the suggestions like using transient fields. I am not sure this is supported in my JPA implementation of eclipselink.
I also read a thread suggestion me to implement gson.ExclusionStrategy.
Have not tried this yet.
So that's the story. I am a newbie at Java and JPA.
This is a very difficult issue for me to figure out.
Any suggestions you may have to help me to resolve it would be greatly appreciated.
Maybe it would help someone.
I got same problem. I use annotation #InvisibleJson with custom ExclusionStrategy to hide these fields
public class SafeDataExclusionStrategy implements ExclusionStrategy {
#Override
public boolean shouldSkipClass(Class<?> c) {
return false;
}
#Override
public boolean shouldSkipField(FieldAttributes c) {
return c.getAnnotation(InvisibleJson.class) != null;
}
}
and
#Retention(RetentionPolicy.RUNTIME)
#Target(value = ElementType.FIELD)
public #interface InvisibleJson {}
like
#InvisibleJson
#ManyToMany(mappedBy="roles")
public List<Staff> staff;
so GSON doesn't dig inside staff object.
Your objects toString method is recursively calling toString on the entire model. The stack doesn't show why it is being called at first, but it might be due to some logging setting. Fix your toString method and turn off logging,
Very simple solution, use the annotation #Expose over all the variables and lists you wish to allow GSON serialize/deserialise.
A simple example that would basically only allow GSON to use name:
#Expose
private String name;
#ManyToMany
private List<Users> users
Then where you are using GsonBuilder, do the following:
GsonBuilder builder = new GsonBuilder().disableHtmlEscaping();
builder.excludeFieldsWithoutExposeAnnotation(); //<-- This tells GSON look for #Expose
gson = builder.create();
Also you could put #Expose over the list but you would need go into the users entity and make sure to place #Expose on the stuff in that entity for when GSON digs into it.
I get this error when I try to run this code.
Error:
javax.persistence.TransactionRequiredException: executeUpdate is not supported for a Query object obtained through non-transactional access of a container-managed transactional EntityManager
Code: (_ut is a UserTransaction object)
public void setMainCategory(Integer deptId, Integer catId) {
try {
Query setmain = _entityManager.createNamedQuery("Category.setAsMain");
Query removeMain = _entityManager.createNamedQuery("Category.removeMain");
setmain.setParameter("categoryId", catId);
Department d;
d=_entityManager.find(Department.class, deptId);
removeMain.setParameter("department", d);
_ut.begin();
removeMain.executeUpdate();
_ut.commit();
_ut.begin();
setmain.executeUpdate();
_ut.commit();
} catch (Exception e) {
e.printStackTrace();
}
}
I have other functions that are identical in implementation and they do not throw this error.
Any suggestions would be greatly appreciated.
Thanks.
You are using an EntityManager to get the Named Queries and also using an (what I think is) injected UserTransaction.
See that the error message says "...Query object obtained through non-transactional access...". This means you are getting the "NamedQuery" through non-transactional access, because the EntityManager is not in the same transaction as _ut. So, you first join the EntityManager to the UserTransaction then you get and execute the query.
Finally your block should look like:
#PersistenceContext(unitName = "myPU")
EntityManager em;
#Inject
UserTransaction ut;
public void doSomeStuff()
{
ut.begin();
em.joinTransaction();
em.createNamedQuery("query1").executeUpdate();
ut.commit();
}
The problem does not come from your method implementation but from your execution context. All method that update database must be executed within an opened transaction. The way you ensure that depends on the way you manage transaction. If transactions are user-managed you have to explicitly retrieve and join an existing transaction or open a new one. If it's container-managed just add #transactional annotation on your method or ensure that there is a method in your call hierarchy holding the annotation.
Here you're using user-managed transactions in a container-managed transaction context (see "container-managed transactional EntityManager" in your error message). You shouldn't so begin and commit / rollback yourself the transactions. If you want to do so, just retrieve an application-managed EntityManager to be able to properly access to JTA transactions.
cf. http://docs.oracle.com/cd/E19226-01/820-7627/bnbqy/index.html
According to the error message, I think the reason is that your order of transaction, you didn't get the Department d in transaction, so the statement of d is detached, then you want to directly update it which will change the statement to managed, the JPA can't do this. just move the find code in the transaction, I think it will be ok.
I'm writing updates as part of CRUD testing and when I test my code, I get an error saying no entities are found. I have no idea why, because my partner did the exact same code and he worked perfectly. Neither of us is able to figure out what's going on. I'm getting an error on the getSingleResult() method.
#Test
public void updateBookTest() {
Book book = em.createQuery("select b from Book b where b.title = :title", Book.class).setParameter("title", "createABook").getSingleResult();
tx.begin();
book.setTitle("updatedThisBook");
book.setAuthor("newAuthor");
tx.commit();
Book updatedBook = em.find(Book.class, book.getBookId());
assertEquals(book.getTitle(), updatedBook.getTitle());
assertEquals(book.getAuthor(), updatedBook.getAuthor());
System.out.println("updateBookTest:\t" + book.toString());
tx.begin();
book.setTitle("createABook");
tx.commit();
}
This is my code. Let me know if more information is needed.
getSingleResult must to throw a NoResultException if there is no result.
So, your test is Ok.
Check that both are using same database (and there is no data returned), both are running same query, and both are using same jpa implementation versions.
From javadoc (since jpa 1.0):
getSingleResult
java.lang.Object getSingleResult(): Execute a SELECT query that returns a single untyped result.
Returns: the result
Throws:
NoResultException - if there is no result
NonUniqueResultException - if more than one result
IllegalStateException - if called for a Java Persistence query language UPDATE or DELETE statement
QueryTimeoutException - if the query execution exceeds the query timeout value set and only the statement is rolled back
TransactionRequiredException - if a lock mode has been set and there is no transaction
PessimisticLockException - if pessimistic locking fails and the transaction is rolled back
LockTimeoutException - if pessimistic locking fails and only the statement is rolled back
PersistenceException - if the query execution exceeds the query timeout value set and the transaction is rolled back
Reference javadoc getSingleResult
Another point, check that your friend is not calling getResultList instead of getSingleResult. This method returns a list and no throw an exception if empty.
Reference javadoc getResultList