I have an procedure in oracle database which takes two parameters:
procedure some_procedure(int x, int y)
My project is using spring boot + hibernate. So, the question is how I can execute this stored procedure using batching (for example 100) from java code?
I've found some examples with usage of #Procedure annotation and also with StoredProcedureQuery - but those were only for simple not batching call.
And also could someone explain when I should use #Procedure annotation instead of StoredProcedureQuery?
When calling stored procedure, you get OUT parameters and can,t change it through calling. If stored procedure return ref_cursor, and you want to limit records, change stored procedure.
With StoredProcedureQuery you will write more code and manually manage calling process (get entityManager, open transaction, make a call, close transaction ...). With JPA repository you describe procedure by annotations and go. But both approaches work.
UPD:
Oh, little misunderstanding. You can call procedure several times in one transaction with StoredProcedureQuery, is this what you want? Code looks like this:
public class ProcedureCall {
#Autowired
private EntityManagerFactory entityManagerFactory;
public void executeProcedureWithoutResult(Map<String, ?> parameters, String procedureName) {
EntityManager em = entityManagerFactory.createEntityManager();
StoredProcedureQuery procedureQuery1 = em.createNamedStoredProcedureQuery(procedureName);
StoredProcedureQuery procedureQuery2 = em.createNamedStoredProcedureQuery(procedureName);
fillProcedureParameters(procedureQuery1, parameters);
fillProcedureParameters(procedureQuery2, parameters);
try {
em.getTransaction().begin();
procedureQuery1.execute();
procedureQuery2.execute();
em.getTransaction().commit();
} catch (Exception e) {
rollbackTransaction(e, em.getTransaction());
} finally {
if (em.isOpen()) {
em.close();
}
}
}
private void fillProcedureParameters(StoredProcedureQuery sp, Map<String, ?> parameters) {
if (parameters.size() > 0) {
for (HashMap.Entry<String, ?> rec : parameters.entrySet()) {
sp.setParameter(rec.getKey(), rec.getValue());
}
}
}
Maybe it even works, if you execute one procedureQuery twice, but I didn't try.
Related
I want to test that my controller endpoint returns an appropriate error code when trying to delete a record with referencing child records. In my integration test, I need to set up the state so that the related records exist, then invoke the deletion endpoint, expect the error condition, and then (ideally) roll the entire DB back to the state it was in before the test.
e.g.
INSERT INTO parent_rec (id) VALUES ("foo");
INSERT INTO child_rec (id, parent_id) VALUES ("bar", "foo");
COMMIT;
DELETE FROM parent_rec WHERE id = "foo"; -- bang!
#PersistenceContext
EntityManager em;
#Transactional
void testDelete() {
// Set up records
ParentRecord record = new ParentRecord("foo");
em.persist(record);
em.persist(new ChildRecord("bar", record));
//delete
mockMvc.perform(delete("/parent/foo")).andExpect(/* some error code */);
}
However, I'm running into issues. If I put the #Transactional annotation at the method or class level, the records aren't persisted until after the deletion is attempted so the deletion returns a 200 OK rather than a 400 Bad Request or similar.
The current solution is for the tests to be run in order (with a previous test setting up records which a subsequent test tries to operate on). However, this makes the tests pretty brittle and dependent on each other, which I'd like to avoid primarily to make changing the code easier.
Can I accomplish what I want without using an additional layer of tooling? In the past, I'd have used DBUnit to do something like this, but if I can avoid adding the additional dependency I'd prefer to keep it simple.
In JEE I solved these issues kind of simply by splitting my code into two parts:
#Transactional(propagation = Propagation.REQUIRES_NEW)
public class ParentRecordTestFacade {
public void create() {
// Create record here
}
public void delete() {
// Delete record here
}
}
and then call both methods in the actual unit test one after another.
Running only some code in a separate transaction also comes in handy. You can achieve it for example by creating a method fo the block of code to invoke in transaction:
protected <T> T getInsideTransaction(Function<EntityManager, T> transactional) {
EntityManager em = null;
EntityTransaction trx = null;
try {
em = entityManagerFactory.createEntityManager();
trx = em.getTransaction();
trx.begin();
return transactional.apply(em);
} catch (Throwable throwable) {
throw throwable;
} finally {
if (trx != null) {
if (!trx.getRollbackOnly()) {
trx.commit();
} else {
trx.rollback();
}
}
if (em != null) {
em.close();
}
}
}
Now you can invoke it like that:
void testDelete() {
// Set up records
getInsideTransaction(em -> {
ParentRecord record = new ParentRecord("foo");
em.persist(record);
em.persist(new ChildRecord("bar", record));
}
//delete
mockMvc.perform(delete("/parent/foo")).andExpect(/* some error code */);
}
You can invoke an arbitrary block of code within separate transaction that way.
In spring especially for test such cases in repository layer I using, looks like should works and for you - org.springframework.test.context.transaction.TestTransaction. Pay attention on #Commit annotation on test method, otherwise your record will not be saved.
#Commit
void testDelete() {
// Set up records
ParentRecord record = new ParentRecord("foo");
em.persist(record);
em.persist(new ChildRecord("bar", record));
TestTransaction.end()
TestTransaction.start()
//delete
mockMvc.perform(delete("/parent/foo")).andExpect(/* some error code */);
}
But of course after commit you should delete manually you record.
I want to use stored procedure to add and delete hierarchical data using hibernate. For doing that I want to first check if that procedure exist in database and if not create it. I looked for the standard way of doing this in hibernate but found nothing. I am just curious to know that is it good to create a procedure using hibernate or is there a better way of doing operation on hierarchical data in hibernate.
I am calling webservice from my app that is using the stored procedure to return data in a hierarchical format. If the procedure is deleted at runtime unknowingly, my app will never be able to retrieve data. So what I want if procedure does not exist create it. I am not sure is it the right way or not?
I need guidance...
You can call stored function or procedure in Hibernate as follow,
session.doWork(new Work() {
public void execute(Connection connection) throws SQLException {
CallableStatement call = connection.prepareCall("{ ? = call MYSCHEMA.MYFUNC(?,?) }");
call.registerOutParameter( 1, Types.INTEGER ); // or whatever it is
call.setLong(2, id);
call.setLong(3, transId);
call.execute();
int result = call.getInt(1); // propagate this back to enclosing class
}
});
Similarly
int result = session.doReturningWork(new ReturningWork<Integer>() {
#Override
public Integer execute(Connection connection) throws SQLException {
CallableStatement call = connection.prepareCall("{ ? = call MYSCHEMA.MYFUNC(?,?) }");
call.registerOutParameter( 1, Types.INTEGER ); // or whatever it is
call.setLong(2, id);
call.setLong(3, transId);
call.execute();
return call.getInt(1); // propagate this back to enclosing class
}
});
I have an issue with a webapp running in tomcat where I have an abstract DAO class with a method called all() which returns all entities from the database or JPA cache. It seems to correctly return the entities on the initial call but subsequent calls don't reflect updates happening from separate UI calls which will use the entity managers find method to find the specific entity from the list, update the relative fields and commit that. When I view that list via the same all() method later I still see the original values. If I make another update in the logs I can see the value changing from the correct value(not the original value) to the updated value and the logs shows those updates happening correctly each time.
I'm using guice for injection. I've played around with the logging and can see the same hashcode on the entity manager being used throughout a request but different for each request. I've played with the following the persistance.xml file which didn't seem to help either...
<property name="eclipselink.cache.shared.default" value="false" />
<shared-cache-mode>NONE</shared-cache-mode>
I can't see why my all() won't return updated results, I've also tried adding code to find the specific entity I'm updating in the list then replaced it by calling the following...
entity = em.find(Class.class, id)
This seemed to fix the issue on that particular entity so it appears my query is reusing old.
Here's a snippet from my DAO class
private final Provider<EntityManager> emP;
protected EntityManager em(boolean useTransaction) throws DaoException {
return useTransaction ? begin() : emP.get();
}
public List<T> all() throws DaoException {
EntityManager em = em(false);
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<T> cq = cb.createQuery(eClass);
Root<T> from = cq.from(eClass);
return em.createQuery(cq.select(from)).getResultList();
}
public T save(T t) throws DaoException {
return save(t, em(true));
}
protected T save(T t, EntityManager em) throws DaoException {
if (Objects.isNull(t)) {
throw new DaoException("can't save null object: " + getDaoClass(), new NullPointerException());
}
T saved;
if (t.getId() > 0) {
saved = em.merge(t);
} else {
em.persist(t);
saved = t;
}
autoCommit();
return saved;
}
protected void autoCommit() throws DaoException {
if (autoCommit) {
commit();
}
}
public void commit() throws DaoException {
EntityManager em = emP.get();
if (!em.getTransaction().isActive()) {
throw new DaoException("transaction isn't active, unable to commit");
}
try {
em.getTransaction().commit();
} catch (IllegalStateException e) {
throw new DaoException("transaction not active", e);
} catch (RollbackException e) {
throw new DaoException("commit rolled back", e);
}
}
So I'm wondering if anyone has any insights on why this might be happening or have any suggestions on what else I can try to check?
So I found the cause of the issue I was having. I was using the ElementCollection annotation in my entities when referencing lists. I removed the annotation and replaced it with a JoinTable and OneToMany annotations and things are working correctly.
The issue I had was that the entity would be stored in the database fine and I was updating that entity as expected but JPA had embedded the list of entities where it was referenced.
So I was seeing the embedded list returned each time which was not the actual entity I had updated. My entities are now using proper join tables instead of embedded objects and everything is behaving as expected.
I have got a Springboot Application and a Oracle DB with lots of PL/SQL Procedures and these change the state of the DB all the Time.
So now I want to change a loaded entity an want to save it. If the entitystate of the entitymanager and the state of the db is equal everything works fine. But in some cases they are not equal. So if I load an entity and make some changes an druring this a PL/SQL Procedure changes the DB Table. If I save the Entity I will get an Execption of course. So I tried to catch the Exception and then in the catch block I want to refresh the Entity before saving it. But I still get an Exception. Is the Transaction not jet finished? How can I handle this Problem?
I hope the example code explains a little bit.
#RestController
#RequestMapping("/*")
public class FacadeController {
...
#ResponseStatus(HttpStatus.OK)
#RequestMapping( value= "/test4" , method=RequestMethod.GET)
public String test4(){
Unit unit = unitSvice.loadUnit(346497519L);
List<UnitEntry> entries = unit.getEntries();
for (UnitEntry g : entries) {
if (g.getUnitEntryId == 993610345L) {
g.setTag("AA");
g.setVersion(g.getVersion() + 1);
g.setstatus("SaveOrUpdate");
}
}
//<-- DB Table changed entity managed by entitymanger and DB Table
// are no langer equal.
try {
unitSvice.updateUnit(unit , false);
}catch(DataAccessException | IllegalArgumentException e) {
unitSvice.updateUnit(unit , true);
}
...
}
}
#Service("unitSvice")
public class UnitSvice {
#Autowired
private UnitDao repoUnit;
#Transactional
public Unit loadUnit(Long _id) {
Unit unit = repoUnit.findOne(_id);
return unit;
}
#Transactional
public void updateUnit(Unit unit, boolean _withrefrsh ) {
if(_withrefrsh) {
getEntityManager().refresh(unit.getId());
}
repoUnit.save(unit);
}
}
I hope, anyone can help me.
Thanks
yes the problem is ..when you call load all method which is transactional method where entities became detached from session/entitymanager when you are returning from that method.. so,next you are trying to persist detached object. That's why you get exception.
so probably you can use session.update() or session.merge() to save the new update into database.
I use Hibernate and Oracle SQL in my project. When I call createNativeQuery method of the entity Manager, the entity manager doesn't answer any call (even from different browsers) before the method returns. The query takes long time but it is called in new a thread. Why is the entity manager blocked?
NOTE: When I call JPQL query, the problem disappears.
#PersistenceContext
private EntityManager entityManager;
//blocking other transactions, cannot make any read from the same entityManager until this method is completed.
#Transactional(readOnly=true)
public void testMethod1(String query) {
Query q = entityManager.createNativeQuery(query);
// CANNOT SET LOCKMODE because it is not JPQL : q.setLockMode(LockModeType.NONE) //throws exception
List<Object[]> result = q.getResultList();
}
#Transactional(readOnly=true)
public void testMethod2(String jpql) {
Query q = entityManager.createQuery(jpql);
List<Object> result = q.getResultList();
}
I was able to reproduce your problem using other databases (SQL Anywhere in my case).
After this I took a look at Hibernate JavaDocs and it says that your problem is the default behavior, because JPA requires that setLockMode should be applied only on non-native queries.
To have a workaround to this, Hibernate makes use of the QueryHints of the query:
NATIVE_LOCKMODE:
Available to apply lock mode to a native SQL query since JPA requires that Query.setLockMode(javax.persistence.LockModeType) throw an IllegalStateException if called for a native query.
To use QueryHints you should do something like this:
#Transactional(readOnly=true)
public void testMethod1(String query) {
Query q = entityManager.createNativeQuery(query);
q.setHint(QueryHints.NATIVE_LOCKMODE, LockModeType.NONE);
List<Object[]> result = q.getResultList();
}
I have also identified that if you use the #TransactionAttribute above your method the problem might not occur, example:
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void testMethod1(String query) {
Query q = entityManager.createNativeQuery(query);
q.setHint(QueryHints.NATIVE_LOCKMODE, LockModeType.NONE);
List<Object[]> result = q.getResultList();
}
Please, try the solutions given above and let us know if the problem was solved or not, good luck!