"Write operations are not allowed in read-only mode" error : confused with Spring @Service @transaction @Repository and Hibernate
When extending HibernateDaoSupport
you will not benefit from autowiring, you will have to override the setSessionFactory
method and put an @Autowired
annotation on it. Else it won't work.
I would also expect a <tx:annotation-driven />
without that the @Transactional
is pretty much useless and doesn't do anything.
Flop000
Updated on August 21, 2022Comments
-
Flop000 over 1 year
I am working on an existing project using Spring and Hibernate and am getting confused because I get a
org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read-only mode (FlushMode.MANUAL): Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly' marker from transaction definition.
error when trying to save objects but I still can't find what exactly is wrong.
There is a service layer that is annotated using
@Service
and asave
method that it should be transactional so it is annotated with@Transactional(readOnly = false)
. To me that means that spring should handle transactions itself.@Service public class LadyService { Logger log = Logger.getLogger(LadyService.class); @Autowired private PictureDAO pictureDao; @Autowired private LadyDAO ladyDao; @Autowired private AddressDAO addressDao; @Transactional(readOnly = false) public void save(Lady lady) { Address a = addressDao.getExistingAddress(lady.getAddress()); if (a == null) { a = addressDao.save(lady.getAddress()); } lady.setAddress(a); ladyDao.save(lady); pictureDao.savePictures(lady.getPictures()); }
The error happens when doing a save in the
AddressDAO
. It is annotated as@Repository
.@Repository public class AddressDAO extends HibernateDaoSupport { public Address save(Address address) { getHibernateTemplate().save(address); <-- write not permitted error happens here return address; } @SuppressWarnings({ "unchecked" }) public Address getExistingAddress(Address address) { DetachedCriteria cd = DetachedCriteria.forClass(Address.class); cd.add(Restrictions.eqOrIsNull("administrative_area_level_1", address.getAdministrative_area_level_1())); cd.add(Restrictions.eqOrIsNull("administrative_area_level_2", address.getAdministrative_area_level_2())); List<Address> result = (List<Address>) getHibernateTemplate() .findByCriteria(cd); if (result.isEmpty()) { return null; } else { return (Address) result.get(0); } } }
What I thought would happen was that
@Transactional
makes spring create a session and a transaction for the save on the service layer, and that in the DAOs, the hibernate template would get the current session and transaction that spring manages and use it to save the objects.The error message, though, makes me think that my service method and dao methods are not in the same transaction.
In the servlet-context.xml there are these statements:
<annotation-driven /> <context:component-scan base-package="com.kog.fable" /> <beans:bean id="mySessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <beans:property name="dataSource" ref="myDataSource" /> <beans:property name="packagesToScan"> <beans:array> <beans:value>com.kog.fable.**.*</beans:value> </beans:array> </beans:property> <beans:property name="hibernateProperties"> <beans:props> <beans:prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect </beans:prop> <!-- create, validate, update --> <beans:prop key="hibernate.hbm2ddl.auto">create</beans:prop> <beans:prop key="hibernate.show_sql">false</beans:prop> <beans:prop key="hibernate.connection.pool_size">10</beans:prop> <beans:prop key="hibernate.connection.autocommit ">false</beans:prop> </beans:props> </beans:property> </beans:bean> <tx:annotation-driven transaction-manager="transactionManager" /> <beans:bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <beans:property name="sessionFactory" ref="mySessionFactory" /> </beans:bean> <beans:bean id="addressDAO" class="com.kog.fable.dao.AddressDAO"> <beans:property name="sessionFactory" ref="mySessionFactory" /> </beans:bean> <beans:bean id="ladyDAO" class="com.kog.fable.dao.LadyDAO"> <beans:property name="sessionFactory" ref="mySessionFactory" /> </beans:bean> <beans:bean id="pictureDAO" class="com.kog.fable.dao.PictureDAO"> <beans:property name="sessionFactory" ref="mySessionFactory" /> </beans:bean>
Here I don't understand why, if component scan is used, the DAO beans are still declared explicitly. Shouldn't the component scan feature be able to create those by itself since the DAO classes are annotated with
@Repository
? Since I thought this configuration could create duplicate beans, I tried deleting the xml entries, but then I started getting:org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'addressController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.kog.fable.dao.AddressDAO com.kog.fable.controller.AddressController.addressDAO; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'addressDAO' defined in file [***\com\kog\fable\dao\AddressDAO.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: 'sessionFactory' or 'hibernateTemplate' is required
Here I thought that the extension of HibernateDaoSupport for my DAOs would make them inherit the sessionFactory and related methods so I don't understand what happens.
I have read that I could set the flush mode to AUTO or set the setCheckWriteOperations on the template to FALSE to solve that kind of problems and it seems to work, but I guess that this would not ensure the transaction coherence in all cases as I would like it.
Any help would be appreciated as I am quite new to Spring and Hibernate and am a bit stuck here.
-
M. Deinum about 9 yearsWhen extending
HibernateDaoSupport
you will not benefit from autowiring, you will have to override thesetSessionFactory
method and put a@Autowired
on it. Else it won't work. I would also expect a<tx:annotation-driven />
without that the@Transactional
is pretty much useless and doesn't do anything. -
Flop000 about 9 years@M. Deinum thank you for your input. Indeed I missed the override, and now that it is done I can remove the dao beans entries in the xml without errors so I guess now I am sure the beans are instantiated once. Concerning the annotation-driven tag, it is an error on my part, I forgot to copy it here. I have this entry in the config file:
<tx:annotation-driven transaction-manager="transactionManager" />
so it wasn't actually missing. The "write operations not allowed" error is still there. -
M. Deinum about 9 yearsMake sure you aren't scanning for the same components twice. If you have an
applicationContext.xml
or whatever file that is loaded by theContextLoaderListener
make sure that you don't have the same<context:component-scan />
in there. -
Flop000 about 9 years@M.Deinum Again thank you for your input. I checked and the configuration seems to be correct. I did some more tests and actually after the modifications of your first comment it had solved my problem when calling the
save
method already. The problem was still there when I used aupsert
method without the@transactional
annotation that would then call thesave
method. And then I found out that this is normal and that thisupsert
method should have the@transactional
annotation in order to work. So your comment solved the problem. -
Flop000 about 9 years@M.Deinum Also, if you write your comment as an answer I will mark it as solution since it has solved my problem.
-
-
Tan Jinfu about 8 yearsBut the method setSessionFactory is final in srping 4. Could you please tell how to resolve the problem?
-
M. Deinum about 8 yearsCreate a constructor, autowire that, and call
setSessionFactory
from that. Of simply don't useHibernateDaoSupport
as that isn't recommended anymore,