JPA, Spring nested transactions - spring

I have to persist an object O that contains a list of OL objects in one transaction:
#Entity
public class O {
#OneToMany
private List<OL> olist;
// getters/setters
}
I'm reading O and OL from a file (xml interface) and have to insert them into the DB.
The condition is the following: if an exception is thrown while persisting an OL object, ignore and continue with other OLs:
#Transactional
private persistO(...) {
O o = new O();
o.set(...);
oDao.persist(o);
for (int i = 0; i < olCount; i++) {
OL ol = new OL();
ol.set(...);
try {
olDao.persist(ol);
em.flush();
}
catch(ConstraintViolationException ex) {
logger.warn()...;
}
}
}`
The problem is that at the first ConstraintViolationException the transaction is set to rollbackOnly and nothing gets persisted.
org.springframework.transaction.TransactionSystemException:
Could not commit JPA transaction;
nested exception is javax.persistence.RollbackException:
Transaction marked as rollbackOnly
Question: How can this be achieved with JPA (Hibernate + Spring)?
Note
I know that it might be possible to first make a query in the DB, make sure that the OL object doesn't exists yet, but let's assume the procedure is very complex and the performance would suffer a lot.
The requirement doesn't allow me to persist the OL objects in new transactions (it
s all or nothing), so #Transactional(propagation = Propagation.PROPAGATION_REQUIRES_NEW) cannot be used.

What you are describing sounds a bit self-contradictory, in terms of transactions. Transactions are meant to be all or nothing. Why would an OL add fail?
Reading between the lines, you are trying to do an add with no overwrite. That sounds a bit suspicious, from a design standpoint. How would there be existing OL without an existing O? You could using SQL to blindly delete any orphan OL, if that's what's going on. This also raises the question whether you. using surrogate keys.
You could add the new O and persist, before adding any OL instances. You could then flush and retrieve the O, which would retrieve any existing OL. You would then add your new OL and persist.

Related

Spring ACL with Kotlin Exposed

I'm using kotlin exposed and spring acl: JdbcMutableAclService.
The dummy code looks like:
transaction {
//operation 1
dao.updateSomething(resourceId)
val sids = dao.getUserIdsByResourceId(resourceId)
//operation 2
val pObjectIdentity = ObjectIdentityImpl(PROJECT, resourceId)
val pMutableAcl = aclService.readAclById(pObjectIdentity) as MutableAcl
var i = pMutableAcl.entries.size
sids.forEach {
pMutableAcl.insertAce(i++, BasePermission.READ, PrincipalSid(it), true)
}
aclService.updateAcl(pMutableAcl)
//operation 3
val rObjectIdentity = ObjectIdentityImpl(RESOURCE, resourceId)
val rMutableAcl = aclService.readAclById(rObjectIdentity) as MutableAcl
var i = rMutableAcl.entries.size
sids.forEach {
rMutableAcl.insertAce(i++, BasePermission.READ, PrincipalSid(it), true)
}
aclService.updateAcl(rMutableAcl)
}
If something happens in operation 3 - it won't write nothing to db, the outer transaction will also rolled back, and operation 1 won't be committed as well.
Unfortunately operation 2 won't be rolled back.
So my assumption every time of using updateAcl it creates its own isolated transaction.
I don't know how it work in case of Spring Jpa, and #Transactional annotation (is JdbcMutableAclService take into consideration outer transaction or not), but in case of Exposed it is not.
Is it correct behaviour at all? Should every acl update be an isolated transaction?
Is there a way to integrate Exposed and JdbcMutableAclService without implementing my own MutableAclService?
UPD for #Tapac
I'm using org.jetbrains.exposed:exposed-spring-boot-starter without any additional configuration, so based on ExposedAutoConfiguration it is org.jetbrains.exposed.spring.SpringTransactionManager.
But during the debugging i saw in stacktrace some refs to ThreadLocalTransactionManager.
And don't know is it useful information, but i don't use spring transaction annotation and instead of that i use exposed transaction{} block.

REPEATABLE READ isolation level at update method is meaningful?

The Repeatable Read isolation level only sees data committed before the transaction began.
So, I think it is only meaningful at Method doing repeat select.
But, I see a below code.
#Transactional(isolation = Isolation.REPEATABLE_READ)
#Override
public void buyItem(Credit playerCredit, Long haveToPayCredit) {
if(haveToPayCredit > playerCredit.getFreeCredit()) {
Long remainHaveToPay = haveToPayCredit - playerCredit.getFreeCredit();
playerCredit.updateCredit(playerCredit.getPaidCredit() - remainHaveToPay, 0L);
}
else {
playerCredit.updateCredit(playerCredit.getPaidCredit(), playerCredit.getFreeCredit() - haveToPayCredit);
}
}
This method do only update. So, It look so meaningless to add Transactional annotation.
The REPEATABLE READ isolation level at update method is meaningful?

Hibernate persist failure with PostGIS Geometry

Related to previous question. I have a Spring Roo application using Hibernate to write a Geometry object to a PostGIS database using JTS. I believe I've fixed the problems I had in defining my Geometry object, and now Hibernate is executing its persist() method, but something is going wrong just before it hits the DB and I'm getting the exception below.
Here are some interesting lines. First from the Hibernate logs, the object to be persisted, and then an SQL query (presumably the ? are substituted):
...
DEBUG org.hibernate.pretty.Printer - com.test.LandUse{id=1, centerPoint=POINT (5 6), version=0}
...
DEBUG org.hibernate.SQL - insert into land_use (center_point, version, id) values (?, ?, ?)
...
Then some more things happen, though nothing obviously bad. However I don't see any 'final' SQL, and there is an attempt to roll back the transaction. Then:
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing the transaction
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
at org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$afterReturning$org_springframework_transaction_aspectj_AbstractTransactionAspect$3$2a73e96c(AbstractTransactionAspect.aj:78)
at com.test.LandUse_Roo_Jpa_ActiveRecord.ajc$interMethod$com_test_LandUse_Roo_Jpa_ActiveRecord$com_test_LandUse$persist(LandUse_Roo_Jpa_ActiveRecord.aj:44)
at com.test.LandUse.persist(LandUse.java:1)
at com.test.LandUse_Roo_Jpa_ActiveRecord.ajc$interMethodDispatch1$com_test_LandUse_Roo_Jpa_ActiveRecord$com_test_LandUse$persist(LandUse_Roo_Jpa_ActiveRecord.aj)
at com.test.LandUseController_Roo_Controller.ajc$interMethod$com_test_LandUseController_Roo_Controller$com_test_LandUseController$create(LandUseController_Roo_Controller.aj:29)
at com.test.LandUseController.create(LandUseController.java:1)
...
Caused by: javax.persistence.RollbackException: Error while committing the transaction
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:93)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:512)
... 54 more
Caused by: java.lang.UnsupportedOperationException
at org.hibernate.spatial.GeometrySqlTypeDescriptor.getBinder(GeometrySqlTypeDescriptor.java:52)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:283)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:278)
at org.hibernate.type.AbstractSingleColumnStandardBasicType.nullSafeSet(AbstractSingleColumnStandardBasicType.java:89)
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:2184)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2430)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2874)
at org.hibernate.action.EntityInsertAction.execute(EntityInsertAction.java:79)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265)
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)
... 55 more
I've been trying to get this simple use case (an object with just a single Geometry property) working for over a week now, and am about at my wits' end. If I replace the Geometry object with a String it works just fine. Does anyone know what might be causing such an error?
EDIT: Thierry's answer below got me poking through the source, and I noticed the exception is thrown in GeometrySqlTypeDescriptor, which has some interesting contents:
/**
* A generic <code>SqlTypeDescriptor</code>, intended to be remapped
* by the spatial dialect.
*
* #author Karel Maesen, Geovise BVBA
* creation-date: 7/27/11
*/
public class GeometrySqlTypeDescriptor implements SqlTypeDescriptor {
public static final GeometrySqlTypeDescriptor INSTANCE = new GeometrySqlTypeDescriptor();
#Override
public int getSqlType() {
return 3000; //this value doesn't conflict with presently defined java.sql.Types values.
}
#Override
public boolean canBeRemapped() {
return true;
}
#Override
public <X> ValueBinder<X> getBinder(JavaTypeDescriptor<X> javaTypeDescriptor) {
throw new UnsupportedOperationException();
}
#Override
public <X> ValueExtractor<X> getExtractor(JavaTypeDescriptor<X> javaTypeDescriptor) {
throw new UnsupportedOperationException();
}
}
In particular, note the class comment suggesting something is clearly wrong with the Hibernate dialect mapping. Unfortunately I have no idea what that means, but I'm guessing due to some kind of version mismatch. (Note also the declaration of SQL type 3000, as per my previous error!)
My current dialect is org.hibernate.spatial.dialect.postgis.PostgisDialect, as per the Hibernate Spatial usage guide. I'm using Hibernate Spatial 4.0-M1, JTS 1.12, and PostGIS 2.0.1. I'll try with a couple of different versions of PostGIS perhaps, particularly since that's the one dependency that Hibernate Spatial is supposed to provide but doesn't seem to.
It seems the problem was that the PostgisDialect was not been picked up and integrated correctly, and hence the required operations were not supported. The solution was as simple as upgrading from Hibernate 3.6.9.Final to 4.1.6.Final!
See my thread on the mailing list for more information.
As per that thread, you should also be aware that as of Hibernate Spatial 4.0-M1, only the Geometry type is specified to Hibernate, and hence the #Column annotation must set columnDefinition="Geometry", and not Point or anything else. This may be fixed in the future.
With this anthology of modifications, I can finally write a Point to a database! The correct property specification is:
#Column(columnDefinition="Geometry")
#Type(type = "org.hibernate.spatial.GeometryType")
private Point centerPoint;
I got this exception when I forgot to add the Postgis Dialect in hibernate configuration file.
Add following line to hibernate.cfg.xml
<property name="dialect">org.hibernate.spatial.dialect.postgis.PostgisDialect</property>
Yes, the ? are substituted by the values you need to store.
Did you try to use the following type: GeometryUserType and not the GeometryType?
I suspect GeometryType is not directly supported by the API of Hibernate Spatial Project. It is maybe an abstract class which you could not instantiate directly to map your datas with annotations - it acts beyond the scene as we have experimented.
Caused by: java.lang.UnsupportedOperationException which has make me tell that.
And the last XML stuff inside the tutorial you have followed is clear:
...
<property name="geometry" type="org.hibernatespatial.GeometryUserType">
<column name="geom" />
</property>
...
Looking at the code inside the GeometryUserType I see only one place where these exception could be thrown.
public Object conv2DBGeometry(Geometry jtsGeom, Connection connection) {
org.postgis.Geometry geom = null;
jtsGeom = forceEmptyToGeometryCollection(jtsGeom);
if (jtsGeom instanceof com.vividsolutions.jts.geom.Point) {
geom = convertJTSPoint((com.vividsolutions.jts.geom.Point) jtsGeom);
} else if (jtsGeom instanceof com.vividsolutions.jts.geom.LineString) {
geom = convertJTSLineString((com.vividsolutions.jts.geom.LineString) jtsGeom);
} else if (jtsGeom instanceof com.vividsolutions.jts.geom.MultiLineString) {
geom = convertJTSMultiLineString((com.vividsolutions.jts.geom.MultiLineString) jtsGeom);
} else if (jtsGeom instanceof com.vividsolutions.jts.geom.Polygon) {
geom = convertJTSPolygon((com.vividsolutions.jts.geom.Polygon) jtsGeom);
} else if (jtsGeom instanceof com.vividsolutions.jts.geom.MultiPoint) {
geom = convertJTSMultiPoint((com.vividsolutions.jts.geom.MultiPoint) jtsGeom);
} else if (jtsGeom instanceof com.vividsolutions.jts.geom.MultiPolygon) {
geom = convertJTSMultiPolygon((com.vividsolutions.jts.geom.MultiPolygon) jtsGeom);
} else if (jtsGeom instanceof com.vividsolutions.jts.geom.GeometryCollection) {
geom = convertJTSGeometryCollection((com.vividsolutions.jts.geom.GeometryCollection) jtsGeom);
}
if (geom != null)
return new PGgeometry(geom);
else
throw new UnsupportedOperationException("Conversion of "
+ jtsGeom.getClass().getSimpleName()
+ " to PGgeometry not supported");
}
Where PGgeometry stands for PostGis Geometry I think (or maybe PostgreSQL).
I have found some topics where Karel Maesen and others speak about the InnoDB support is not very well, but they are maybe outdated (05-2011).
Good luck!

Jpa testing and automatic rollback with Spring

I am in reference to Spring Roo In Action (book from Manning). Somewhere in the book it says "Roo marks the test class as #Transactional so that the unit tests automatically roll back any change.
Here is the illustrating method:
#Test
#Transactional
public void addAndFetchCourseViaRepo() {
Course c = new Course();
c.setCourseType(CourseTypeEnum.CONTINUING_EDUCATION);
c.setName("Stand-up Comedy");
c.setDescription(
"You'll laugh, you'll cry, it will become a part of you.");
c.setMaxiumumCapacity(10);
c.persist();
c.flush();
c.clear();
Assert.assertNotNull(c.getId());
Course c2 = Course.findCourse(c.getId());
Assert.assertNotNull(c2);
Assert.assertEquals(c.getName(), c2.getName());
Assert.assertEquals(c2.getDescription(), c.getDescription());
Assert.assertEquals(
c.getMaxiumumCapacity(), c2.getMaxiumumCapacity());
Assert.assertEquals(c.getCourseType(), c2.getCourseType());
}
However, I don't understand why changes in this method would be automatically rolled back if no RuntimeException occurs...
Quote from documentation:
By default, the framework will create and roll back a transaction for each test. You simply write code that can assume the existence of a transaction. [...] In addition, if test methods delete the contents of selected tables while running within a transaction, the transaction will roll back by default, and the database will return to its state prior to execution of the test. Transactional support is provided to your test class via a PlatformTransactionManager bean defined in the test's application context.
So, in other words, SpringJUnit4ClassRunner who runs your tests always do transaction rollback after test execution.
I'm trying to find a method that allows me to do a rollback when one of the elements of a list fails for a reason within the business rules established (ie: when throw my customize exception)
Example, (the idea is not recording anything if one element in list fails)
public class ControlSaveElement {
public void saveRecords(List<MyRecord> listRecords) {
Boolean status = true;
foreach(MyRecord element: listRecords) {
// Here is business rules
if(element.getStatus() == false) {
// something
status = false;
}
element.persist();
}
if(status == false) {
// I need to do roll back from all elements persisted before
}
}
...
}
Any idea? I'm working with Roo 1.2.2..

TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing

I know this question has been asked numerous times but I couldn't find a suiting answer for me.
I've got two entities in a Spring Roo-application which are in a Many-To-Many-relationship, Release and Component.
First I fetch an instance of an existing Release via
selectedRelease = Release.findReleasesByReleaseNumberEquals(version).getSingleResult();
The method above is a Roo-generated finder which looks like this:
public static TypedQuery<Release> Release.findReleasesByReleaseNummerEquals(String releaseNumber) {
EntityManager em = Release.entityManager();
TypedQuery<Release> q = em.createQuery("SELECT o FROM Release AS o WHERE LOWER(o.releaseNumber) LIKE LOWER(:releaseNummer)", Release.class);
q.setParameter("releaseNumber", releaseNumber);
return q;
}
Then I create a new instance of Component and try to assign it to the selected Release
Component component = new Component();
Set<Release> releases = new HashSet<Release>();
releases.add(selectedRelease);
component.setReleases(releases);
component.persist();
Upon trying to execute persist() I get the exception:
TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.Release;
Does anyone have advice regarding this problem?
The mappings look like this:
Release.java:
#ManyToMany(cascade = CascadeType.PERSIST, mappedBy = "releases")
private Set<Component> components = new HashSet<Component>();
Component.java
#ManyToMany(cascade=CascadeType.PERSIST)
private Set<Release> releases = new HashSet<Release>();
The message is clear: you are trying to save an object, component, that references another object that hasn't been saved yet, selectedRelease.
The solution seems to be easy: just save (and flush whenever you want the changes be comunicated to the database) the Release objects before saving the Component.
However, JPA allows you to avoid all these sentences with TRANSITIVE PERSISTENCE, that is, the cascade options in the #...ToMany annotations.
Just a warning in case you have a bidirectional relationship, when you have a mappedBy attribute in one of the #...ToMany associations. In this case, you'll need a convenient method to manage the association, that is, to set both sides of the association.
For instance, an addRelease(Release r) in Component that both adds the release to the component's set, and sets the component (actually, the this instance) to the release passed as parameter.

Resources