如何使用MySQL只读副本配置Spring / Hibernate

时间:2012-11-26 07:04:17

标签: java mysql spring hibernate

我有一个Java应用程序,它使用Spring / Hibernate(在Amazon Elastic Beanstalk上运行)和普通的Amazon RDS实例。为了分配负载,我正在添加一个只读副本。我将DB.url交换到只读副本并测试了一个只读取数据的方法(这些方法肯定不使用UPDATE,INSERT或DELETE)。但是,该应用程序返回错误。

我的问题:为什么只在读取数据时应用程序失败了?如何配置Spring / Hibernate以使用只读副本?

详细信息:

我得到的错误是:

INFO: Internal error in method IEngageManager.getSimpleExamActivityLiveResults, ARGUMENTS=[
  Credentials {
(m) password=0c2a765c057a31c51b68d0f6c75cef93, 
(m) email=w1jg
  }
]
com.iengage.exceptions.IEException: Internal error in method IEngageManager.getSimpleExamActivityLiveResults, ARGUMENTS=[
  Credentials {
    (m) password=0c2a765c057a31c51b68d0f6c75cef93, 
    (m) email=w1jg
  }
]
    at com.iengage.exceptions.ExceptionWrapper.overridingInvoke(ExceptionWrapper.java:25)
    at com.iengage.springobjects.SingletonMethodInterceptor.invoke(SingletonMethodInterceptor.java:18)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:621)
    at com.iengage.managers.IEngageManager$$EnhancerByCGLIB$$c8a2c385.getSimpleExamActivityLiveResults(<generated>)
    at com.iengage.services.IEngageService.getSimpleExamActivityLiveResults(IEngageService.java:379)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:616)
    at com.sun.xml.ws.api.server.InstanceResolver$1.invoke(InstanceResolver.java:246)
    at com.sun.xml.ws.server.InvokerTube$2.invoke(InvokerTube.java:146)
    at com.sun.xml.ws.server.sei.EndpointMethodHandler.invoke(EndpointMethodHandler.java:257)
    at com.sun.xml.ws.server.sei.SEIInvokerTube.processRequest(SEIInvokerTube.java:93)
    at com.sun.xml.ws.api.pipe.Fiber.__doRun(Fiber.java:595)
    at com.sun.xml.ws.api.pipe.Fiber._doRun(Fiber.java:554)
    at com.sun.xml.ws.api.pipe.Fiber.doRun(Fiber.java:539)
    at com.sun.xml.ws.api.pipe.Fiber.runSync(Fiber.java:436)
    at com.sun.xml.ws.server.WSEndpointImpl$2.process(WSEndpointImpl.java:243)
    at com.sun.xml.ws.transport.http.HttpAdapter$HttpToolkit.handle(HttpAdapter.java:444)
    at com.sun.xml.ws.transport.http.HttpAdapter.handle(HttpAdapter.java:244)
    at com.sun.xml.ws.transport.http.servlet.ServletAdapter.handle(ServletAdapter.java:135)
    at com.sun.xml.ws.transport.http.servlet.WSServletDelegate.doGet(WSServletDelegate.java:129)
    at com.sun.xml.ws.transport.http.servlet.WSServletDelegate.doPost(WSServletDelegate.java:160)
    at com.sun.xml.ws.transport.http.servlet.WSSpringServlet.doPost(WSSpringServlet.java:52)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:225)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:169)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
    at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:680)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:927)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:999)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:565)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:307)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
    at java.lang.Thread.run(Thread.java:679)

调用DB的代码:

public LecturerBO getLecturerByCode(Credentials credentials) {
    return findSingleResultByCriteria(LecturerBO.class,Restrictions.eq("loginCode",credentials.getPassword()));
}

,其中

protected <T> T findSingleResultByCriteria(Class<T> clazz, Criterion... criterion) {
    Criteria crit = getSession().createCriteria(clazz);
    for (Criterion c : criterion) {
        crit.add(c);
    }
    return (T) crit.uniqueResult();
}

最后,Hibernate配置:

<bean id="generalManagerProxyTemplate"
      class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
      abstract="true">
    <property name="transactionManager" ref="hibernateTransactionManager"/>
    <property name="proxyTargetClass" value="true"/>
    <property name="transactionAttributes">
        <props>
            <prop key="*">PROPAGATION_REQUIRED</prop>
        </props>
    </property>
    <property name="preInterceptors">
        <list>
            <bean class="com.iengage.exceptions.ExceptionWrapper"/>
            <bean class="com.iengage.utils.PerformanceLogger"/>
        </list>
    </property>
</bean>

<bean id="hibernateTransactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="pooledDataSource"/>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
            <prop key="transaction.auto_close_session">true</prop>
            <prop key="transaction.flush_before_completion">true</prop>
            <prop key="show_sql">false</prop>
            <prop key="hibernate.cache.use_query_cache">false</prop>
            <prop key="hibernate.show_sql">false</prop>
        </props>
    </property>
    <property name="annotatedClasses">
        <bean class="com.iengage.dao.EntityList"/>
    </property>
</bean>

<bean id="pooledDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${db.driverClassName}"/>
    <property name="url" value="${db.url}"/>
    <property name="username" value="${db.username}"/>
    <property name="password" value="${db.password}"/>
    <property name="defaultAutoCommit" value="false"/>
    <property name="testWhileIdle" value="true"/>
</bean>

<bean id="abstractDAO" class="com.iengage.dao.GeneralDAO" abstract="true">
    <property name="sessionFactory" ref="sessionFactory"/>
    <property name="dataSource" ref="pooledDataSource"/>
</bean>

<bean id="dao" class="com.iengage.dao.IEngageDAO" parent="abstractDAO"/>

最后,作为一个完整性检查,我确实直接连接了读取副本,并通过执行我在第三方服务器上托管的SELECT查询的简单脚本。一切都按预期工作。

此外,在发布此问题之前的研究中,大多数答案都集中在@Transactional注释上,我们没有使用它。悲伤的熊猫。

感谢您的帮助。

1 个答案:

答案 0 :(得分:1)

要提供该类型的访问(仅读取副本),您必须实现此属性。

        <property name="transactionAttributes">
            <props>
                <prop key="get*">PROPAGATION_REQUIRED,readOnly
                </prop>
                <prop key="find*">PROPAGATION_REQUIRED,readOnly
                </prop>
                <prop key="load*">PROPAGATION_REQUIRED,readOnly
                </prop>
                <prop key="store*">PROPAGATION_REQUIRED</prop>
                <prop key="add*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>

在这里,我使用这两个来允许存储并添加到DB中。如果你不需要,你必须删除它。

       <prop key="store*">PROPAGATION_REQUIRED</prop>
       <prop key="add*">PROPAGATION_REQUIRED</prop>