case 1:
class TestService{
#Autowired
private SqlSessionTemplate sqlSession;
public void insert(Map map1,Map map2,Map map3){
sqlSession.insert("testMapper.insert",map1);
sqlSession.insert("testMapper.insert",map2);
sqlSession.insert("testMapper.insert",map3);
}
}
case 2:
#Service
class ObjectService{
#Autowired
private SqlSessionTemplate sqlSession;
public void insert(Map map){
sqlSession.insert("testMapper.insert",map);
}
}
class TestService{
#Autowired
private ObjectService objectService;
public void insert(Map map1,Map map2,Map map3){
objectService.insert(map1);
objectService.insert(map2);
objectService.insert(map3);
}
}
I use mybatis and I have configured transactions using spring. I have a problem that the code described in the case #1 throws exception and the transaction is rolled back (so nothing is really inserted into database).
When I run case #2 the error is thrown again but data is still inserted into database and rollback does not happen.
Most probably you transaction configuration does not treat TestService.insert as a transactional method so it is not executed in transaction so each invocation of objectService.insert runs in its own transaction.
It means that for example the first invocation objectService.insert(map1) runs fine and the the problem happens during the second invocation. At that time the first transaction is already committed and even there is some problem with insertion of the second record the result of the first transaction is already persisted to database. And this looks like the absence of the rollback on the TestService.insert.
Related
I am sure that I am missing something, but I don't know exactly what...
Giving the following snippet:
#Service
public class MyClass {
private MyClass self;
private UserRepository userRepository;
private ApplicationContext applicationContext;
#PostConstruct
private void init() {
self = applicationContext.getBean(MyClass.class);
}
#Transactional
public void doA(User user) {
...
if (condition) {
self.doB(user);
throw new SecurityException();
}
user.setRandomField("x");
userRepository.save(user);
}
#Transactional(value = Transactional.TxType.REQUIRES_NEW)
public void doB(User user) {
...
userRepository.save(user);
}
}
What do I know about #Transactional is that if it is used, is redundant to call repository.save(entity).
What I am trying to do, is to process an entity from a transactional method, and if there is a breaking condition, call a new method (annotated with REQUIRES_NEW) that will update some fields of the entity and save it. The root method (doA) then throws an exception. FYI: the #Transactional(dontRollbackOn = SecurityException.class) is not an option in this situation.
For using this commiting mechanism, instead of creating a new bean just with one method I just injected the current bean into a variable just called self, therefore I can use the bean proxy for transaction management.
The odd thing is that if I am removing from doB the save call, when doA transaction is rollbacked because of the SecurityException, the changes performed by doB are rollbacked as well. But if I let it in there, this is working as expected.
Am I doing something wrong or am I missing something?
Thanks!
Try to do not pass User instance in the doB().
Pass an Id instead and read the User from the repo internally. I am not sure how the attached entity is handled between the different sessions.
I have a system based on hibernate 4. I have an unique constraint in a table and need to handle it the following way:
try{
getMyService().create(myobj);
}catch(PersistenceException p){
//constraint fails
myobj.setConstraintColumn("new non unique value");
getMyService().create(myobj);//should save it
}
unfortunately I can't change the design of the system so I need just to figure it out this way.
EDIT
I get the following exception:
org.hibernate.AssertionFailure: null id in entry (don't flush the Session after an exception occurs)
The code of create method:
public E create(E entity) {
entityManager.persist(entity);
entityManager.flush();
entityManager.refresh(entity);
return entity;
}
It is not clear where your transaction boundaries are.
When the exception is thrown, you will need to:
1) Ensure that the first transaction is closed (it should be, but not sure - see if you get a nested transaction trying #2 alone)
2) begin a new transaction before you are able to persist/flush again (and subsequently commit that).
I finally figured out the issue. So, let me explain one by one.
First of all, take a look at
Propagation.REQUIRES_NEW does not create a new transaction in Spring with JPA
Then, for example we have a class and method like:
public class MyClass{
#Transactional
public void myMethod(){
....
}
}
So, first of all lets consider that myMethod is in it's own transaction completely, because transactions are AOP based, and it will be committed when appropriate aspect will fire, but only after method completes, throws exception, etc. So we can't partially commit, partially rollback, rollback incompletely,etc. So, we need to do the following:
Start big outer transaction
Start a new nested transaction, try to insert a record.
If nested transaction will fail, it will be rolled back, but the outer one will still be running.
If the first nested transaction failed, then start a new nested transaction and insert a record with new data, which will prevent ConstaintViolationException from being thrown.
So, in this case, we create a class:
public class ServiceHelper{
#Transational(proparation = **Propagation.REQUIRED_NEW**)
public void tryConstraint throws MyConstraintException{
try{
//insert
}catch(ConstraintViolationException e){
throw new MyConstraintException(e);
}catch(Exception ex){
throw new Exception(ex);
}
}
#Transational(proparation = **Propagation.REQUIRED_NEW**)
public void insertWithNoConflict throws Exception {
//Set new data
//insert, if still CVE or anything other, just throw it , or leave it for unchecked exceptions then
}
}
And our service:
public class MyService{
#Autowired
private ServiceHelper serviceHelper;
#Transactional(propagation = **Propagation.REGUIRED_NEW**)
public void createWithCheck(){
try{
serviceHelper.tryConstraint();
}catch(MyConstraintException e){
serviceHelper.insertWithNoConflict();
}
}
}
But there is still a weird situation because I need to use MyService methods for records creation in ServiceHelper, but I can't obtain them there because it will cause circular injections, so I have to get them via services factory like:
MyService service = (MyService)ServicesFactory.getInstance().getBean(MyService.BEAN_ID)
And I don't like it. But this approach works, I checked it today.
We should know 2 things: first of all we can't do anything with a transaction inside a method, we can't start there any new transaction, etc. A transaction context is relevant to method completely, it will still be there before a method ends. And the second thing is required_new doesn't work when we launch the method from the method of the same proxy class.
I am trying to test the mechanism of rollbackin transaction in cases of somthing went wrong. I read many similar topics, but nothing helps me. Here is what i try:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/rest-servlet.xml" })
#Transactional
public class TagDaoImplTest extends DatabaseTestCase {
private static final String FLAT_XML_DATASET = "FlatXmlDataSet.xml";
#Autowired
private TagDao tagDao;
#Autowired
private SessionFactory sessionFactory;
#Before
public void setUp() throws Exception {
DatabaseOperation.REFRESH.execute(getConnection(), getDataSet());
}
#Test
public void testAddWithRollback() throws Exception {
addWithRollback("testTagToAdd"); //i suppouse that we tried to add, but somthing went wrong and transaction was rolled back
final Tag testTagToAdd = tagDao.findByTag("testTagToAdd"); // so db must be without changes and i check this
assertNull(testTagToAdd);
}
#Rollback(true) //i want this method to rollback after its work to imitate corrupted transaction
private void addWithRollback(String tag) throws Exception {
tagDao.add(new Tag(tag));
}
}
My dao looks like this:
#Repository("tagDao")
public class TagDaoImpl implements TagDao {
#Override
public void add(Tag tag) {
final Session session = sessionFactory.getCurrentSession();
session.persist(tag);
}
}
But my test fails because it finds that tag in db (and this means transaction wasn`t rolled back). I tried many different things like get current session and transaction and manually invoke rollback, but nothing happend. Can you please help me?
First of all, for me it feels a bit weird that you try to test the repository layer. Normally there should not be any business logic, thus this means that you try to test Hibernate or SQL which are already tested million of times and there is no point of doing it.
I would suggest to annotate your method with something like this:
#Transactional(rollbackFor=Exception.class)
and maybe specify the Exception. Then in the test you prepare your system in a way that this method throws this exception. Thus it should be rolled back and no data should be changed.
Furthermore, I would like to add that actually loading spring context at this point most probably is different compare to the production one. Thus this is an additional point where I would say that there is no point of doing it. At least on the repository layer.
All MyService methods are transactional. The junit test below, gets count of items, saves a new item, and gets count of items to make sure that counts has been incremented by 1.
public class MyTest extends ServiceTest{
1. int countBefore = myService.getCount(); //return n
2. myService.add(item); //item is really added to DB
3. int countAfter = myService.getCount(); //return n (sometimes n+1)
}
#Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.READ_COMMITTED)
getCount(){…}
#Transactional(propagation=Propagation.REQUIRES_NEW, isolation=Isolation.SERIALIZABLE)
add(){…}
#Ignore
#ContextConfiguration(locations = { "file:src/main/resources/xxx-context.xml",
"file:src/main/resources/xxx-data.xml",
"file:src/main/resources/xxx-services.xml" })
#TransactionConfiguration(transactionManager = "txManager", defaultRollback = false)
#TestExecutionListeners( { DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
TestListener.class})
public class ServiceTest extends AbstractUT{
#Ignore
#RunWith(SpringJUnit4ClassRunner.class)
#TestExecutionListeners( {TestListener.class})
public class AbstractUT{
When debugging (3.) returns n+1 which is what I want. But when running the test without debug I get n.
Even sometimes when running the test I get n+1 and next time I get n and when comparing the std output between the two execution, it looks exactly the same. I have enabled log4j.logger.org.springframework.transaction=TRACE and I can see:
Initializing transaction synchronization
Getting transaction for MyService.getCount
...
Completing transaction for MyService.getCount
Clearing transaction synchronization
...
Initializing transaction synchronization
Getting transaction for MyService.add
...
Completing transaction for MyService.add
Clearing transaction synchronization
...
Initializing transaction synchronization
Getting transaction for MyService.getCount
...
Completing transaction for MyService.getCount
Clearing transaction synchronization
So transactions are being executed one after the other, but how is possible that (3.) don't see the saved item?
Transaction managment is setup in my test class as per: https://stackoverflow.com/a/28657650/353985
How can I find what is going wrong?
Thanks!
Had similar issue, but in my case it did not rollback. It seems that you forgot to add #Transactional. From documentation (link)
Transaction management
In the TestContext framework, transactions are managed by the
TransactionalTestExecutionListener which is configured by default,
even if you do not explicitly declare #TestExecutionListeners on your
test class. To enable support for transactions, however, you must
configure a PlatformTransactionManager bean in the ApplicationContext
that is loaded via #ContextConfiguration semantics (further details
are provided below). In addition, you must declare Spring’s
#Transactional annotation either at the class or method level for your
tests.
Here is example form the link above.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#TransactionConfiguration(transactionManager="txMgr", defaultRollback=false)
#Transactional
public class FictitiousTransactionalTest {
#BeforeTransaction
public void verifyInitialDatabaseState() {
// logic to verify the initial state before a transaction is started
}
#Before
public void setUpTestDataWithinTransaction() {
// set up test data within the transaction
}
#Test
// overrides the class-level defaultRollback setting
#Rollback(true)
public void modifyDatabaseWithinTransaction() {
// logic which uses the test data and modifies database state
}
#After
public void tearDownWithinTransaction() {
// execute "tear down" logic within the transaction
}
#AfterTransaction
public void verifyFinalDatabaseState() {
// logic to verify the final state after transaction has rolled back
}
}
A solution I found till now to pass test is to put assert in afterTransaction method
public class MyTest extends ServiceTest{
#Test
public void test(){
1. int countBefore = myService.getCount(); //return n
2. myService.add(item); //item is really added to DB
}
#AfterTransaction
public void verifyFinalDatabaseState() {
3. int countAfter = myService.getCount(); //return n (sometimes n+1)
//Now always return n+1
}
I would have asked this in a comment but since my reputation does not allow it, I would just try to provide an answer.
It is possible that your are using and ORM that caches the results of count query. Depending how your add/getCount methods are implemented and the configurations of the ORM and datasource, on your second invocation of getCount, you might get a cached value obtained during first invocation of getCount.
This does not explain however why in debug mode you always get the correct result.
Because the current running transaction is set at the test method level, you have two options:
You either remove the #Transactional from the test method and rely on your service method #Transactional boundaries. This way when you call:
int countBefore = myService.getCount();
myService.add(item);
int countAfter = myService.getCount();
Each service call will run in an isolated transaction, just like it happens in the run-time production call.
You flush the Hibernate Session, just after adding the item:
int countBefore = myService.getCount();
myService.add(item);
transactionTemplate.execute(new TransactionCallback<Void>() {
#Override
public Company doInTransaction(TransactionStatus transactionStatus) {
entityManager.flush();
return null;
}
});
int countAfter = myService.getCount();
A HQL/JPQL count query should trigger a flush in AUTO flush mode, but an SQL native query doesn't flush the Session.
Methods invoked:
1. Struts Action
2. Service class method (annotated by #Transactional)
3. Xfire webservice call
Everything including struts (DelegatingActionProxy) and transactions is configured with Spring.
Persistence is done with JPA/Hibernate.
Sometimes the webservice will throw an unchecked exception. I catch this exception and throw a checked exception. I don't want the transaction to roll back since the web service exception changes the current state. I have annotated the method like this:
#Transactional(noRollbackFor={XFireRuntimeException.class, Exception.class})
public ActionForward callWS(Order order, ....) throws Exception
(...)
OrderResult orderResult = null;
try {
orderResult = webService.order(product, user)
} catch (XFireRuntimeException xfireRuntimeException) {
order.setFailed(true);
throw new WebServiceOrderFailed(order);
} finally {
persist(order);
}
}
I still get this exception:
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
When I try to reproduce this with junit, the transaction isn't marked for roll back and it's still possible to commit the transaction.
How do I make Spring not to roll back the transaction?
Managed to create a test case for this problem:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"file:web/WEB-INF/spring/applicationContext.xml",
"file:web/WEB-INF/spring/services.xml"})
#Transactional
public class DoNotRollBackTest {
#Autowired FakeService fakeService;
#Test
#Rollback(false)
public void testRunXFireException() {
fakeService.doSomeTransactionalStuff();
}
}
FakeService:
#Service
public class FakeService {
#Autowired private EcomService ecomService;
#Autowired private WebService webService;
#Transactional(noRollbackFor={XFireRuntimeException.class})
public void doSomeTransactionalStuff() {
Order order = ecomService.findOrderById(459);
try {
webService.letsThrowAnException();
} catch (XFireRuntimeException e) {
System.err.println("Caugh XFireRuntimeException:" + e.getMessage());
}
order.setBookingType(BookingType.CAR_BOOKING);
ecomService.persist(order);
}
}
WebService:
#Transactional(readOnly = true)
public class WebService {
public void letsThrowAnException() {
throw new XFireRuntimeException("test!");
}
}
This will recreate the rollback-exception.
Then I realized that the transaction is probably being marked as rollbackOnly in WebService.letsThrowAnException since WebService is also transactional. I moved to annotation:
#Transactional(noRollbackFor={XFireRuntimeException.class})
public void letsThrowAnException() {
Now the transaction isn't being rolled back and I can commit the changes to Order.
You must not throw an exception where Spring can see it. In this case, you must not throw WebServiceOrderFailed(). The solution is to split the code into two methods. The first method does the error handling and returns the exception, the outer method creates the transaction.
[EDIT] As for noRollbackFor: Try to replace Exception.class with WebServiceOrderFailed.class.