2014-09-20 87 views
2

我正在開發一個使用Spring和jOOQ的webapp。jOOQ + Spring:PSQLException:當前事務被中止,命令被忽略直到事務結束

考慮以下用例:

  1. 我打開這使得該數據庫查詢的結果網址:select * from contract_ref,一切工作正常。

  2. 我打開它試圖運行查詢其觸發Postgres的錯誤,即select * from users_ref(表users_ref不存在)的網址,並得到了錯誤:

    Servlet.service() for servlet [dispatcher] in context with path [/astra] threw exception [Request processing failed; nested exception is org.springframework.jdbc.BadSqlGrammarException: jOOQ; bad SQL grammar [select * from "users_ref"]; nested exception is org.postgresql.util.PSQLException: ОШИБКА: отношение "users_ref" не существует

  3. 當我嘗試從步驟1中打開網頁,我得到的錯誤PSQLException: current transaction is aborted, commands ignored until end of transaction block,但我看到的webapp試圖從步執行語句1

這似乎是我的情況是這樣Postgres沒有關閉交易,但我只有select,所以沒有任何交易要求。

當我添加@Transactional註釋爲ReferenceController.view上述行爲消失。

完整的日誌:http://pastebin.com/t3UmbeCy

的applicationContext.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:cache="http://www.springframework.org/schema/cache" 
     xmlns:bean="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.xsd 

     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> 

    <cache:annotation-driven/> 

    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" /> 

    <context:component-scan base-package="net.kerba"/> 

    <context:property-placeholder location="classpath:config.properties"/> 

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
     <property name="dataSource" ref="localDbDataSource"/> 
    </bean> 

    <bean id="jacksonMessageConverter" 
      class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> 

    <bean:annotation-driven> 
     <bean:message-converters> 
      <ref bean="jacksonMessageConverter"/> 
     </bean:message-converters> 
    </bean:annotation-driven> 


    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager"> 
     <property name="caches"> 
      <set> 
       <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean"> 
        <property name="name" value="user-page"/> 
       </bean> 
      </set> 
     </property> 
    </bean> 

    <bean id="viewResolver" class="org.springframework.web.servlet.view.tiles3.TilesViewResolver"> 
     <property name="requestContextAttribute" value="requestContext"/> 
     <property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/> 
    </bean> 

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> 
     <property name="exceptionMappings"> 
      <props> 
       <prop key="java.lang.NullPointerException">exception</prop> <!-- map exception to view name --> 
       <prop key="org.springframework.jdbc.UncategorizedSQLException">exception</prop> <!-- map exception to view name --> 
      </props> 
     </property> 
    </bean> 

    <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer"> 
     <property name="definitions"> 
      <list> 
       <value>/WEB-INF/tiles/tiles-common.xml</value> 
       <value>/WEB-INF/tiles/tiles-admin.xml</value> 
       <value>/WEB-INF/tiles/tiles-requests.xml</value> 
      </list> 
     </property> 
     <property name="preparerFactoryClass" 
        value="org.springframework.web.servlet.view.tiles3.SpringBeanPreparerFactory"/> 
    </bean> 

    <bean id="liquibase" class="liquibase.integration.spring.SpringLiquibase"> 
     <property name="dropFirst" value="true"/> 
     <property name="dataSource" ref="localDbDataSource"/> 
     <property name="changeLog" value="classpath:db.astra.index.xml"/> 
    </bean> 

    <bean id="localDbDataSource" 
      class="org.apache.tomcat.jdbc.pool.DataSource"> 
     <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="maxIdle" value="${db.maxIdle}"/> 
     <property name="minIdle" value="${db.minIdle}"/> 
     <property name="maxActive" value="${db.maxActive}"/> 
     <property name="timeBetweenEvictionRunsMillis" value="${db.timeBetweenEvictionRunsMillis}"/> 
     <property name="minEvictableIdleTimeMillis" value="${db.minEvictableIdleTimeMillis}"/> 
     <property name="testWhileIdle" value="${db.testWhileIdle}"/> 
     <property name="validationQuery" value="${db.validationQuery}"/> 
     <property name="removeAbandoned" value="${db.removeAbandoned}"/> 
     <property name="logAbandoned" value="${db.logAbandoned}"/> 
     <property name="initialSize" value="${db.initialSize}"/> 
     <property name="defaultAutoCommit" value="false" /> 
    </bean> 

    <bean id="transactionAwareDataSource" 
      class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"> 
     <constructor-arg ref="localDbDataSource"/> 
    </bean> 

    <bean class="org.jooq.impl.DataSourceConnectionProvider" name="connectionProvider"> 
     <constructor-arg ref="transactionAwareDataSource"/> 
    </bean> 

    <bean id="exceptionTranslator" 
      class="net.kerba.astra.exception.SpringExceptionTranslator"/> 

    <bean class="org.jooq.impl.DefaultConfiguration" name="jooqConfig"> 
     <constructor-arg index="0" ref="connectionProvider"/> 
     <constructor-arg index="1"> 
      <null/> 
     </constructor-arg> 
     <constructor-arg index="2"> 
      <null/> 
     </constructor-arg> 
     <constructor-arg index="3"> 
      <list> 
       <bean class="org.jooq.impl.DefaultExecuteListenerProvider"> 
        <constructor-arg index="0" ref="exceptionTranslator"/> 
       </bean> 
      </list> 
     </constructor-arg> 
     <constructor-arg index="4"> 
      <null/> 
     </constructor-arg> 
     <constructor-arg index="5"> 
      <value type="org.jooq.SQLDialect">POSTGRES</value> 
     </constructor-arg> 
     <constructor-arg index="6"> 
      <null/> 
     </constructor-arg> 
     <constructor-arg index="7"> 
      <null/> 
     </constructor-arg> 
    </bean> 

    <bean id="dsl" class="org.jooq.impl.DefaultDSLContext"> 
     <constructor-arg ref="jooqConfig"/> 
    </bean> 

    <bean id="patientDao" class="net.kerba.astra.jooq.tables.daos.PatientDao"> 
     <property name="configuration" ref="jooqConfig" /> 
    </bean> 

    <bean id="patientStatusDao" class="net.kerba.astra.jooq.tables.daos.PatientStatusRefDao"> 
     <property name="configuration" ref="jooqConfig" /> 
    </bean> 

    <bean id="requestUrgencyRefDao" class="net.kerba.astra.jooq.tables.daos.RequestUrgencyRefDao"> 
     <property name="configuration" ref="jooqConfig" /> 
    </bean> 

    <bean id="divisionRefDao" class="net.kerba.astra.jooq.tables.daos.DivisionRefDao"> 
     <property name="configuration" ref="jooqConfig" /> 
    </bean> 

    <bean id="requestStateRefDao" class="net.kerba.astra.jooq.tables.daos.RequestStateRefDao"> 
     <property name="configuration" ref="jooqConfig" /> 
    </bean> 
</beans> 

ReferencesController:

package net.kerba.astra.controller; 

@Controller 
@RequestMapping("references") 
public class ReferencesController { 

    private static final Logger logger = LoggerFactory.getLogger(ReferencesController.class); 

    @Autowired 
    private ReferenceService referenceService; 

    @Autowired 
    private DSLContext dsl; 

    private static final Map<String,Object> REFERENCES_CONFIG = loadReferencesList(); 

    private static Map<String,Object> loadReferencesList() { 
     final InputStream resourceAsStream = ReferencesController.class.getResourceAsStream("ReferencesController.data.json"); 
     Objects.requireNonNull(resourceAsStream, "resourceAsStream must not be null"); 

     final InputStreamReader json; 

     try { 
      json = new InputStreamReader(resourceAsStream, "utf8"); 
      Objects.requireNonNull(json, "json must not be null"); 
     } catch (UnsupportedEncodingException e) { 
      throw new RuntimeException("Encoding not supported: utf8", e); 
     } 

     final Map map = new Gson().fromJson(json, Map.class); 
     Objects.requireNonNull(map, "map must not be null"); 

     SortedMap<String,Object> sortedMap = new TreeMap(new Comparator() { 
      @Override 
      public int compare(Object o1, Object o2) { 
       String key1 = (String) o1; 
       String key2 = (String) o2; 

       if (key1 != null && key2 != null) { 
        return key1.compareTo(key2); 
       } else { 
        return 0; 
       } 
      } 
     }); 
     sortedMap.putAll(map); 

     return sortedMap; 
    } 

    /** 
    * Индексная страница справочников 
    * @param model 
    * @return 
    */ 
    @RequestMapping(value = "", method = RequestMethod.GET) 
    public String index(Model model) { 
     model.addAttribute("referencesConfig", REFERENCES_CONFIG); 
     model.addAttribute("pageTitle", "Справочники"); 
     return "references.index"; 
    } 


    @RequestMapping(value = "view/{referenceName}", method = RequestMethod.GET) 
    public String view(Model model,@PathVariable("referenceName") String referenceName) { 
     model.addAttribute("referencesConfig", REFERENCES_CONFIG); 

     if (!REFERENCES_CONFIG.containsKey(referenceName)) { 
      model.addAttribute("pageTitle", "Справочник не найден"); 
      model.addAttribute("message", "Справочник не найден!"); 
      return "error"; 
     } else { 
      final Map currentRef = (Map) REFERENCES_CONFIG.get(referenceName); 
      model.addAttribute("pageTitle", "Справочник: «" + currentRef.get("name") + "»"); 
      model.addAttribute("selectedReferenceConfig", currentRef); 
      model.addAttribute("selectedReferenceConfigKey", referenceName); 

      SelectQuery selectQuery = dsl.selectQuery(); 
      final String tableName = currentRef.get("tableName").toString(); 
      Objects.requireNonNull(tableName, "tableName must not be null"); 
      selectQuery.addFrom(DSL.tableByName(tableName)); 
      final Result result = selectQuery.fetch(); 

      logger.info("result: {}", result.intoMaps()); 
      model.addAttribute("referenceData", result.intoMaps()); 
      return "references.index"; 
     } 
    } 
} 

回答

3

交易目前與總是Postgres的。有不同的選項來控制它們,如明確的交易 - 與隱性交易,在那裏他們沒有宣佈,但暗示(BEGINSQLEND塊)。

還有自動提交,在該交易將自動爲您每個語句後結束 - 即COMMIT如果成功或ROLLBACK如果不是。這不允許在同一個事務下有多個單獨的命令,但是,這通常是可取的。

jOOQ不管理事務可言,並按照現有方法對它們進行管理(春天你的情況TransactionAwareDataSourceProxy)。添加@Transactional定義爲彈簧交易的屬性(您可以執行諸如設置隔離級別等等)。

因此,如果您在預期的行爲中獲得了該註釋,那麼我認爲這很好,因爲如上所述,Postgres始終是事務性的。如果沒有這個註釋,Spring不會像交易那樣處理與交易相關的交互,即使在它的級別上,因此您會得到諸如嘗試對中止事務執行其他查詢的行爲,從而導致在例外。

相關問題