So my Spring education continues. Currently I'm trying to learn some of the annotations and the things they bring to Spring 3. So I've got a mini webapp that can connect to a DB and put stuff in through a form and display records and so on. Everything works fine. I decided to try and get Spring to auto-detect the service bean that I have marked as @Transactional but doing that stops the app from saving to the DB. So:
@Transactional
public class ReservationServiceImpl implements ReservationService {
that works. I have a bean declaration of this class in my springcourt-data.xml files. No problems. When I do this though:
@Transacational
@Service("reservationService")
public class ReservationServiceImpl implements ReservationService {
it no longer works. And I do have
<context:component-scan base-package="com.springcourt" />
in the springcourt-servlet.xml file. 开发者_如何学运维So can anyone tell me what I'm screwing up? All I do is add another annotation to this class and remove the bean definition from the xml file and it no longer saves data to the DB. I can still query records and stuff from the DB though so obviously it's using the autodetected service bean.
Here are the config files:
springcourt-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<context:component-scan base-package="com.springcourt" />
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="webBindingInitializer">
<bean class="com.springcourt.web.ReservationBindingInitializer" />
</property>
</bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
And:
springcourt-data.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="MYSQL" />
<property name="showSql" value="true" />
</bean>
</property>
</bean>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="admin" />
<property name="initialSize" value="5" />
</bean>
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven />
<bean id="reservationService" class="com.springcourt.service.ReservationServiceImpl"/>
</beans>
When you use @Service and component scanning the bean is created by the context created by the dispatcher servlet (mvc). Since the transaction:annotation driven is defined in the root application context it doesn't apply to the beans in the servlets context. You can verify this by removing the @Service and moving the bean definition to the servlet context file - you should see the same result.
Where as when you don't use component scanning - the bean is defined in the XML of the root application context.
The fix is to change the component-scan tag in the web layer to only include web layer classes - either by using a different base package or by using an include / exclude filter. Add another component scan in the root application context for the other beans.
Querying might be working because you might have a OpenEntityManagerInViewInterceptor / Filter configured.
Since you can query DB via the same bean, your @Transactional
works or you would often get an exception (at least with Hibernate). Most likely in save operation you get some runtime exception that causes transaction rollback. Try to find out what exception is and go from there.
Update
To see if @Transactional
got applied, print stack trace from inside the method. If you see long stack trace with a lot of transactional interceptors that means transactional aspect works.
I have same problem and solve it.
1st you must separate your context:component-scan to web and data level, like this:
<!--in springcourt-servlet.xml -->
<context:component-scan base-package="com.springcourt.web" />
<!--in springcourt-data.xml -->
<context:component-scan base-package="com.springcourt.dao" />
2nd add to springcourt-data.xml
<aop:aspectj-autoproxy/>
I hope it will be helpful
Try this
Add context component scan to spring-court-data.xml
<context:component-scan base-package="com.springcourt" />
Test service in isolation, Create a JUNIT Test some thing like this
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:springcourt-data.xml"}) @Transactional public class ReservationServiceImplTest() { @Autowired ReservationServiceImpl service; @Test public void validateContext() { Assert.assertNotNull(service); } @Test @Rollback(false) public void save() { service.save(data); } }
A very good alternative to what you have is to use the following: `
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class AppConfig {
(...)
}
What you need to do is to inject everything in the same scope. One way is to change the ApplicationContext xml, as said before, another is to use the CGLIB like Spring proxies so that you get sub class based proxies and in there write your bean implementations and definitions.
Further reading:
- http://docs.spring.io/spring/docs/4.2.4.RELEASE/javadoc-api/org/springframework/context/annotation/EnableAspectJAutoProxy.html
- http://jnb.ociweb.com/jnb/jnbNov2005.html
精彩评论