In Spring 3.0.5 and before, it was possible to create a base controller test class that fetched the HandlerMapping object from the Spring Application Context to directly access and call into a controller's method via the URL. I found this approach to be really awesome because in addition to testing the controller's methods, I was also testing the path variables and such in my unit/integration tests.
With Spring 3.2.4, this approach appears not to be possible any longer due to the restructuring of how Spring deals with Url mappings. I see that Spring provides a new MVC test framework, but to be honest, I think it's design is too verbose and looks nothing like the rest of the framework or my application code. It also doesn't play nicely with intellisense features in IntelliJ. To be honest, I'd rather not use it.
So, is there an alternative way to test controller URL's that does not use the new Spring MVC test framework, like I was doing before? I have an existing project with 371 controller tests, and I'd REALLY like to avoid migrating everything over to use the Spring MVC test framework.
Here is the handle() method I was using to test controllers using Spring 3.0.5:
protected ModelAndView handle(HttpServletRequest request, HttpServletResponse response) throws Exception {
final HandlerMapping handlerMapping = applicationContext.getBean(HandlerMapping.class);
final HandlerExecutionChain handler = handlerMapping.getHandler(request);
assertNotNull("No handler found for request, check you request mapping", handler);
final Object controller = handler.getHandler();
final HandlerInterceptor[] interceptors = handlerMapping.getHandler(request).getInterceptors();
for (HandlerInterceptor interceptor : interceptors) {
final boolean carryOn = interceptor.preHandle(request, response, controller);
if (!carryOn) {
return null;
}
}
return handlerAdapter.handle(request, response, controller);
}
protected ModelAndView handle(String method, String path, String queryString) throws Exception {
request.setMethod(method);
request.setRequestURI(path);
if(queryString != null) {
String[] parameters = queryString.split("&");
for(String parameter : parameters) {
String[] pair = parameter.split("=");
if(pair.length == 2) {
request.setParameter(pair[0], pair[1]);
} else {
request.setParameter(pair[0], "");
}
}
}
return handle(request, response);
}
protected ModelAndView handle(String method, String path, String attribute, Object object) throws Exception {
MockHttpSession session = new MockHttpSession();
session.setAttribute(attribute, object);
request.setSession(session);
return handle(method, path, null);
}
protected ModelAndView handle(String method, String path) throws Exception {
return handle(method, path, null);
}
Here is some test code illustrating how I was using the handle() method:
#Test
public void show() throws Exception {
ModelAndView modelAndView = handle("GET", "/courseVersion/1/section/1");
Section section = (Section) modelAndView.getModel().get("section");
assertEquals(1, section.getId());
}
Here is my servlet application context:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:security="http://www.springframework.org/schema/security"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:applicationContext.properties"/>
</bean>
<bean id="expressionHandler"
class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
</bean>
<security:global-method-security pre-post-annotations="enabled">
<security:expression-handler ref="expressionHandler"/>
</security:global-method-security>
<context:component-scan base-package="keiko.web.controllers"/>
<mvc:annotation-driven validator="validator" />
<mvc:interceptors>
<bean class="keiko.web.interceptors.IpValidationInterceptor" />
<bean class="keiko.web.interceptors.UnreadMessagesInterceptor" />
<bean class="keiko.web.interceptors.ThemeInterceptor" />
<bean class="keiko.web.interceptors.ApplicationMenuInterceptor" />
</mvc:interceptors>
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
<property name="freemarkerSettings">
<props>
<prop key="auto_import">lib/common.ftl as common, lib/layouts.ftl as layouts</prop>
<prop key="whitespace_stripping">true</prop>
</props>
</property>
<property name="freemarkerVariables">
<map>
<entry key="template_update_delay" value="0"/>
<entry key="default_encoding" value="ISO-8859-1"/>
<entry key="number_format" value="0.##"/>
<entry key="xml_escape">
<bean class="freemarker.template.utility.XmlEscape"/>
</entry>
</map>
</property>
</bean>
<bean id="contentNegotiatingViewResolver"
class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="order" value="1"/>
<property name="ignoreAcceptHeader" value="true" />
<property name="defaultContentType" value="text/html" />
<property name="mediaTypes">
<map>
<entry key="html" value="text/html"/>
<entry key="json" value="application/json"/>
</map>
</property>
<property name="useNotAcceptableStatusCode" value="true" />
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="contentType" value="text/html" />
<property name="order" value="2"/>
<property name="cache" value="${freemarker.cache}"/>
<property name="prefix" value=""/>
<property name="suffix" value=".ftl"/>
<property name="exposeSpringMacroHelpers" value="true"/>
</bean>
</list>
</property>
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView">
<property name="contentType" value="application/json" />
</bean>
</list>
</property>
</bean>
<bean id="viewNameTranslator" class="org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator"/>
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="1000000"/>
</bean>
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="keiko.domain.courseauthor.SectionIsDelayedException">error/sectionIsDelayed</prop>
<prop key="keiko.service.director.CompanyHomepageClosedException">error/registrationClosed</prop>
<prop key="keiko.service.director.IpDeniedException">error/ipDenied</prop>
</props>
</property>
</bean>
</beans>
Basically your test method has been flawed basically from the start. There was always a possibility that there where more then 1 HandlerMapping and 1 HandlerAdapter. What you are basically doing is mimic the DispatcherServlet.
What you should do is lookup all HandlerMappings and HandlerAdapters and check if one of them has a match for the URL (i.e. returning a HandlerExecutionChain) and select the appropriate HandlerAdapter (calling the supports method). What you are doing is basically what the DispatcherServlet is doing.
Related
I am working on project with Spring mvc and i want to use jpa features as well.
I have an 3 entity classes, their corresponding repository interfaces. I have their autowired objects in common service class. However I am facing issues while creating bean for this service class object which is used in controller.
The 3 model class are User, Appointment and Notification.
The repository interface extends CRUDRepository interface.
Service class :
#Service
public class EHealthService
{
#Autowired
UserRepository uRepo;
#Autowired
AppointmentRepository aRepo;
#Autowired
NotificationRepository nRepo;
public void registerUser(User u)
{
uRepo.save(u);
}
public boolean login(User u)
{
if(uRepo.findByemail(u.getEmail())!=null)
return true;
else
return false;
}
public List<User> getDoctorList()
{
return uRepo.findByisdoctor(true);
}
// some more functions
}
Controller class:
#Controller
public class EHealthController
{
#Autowired
EHealthService eservice;
//Some code
}
ehealth-dispacter-servlet.xml file:
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">
<context:component-scan base-package="com.cl3.controller" />
<context:component-scan base-package="com.cl3.model" />
<context:component-scan base-package="com.cl3.service" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true"/>
<property name="generateDdl" value="true"/>
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
<property name="packagesToScan" value="com.cl3.model"/>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
<jpa:repositories base-package="com.cl3.model"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="org.postgresql.Driver" />
<property name="url" value="jdbc:postgresql://localhost:5432/cl3" />
<property name="username" value="ucan" />
<property name="password" value="ucan" />
</bean>
<bean id="eservice" class="com.cl3.service.EHealthService">
<property name="uRepo" ref="uRepo"></property>
<property name="nRepo" ref="nRepo"></property>
<property name="aRepo" ref="aRepo"></property>
</bean>
<bean id="uRepo" class="com.cl3.model.UserRepository">
</bean>
<bean id="nRepo" class="com.cl3.model.NotificationRepository">
</bean>
<bean id="aRepo" class="com.cl3.model.AppointmentRepository">
</bean>
It says the class is an interface.
What will be the bean for eservice object and to enable jpa in dispacter servel xml file?
Thank you.
If you are using spring xml based configuration then add below bean's to configuration file :
<bean id="entityManagerFactoryBean" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<!-- This makes /META-INF/persistence.xml is no longer necessary -->
<property name="packagesToScan" value="com.howtodoinjava.demo.model" />
<!-- JpaVendorAdapter implementation for Hibernate EntityManager.
Exposes Hibernate's persistence provider and EntityManager extension interface -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
</property>
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">validate</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
</props>
</property>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<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="password" />
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactoryBean" />
Register component scanning by adding this binding annotation in xml file :
<context:component-scan base-package="com.mycompany.projectname.demo" />
If your project is spring mvc the add below binding annotation to xl file
<!-- This allow for dispatching requests to Controllers -->
<mvc:annotation-driven />
For declarative transaction management add below piece in xml file.
<tx:annotation-driven />
Basically you no need to add service bean in xml configuration file if you are enabled component scanning feature in spring.
Add required dependencies to integrate jpa with spring.
Refer this link will help you more :
https://howtodoinjava.com/jpa/spring-hibernate-jpa-configuration-example/
#Transactional is not working in spring mvc. suppose i removed
#Transactional annotation data is reached to RepositoryClass.
Throwable targetException - org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(Object...)
i need to reach data to repository class.
please help me.,
Thank you.
ServiceImplClass
#Service("userService")
public class UserServiceImpl implements UserService{
#Autowired
UserRepository userRepository;
public String saveUserData(User user,HttpSession session) {
return userRepository.saveUserData(user);
}
}
RepositoryClass:
#Component
#Transactional
public class UserRepository {
#Autowired
protected SessionFactory sessionFactory;
public SessionFactory getSessionFactory() {
return sessionFactory;
}
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public String saveUserData(User user) {
final Session session = (Session) getSessionFactory();
try {
session.beginTransaction();
Query query=session.createQuery("UPDATE User set user_Name =:userName,"
+ "reg_Date =:regDate,"
+ "img_Id=:imgId, emailId =:emailId");
query.setParameter("userName", user.getUserName());
query.setParameter("regDate", user.getRegDate());
query.setParameter("imgId", user.getImgId());
query.setParameter("emailId", user.getEmailId());
session.save(user);
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
dispatcher-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<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:util="http://www.springframework.org/schema/util" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
">
<context:annotation-config />
<context:component-scan base-package="com.demo.app" />
<mvc:default-servlet-handler />
<mvc:annotation-driven>
<mvc:message-converters register-defaults="true">
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="text/plain;charset=UTF-8" />
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="jspViewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/html/" />
<property name="suffix" value="html" />
</bean>
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/UserDB" />
<property name="username" value="root" />
<property name="password" value="password" />
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="annotatedClasses">
<list>
<value>com.demo.app.model.User</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.Exception">Error</prop>
</props>
</property>
</bean>
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="2097152" />
</bean>
</beans>
You're managing your transactions manually. That's the task of transaction manager. saveUserData should be like:
public User saveUserData(User user) {
return (User)sessionFactory.getCurrentSession().merge(user);
}
And that's it.
And you'll probably want to annotate your service with #Transactional and not repository.
Your Repository don`t use #component annotation. and you use
<jpa:repositories base-package="your.package.put.repository"></jpa:repositories>
so, you already used
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
First of all all transaction and session should managed by spring container is good practice so please don't manage session at your own, just use the existing session for data base querying. Now, for only your situation, try #Transactional annotation at Controller level and if it will work then you require some modification as per given below.
For web MVC Spring app should #Transactional go on controller or service?
Use #Transactional at Service level and when one of the operations doesnt work as it should(for example, an update operation returns 0 which means it failed) throw new RuntimeException(). If one of the operation fails, all other operations which are part of the transaction will be rolled back.
You are trying to use both container managed transaction and User managed transaction at the same time. Try to use only one at a time.
Either remove #Transactional annotation or remove the transaction statements from your method.
Spring Transaction Hibernate #Transaction annotation is not working with #Autowired correctly. If I create the Dao`` element byxml(UserDao2 userDao2)`,
the transactional anotation in the Service class is working, if its by #Repository annotation in the Dao class when tries to getCurrentSesion, says:
org.hibernate.HibernateException: No Session found for current thread
at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:97) ~[spring-orm-3.2.8.RELEASE.jar:3.2.8.RELEASE]
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:993) ~[hibernate-core-4.2.11.Final.jar:4.2.11.Final]
It appears that is not linking well the #Transactional annotation with the session factory
Library versions:
<jdk.version>1.6</jdk.version>
<spring.version>3.2.8.RELEASE</spring.version>
<spring.security.version>3.2.3.RELEASE</spring.security.version>
<hibernate.version>4.2.11.Final</hibernate.version>
spring-database.xml
<context:annotation-config />
<jee:jndi-lookup id="datasourcenn" jndi-name="java:/comp/env/nn_datasource" />
<bean id="sesionHibernate"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="datasourcenn"/>
<property name="packagesToScan" value="com.web.entity"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle9iDialect</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.show_sql">true</prop>
<!-- nuevas properties de configuraciĆ³n -->
<prop key="hibernate.c3p0.min_size">5</prop>
<prop key="hibernate.c3p0.max_size">50</prop>
<prop key="hibernate.c3p0.timeout">300</prop>
<prop key="hibernate.c3p0.max_statements">50</prop>
<prop key="hibernate.bytecode.provider">cglib</prop>
</props>
</property>
</bean>
<bean id="us" class="com.web.dao.UserDaoImpl">
<property name="sesionHibernate" ref="sesionHibernate" />
</bean>
<!-- -->
<bean id="userDao2" class="com.web.dao.UserDao2Impl">
<property name="sesionHibernate" ref="sesionHibernate" />
</bean>
<bean id="myUserDetailsService" class="com.web.service.UsuarioServiceImpl">
<property name="userDao" ref="us" />
</bean>
<!--
proxy-target-class="true" mode="aspectj"
-->
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sesionHibernate"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
UserDao2 (I remove #Repository if I create it by xml)
#Repository
public class UserDao2Impl implements UserDao2 {
private static Logger log = LoggerFactory.getLogger(UserDao2Impl.class);
#Autowired
#Qualifier("sesionHibernate")
private SessionFactory sesionHibernate;
#SuppressWarnings("unchecked")
public Usuario findByUserName(String username) {
try {
log.info("findByUserName" + sesionHibernate);
List<Usuario> users = new ArrayList<Usuario>();
System.out.println(sesionHibernate+"\n----------");
users = sesionHibernate.getCurrentSession().createQuery("from Usuario where nombre=?").setParameter(0, username).list();
// users = sesionHibernate.getCurrentSession().createQuery("from Usuario
// where nombre=?").setParameter(0, username).list();
if (users.size() > 0) {
return users.get(0);
} else {
return null;
}
} catch (Exception e) {
log.error("findByUserName ", e);
return null;
}
}
Usuario2ServiceImpl If have the service with #Transacional
#Service
#Transactional
public class Usuario2ServiceImpl implements Usuario2Service {
private static Logger log = LoggerFactory.getLogger(Usuario2ServiceImpl.class);
//Qualifier("userDaoImpl")
#Autowired
private UserDao2 userDao2;
#Override
public com.web.entity.Usuario getUsuariodetalles(final String nombreUsuario) throws UsernameNotFoundException {
log.info("getUsuariodetalles - 1");
System.out.println("ssss"+userDao2);
com.web.entity.Usuario usuario = userDao2.findByUserName(nombreUsuario);
log.info("getUsuariodetalles - 2");
return usuario;
}
Did you try putting #Transactional annotation at service layer method, you have placed it on class. I think it should work.
I solved in Tomcat the issue by putting in the spring-database.xml the next line:
<context:load-time-weaver aspectj-weaving="autodetect"/>
But it doesn't work in JBoss, which is where i need to deploy the final version. Other solution?
In Jboss i need to create both things in the xml(Service and Dao), keeping the transactional annotatations. But i doesn't work if create them(dao & service) with #Repository or #Service.
Solution, i add to mvc-dispatcher-servlet.xml, mvc:interceptors configuration to open session with org.springframework.orm.hibernate4.support.OpenSessionInViewInterceptor. Just need to define the sessionFactory in the properties.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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:annotation-config />
<context:component-scan base-package="com.web" />
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean name="OpenSessionInViewInterceptorCom" class="org.springframework.orm.hibernate4.support.OpenSessionInViewInterceptor">
<property name="sessionFactory">
<ref bean ="sesionHibernate" />
</property>
</bean>
</mvc:interceptor>
</mvc:interceptors>
<!-- Configures the #Controller programming model -->
<mvc:annotation-driven />
<mvc:resources mapping="/css/**" location="/css/" />
<mvc:resources mapping="/img/**" location="/img/" />
<mvc:resources mapping="/js/**" location="/js/" />
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
I'm trying to create a persistance project so it can be re-used by some other projects I'm building on top. I.e it will be used by a web service/spring mvc project and by standalone jar which does some file processing.
I've used hibernate with spring mvc before but never as a standalone executable java jar so I have everything setup and working(application context) :
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
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-3.0.xsd">
<!-- Enable annotation style of managing transactions -->
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Root Context: defines shared resources visible to all other web components -->
<!-- HIBERNATE -->
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:spring.properties" />
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driverClassName}" />
<property name="jdbcUrl" value="${jdbc.databaseurl}" />
<property name="user" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="acquireIncrement" value="5" />
<property name="idleConnectionTestPeriod" value="60"/>
<property name="maxPoolSize" value="100"/>
<property name="maxStatements" value="50"/>
<property name="minPoolSize" value="10"/>
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mappingResources">
<list> <value>com/project/utility/persistence/mapping/TestProp.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${jdbc.dialect}</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- END HIBERNATE -->
</beans>
When I test it from main class everything looks ok with mapping/dependencies :
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("appCtx.xml");
}
What I want to do next is to build few dao classes which will get some data and I'd build some interface above that so it can be re-used by both webservice and processing tool as a jar(maven dependency).
In order to build dao classes I need sessionFactory to be != null always. How do I do this?
There are many approaches to this. One solution I use is to use the javax.persistence.PersistenceContext annotation. Spring will respect this annotation and inject a proxy to a thread local EntityManager. Provided your DAO is created by Spring this allows access to the entity manager from within your DAO.
public class DAO {
private EntityManager entityManager;
#PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
}
#Repository
public class MyDAO {
#Autowired
private SessionFactory sessionFactory;
// ...
}
and add the MyDAO bean to the context XML file, or simply add the following lines to this file:
<context:annotation-config />
<context:component-scan base-package="one.of.the.parent.packages.of.your.dao" />
I am using Spring 3.0.5.Release MVC for exposing a webservice and below is my servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<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"
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">
<!-- To enable #RequestMapping process on type level and method level -->
<context:component-scan base-package="com.pyramid.qls.progressReporter.service" />
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<ref bean="marshallingConverter" />
<ref bean="atomConverter" />
<ref bean="jsonConverter" />
</list>
</property>
</bean>
<bean id="marshallingConverter" class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<constructor-arg ref="jaxbMarshaller" />
<property name="supportedMediaTypes" value="application/xml"/>
</bean>
<bean id="atomConverter" class="org.springframework.http.converter.feed.AtomFeedHttpMessageConverter">
<property name="supportedMediaTypes" value="application/atom+xml" />
</bean>
<bean id="jsonConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json" />
</bean>
<!-- Client -->
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="messageConverters">
<list>
<ref bean="marshallingConverter" />
<ref bean="atomConverter" />
<ref bean="jsonConverter" />
</list>
</property>
</bean>
<bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
<property name="classesToBeBound">
<list>
<value>com.pyramid.qls.progressReporter.impl.BatchProgressMetricsImpl</value>
<value>com.pyramid.qls.progressReporter.datatype.InstrumentStats</value>
<value>com.pyramid.qls.progressReporter.datatype.InstrumentInfo</value>
<value>com.pyramid.qls.progressReporter.datatype.BatchProgressMetricsList</value>
<value>com.pyramid.qls.progressReporter.datatype.LoadOnConsumer</value>
<value>com.pyramid.qls.progressReporter.datatype.HighLevelTaskStats</value>
<value>com.pyramid.qls.progressReporter.datatype.SessionStats</value>
<value>com.pyramid.qls.progressReporter.datatype.TaskStats</value>
<value>com.pyramid.qls.progressReporter.datatype.ComputeStats</value>
<value>com.pyramid.qls.progressReporter.datatype.DetailedInstrumentStats</value>
<value>com.pyramid.qls.progressReporter.datatype.ImntHistoricalStats</value>
</list>
</property>
</bean>
<bean id="QPRXmlView" class="org.springframework.web.servlet.view.xml.MarshallingView">
<constructor-arg ref="jaxbMarshaller" />
</bean>
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="mediaTypes">
<map>
<entry key="xml" value="application/xml"/>
<entry key="html" value="text/html"/>
</map>
</property>
<property name="viewResolvers">
<list>
<bean class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</list>
</property>
</bean>
<bean id="QPRController" class="com.pyramid.qls.progressReporter.service.QPRController">
<property name="jaxb2Mashaller" ref="jaxbMarshaller" />
</bean>
</beans>
Following is what i am doing in controller (QPRController)
#RequestMapping(value = "/clientMetrics/{clientId}", method = RequestMethod.GET)
public ModelAndView getBatchProgressMetrics(#PathVariable String clientId) {
List<BatchProgressMetrics> batchProgressMetricsList = null;
batchProgressMetricsList = batchProgressReporter.getBatchProgressMetricsForClient(clientId);
BatchProgressMetricsList batchList = new BatchProgressMetricsList(batchProgressMetricsList);
ModelAndView mav = new ModelAndView("QPRXmlView", BindingResult.MODEL_KEY_PREFIX + "batchProgressMetrics", batchList);
return mav;
}
This is what my batchprogressmetricsList looks like:
#XmlRootElement(name = "batchProgressMetrics")
public class BatchProgressMetricsList implements Serializable{
private int count;
private List<BatchProgressMetrics> batchProgressMetricsList;
public BatchProgressMetricsList() {
}
public BatchProgressMetricsList(List<BatchProgressMetrics> batchProgressMetricsList) {
this.batchProgressMetricsList = batchProgressMetricsList;
this.count = batchProgressMetricsList.size();
}
public int getCount() {
return count;
}
#XmlElement(name = "batchProgressMetrics1")
public List<BatchProgressMetrics> getBatchProgressMetrics() {
return batchProgressMetricsList;
}
Now i get the following:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'QPRController' defined in ServletContext resource [/WEB-INF/rest-servlet.xml]: Cannot resolve reference to bean 'jaxbMarshaller' while setting bean property 'jaxb2Mashaller'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jaxbMarshaller' defined in ServletContext resource [/WEB-INF/rest-servlet.xml]: Invocation of init method failed; nested exception is org.springframework.oxm.UncategorizedMappingException: Unknown JAXB exception; nested exception is com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationsException: 2 counts of IllegalAnnotationExceptions
com.pyramid.qls.progressReporter.iface.BatchProgressMetrics is an interface, and JAXB can't handle interfaces.
this problem is related to the following location:
at com.pyramid.qls.progressReporter.iface.BatchProgressMetrics
at public java.util.List com.pyramid.qls.progressReporter.datatype.BatchProgressMetricsList.getBatchProgressMetrics()
at com.pyramid.qls.progressReporter.datatype.BatchProgressMetricsList
com.pyramid.qls.progressReporter.iface.BatchProgressMetrics does not have a no-arg default constructor.
this problem is related to the following location:
at com.pyramid.qls.progressReporter.iface.BatchProgressMetrics
at public java.util.List com.pyramid.qls.progressReporter.datatype.BatchProgressMetricsList.getBatchProgressMetrics()
at com.pyramid.qls.progressReporter.datatype.BatchProgressMetricsList
org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:328)
Previously i got this without the change.
SEVERE: Servlet.service() for servlet rest threw exception
javax.servlet.ServletException: Unable to locate object to be marshalled in model: {org.springframework.validation.BindingResult.batchProgressMetrics=
Note that BatchProgressMetrics is an interface so my MAV is returning list of BatchProgressMetrics objects and i have entry for its impl in classes to be bound in servlet.xml.
Can you please help me as to what i am doing wrong. And yes if i send just batchProgressMetricsList.get(0) in MAV it just works fine.
This because the JAXB context doesn't know how to handle lists of objects, only the objects themselves. It makes sense when you think about it - the only way to represent a list in XML is to wrap it in a container element, and it has no information as to how to do that.
So you need to define a class which is the container for the list of BatchProgressMetrics, and return that in the model instead.